feat: add shared formula packages (#7294)

This commit is contained in:
Univer
2026-07-23 02:58:44 +08:00
committed by GitHub
parent 73a3de7232
commit 3b73c60ebf
402 changed files with 16561 additions and 14853 deletions
+1
View File
@@ -42,6 +42,7 @@
"@univerjs/drawing-ui": "workspace:*",
"@univerjs/engine-render": "workspace:*",
"@univerjs/find-replace": "workspace:*",
"@univerjs/formula": "workspace:*",
"@univerjs/sheets": "workspace:*",
"@univerjs/sheets-conditional-formatting": "workspace:*",
"@univerjs/sheets-conditional-formatting-ui": "workspace:*",
+8
View File
@@ -529,6 +529,14 @@ export interface IGridCellSelection {
viewId: ViewId;
recordId: RecordId;
fieldId: FieldId;
mode?: 'cell' | 'row' | 'column';
anchorRecordId?: RecordId;
focusRecordId?: RecordId;
anchorFieldId?: FieldId;
focusFieldId?: FieldId;
selectedRecordIds?: RecordId[];
showBorder?: boolean;
virtual?: boolean;
}
export interface IGridGroupSelection {
@@ -27,6 +27,7 @@ export const FOCUSING_UNIT = 'FOCUSING_UNIT';
export const FOCUSING_SHEET = 'FOCUSING_SHEET';
export const FOCUSING_DOC = 'FOCUSING_DOC';
export const FOCUSING_SLIDE = 'FOCUSING_SLIDE';
export const FOCUSING_BOARD = 'FOCUSING_BOARD';
/** @deprecated */
export const FOCUSING_EDITOR_BUT_HIDDEN = 'FOCUSING_EDITOR_BUT_HIDDEN';
@@ -24,7 +24,7 @@ import { Injector } from '../../../common/di';
import { UnitModel, UniverInstanceType } from '../../../common/unit';
import { DocumentDataModel } from '../../../docs/data-model/document-data-model';
import { Workbook as WorkbookModel } from '../../../sheets/workbook';
import { FOCUSING_DOC, FOCUSING_SHEET, FOCUSING_SLIDE, FOCUSING_UNIT } from '../../context/context';
import { FOCUSING_BOARD, FOCUSING_DOC, FOCUSING_SHEET, FOCUSING_SLIDE, FOCUSING_UNIT } from '../../context/context';
import { ContextService, IContextService } from '../../context/context.service';
import { DesktopLogService, ILogService, LogLevel } from '../../log/log.service';
import { IUniverInstanceService, UniverInstanceService } from '../instance.service';
@@ -145,24 +145,35 @@ describe('UniverInstanceService', () => {
expect(service.getUnitType('missing')).toBe(UniverInstanceType.UNRECOGNIZED);
});
it('should focus sheet, doc, slide and reset contexts on null focus', () => {
it('should focus sheet, doc, slide, board and reset contexts on null focus', () => {
const workbook = service.createUnit<Partial<IWorkbookData>, WorkbookModel>(UniverInstanceType.UNIVER_SHEET, createWorkbookData());
const doc = service.createUnit<Partial<IDocumentData>, DocumentDataModel>(UniverInstanceType.UNIVER_DOC, createDocData());
const slide = service.createUnit<object, MockSlideUnit>(UniverInstanceType.UNIVER_SLIDE, {});
const board = service.createUnit<object, MockBoardUnit>(UniverInstanceType.UNIVER_BOARD, {});
service.focusUnit(workbook.getUnitId());
expect(contextService.getContextValue(FOCUSING_UNIT)).toBe(true);
expect(contextService.getContextValue(FOCUSING_SHEET)).toBe(true);
expect(contextService.getContextValue(FOCUSING_DOC)).toBe(false);
expect(contextService.getContextValue(FOCUSING_BOARD)).toBe(false);
service.focusUnit(doc.getUnitId());
expect(service.getFocusedUnit()?.getUnitId()).toBe(doc.getUnitId());
expect(contextService.getContextValue(FOCUSING_DOC)).toBe(true);
expect(contextService.getContextValue(FOCUSING_SHEET)).toBe(false);
expect(contextService.getContextValue(FOCUSING_BOARD)).toBe(false);
service.focusUnit(slide.getUnitId());
expect(contextService.getContextValue(FOCUSING_SLIDE)).toBe(true);
expect(contextService.getContextValue(FOCUSING_DOC)).toBe(false);
expect(contextService.getContextValue(FOCUSING_BOARD)).toBe(false);
service.focusUnit(board.getUnitId());
expect(contextService.getContextValue(FOCUSING_UNIT)).toBe(true);
expect(contextService.getContextValue(FOCUSING_BOARD)).toBe(true);
expect(contextService.getContextValue(FOCUSING_DOC)).toBe(false);
expect(contextService.getContextValue(FOCUSING_SHEET)).toBe(false);
expect(contextService.getContextValue(FOCUSING_SLIDE)).toBe(false);
service.focusUnit(null);
expect(service.getFocusedUnit()).toBeNull();
@@ -170,6 +181,7 @@ describe('UniverInstanceService', () => {
expect(contextService.getContextValue(FOCUSING_DOC)).toBe(false);
expect(contextService.getContextValue(FOCUSING_SHEET)).toBe(false);
expect(contextService.getContextValue(FOCUSING_SLIDE)).toBe(false);
expect(contextService.getContextValue(FOCUSING_BOARD)).toBe(false);
});
it('should replace docs and dispose units while resetting focus and current', () => {
@@ -24,7 +24,7 @@ import { UniverInstanceType } from '../../common/unit';
import { DocumentDataModel } from '../../docs/data-model/document-data-model';
import { Disposable } from '../../shared/lifecycle';
import { Workbook } from '../../sheets/workbook';
import { FOCUSING_DOC, FOCUSING_SHEET, FOCUSING_SLIDE, FOCUSING_UNIT } from '../context/context';
import { FOCUSING_BOARD, FOCUSING_DOC, FOCUSING_SHEET, FOCUSING_SLIDE, FOCUSING_UNIT } from '../context/context';
import { IContextService } from '../context/context.service';
import { ILogService } from '../log/log.service';
@@ -298,24 +298,35 @@ export class UniverInstanceService extends Disposable implements IUniverInstance
this._contextService.setContextValue(FOCUSING_DOC, false);
this._contextService.setContextValue(FOCUSING_SHEET, true);
this._contextService.setContextValue(FOCUSING_SLIDE, false);
this._contextService.setContextValue(FOCUSING_BOARD, false);
this.setCurrentUnitForType(id!);
} else if (this.focused instanceof DocumentDataModel) {
this._contextService.setContextValue(FOCUSING_UNIT, true);
this._contextService.setContextValue(FOCUSING_DOC, true);
this._contextService.setContextValue(FOCUSING_SHEET, false);
this._contextService.setContextValue(FOCUSING_SLIDE, false);
this._contextService.setContextValue(FOCUSING_BOARD, false);
this.setCurrentUnitForType(id!);
} else if (this.focused?.type === UniverInstanceType.UNIVER_SLIDE) {
this._contextService.setContextValue(FOCUSING_UNIT, true);
this._contextService.setContextValue(FOCUSING_DOC, false);
this._contextService.setContextValue(FOCUSING_SHEET, false);
this._contextService.setContextValue(FOCUSING_SLIDE, true);
this._contextService.setContextValue(FOCUSING_BOARD, false);
this.setCurrentUnitForType(id!);
} else if (this.focused?.type === UniverInstanceType.UNIVER_BOARD) {
this._contextService.setContextValue(FOCUSING_UNIT, true);
this._contextService.setContextValue(FOCUSING_DOC, false);
this._contextService.setContextValue(FOCUSING_SHEET, false);
this._contextService.setContextValue(FOCUSING_SLIDE, false);
this._contextService.setContextValue(FOCUSING_BOARD, true);
this.setCurrentUnitForType(id!);
} else {
this._contextService.setContextValue(FOCUSING_UNIT, false);
this._contextService.setContextValue(FOCUSING_DOC, false);
this._contextService.setContextValue(FOCUSING_SHEET, false);
this._contextService.setContextValue(FOCUSING_SLIDE, false);
this._contextService.setContextValue(FOCUSING_BOARD, false);
}
}
@@ -39,10 +39,12 @@ import {
IconManager,
IPlatformService,
IShortcutService,
IUIRuntimeScopeService,
KeyCode,
PlatformService,
RediContext,
ShortcutService,
UIRuntimeScopeService,
} from '@univerjs/ui';
import { act } from 'react';
import { createRoot } from 'react-dom/client';
@@ -146,6 +148,7 @@ function createQuickInsertPopupTestBed(options?: {
injector.add([ICommandService, { useClass: CommandService }]);
injector.add([LocaleService, { useClass: LocaleService }]);
injector.add([IPlatformService, { useClass: PlatformService }]);
injector.add([IUIRuntimeScopeService, { useClass: UIRuntimeScopeService }]);
injector.add([IShortcutService, { useClass: ShortcutService }]);
injector.add([ComponentManager, { useClass: ComponentManager }]);
injector.add([IconManager, { useClass: IconManager }]);
@@ -129,7 +129,12 @@ export class FFormula extends FBase {
}
/**
* Start the calculation of the formula.
* Forces formula calculation explicitly.
*
* Normal facade mutations such as `range.setFormula()` already mark formula
* data dirty and schedule calculation automatically. Use this method only when
* an external data change did not produce a dirty command or an explicit full
* recalculation is required.
*
* @example
* ```ts
@@ -126,6 +126,38 @@ describe('RegisterOtherFormulaService', () => {
expect((value?.result as FormulaResultMatrix | undefined)?.[0]?.[0]?.[0]?.v).toBe(2);
});
it('should acknowledge other-formula application after cache and result subscribers update', async () => {
const { service, commandService } = createService();
const formulaId = service.registerFormulaWithRange('unit-1', 'sheet-1', '=A1+1');
const resultPayload = {
unitData: {},
unitOtherData: {
'unit-1': {
'sheet-1': {
[formulaId]: { 0: { 0: [{ v: 2 }] } },
},
},
},
};
let resultDistributed = false;
let acknowledgedPayload: unknown;
const resultSubscription = service.formulaResult$.subscribe(() => {
resultDistributed = true;
});
const appliedSubscription = service.otherFormulaResultApplied$.subscribe((payload) => {
expect(resultDistributed).toBe(true);
expect(service.getFormulaValueSync('unit-1', 'sheet-1', formulaId)?.status).toBe(FormulaResultStatus.SUCCESS);
acknowledgedPayload = payload;
});
await commandService.executeCommand(SetFormulaCalculationResultMutation.id, resultPayload);
expect(acknowledgedPayload).toBe(resultPayload);
resultSubscription.unsubscribe();
appliedSubscription.unsubscribe();
});
it('should support delete and dirty marking', async () => {
const { service, commandService } = createService();
const executedIds: string[] = [];
@@ -37,6 +37,7 @@ export enum OtherFormulaBizType {
CONDITIONAL_FORMATTING = 'cf',
DOC = 'doc',
SLIDE = 'slide',
SHAPE = 'shape',
}
export class RegisterOtherFormulaService extends Disposable {
@@ -49,6 +50,9 @@ export class RegisterOtherFormulaService extends Disposable {
private _formulaResult$ = new Subject<Record<string, Record<string, IOtherFormulaResult[]>>>();
public formulaResult$ = this._formulaResult$.asObservable();
private _otherFormulaResultApplied$ = new Subject<ISetFormulaCalculationResultMutation>();
public otherFormulaResultApplied$ = this._otherFormulaResultApplied$.asObservable();
public calculateStarted$ = new BehaviorSubject(false);
constructor(
@@ -66,6 +70,7 @@ export class RegisterOtherFormulaService extends Disposable {
this._formulaChangeWithRange$.complete();
this._formulaResult$.complete();
this._otherFormulaResultApplied$.complete();
this.calculateStarted$.complete();
}
@@ -198,6 +203,7 @@ export class RegisterOtherFormulaService extends Disposable {
}
}
this._formulaResult$.next(results);
this._otherFormulaResultApplied$.next(params);
}
}));
}
+59
View File
@@ -0,0 +1,59 @@
{
"name": "@univerjs/formula-ui",
"version": "1.0.0-alpha.4",
"private": false,
"description": "Shared formula search and highlighting helpers for Univer editors.",
"author": "DreamNum Co., Ltd. <developer@univer.ai>",
"license": "Apache-2.0",
"homepage": "https://univer.ai",
"repository": {
"type": "git",
"url": "https://github.com/dream-num/univer"
},
"exports": {
".": "./src/index.ts",
"./*": "./src/*"
},
"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"
},
"./lib/*": "./lib/*"
}
},
"files": [
"lib"
],
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"coverage": "vitest run --coverage",
"typecheck": "tsc --noEmit",
"build:bundle": "univer-cli build",
"build:types": "tsc -p tsconfig.node.json",
"build": "pnpm run build:bundle && pnpm run build:types"
},
"dependencies": {
"@univerjs/engine-formula": "workspace:*"
},
"devDependencies": {
"@univerjs-infra/shared": "workspace:*",
"@univerjs/core": "workspace:*",
"@univerjs/formula": "workspace:*",
"typescript": "^6.0.3",
"vitest": "^4.1.10"
}
}
@@ -0,0 +1,131 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* 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 { FunctionType, matchToken, sequenceNodeType } from '@univerjs/engine-formula';
import { describe, expect, it } from 'vitest';
import {
findFormulaStructuredReferences,
getFormulaHighlightDataStream,
getFormulaReplaceResult,
resolveFormulaReferenceEditingContext,
} from '../formula-editor-helpers';
describe('formula editor helpers', () => {
it('replaces a partial function and appends its opening bracket', () => {
expect(getFormulaReplaceResult(['SU'], 0, 'SUM', FunctionType.Math)).toEqual({
text: `SUM${matchToken.OPEN_BRACKET}`,
offset: -2,
});
});
it('does not append a bracket for table references', () => {
expect(getFormulaReplaceResult(['SalesTa'], 0, 'SalesTable', FunctionType.Table)).toEqual({
text: 'SalesTable',
offset: -3,
});
});
it('serializes parsed references into an editor document stream', () => {
const nodes = [{
token: '[Book]Sheet1!A1',
nodeType: sequenceNodeType.REFERENCE,
startIndex: 0,
endIndex: 15,
}];
expect(getFormulaHighlightDataStream('=', nodes)).toBe('=[Book]Sheet1!A1\r\n');
});
it('uses the same cursor rules for adding and replacing references', () => {
const reference = {
token: 'A1:B2',
nodeType: sequenceNodeType.REFERENCE,
startIndex: 4,
endIndex: 8,
};
const sequenceNodes = ['SUM(', reference, '+'];
expect(resolveFormulaReferenceEditingContext({
formulaText: 'SUM(A1:B2+',
sequenceNodes,
offset: 4,
})).toMatchObject({ mode: 'add', referenceIndex: -1 });
expect(resolveFormulaReferenceEditingContext({
formulaText: 'SUM(A1:B2+',
sequenceNodes,
offset: 9,
})).toMatchObject({ mode: 'replace', referenceIndex: 0 });
expect(resolveFormulaReferenceEditingContext({
formulaText: 'SUM(A1:B2+',
sequenceNodes,
offset: 10,
})).toMatchObject({ mode: 'add', referenceIndex: -1 });
});
it('does not draw a reference in plain formula text', () => {
expect(resolveFormulaReferenceEditingContext({
formulaText: 'SUM',
sequenceNodes: ['SUM'],
offset: 3,
})).toMatchObject({ mode: 'none', referenceIndex: -1 });
});
it('allows adding a reference when the cursor is before the first sequence node', () => {
expect(resolveFormulaReferenceEditingContext({
formulaText: 'SUM(A1)',
sequenceNodes: [
'SUM(',
{
token: 'A1',
nodeType: sequenceNodeType.REFERENCE,
startIndex: 4,
endIndex: 5,
},
')',
],
offset: 0,
})).toMatchObject({
mode: 'add',
nodeIndex: -1,
referenceIndex: -1,
});
});
it('edits structured table references as selectable references', () => {
expect(resolveFormulaReferenceEditingContext({
formulaText: 'SUM(SalesTable[Amount])',
sequenceNodes: [
'SUM(',
{
token: 'SalesTable[Amount]',
nodeType: sequenceNodeType.TABLE,
startIndex: 4,
endIndex: 21,
},
')',
],
offset: 10,
})).toMatchObject({ mode: 'replace', referenceIndex: 0 });
});
it('finds cross-unit structured references that are not resolved by the local table map', () => {
expect(findFormulaStructuredReferences('SUM([Sales Base]!Orders[Amount])')).toEqual([{
token: '[Sales Base]!Orders[Amount]',
startIndex: 4,
endIndex: 30,
}]);
});
});
@@ -0,0 +1,324 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* 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 { ITextRun } from '@univerjs/core';
import type { ISequenceNode } from '@univerjs/engine-formula';
import type { IDescriptionService, ISearchItemWithType } from '@univerjs/formula';
import { FunctionType, isFormulaLexerToken, matchRefDrawToken, matchToken, sequenceNodeType } from '@univerjs/engine-formula';
export type FormulaSequenceNode = string | ISequenceNode;
export interface IFormulaHighlightColors {
formulaRefColors: string[];
numberColor: string;
stringColor: string;
plainTextColor: string;
}
export interface IFormulaRefSelection {
refIndex: number;
themeColor: string;
token: string;
nodeType: sequenceNodeType;
startIndex: number;
endIndex: number;
index: number;
}
export interface IFormulaStructuredReferenceRange {
token: string;
startIndex: number;
endIndex: number;
}
export type FormulaReferenceEditingMode = 'add' | 'replace' | 'none';
export interface IFormulaReferenceEditingContext {
mode: FormulaReferenceEditingMode;
nodeIndex: number;
referenceIndex: number;
offset: number;
}
function shouldAppendOpenBracket(functionType: FunctionType): boolean {
return functionType !== FunctionType.DefinedName && functionType !== FunctionType.Table;
}
export function getFormulaReplaceResult(
nodes: FormulaSequenceNode[],
index: number,
formulaName: string,
functionType: FunctionType
): { text: string; offset: number } | undefined {
if (index === -1) {
return undefined;
}
const cloneNodes = [...nodes];
const lastNodes = cloneNodes.splice(index + 1);
const oldNode = cloneNodes.pop() ?? '';
let offset = (typeof oldNode === 'string' ? oldNode.length : oldNode.token.length) - formulaName.length;
cloneNodes.push(formulaName);
if (lastNodes[0] !== matchToken.OPEN_BRACKET && shouldAppendOpenBracket(functionType)) {
cloneNodes.push(matchToken.OPEN_BRACKET);
offset -= 1;
}
return {
text: [...cloneNodes, ...lastNodes].map((node) => typeof node === 'string' ? node : node.token).join(''),
offset,
};
}
export function searchFormulaFunctions(
descriptionService: IDescriptionService,
token: string,
limit = 10
): ISearchItemWithType[] {
return descriptionService.getSearchListByNameFirstLetter(token).slice(0, limit);
}
export function findFormulaStructuredReferences(formulaText: string): IFormulaStructuredReferenceRange[] {
const referencePattern = /\[[^\]\r\n]+\]!(?:'(?:[^']|'')+'|[^\s()[\],:+\-*/&=<>!]+)(?:\[\[[^\r\n]*?\]\]|\[[^\]\r\n]+\])/g;
return Array.from(formulaText.matchAll(referencePattern), (match) => ({
token: match[0],
startIndex: match.index,
endIndex: match.index + match[0].length - 1,
}));
}
export function getFormulaSequenceNodeIndex(
sequenceNodes: FormulaSequenceNode[],
offset: number,
isEqual = false
): number {
let currentOffset = 0;
for (let index = 0; index < sequenceNodes.length; index++) {
const node = sequenceNodes[index];
const nextOffset = currentOffset + (typeof node === 'string' ? node.length : node.token.length);
if (isEqual ? nextOffset === offset : offset > currentOffset && offset <= nextOffset) {
return index;
}
currentOffset = nextOffset;
}
return -1;
}
export function getFormulaReferenceIndex(sequenceNodes: FormulaSequenceNode[], nodeIndex: number): number {
if (nodeIndex < 0 || nodeIndex >= sequenceNodes.length) {
return -1;
}
const node = sequenceNodes[nodeIndex];
if (typeof node === 'string' || !isFormulaReferenceNode(node)) {
return -1;
}
let referenceIndex = -1;
for (let index = 0; index <= nodeIndex; index++) {
const currentNode = sequenceNodes[index];
if (typeof currentNode !== 'string' && isFormulaReferenceNode(currentNode)) {
referenceIndex += 1;
}
}
return referenceIndex;
}
export function getFormulaSequenceCharacterAtOffset(
sequenceNodes: Array<string | { token: string }>,
offset: number
): string | undefined {
let currentOffset = 0;
for (const node of sequenceNodes) {
const text = typeof node === 'string' ? node : node.token;
const nextOffset = currentOffset + text.length;
if (offset > currentOffset && offset <= nextOffset) {
return text[offset - currentOffset - 1];
}
currentOffset = nextOffset;
}
return undefined;
}
export function isFormulaReferenceAddingContext(
sequenceNodes: Array<string | { token: string }>,
offset: number
): boolean {
const character = getFormulaSequenceCharacterAtOffset(sequenceNodes, offset);
return Boolean(character && matchRefDrawToken(character));
}
export function isFormulaReferenceAddingTextContext(formulaText: string, offset: number): boolean {
const character = formulaText[offset - 1];
const nextCharacter = formulaText[offset];
return Boolean(
character &&
matchRefDrawToken(character) &&
(!nextCharacter || (isFormulaLexerToken(nextCharacter) && nextCharacter !== matchToken.OPEN_BRACKET))
);
}
export function resolveFormulaReferenceEditingContext(options: {
formulaText: string;
sequenceNodes: FormulaSequenceNode[];
offset: number;
selectionStart?: number;
selectionEnd?: number;
}): IFormulaReferenceEditingContext {
const { formulaText, sequenceNodes } = options;
const offset = Math.max(0, Math.min(options.offset, formulaText.length));
let nodeIndex = getFormulaSequenceNodeIndex(sequenceNodes, offset);
const selectionStart = options.selectionStart ?? offset;
const selectionEnd = options.selectionEnd ?? selectionStart;
if (selectionEnd > selectionStart) {
nodeIndex = sequenceNodes.findIndex((node) =>
typeof node !== 'string' &&
isFormulaReferenceNode(node) &&
node.startIndex < selectionEnd &&
node.endIndex + 1 > selectionStart
);
}
const referenceIndex = getFormulaReferenceIndex(sequenceNodes, nodeIndex);
if (referenceIndex !== -1) {
return { mode: 'replace', nodeIndex, referenceIndex, offset };
}
const canAdd = offset === 0 ||
isFormulaReferenceAddingContext(sequenceNodes, offset) ||
isFormulaReferenceAddingTextContext(formulaText, offset);
return {
mode: canAdd ? 'add' : 'none',
nodeIndex,
referenceIndex: -1,
offset,
};
}
export function getFormulaHighlightDataStream(
leadingCharacter: string,
sequenceNodes: FormulaSequenceNode[],
sourceText?: string
): string {
const text = sourceText ?? sequenceNodes.map((node) => typeof node === 'string' ? node : node.token).join('');
return `${leadingCharacter}${text}\r\n`;
}
export function buildFormulaTextRuns(
descriptionService: IDescriptionService,
colors: IFormulaHighlightColors,
sequenceNodes: FormulaSequenceNode[],
options?: { includeTableReferences?: boolean }
): { textRuns: ITextRun[]; refSelections: IFormulaRefSelection[] } {
const textRuns: ITextRun[] = [];
const refSelections: IFormulaRefSelection[] = [];
const referenceColors = new Map<string, string>();
let refColorIndex = 0;
for (let index = 0; index < sequenceNodes.length; index++) {
const node = sequenceNodes[index];
if (typeof node === 'string') {
const start = textRuns.at(-1)?.ed ?? 0;
textRuns.push(createTextRun(start, start + node.length, colors.plainTextColor));
continue;
}
let color = colors.plainTextColor;
if (!descriptionService.hasDefinedNameDescription(node.token.trim())) {
if (isFormulaReferenceNode(node)) {
color = referenceColors.get(node.token) ?? colors.formulaRefColors[refColorIndex % colors.formulaRefColors.length];
if (!referenceColors.has(node.token)) {
referenceColors.set(node.token, color);
refColorIndex += 1;
}
if (node.nodeType === sequenceNodeType.REFERENCE || options?.includeTableReferences) {
refSelections.push({
refIndex: index,
themeColor: color,
token: node.token,
nodeType: node.nodeType,
startIndex: node.startIndex,
endIndex: node.endIndex,
index: refSelections.length,
});
}
} else if (node.nodeType === sequenceNodeType.NUMBER) {
color = colors.numberColor;
} else if (node.nodeType === sequenceNodeType.STRING || node.nodeType === sequenceNodeType.ARRAY) {
color = colors.stringColor;
}
}
textRuns.push(createTextRun(node.startIndex, node.endIndex + 1, color));
}
if (options?.includeTableReferences) {
const formulaText = sequenceNodes.map((node) => typeof node === 'string' ? node : node.token).join('');
const structuredReferences = findFormulaStructuredReferences(formulaText);
for (const reference of structuredReferences) {
if (refSelections.some((selection) => selection.startIndex === reference.startIndex && selection.endIndex === reference.endIndex)) {
continue;
}
const color = colors.formulaRefColors[refColorIndex % colors.formulaRefColors.length];
refColorIndex += 1;
applyTextRunColor(textRuns, reference.startIndex, reference.endIndex + 1, color);
refSelections.push({
...reference,
refIndex: sequenceNodes.length + refSelections.length,
themeColor: color,
nodeType: sequenceNodeType.TABLE,
index: refSelections.length,
});
}
refSelections.sort((first, second) => first.startIndex - second.startIndex);
refSelections.forEach((selection, index) => {
selection.index = index;
});
}
return { textRuns, refSelections };
}
function applyTextRunColor(textRuns: ITextRun[], start: number, end: number, color: string): void {
const nextRuns: ITextRun[] = [];
for (const run of textRuns) {
if (run.ed <= start || run.st >= end) {
nextRuns.push(run);
continue;
}
if (run.st < start) {
nextRuns.push(createTextRun(run.st, start, run.ts?.cl?.rgb ?? color));
}
nextRuns.push(createTextRun(Math.max(run.st, start), Math.min(run.ed, end), color));
if (run.ed > end) {
nextRuns.push(createTextRun(end, run.ed, run.ts?.cl?.rgb ?? color));
}
}
textRuns.splice(0, textRuns.length, ...nextRuns);
}
function isFormulaReferenceNode(node: ISequenceNode): boolean {
return node.nodeType === sequenceNodeType.REFERENCE || node.nodeType === sequenceNodeType.TABLE;
}
function createTextRun(start: number, end: number, color: string): ITextRun {
return {
st: start,
ed: end,
ts: { cl: { rgb: color }, fs: 11 },
};
}
+37
View File
@@ -0,0 +1,37 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* 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 {
buildFormulaTextRuns,
findFormulaStructuredReferences,
getFormulaHighlightDataStream,
getFormulaReferenceIndex,
getFormulaReplaceResult,
getFormulaSequenceCharacterAtOffset,
getFormulaSequenceNodeIndex,
isFormulaReferenceAddingContext,
isFormulaReferenceAddingTextContext,
resolveFormulaReferenceEditingContext,
searchFormulaFunctions,
} from './formula-editor-helpers';
export type {
FormulaReferenceEditingMode,
FormulaSequenceNode,
IFormulaHighlightColors,
IFormulaReferenceEditingContext,
IFormulaRefSelection,
IFormulaStructuredReferenceRange,
} from './formula-editor-helpers';
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "@univerjs-infra/shared/tsconfigs/base",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib/types"
},
"include": ["src"]
}
+7
View File
@@ -0,0 +1,7 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"declaration": true,
"emitDeclarationOnly": true
}
}
+3
View File
@@ -0,0 +1,3 @@
import createConfig from '@univerjs-infra/shared/vitest';
export default createConfig();
+78
View File
@@ -0,0 +1,78 @@
{
"name": "@univerjs/formula",
"version": "1.0.0-alpha.4",
"private": false,
"description": "Shared formula function catalog and registration services for Univer.",
"author": "DreamNum Co., Ltd. <developer@univer.ai>",
"license": "Apache-2.0",
"homepage": "https://univer.ai",
"repository": {
"type": "git",
"url": "https://github.com/dream-num/univer"
},
"exports": {
".": "./src/index.ts",
"./*": "./src/*",
"./locale/*": "./src/locale/*.ts",
"./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"
},
"./locale/*": {
"import": "./lib/es/locale/*.js",
"require": "./lib/cjs/locale/*.js",
"types": "./lib/types/locale/*.d.ts"
},
"./facade": {
"import": "./lib/es/facade.js",
"require": "./lib/cjs/facade.js",
"types": "./lib/types/facade/index.d.ts"
},
"./lib/facade": {
"import": "./lib/es/facade.js",
"require": "./lib/cjs/facade.js",
"types": "./lib/types/facade/index.d.ts"
},
"./lib/*": "./lib/*"
}
},
"files": [
"lib"
],
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"coverage": "vitest run --coverage",
"typecheck": "tsc --noEmit",
"build:bundle": "univer-cli build",
"build:types": "tsc -p tsconfig.node.json",
"build": "pnpm run build:bundle && pnpm run build:types"
},
"peerDependencies": {
"rxjs": ">=7.0.0"
},
"dependencies": {
"@univerjs/core": "workspace:*",
"@univerjs/engine-formula": "workspace:*"
},
"devDependencies": {
"@univerjs-infra/shared": "workspace:*",
"typescript": "^6.0.3",
"vitest": "^4.1.10"
}
}
@@ -0,0 +1,117 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* 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 } from '@univerjs/core';
import type { ISetFormulaCalculationNotificationMutation, ISetFormulaCalculationResultMutation } from '@univerjs/engine-formula';
import { Disposable, ICommandService, Inject } from '@univerjs/core';
import {
RegisterOtherFormulaService,
SetFormulaCalculationNotificationMutation,
SetFormulaCalculationResultMutation,
SetFormulaCalculationStartMutation,
} from '@univerjs/engine-formula';
import { FormulaCalculationSessionService, FormulaResultApplicationType } from '../services/formula-calculation-session.service';
function isFormulaCalculationNotification(
params: object | undefined
): params is ISetFormulaCalculationNotificationMutation {
return params != null;
}
function isFormulaCalculationResult(
params: object | undefined
): params is ISetFormulaCalculationResultMutation {
return params != null && 'unitData' in params && 'unitOtherData' in params;
}
export class FormulaCalculationSessionController extends Disposable {
constructor(
@ICommandService private readonly _commandService: ICommandService,
@Inject(FormulaCalculationSessionService) private readonly _sessionService: FormulaCalculationSessionService,
@Inject(RegisterOtherFormulaService) private readonly _registerOtherFormulaService: RegisterOtherFormulaService
) {
super();
this._sessionService.initialize();
this._initialize();
}
private _initialize(): void {
this.disposeWithMe(this._registerOtherFormulaService.otherFormulaResultApplied$.subscribe((result) => {
this._sessionService.markResultApplied(FormulaResultApplicationType.OTHER_FORMULA, result);
}));
this.disposeWithMe(
this._commandService.onCommandExecuted((command: ICommandInfo) => {
if (command.id === SetFormulaCalculationStartMutation.id) {
this._sessionService.start();
return;
}
if (command.id === SetFormulaCalculationNotificationMutation.id) {
if (isFormulaCalculationNotification(command.params)) {
this._handleNotification(command.params);
}
return;
}
if (command.id === SetFormulaCalculationResultMutation.id) {
if (isFormulaCalculationResult(command.params)) {
this._handleResult(command.params);
}
}
})
);
}
private _handleNotification(params: ISetFormulaCalculationNotificationMutation): void {
if (params.stageInfo != null) {
this._sessionService.updateProgress(params.stageInfo);
}
if (params.functionsExecutedState !== undefined) {
this._sessionService.markCompleted(params.functionsExecutedState);
}
}
private _handleResult(params: ISetFormulaCalculationResultMutation): void {
const pendingApplications: FormulaResultApplicationType[] = [];
if (this._hasSheetResultToApply(params)) {
pendingApplications.push(FormulaResultApplicationType.SHEET);
}
if (this._hasOtherFormulaResultToApply(params)) {
pendingApplications.push(FormulaResultApplicationType.OTHER_FORMULA);
}
this._sessionService.markResultEmitted(params, pendingApplications);
}
private _hasSheetResultToApply(result: ISetFormulaCalculationResultMutation): boolean {
return Object.values(result.unitData).some((sheetData) =>
sheetData != null && Object.values(sheetData).some((cellData) => cellData != null)
);
}
private _hasOtherFormulaResultToApply(result: ISetFormulaCalculationResultMutation): boolean {
return Object.values(result.unitOtherData).some((subUnitData) =>
subUnitData != null && Object.values(subUnitData).some((formulaData) =>
formulaData != null && Object.keys(formulaData).length > 0
)
);
}
}
+97
View File
@@ -0,0 +1,97 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* 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 { IDisposable } from '@univerjs/core';
import type { ISetFormulaCalculationResultMutation } from '@univerjs/engine-formula';
import { FFormula } from '@univerjs/engine-formula/facade';
import { FormulaCalculationSessionService } from '@univerjs/formula';
/**
* @ignore
*/
export interface IFFormulaCalculationSessionMixin {
/**
* Listens for formula results after every affected model has applied them.
* @param callback Called with the calculation result payload.
* @returns A disposable used to unsubscribe.
* @example
* ```ts
* const workbook = univerAPI.getActiveWorkbook();
* if (!workbook) throw new Error('No active workbook.');
* const range = workbook.getActiveSheet().getRange('A1');
* const formula = univerAPI.getFormula();
* const applied = formula.onCalculationResultApplied(10_000);
* const disposable = formula.calculationResultApplied((result) => {
* console.log('Formula results applied:', result);
* });
*
* // Setting a formula automatically schedules calculation.
* range.setFormula('=SUM(B1:B3)');
* await applied;
*
* // Dispose the listener when it is no longer needed.
* disposable.dispose();
* ```
*/
calculationResultApplied(callback: (result: ISetFormulaCalculationResultMutation) => void): IDisposable;
/**
* Waits until the latest formula-calculation results have been applied.
* Create the returned promise before the mutation whose result you need to
* observe. Formula-aware facade mutations schedule calculation automatically.
* @param timeout Optional timeout in milliseconds.
* @returns A promise that resolves after result application, or when no calculation starts.
* @example
* ```ts
* const workbook = univerAPI.getActiveWorkbook();
* if (!workbook) throw new Error('No active workbook.');
* const range = workbook.getActiveSheet().getRange('A1');
* const formula = univerAPI.getFormula();
* const applied = formula.onCalculationResultApplied(10_000);
* range.setFormula('=SUM(B1:B3)');
* await applied;
* console.log(range.getValue());
* ```
*/
onCalculationResultApplied(timeout?: number): Promise<void>;
}
export class FFormulaCalculationSessionMixin extends FFormula implements IFFormulaCalculationSessionMixin {
override calculationResultApplied(callback: (result: ISetFormulaCalculationResultMutation) => void): IDisposable {
const subscription = this._injector.get(FormulaCalculationSessionService).resultApplied$.subscribe((result) => {
if (typeof requestIdleCallback === 'function') {
requestIdleCallback(() => callback(result));
return;
}
queueMicrotask(() => callback(result));
});
return {
dispose: () => subscription.unsubscribe(),
};
}
override onCalculationResultApplied(timeout?: number): Promise<void> {
return this._injector.get(FormulaCalculationSessionService).waitForLatestApplied(timeout);
}
}
FFormula.extend(FFormulaCalculationSessionMixin);
declare module '@univerjs/engine-formula/facade' {
// eslint-disable-next-line ts/naming-convention
interface FFormula extends IFFormulaCalculationSessionMixin {}
}
+19
View File
@@ -0,0 +1,19 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* 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-formula';
export type * from './f-formula';
+50
View File
@@ -0,0 +1,50 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* 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 { FormulaCalculationSessionController } from './controllers/formula-calculation-session.controller';
export { UniverFormulaPlugin } from './plugin';
export { DescriptionService, IDescriptionService } from './services/description.service';
export type { ISearchItem, ISearchItemWithType } from './services/description.service';
export { FormulaCalculationSessionService, FormulaResultApplicationType } from './services/formula-calculation-session.service';
export type { IFormulaCalculationSessionState } from './services/formula-calculation-session.service';
export { FUNCTION_LIST_ARRAY } from './services/function-list/array';
export { FUNCTION_LIST_COMPATIBILITY } from './services/function-list/compatibility';
export { FUNCTION_LIST_CUBE } from './services/function-list/cube';
export { FUNCTION_LIST_DATABASE } from './services/function-list/database';
export { FUNCTION_LIST_DATE } from './services/function-list/date';
export { FUNCTION_LIST_ENGINEERING } from './services/function-list/engineering';
export { FUNCTION_LIST_FINANCIAL } from './services/function-list/financial';
export { FUNCTION_LIST } from './services/function-list/function-list';
export { FUNCTION_LIST_INFORMATION } from './services/function-list/information';
export { FUNCTION_LIST_LOGICAL } from './services/function-list/logical';
export { FUNCTION_LIST_LOOKUP } from './services/function-list/lookup';
export { FUNCTION_LIST_MATH } from './services/function-list/math';
export { FUNCTION_LIST_STATISTICAL } from './services/function-list/statistical';
export { FUNCTION_LIST_TEXT } from './services/function-list/text';
export { FUNCTION_LIST_UNIVER } from './services/function-list/univer';
export { FUNCTION_LIST_WEB } from './services/function-list/web';
export {
IRegisterFunctionService,
RegisterFunctionService,
} from './services/register-function.service';
export type {
IRegisterAsyncFunction,
IRegisterFunction,
IRegisterFunctionParams,
ISingleFunctionRegisterParams,
IUnregisterFunctionParams,
} from './services/register-function.service';
export { generateParam, getFunctionName } from './services/utils';
+57
View File
@@ -0,0 +1,57 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* 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';
import array from './function-list/array/ar-SA';
import compatibility from './function-list/compatibility/ar-SA';
import cube from './function-list/cube/ar-SA';
import database from './function-list/database/ar-SA';
import date from './function-list/date/ar-SA';
import engineering from './function-list/engineering/ar-SA';
import financial from './function-list/financial/ar-SA';
import information from './function-list/information/ar-SA';
import logical from './function-list/logical/ar-SA';
import lookup from './function-list/lookup/ar-SA';
import math from './function-list/math/ar-SA';
import statistical from './function-list/statistical/ar-SA';
import text from './function-list/text/ar-SA';
import univer from './function-list/univer/ar-SA';
import web from './function-list/web/ar-SA';
const locale: typeof enUS = {
formula: {
functionList: {
...array,
...compatibility,
...cube,
...database,
...date,
...engineering,
...financial,
...information,
...logical,
...lookup,
...math,
...statistical,
...text,
...univer,
...web,
},
},
};
export default locale;
+57
View File
@@ -0,0 +1,57 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* 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';
import array from './function-list/array/ca-ES';
import compatibility from './function-list/compatibility/ca-ES';
import cube from './function-list/cube/ca-ES';
import database from './function-list/database/ca-ES';
import date from './function-list/date/ca-ES';
import engineering from './function-list/engineering/ca-ES';
import financial from './function-list/financial/ca-ES';
import information from './function-list/information/ca-ES';
import logical from './function-list/logical/ca-ES';
import lookup from './function-list/lookup/ca-ES';
import math from './function-list/math/ca-ES';
import statistical from './function-list/statistical/ca-ES';
import text from './function-list/text/ca-ES';
import univer from './function-list/univer/ca-ES';
import web from './function-list/web/ca-ES';
const locale: typeof enUS = {
formula: {
functionList: {
...array,
...compatibility,
...cube,
...database,
...date,
...engineering,
...financial,
...information,
...logical,
...lookup,
...math,
...statistical,
...text,
...univer,
...web,
},
},
};
export default locale;
+57
View File
@@ -0,0 +1,57 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* 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';
import array from './function-list/array/de-DE';
import compatibility from './function-list/compatibility/de-DE';
import cube from './function-list/cube/de-DE';
import database from './function-list/database/de-DE';
import date from './function-list/date/de-DE';
import engineering from './function-list/engineering/de-DE';
import financial from './function-list/financial/de-DE';
import information from './function-list/information/de-DE';
import logical from './function-list/logical/de-DE';
import lookup from './function-list/lookup/de-DE';
import math from './function-list/math/de-DE';
import statistical from './function-list/statistical/de-DE';
import text from './function-list/text/de-DE';
import univer from './function-list/univer/de-DE';
import web from './function-list/web/de-DE';
const locale: typeof enUS = {
formula: {
functionList: {
...array,
...compatibility,
...cube,
...database,
...date,
...engineering,
...financial,
...information,
...logical,
...lookup,
...math,
...statistical,
...text,
...univer,
...web,
},
},
};
export default locale;
+55
View File
@@ -0,0 +1,55 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* 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 array from './function-list/array/en-US';
import compatibility from './function-list/compatibility/en-US';
import cube from './function-list/cube/en-US';
import database from './function-list/database/en-US';
import date from './function-list/date/en-US';
import engineering from './function-list/engineering/en-US';
import financial from './function-list/financial/en-US';
import information from './function-list/information/en-US';
import logical from './function-list/logical/en-US';
import lookup from './function-list/lookup/en-US';
import math from './function-list/math/en-US';
import statistical from './function-list/statistical/en-US';
import text from './function-list/text/en-US';
import univer from './function-list/univer/en-US';
import web from './function-list/web/en-US';
const locale = {
formula: {
functionList: {
...array,
...compatibility,
...cube,
...database,
...date,
...engineering,
...financial,
...information,
...logical,
...lookup,
...math,
...statistical,
...text,
...univer,
...web,
},
},
};
export default locale;
+57
View File
@@ -0,0 +1,57 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* 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';
import array from './function-list/array/es-ES';
import compatibility from './function-list/compatibility/es-ES';
import cube from './function-list/cube/es-ES';
import database from './function-list/database/es-ES';
import date from './function-list/date/es-ES';
import engineering from './function-list/engineering/es-ES';
import financial from './function-list/financial/es-ES';
import information from './function-list/information/es-ES';
import logical from './function-list/logical/es-ES';
import lookup from './function-list/lookup/es-ES';
import math from './function-list/math/es-ES';
import statistical from './function-list/statistical/es-ES';
import text from './function-list/text/es-ES';
import univer from './function-list/univer/es-ES';
import web from './function-list/web/es-ES';
const locale: typeof enUS = {
formula: {
functionList: {
...array,
...compatibility,
...cube,
...database,
...date,
...engineering,
...financial,
...information,
...logical,
...lookup,
...math,
...statistical,
...text,
...univer,
...web,
},
},
};
export default locale;
+57
View File
@@ -0,0 +1,57 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* 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';
import array from './function-list/array/en-US';
import compatibility from './function-list/compatibility/en-US';
import cube from './function-list/cube/en-US';
import database from './function-list/database/en-US';
import date from './function-list/date/en-US';
import engineering from './function-list/engineering/en-US';
import financial from './function-list/financial/en-US';
import information from './function-list/information/en-US';
import logical from './function-list/logical/en-US';
import lookup from './function-list/lookup/en-US';
import math from './function-list/math/en-US';
import statistical from './function-list/statistical/en-US';
import text from './function-list/text/en-US';
import univer from './function-list/univer/en-US';
import web from './function-list/web/en-US';
const locale: typeof enUS = {
formula: {
functionList: {
...array,
...compatibility,
...cube,
...database,
...date,
...engineering,
...financial,
...information,
...logical,
...lookup,
...math,
...statistical,
...text,
...univer,
...web,
},
},
};
export default locale;
+57
View File
@@ -0,0 +1,57 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* 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';
import array from './function-list/array/fr-FR';
import compatibility from './function-list/compatibility/fr-FR';
import cube from './function-list/cube/fr-FR';
import database from './function-list/database/fr-FR';
import date from './function-list/date/fr-FR';
import engineering from './function-list/engineering/fr-FR';
import financial from './function-list/financial/fr-FR';
import information from './function-list/information/fr-FR';
import logical from './function-list/logical/fr-FR';
import lookup from './function-list/lookup/fr-FR';
import math from './function-list/math/fr-FR';
import statistical from './function-list/statistical/fr-FR';
import text from './function-list/text/fr-FR';
import univer from './function-list/univer/fr-FR';
import web from './function-list/web/fr-FR';
const locale: typeof enUS = {
formula: {
functionList: {
...array,
...compatibility,
...cube,
...database,
...date,
...engineering,
...financial,
...information,
...logical,
...lookup,
...math,
...statistical,
...text,
...univer,
...web,
},
},
};
export default locale;

Some files were not shown because too many files have changed in this diff Show More