perf(design): optimize clsx runtime (#7633)

This commit is contained in:
白熱
2026-09-05 14:43:47 +08:00
committed by GitHub
parent dee08c357c
commit 0a25893dbe
71 changed files with 910 additions and 425 deletions
+2 -2
View File
@@ -33,7 +33,7 @@
"dependencies": {
"@tsdown/css": "^0.22.14",
"@typescript-eslint/parser": "^8.65.0",
"@vitest/coverage-istanbul": "^4.1.11",
"@vitest/coverage-istanbul": "^5.0.0",
"autoprefixer": "^10.5.4",
"eslint-plugin-better-tailwindcss": "^4.7.0",
"eslint-plugin-header": "^3.1.1",
@@ -48,7 +48,7 @@
"tailwindcss": "3.4.18",
"tsdown": "^0.22.14",
"unplugin-vue": "^7.2.0",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
},
"devDependencies": {
"@univerjs/icons": "1.38.0",
+3 -3
View File
@@ -192,14 +192,14 @@ A package MUST NOT reference a key whose first segment is another package's name
```typescript
// In @univerjs/sheets-filter
// 🚫 references a key from the UI package
localeService.t('sheets-filter-ui.panel.empty')
localeService.t<LocaleKey>('sheets-filter-ui.panel.empty')
// In @univerjs/sheets-filter-ui
// 🚫 references a key from a common/utility package
localeService.t('sheets-ui.permission.dialog.filterErr')
localeService.t<LocaleKey>('sheets-ui.permission.dialog.filterErr')
// ✅ sink the key into your own locale file
localeService.t('sheets-filter-ui.permission.filterErr')
localeService.t<LocaleKey>('sheets-filter-ui.permission.filterErr')
```
### Namespace must be the single root key
+1 -1
View File
@@ -52,6 +52,6 @@
"tsx": "^4.22.4",
"typescript": "^6.0.3",
"vite": "^8.0.11",
"vitest": "^4.1.10"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -68,7 +68,7 @@
"tailwindcss": "3.4.18",
"turbo": "^2.10.8",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
},
"lint-staged": {
"*": "eslint --fix"
+1 -1
View File
@@ -88,6 +88,6 @@
"rxjs": "^7.8.2",
"tailwindcss": "3.4.18",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -95,6 +95,6 @@
"@univerjs-infra/shared": "workspace:*",
"rxjs": "^7.8.2",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -81,6 +81,6 @@
"@univerjs/sheets": "workspace:*",
"rxjs": "^7.8.2",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1
View File
@@ -0,0 +1 @@
/src/helper/clsx.generated.ts
+4 -4
View File
@@ -61,6 +61,7 @@
"lib"
],
"scripts": {
"prepare": "node scripts/generate-clsx.mts",
"test": "vitest run",
"test:watch": "vitest",
"coverage": "vitest run --coverage",
@@ -83,10 +84,8 @@
"@radix-ui/react-slot": "^1.3.3",
"@univerjs/icons": "1.38.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "1.1.1",
"sonner": "^2.0.7",
"tailwind-merge": "2.6.0"
"sonner": "^2.0.7"
},
"devDependencies": {
"@testing-library/jest-dom": "6.9.1",
@@ -95,9 +94,10 @@
"postcss": "^8.5.26",
"react": "18.3.1",
"react-dom": "18.3.1",
"tailwind-merge": "2.6.0",
"tailwindcss": "3.4.18",
"tailwindcss-animate": "^1.0.7",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+87
View File
@@ -0,0 +1,87 @@
import type { Config } from 'tailwind-merge';
import { readFileSync, writeFileSync } from 'node:fs';
import { createRequire } from 'node:module';
import { dirname, resolve } from 'node:path';
import { getDefaultConfig, validators } from 'tailwind-merge';
type ClassGroup = Config<string, string>['classGroups'][string];
const config = getDefaultConfig();
const groupNames = Object.keys(config.classGroups);
const validatorNames = Object.keys(validators).sort() as Array<keyof typeof validators>;
const groupIds = new Map(groupNames.map((name, index) => [name, index + 1]));
interface IRule {
literals: string[];
validators: number;
}
function visit(definitions: ClassGroup, prefix: string, rules: Map<string, IRule>) {
const rule = rules.get(prefix) ?? { literals: [], validators: 0 };
rules.set(prefix, rule);
for (const definition of definitions) {
if (typeof definition === 'string') {
rule.literals.push(definition);
} else if (typeof definition === 'function') {
if ('isThemeGetter' in definition) {
visit(definition(config.theme), prefix, rules);
continue;
}
const validator = validatorNames.findIndex((name) => validators[name] === definition);
if (validator === -1) {
throw new Error(`Unknown Tailwind v3 validator: ${definition.name}`);
}
rule.validators |= 1 << validator;
} else {
for (const [part, children] of Object.entries(definition)) {
visit(children, prefix + (prefix ? '-' : '') + part, rules);
}
}
}
}
const classGroups = Object.values(config.classGroups).map((definitions) => {
const rules = new Map<string, IRule>();
visit(definitions, '', rules);
return Array.from(rules)
.filter(([, rule]) => rule.literals.length || rule.validators)
.map(([prefix, rule]) => `['${prefix}', ${rule.literals.length ? `'${rule.literals.join(' ')}'` : 'null'}, ${rule.validators}]`);
});
function serializeConflicts(conflicts: Record<string, readonly string[] | undefined>) {
return Object.entries(conflicts).map(([name, targets]) =>
` ${groupIds.get(name)}: [${targets!.map((target) => groupIds.get(target)).join(', ')}],`
).join('\n');
}
const require = createRequire(import.meta.url);
const packageDir = resolve(dirname(require.resolve('tailwind-merge')), '..');
const license = readFileSync(resolve(packageDir, 'LICENSE.md'), 'utf8').trim();
const header = readFileSync(new URL('../src/helper/clsx.ts', import.meta.url), 'utf8').split('\n\n')[0];
const output = `${header}
// Generated by pnpm --filter @univerjs/design prepare. Do not edit.
// Complete tailwind-merge 2.6.0 / Tailwind CSS 3.4 rules; the runtime supplies the univer- prefix.
/*!
${license.split('\n').map((line) => ` *${line ? ` ${line}` : ''}`).join('\n')}
*/
export const VALIDATOR_NAMES = [${validatorNames.map((name) => `'${name}'`).join(', ')}] as const;
// Group IDs are one-based. Each rule contains a prefix, literal suffixes and a validator bitmask.
export const CLASS_GROUPS: ReadonlyArray<ReadonlyArray<readonly [prefix: string, literals: string | null, validators: number]>> = [
${classGroups.map((rules) => ` [${rules.join(', ')}],`).join('\n')}
];
export const CLASS_CONFLICTS: Record<number, readonly number[]> = {
${serializeConflicts(config.conflictingClassGroups)}
};
export const POSTFIX_CONFLICTS: Record<number, readonly number[]> = {
${serializeConflicts(config.conflictingClassGroupModifiers)}
};
`;
writeFileSync(new URL('../src/helper/clsx.generated.ts', import.meta.url), output);
@@ -16,9 +16,24 @@
import { cva } from 'class-variance-authority';
import { describe, expect, it } from 'vitest';
import { clsx } from '../clsx';
describe('clsx', () => {
it('flattens conditional values without caching mutable arrays or objects by identity', () => {
const active = { 'univer-h-8': false };
const values = ['univer-h-6'];
const inputs = [values, active, [false, null, undefined, 0, Number.NaN, '', true, 7, 1n]];
expect(clsx(...inputs)).toBe('univer-h-6 7');
active['univer-h-8'] = true;
expect(clsx(...inputs)).toBe('univer-h-8 7');
values.push('univer-p-2');
expect(clsx(...inputs)).toBe('univer-p-2 univer-h-8 7');
expect(clsx()).toBe('');
expect(clsx(' \n univer-p-2\t univer-p-4 ')).toBe('univer-p-4');
});
it('merges Univer-prefixed conflicting utilities', () => {
expect(clsx('univer-h-6', 'univer-h-[78px]')).toBe('univer-h-[78px]');
expect(clsx('univer-px-3', 'univer-px-8')).toBe('univer-px-8');
@@ -37,4 +52,53 @@ describe('clsx', () => {
expect(clsx(buttonVariants({ size: 'small', className: 'univer-h-[78px]' }))).toBe('univer-h-[78px]');
});
it.each([
['univer-pr-2 univer-px-4', 'univer-px-4'],
['univer-px-4 univer-pr-2', 'univer-px-4 univer-pr-2'],
['univer-p-2 univer-px-4', 'univer-p-2 univer-px-4'],
['univer-px-4 univer-p-2', 'univer-p-2'],
['univer-w-2 univer-h-4 univer-size-8', 'univer-size-8'],
['univer-size-8 univer-w-2', 'univer-size-8 univer-w-2'],
['-univer-m-2 univer-m-4', 'univer-m-4'],
['univer-p-2 !univer-p-4 !univer-p-8', 'univer-p-2 !univer-p-8'],
['univer-leading-8 univer-text-sm/6', 'univer-text-sm/6'],
['univer-text-sm/6 univer-leading-8', 'univer-text-sm/6 univer-leading-8'],
['univer-w-1/2 univer-w-3/4', 'univer-w-3/4'],
['univer-bg-opacity-25 univer-bg-opacity-50 univer-bg-red-500/50', 'univer-bg-opacity-50 univer-bg-red-500/50'],
['univer-text-opacity-25 univer-text-opacity-75', 'univer-text-opacity-75'],
])('resolves directional and v3-specific conflicts: %s', (input, expected) => {
expect(clsx(input)).toBe(expected);
});
it.each([
['hover:focus:univer-p-2 focus:hover:univer-p-4', 'focus:hover:univer-p-4'],
['hover:univer-p-2 focus:univer-p-4', 'hover:univer-p-2 focus:univer-p-4'],
['dark:hover:!univer-p-2 hover:dark:!univer-p-4', 'hover:dark:!univer-p-4'],
['[&:nth-child(2)]:univer-p-2 [&:nth-child(2)]:univer-p-4', '[&:nth-child(2)]:univer-p-4'],
['hover:[&_a]:univer-p-2 [&_a]:hover:univer-p-4', 'hover:[&_a]:univer-p-2 [&_a]:hover:univer-p-4'],
['group-hover/menu:univer-p-2 group-hover/menu:univer-p-4', 'group-hover/menu:univer-p-4'],
['data-[state=open]:univer-p-2 data-[state=open]:univer-p-4', 'data-[state=open]:univer-p-4'],
])('keeps variant scopes distinct: %s', (input, expected) => {
expect(clsx(input)).toBe(expected);
});
it.each([
['univer-text-[length:var(--size)] univer-text-[color:var(--color)]', 'univer-text-[length:var(--size)] univer-text-[color:var(--color)]'],
['univer-bg-[url(https://example.com/a-b.svg)] univer-bg-red-500', 'univer-bg-[url(https://example.com/a-b.svg)] univer-bg-red-500'],
['univer-bg-[size:20px_30px] univer-bg-cover', 'univer-bg-cover'],
['univer-shadow-[0_1px_2px_black] univer-shadow-red-500', 'univer-shadow-[0_1px_2px_black] univer-shadow-red-500'],
['[color:red] [color:blue] [background:red]', '[color:blue] [background:red]'],
['hover:[--gutter:1rem] hover:[--gutter:2rem]', 'hover:[--gutter:2rem]'],
['p-2 p-4 univer-custom univer-custom', 'p-2 p-4 univer-custom univer-custom'],
['univer-p-unknown univer-p-2', 'univer-p-unknown univer-p-2'],
['constructor toString __proto__ univer-constructor univer-toString', 'constructor toString __proto__ univer-constructor univer-toString'],
])('recognizes arbitrary values and preserves external classes: %s', (input, expected) => {
expect(clsx(input)).toBe(expected);
});
it('handles the v3 leading important marker before a font-size/line-height postfix', () => {
expect(clsx('!univer-leading-8 !univer-text-sm/6')).toBe('!univer-text-sm/6');
expect(clsx('!univer-text-red-500 !univer-text-sm/6')).toBe('!univer-text-red-500 !univer-text-sm/6');
});
});
+250 -7
View File
@@ -14,13 +14,256 @@
* limitations under the License.
*/
/* eslint-disable no-restricted-imports */
import type { ClassValue } from 'clsx';
import { clsx as cn } from 'clsx';
import { extendTailwindMerge } from 'tailwind-merge';
import { CLASS_CONFLICTS, CLASS_GROUPS, POSTFIX_CONFLICTS, VALIDATOR_NAMES } from './clsx.generated';
const twMerge = extendTailwindMerge({ prefix: 'univer-' });
export type ClassValue = string | number | bigint | boolean | null | undefined | ClassValue[] | Record<string, unknown>;
export function clsx(...inputs: ClassValue[]) {
return twMerge(cn(inputs));
interface IClassToken {
id: number | string;
conflicts: readonly (number | string)[];
}
const arbitraryValue = /^\[(?:([a-z-]+):)?(.+)\]$/i;
const lengthValue = /\d+(%|px|r?em|[sdl]?v([hwib]|min|max)|pt|pc|in|cm|mm|cap|ch|ex|r?lh|cq(w|h|i|b|min|max))|\b(calc|min|max|clamp)\(.+\)|^0$/;
const colorValue = /^(rgba?|hsla?|hwb|(ok)?(lab|lch))\(.+\)$/;
function isNumber(value: string) {
return Boolean(value) && !Number.isNaN(Number(value));
}
function isArbitrary(value: string, labels: string[], test: (value: string) => boolean = () => false) {
const match = arbitraryValue.exec(value);
return !!match && (match[1] ? labels.includes(match[1]) : test(match[2]));
}
// Tailwind v3 validators, adapted from tailwind-merge 2.6.0.
// Its MIT license is included with the generated rules in clsx.generated.ts.
const validators: Record<typeof VALIDATOR_NAMES[number], (value: string) => boolean> = {
isAny: () => true,
isArbitraryImage: (value) => isArbitrary(value, ['image', 'url'], (value) => /^(url|image|image-set|cross-fade|element|(repeating-)?(linear|radial|conic)-gradient)\(.+\)$/.test(value)),
isArbitraryLength: (value) => isArbitrary(value, ['length'], (value) => lengthValue.test(value) && !colorValue.test(value)),
isArbitraryNumber: (value) => isArbitrary(value, ['number'], isNumber),
isArbitraryPosition: (value) => isArbitrary(value, ['position']),
isArbitraryShadow: (value) => isArbitrary(value, [], (value) => /^(inset_)?-?((\d+)?\.?(\d+)[a-z]+|0)_-?((\d+)?\.?(\d+)[a-z]+|0)/.test(value)),
isArbitrarySize: (value) => isArbitrary(value, ['length', 'size', 'percentage']),
isArbitraryValue: (value) => arbitraryValue.test(value),
isInteger: (value) => Boolean(value) && Number.isInteger(Number(value)),
isLength: (value) => isNumber(value) || ['px', 'full', 'screen'].includes(value) || /^\d+\/\d+$/.test(value),
isNumber,
isPercent: (value) => value.endsWith('%') && isNumber(value.slice(0, -1)),
isTshirtSize: (value) => /^(\d+(\.\d+)?)?(xs|sm|md|lg|xl)$/.test(value),
};
const classValidators = VALIDATOR_NAMES.map((name) => validators[name]);
const classPatterns = new Map<string, number[]>();
const classGroups = new Map<string, number>();
const cache = new Map<string, string>();
const cacheKeys = cache.keys();
const tokens = new Map<string, IClassToken | null>();
const tokenKeys = tokens.keys();
const plainTokens: IClassToken[] = [];
CLASS_GROUPS.forEach((rules, index) => {
for (const [prefix, literals, mask] of rules) {
if (literals !== null) {
for (const suffix of literals.split(' ')) {
classGroups.set(prefix + (prefix && suffix ? '-' : '') + suffix, index + 1);
}
}
if (mask) {
const patterns = classPatterns.get(prefix) ?? [];
patterns.push(index + 1, mask);
classPatterns.set(prefix, patterns);
}
}
});
function getClassGroup(className: string): number | string | undefined {
const positive = className.startsWith('-') ? className.slice(1) : className;
if (!positive.startsWith('univer-')) {
// Arbitrary properties are unprefixed in Tailwind v3, including in prefixed projects.
const property = /^\[([^:]+):.+\]$/.exec(className);
return property ? `arbitrary..${property[1]}` : undefined;
}
const utility = positive.slice(7);
const exact = classGroups.get(utility);
if (exact !== undefined) {
return exact;
}
// Longest prefix first preserves v3's precedence (e.g. border-x width before border color).
for (let end = utility.lastIndexOf('-'); end > 0; end = utility.lastIndexOf('-', end - 1)) {
const rules = classPatterns.get(utility.slice(0, end));
if (!rules) {
continue;
}
const value = utility.slice(end + 1);
for (let index = 0; index < rules.length; index += 2) {
// Visit only the validators present in the precompiled bitmask.
for (let mask = rules[index + 1]; mask; mask &= mask - 1) {
const validator = 31 - Math.clz32(mask & -mask);
if (classValidators[validator](value)) {
return rules[index];
}
}
}
}
}
function joinClasses(inputs: ClassValue[]): string {
let result = '';
for (const value of inputs) {
if (!value) {
continue;
}
let joined = '';
if (typeof value === 'string' || typeof value === 'number') {
joined = String(value);
} else if (Array.isArray(value)) {
joined = joinClasses(value);
} else if (typeof value === 'object') {
for (const key in value) {
if (value[key]) {
result += (result ? ' ' : '') + key;
}
}
}
if (joined) {
result += (result ? ' ' : '') + joined;
}
}
return result;
}
function parseClassName(original: string) {
const modifiers: string[] = [];
let sortable: string[] = [];
let depth = 0;
let start = 0;
let slash = -1;
for (let position = 0; position < original.length; position++) {
const character = original[position];
if (depth === 0 && character === ':') {
const modifier = original.slice(start, position);
if (modifier.startsWith('[')) {
modifiers.push(...sortable.sort(), modifier);
sortable = [];
} else {
sortable.push(modifier);
}
start = position + 1;
} else if (depth === 0 && character === '/') {
slash = position;
} else if (character === '[') {
depth++;
} else if (character === ']') {
depth--;
}
}
const important = original[start] === '!';
if (important) {
// Strip v3's leading ! before slicing / so text-sm/6 remains a font size, not a color.
start++;
}
let hasPostfix = slash > start;
let group = getClassGroup(original.slice(start, hasPostfix ? slash : undefined));
if (group === undefined && hasPostfix) {
group = getClassGroup(original.slice(start));
hasPostfix = false;
}
modifiers.push(...sortable.sort());
return { group, hasPostfix, scope: `${modifiers.join(':')}|${important ? '!' : ''}` };
}
function getClassToken(original: string): IClassToken | null {
// Arbitrary values often change on every call; retaining them costs more than reusing their group.
const cacheable = original.indexOf('[') === -1;
if (cacheable) {
const cached = tokens.get(original);
if (cached !== undefined) {
return cached;
}
}
let group: number | string | undefined;
let scope = '|';
let hasPostfix = false;
if (original.indexOf(':') === -1 && original.indexOf('/') === -1) {
const important = original[0] === '!';
group = getClassGroup(important ? original.slice(1) : original);
scope = important ? '|!' : '|';
} else {
({ group, scope, hasPostfix } = parseClassName(original));
}
const token = group === undefined ? null : createClassToken(group, scope, hasPostfix);
if (cacheable) {
if (tokens.size >= 2048) {
tokens.delete(tokenKeys.next().value!);
}
tokens.set(original, token);
}
return token;
}
function createClassToken(group: number | string, scope: string, hasPostfix: boolean): IClassToken {
const plain = scope === '|' || scope === '|!';
const offset = scope === '|!' ? CLASS_GROUPS.length : 0;
const id = plain && typeof group === 'number' ? group + offset : scope + group;
const postfix = hasPostfix && typeof group === 'number' ? POSTFIX_CONFLICTS[group] : undefined;
const cached = typeof id === 'number' && !postfix ? plainTokens[id] : undefined;
if (cached) {
return cached;
}
const conflicts = typeof group === 'number'
? [...(CLASS_CONFLICTS[group] ?? []), ...(postfix ?? [])].map((conflict) => plain ? conflict + offset : scope + conflict)
: [];
const token = { id, conflicts };
if (typeof id === 'number' && !postfix) {
plainTokens[id] = token;
}
return token;
}
/** Joins conditional classes and resolves Tailwind CSS v3 conflicts for the univer- prefix. */
export function clsx(...inputs: ClassValue[]): string {
const classList = inputs.length === 1 && typeof inputs[0] === 'string' ? inputs[0] : joinClasses(inputs);
const cached = cache.get(classList);
if (cached !== undefined) {
return cached;
}
const classes = classList.trim().split(/\s+/);
const conflicts = new Set<number | string>();
let result = '';
for (let index = classes.length - 1; index >= 0; index--) {
const original = classes[index];
const token = getClassToken(original);
if (token) {
if (conflicts.has(token.id)) {
continue;
}
conflicts.add(token.id);
for (const conflict of token.conflicts) {
conflicts.add(conflict);
}
}
result = original + (result ? ` ${result}` : '');
}
if (cache.size >= 1000) {
cache.delete(cacheKeys.next().value!);
}
cache.set(classList, result);
return result;
}
+1 -1
View File
@@ -93,6 +93,6 @@
"rxjs": "^7.8.2",
"tailwindcss": "3.4.18",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -83,6 +83,6 @@
"@univerjs-infra/shared": "workspace:*",
"@univerjs/engine-render": "workspace:*",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -79,6 +79,6 @@
"react": "18.3.1",
"rxjs": "^7.8.2",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -91,6 +91,6 @@
"rxjs": "^7.8.2",
"tailwindcss": "3.4.18",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -69,6 +69,6 @@
"devDependencies": {
"@univerjs-infra/shared": "workspace:*",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -93,6 +93,6 @@
"rxjs": "^7.8.2",
"tailwindcss": "3.4.18",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -93,6 +93,6 @@
"rxjs": "^7.8.2",
"tailwindcss": "3.4.18",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -82,6 +82,6 @@
"@univerjs-infra/shared": "workspace:*",
"rxjs": "^7.8.2",
"typescript": "^6.0.3",
"vitest": "^4.1.10"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -103,6 +103,6 @@
"rxjs": "^7.8.2",
"tailwindcss": "3.4.18",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import type { LocaleKey } from '../locale/types';
import { LocaleService } from '@univerjs/core';
import { useDependency, useObservable } from '@univerjs/ui';
import { DocLayoutProgressService } from '../services/doc-layout-progress.service';
@@ -26,7 +27,7 @@ export function DocLayoutProgress() {
}
const { progress } = progressState;
const label = localeService.t('docs-ui.layout.progress');
const label = localeService.t<LocaleKey>('docs-ui.layout.progress');
return (
<div
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import type { LocaleKey } from '../locale/types';
import { LocaleService } from '@univerjs/core';
import { LoadingMultiIcon } from '@univerjs/icons';
import { useDependency } from '@univerjs/ui';
@@ -37,7 +38,7 @@ export function DocLayoutRecovery() {
aria-hidden
className="univer-size-5 univer-animate-spin univer-text-violet-500"
/>
<span>{localeService.t('docs-ui.layout.recovering')}</span>
<span>{localeService.t<LocaleKey>('docs-ui.layout.recovering')}</span>
</div>
);
}
+1 -1
View File
@@ -87,6 +87,6 @@
"@univerjs-infra/shared": "workspace:*",
"rxjs": "^7.8.2",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -88,6 +88,6 @@
"rxjs": "^7.8.2",
"tailwindcss": "3.4.18",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -74,6 +74,6 @@
"@univerjs-infra/shared": "workspace:*",
"rxjs": "^7.8.2",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -93,6 +93,6 @@
"@univerjs-infra/shared": "workspace:*",
"rxjs": "^7.8.2",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -80,6 +80,6 @@
"rxjs": "^7.8.2",
"tsdown": "^0.22.14",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -87,6 +87,6 @@
"rxjs": "^7.8.2",
"tailwindcss": "3.4.18",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -84,6 +84,6 @@
"@univerjs-infra/shared": "workspace:*",
"rxjs": "^7.8.2",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -70,6 +70,6 @@
"@univerjs-infra/shared": "workspace:*",
"rxjs": "^7.8.2",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -74,6 +74,6 @@
"@univerjs-infra/shared": "workspace:*",
"rxjs": "^7.8.2",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -73,6 +73,6 @@
"@univerjs-infra/shared": "workspace:*",
"rxjs": "^7.8.2",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
@@ -93,6 +93,6 @@
"rxjs": "^7.8.2",
"tailwindcss": "3.4.18",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
@@ -96,6 +96,6 @@
"@univerjs/sheets-formula": "workspace:*",
"rxjs": "^7.8.2",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
@@ -100,6 +100,6 @@
"rxjs": "^7.8.2",
"tailwindcss": "3.4.18",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
@@ -94,6 +94,6 @@
"rxjs": "^7.8.2",
"tailwindcss": "3.4.18",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -95,6 +95,6 @@
"@univerjs-infra/shared": "workspace:*",
"rxjs": "^7.8.2",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -105,6 +105,6 @@
"rxjs": "^7.8.2",
"tailwindcss": "3.4.18",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -83,6 +83,6 @@
"devDependencies": {
"@univerjs-infra/shared": "workspace:*",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -91,6 +91,6 @@
"rxjs": "^7.8.2",
"tailwindcss": "3.4.18",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -94,6 +94,6 @@
"@univerjs-infra/shared": "workspace:*",
"rxjs": "^7.8.2",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -88,6 +88,6 @@
"@univerjs-infra/shared": "workspace:*",
"rxjs": "^7.8.2",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -104,6 +104,6 @@
"rxjs": "^7.8.2",
"tailwindcss": "3.4.18",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -95,6 +95,6 @@
"@univerjs/engine-render": "workspace:*",
"rxjs": "^7.8.2",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -106,6 +106,6 @@
"rxjs": "^7.8.2",
"tailwindcss": "3.4.18",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -93,6 +93,6 @@
"@univerjs-infra/shared": "workspace:*",
"rxjs": "^7.8.2",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -89,6 +89,6 @@
"rxjs": "^7.8.2",
"tailwindcss": "3.4.18",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -88,6 +88,6 @@
"@univerjs/network": "workspace:*",
"rxjs": "^7.8.2",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -91,6 +91,6 @@
"rxjs": "^7.8.2",
"tailwindcss": "3.4.18",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -86,6 +86,6 @@
"@univerjs-infra/shared": "workspace:*",
"rxjs": "^7.8.2",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -91,6 +91,6 @@
"rxjs": "^7.8.2",
"tailwindcss": "3.4.18",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -82,6 +82,6 @@
"devDependencies": {
"@univerjs-infra/shared": "workspace:*",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -94,6 +94,6 @@
"rxjs": "^7.8.2",
"tailwindcss": "3.4.18",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -94,6 +94,6 @@
"@univerjs/sheets-formula": "workspace:*",
"rxjs": "^7.8.2",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
@@ -93,6 +93,6 @@
"rxjs": "^7.8.2",
"tailwindcss": "3.4.18",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -87,6 +87,6 @@
"@univerjs-infra/shared": "workspace:*",
"rxjs": "^7.8.2",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -105,6 +105,6 @@
"rxjs": "^7.8.2",
"tailwindcss": "3.4.18",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -95,6 +95,6 @@
"@univerjs/network": "workspace:*",
"rxjs": "^7.8.2",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -91,6 +91,6 @@
"rxjs": "^7.8.2",
"tailwindcss": "3.4.18",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -74,6 +74,6 @@
"@univerjs-infra/shared": "workspace:*",
"rxjs": "^7.8.2",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -69,6 +69,6 @@
"devDependencies": {
"@univerjs-infra/shared": "workspace:*",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -89,6 +89,6 @@
"rxjs": "^7.8.2",
"tailwindcss": "3.4.18",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -84,6 +84,6 @@
"@univerjs-infra/shared": "workspace:*",
"rxjs": "^7.8.2",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -73,7 +73,7 @@
"devDependencies": {
"@univerjs-infra/shared": "workspace:*",
"typescript": "^6.0.3",
"vitest": "^4.1.11",
"vitest": "^5.0.0",
"vue": "^3.5.40"
}
}
@@ -70,6 +70,6 @@
"devDependencies": {
"@univerjs-infra/shared": "workspace:*",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+1 -1
View File
@@ -104,6 +104,6 @@
"tailwindcss": "3.4.18",
"tailwindcss-animate": "^1.0.7",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
@@ -15,6 +15,7 @@
*/
import type { ComponentProps } from 'react';
import type { LocaleKey } from '../../../locale/types';
import type { IMenuSchema } from '../../../services/menu/menu-manager.service';
import type { MobileDrawerSnap } from '../../components/mobile-drawer/MobileDrawer';
import { LocaleService } from '@univerjs/core';
@@ -62,7 +63,7 @@ function MobileMenuDrawerContent(props: IMobileMenuDrawerProps) {
<div dir={direction} className="univer-fixed univer-inset-0 univer-z-[1080] univer-flex univer-items-end">
<button
type="button"
aria-label={localeService.t('ui.rangeSelector.cancel')}
aria-label={localeService.t<LocaleKey>('ui.rangeSelector.cancel')}
className={clsx(resetButtonClassName, `
!univer-absolute !univer-inset-0 !univer-m-0 !univer-block !univer-appearance-none
!univer-rounded-none !univer-border-0 !univer-bg-black/35 !univer-p-0 univer-backdrop-blur-[2px]
@@ -72,8 +73,8 @@ function MobileMenuDrawerContent(props: IMobileMenuDrawerProps) {
<MobileDrawer
componentName="mobile-menu-drawer"
snap={drawerSnap}
expandLabel={localeService.t('ui.ribbon.more')}
collapseLabel={localeService.t('ui.ribbon.more')}
expandLabel={localeService.t<LocaleKey>('ui.ribbon.more')}
collapseLabel={localeService.t<LocaleKey>('ui.ribbon.more')}
onSnapChange={setDrawerSnap}
onClose={onClose}
role="dialog"
@@ -90,7 +91,7 @@ function MobileMenuDrawerContent(props: IMobileMenuDrawerProps) {
? (
<button
type="button"
aria-label={localeService.t('ui.navigation.back')}
aria-label={localeService.t<LocaleKey>('ui.navigation.back')}
className={clsx(resetButtonClassName, `
univer-size-10 univer-rounded-xl univer-text-gray-700
active:univer-bg-gray-200
@@ -114,7 +115,7 @@ function MobileMenuDrawerContent(props: IMobileMenuDrawerProps) {
</div>
<button
type="button"
aria-label={localeService.t('ui.rangeSelector.cancel')}
aria-label={localeService.t<LocaleKey>('ui.rangeSelector.cancel')}
className={clsx(resetButtonClassName, `
univer-size-10 univer-rounded-xl univer-text-gray-700
active:univer-bg-gray-200
+1 -1
View File
@@ -85,6 +85,6 @@
"@univerjs-infra/shared": "workspace:*",
"rxjs": "^7.8.2",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}
+429 -342
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -19,6 +19,6 @@
"devDependencies": {
"@univerjs-infra/shared": "workspace:*",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
"vitest": "^5.0.0"
}
}