From 38bf2dd9de03f61c92cdd35a2692790767d6199c Mon Sep 17 00:00:00 2001 From: Univer Date: Mon, 15 Jun 2026 16:22:19 +0800 Subject: [PATCH 1/6] feat(core): add board unit type --- .../__tests__/instance.service.spec.ts | 25 +++++++++++++++++++ .../src/ts/univer/constants/univer.ts | 1 + 2 files changed, 26 insertions(+) diff --git a/packages/core/src/services/instance/__tests__/instance.service.spec.ts b/packages/core/src/services/instance/__tests__/instance.service.spec.ts index 2778506ce663..32f9e9c9bc4f 100644 --- a/packages/core/src/services/instance/__tests__/instance.service.spec.ts +++ b/packages/core/src/services/instance/__tests__/instance.service.spec.ts @@ -71,6 +71,17 @@ class MockSlideUnit extends UnitModel { override setRev() {} } +class MockBoardUnit extends UnitModel { + override type = UniverInstanceType.UNIVER_BOARD; + override name$ = new BehaviorSubject(''); + override setName() {} + override getSnapshot() { return {}; } + override getUnitId() { return 'board-unit'; } + override getRev() { return 1; } + override incrementRev() {} + override setRev() {} +} + describe('UniverInstanceService', () => { let service: UniverInstanceService; let contextService: ContextService; @@ -89,12 +100,15 @@ describe('UniverInstanceService', () => { service.registerCtorForType(UniverInstanceType.UNIVER_SHEET, WorkbookModel as never); service.registerCtorForType(UniverInstanceType.UNIVER_DOC, DocumentDataModel as never); service.registerCtorForType(UniverInstanceType.UNIVER_SLIDE, MockSlideUnit as never); + service.registerCtorForType(UniverInstanceType.UNIVER_BOARD, MockBoardUnit as never); service.__setCreateHandler((type, data, _ctor, options) => { let unit: UnitModel; if (type === UniverInstanceType.UNIVER_SHEET) { unit = injector.createInstance(WorkbookModel as never, data as Partial, logService) as UnitModel; } else if (type === UniverInstanceType.UNIVER_DOC) { unit = injector.createInstance(DocumentDataModel as never, data as Partial) as UnitModel; + } else if (type === UniverInstanceType.UNIVER_BOARD) { + unit = new MockBoardUnit(); } else { unit = injector.createInstance(MockSlideUnit); } @@ -192,4 +206,15 @@ describe('UniverInstanceService', () => { expect(currentIds).toContain('sheet-unit'); expect(currentIds).toContain('sheet-unit-2'); }); + + it('should create board units as an independent unit type', () => { + const board = service.createUnit(UniverInstanceType.UNIVER_BOARD, {}); + + expect(UniverInstanceType.UNIVER_BOARD).toBe(6); + expect(board.getUnitId()).toBe('board-unit'); + expect(service.getCurrentUnitOfType(UniverInstanceType.UNIVER_BOARD)?.getUnitId()).toBe('board-unit'); + expect(service.getUnit('board-unit', UniverInstanceType.UNIVER_BOARD)).toBe(board); + expect(service.getAllUnitsForType(UniverInstanceType.UNIVER_BOARD)).toEqual([board]); + expect(service.getUnitType('board-unit')).toBe(UniverInstanceType.UNIVER_BOARD); + }); }); diff --git a/packages/protocol/src/ts/univer/constants/univer.ts b/packages/protocol/src/ts/univer/constants/univer.ts index 1292cf46af22..858f18cb67f3 100644 --- a/packages/protocol/src/ts/univer/constants/univer.ts +++ b/packages/protocol/src/ts/univer/constants/univer.ts @@ -21,5 +21,6 @@ export enum UniverType { UNIVER_SLIDE = 3, UNIVER_PROJECT = 4, UNIVER_BASE = 5, + UNIVER_BOARD = 6, UNRECOGNIZED = -1, } From 2a4dbe69611a528bc1d27a4c776bb9c041481c15 Mon Sep 17 00:00:00 2001 From: Univer Date: Tue, 16 Jun 2026 04:52:13 +0800 Subject: [PATCH 2/6] feat(core): persist authz resources for boards --- .../authz-resource.integration.spec.ts | 73 ++++++++++++++++++- .../authz-io/authz-io-local.service.ts | 9 ++- 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/packages/core/src/services/__tests__/authz-resource.integration.spec.ts b/packages/core/src/services/__tests__/authz-resource.integration.spec.ts index 24f913417dca..25e9fce90d2a 100644 --- a/packages/core/src/services/__tests__/authz-resource.integration.spec.ts +++ b/packages/core/src/services/__tests__/authz-resource.integration.spec.ts @@ -16,8 +16,9 @@ import type { Univer } from '../../univer'; import { UnitAction, UnitObject, UnitRole } from '@univerjs/protocol'; +import { BehaviorSubject } from 'rxjs'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { UniverInstanceType } from '../../common/unit'; +import { UnitModel, UniverInstanceType } from '../../common/unit'; import { IAuthzIoService } from '../authz-io/type'; import { IUniverInstanceService } from '../instance/instance.service'; import { IMentionIOService } from '../mention-io/type'; @@ -25,6 +26,49 @@ import { IResourceLoaderService } from '../resource-loader/type'; import { UserManagerService } from '../user-manager/user-manager.service'; import { createTestBed } from './create-test-bed'; +interface ITestBoardData { + id: string; + name?: string; + resources?: Array<{ name: string; data: string }>; +} + +class MockBoardUnit extends UnitModel { + override readonly type = UniverInstanceType.UNIVER_BOARD; + override name$ = new BehaviorSubject(''); + private readonly _snapshot: ITestBoardData; + + constructor(snapshot: Partial = {}) { + super(); + this._snapshot = { + id: 'board-resource', + name: '', + ...snapshot, + }; + this.name$.next(this._snapshot.name ?? ''); + } + + override getUnitId(): string { + return this._snapshot.id; + } + + override setName(name: string): void { + this._snapshot.name = name; + this.name$.next(name); + } + + override getSnapshot(): ITestBoardData { + return this._snapshot; + } + + override getRev(): number { + return 1; + } + + override incrementRev(): void { } + + override setRev(): void { } +} + describe('Authz/resource integration', () => { let univer: Univer; let unitId: string; @@ -133,6 +177,33 @@ describe('Authz/resource integration', () => { expect(reloaded[0].actions).toEqual([{ action: UnitAction.Edit, allowed: false }]); }); + it('should persist permission resources for board units', async () => { + const injector = univer.__getInjector(); + const authzIoService = injector.get(IAuthzIoService); + const resourceLoaderService = injector.get(IResourceLoaderService); + const univerInstanceService = injector.get(IUniverInstanceService); + + univerInstanceService.registerCtorForType(UniverInstanceType.UNIVER_BOARD, MockBoardUnit); + const board = univer.createUnit(UniverInstanceType.UNIVER_BOARD, { + id: 'board-resource', + }); + const objectID = await authzIoService.create({ + objectType: UnitObject.Workbook, + worksheetObject: { + unitID: board.getUnitId(), + name: 'Board permission', + collaborators: [], + strategies: [{ action: UnitAction.Edit, role: UnitRole.Owner }], + scope: undefined, + }, + }); + + const snapshot = resourceLoaderService.saveUnit(board.getUnitId()); + const authzResource = snapshot?.resources?.find((resource) => resource.name === 'SHEET_AuthzIoMockService_PLUGIN'); + + expect(authzResource?.data).toContain(objectID); + }); + it('should expose current user data consistently through mention and user services', async () => { const injector = univer.__getInjector(); const userManagerService = injector.get(UserManagerService); diff --git a/packages/core/src/services/authz-io/authz-io-local.service.ts b/packages/core/src/services/authz-io/authz-io-local.service.ts index f25e8a444196..326db5426f22 100644 --- a/packages/core/src/services/authz-io/authz-io-local.service.ts +++ b/packages/core/src/services/authz-io/authz-io-local.service.ts @@ -100,7 +100,12 @@ export class AuthzIoLocalService implements IAuthzIoService { return JSON.parse(json); }, pluginName: 'SHEET_AuthzIoMockService_PLUGIN', - businesses: [UniverInstanceType.UNIVER_SHEET, UniverInstanceType.UNIVER_DOC, UniverInstanceType.UNIVER_SLIDE], + businesses: [ + UniverInstanceType.UNIVER_SHEET, + UniverInstanceType.UNIVER_DOC, + UniverInstanceType.UNIVER_SLIDE, + UniverInstanceType.UNIVER_BOARD, + ], onLoad: (_unitId, resource) => { for (const key in resource) { this._permissionMap.set(key, resource[key]); @@ -333,7 +338,7 @@ export class AuthzIoLocalService implements IAuthzIoService { return undefined; } - async putCollaborators(config: IPutCollaboratorsRequest): Promise { + async putCollaborators(_config: IPutCollaboratorsRequest): Promise { return undefined; } From cf9c015ae30ef3c9a3076c5ae35518540dce68c8 Mon Sep 17 00:00:00 2001 From: Univer Date: Tue, 16 Jun 2026 07:05:02 +0800 Subject: [PATCH 3/6] feat(core): load board resources by lifecycle --- .../authz-resource.integration.spec.ts | 24 +++- .../src/services/__tests__/resources.spec.ts | 105 ++++++++++++++++++ .../resource-loader.service.ts | 17 +++ 3 files changed, 145 insertions(+), 1 deletion(-) diff --git a/packages/core/src/services/__tests__/authz-resource.integration.spec.ts b/packages/core/src/services/__tests__/authz-resource.integration.spec.ts index 25e9fce90d2a..15006b57954f 100644 --- a/packages/core/src/services/__tests__/authz-resource.integration.spec.ts +++ b/packages/core/src/services/__tests__/authz-resource.integration.spec.ts @@ -177,7 +177,7 @@ describe('Authz/resource integration', () => { expect(reloaded[0].actions).toEqual([{ action: UnitAction.Edit, allowed: false }]); }); - it('should persist permission resources for board units', async () => { + it('should persist and reload permission resources for board units', async () => { const injector = univer.__getInjector(); const authzIoService = injector.get(IAuthzIoService); const resourceLoaderService = injector.get(IResourceLoaderService); @@ -202,6 +202,28 @@ describe('Authz/resource integration', () => { const authzResource = snapshot?.resources?.find((resource) => resource.name === 'SHEET_AuthzIoMockService_PLUGIN'); expect(authzResource?.data).toContain(objectID); + + expect(univerInstanceService.disposeUnit(board.getUnitId())).toBe(true); + + const unloaded = await authzIoService.list({ + unitID: board.getUnitId(), + objectIDs: [objectID], + actions: [UnitAction.Edit], + }); + + expect(unloaded[0].name).toBe(''); + expect(unloaded[0].actions).toEqual([{ action: UnitAction.Edit, allowed: true }]); + + univer.createUnit(UniverInstanceType.UNIVER_BOARD, snapshot!); + + const reloaded = await authzIoService.list({ + unitID: board.getUnitId(), + objectIDs: [objectID], + actions: [UnitAction.Edit], + }); + + expect(reloaded[0].name).toBe('Board permission'); + expect(reloaded[0].actions).toEqual([{ action: UnitAction.Edit, allowed: true }]); }); it('should expose current user data consistently through mention and user services', async () => { diff --git a/packages/core/src/services/__tests__/resources.spec.ts b/packages/core/src/services/__tests__/resources.spec.ts index 92cca93f8042..fa2d239aec4e 100644 --- a/packages/core/src/services/__tests__/resources.spec.ts +++ b/packages/core/src/services/__tests__/resources.spec.ts @@ -49,6 +49,12 @@ interface ITestSlideData { resources?: IResources; } +interface ITestBoardData { + id: string; + name?: string; + resources?: IResources; +} + class MockSlideUnit extends UnitModel { override type = UniverInstanceType.UNIVER_SLIDE; override name$ = new BehaviorSubject(''); @@ -86,6 +92,43 @@ class MockSlideUnit extends UnitModel { override setRev(): void { } } +class MockBoardUnit extends UnitModel { + override readonly type = UniverInstanceType.UNIVER_BOARD; + override name$ = new BehaviorSubject(''); + private readonly _snapshot: ITestBoardData; + + constructor(snapshot: Partial = {}) { + super(); + this._snapshot = { + id: 'board-resource', + name: '', + ...snapshot, + }; + this.name$.next(this._snapshot.name ?? ''); + } + + override getUnitId(): string { + return this._snapshot.id; + } + + override setName(name: string): void { + this._snapshot.name = name; + this.name$.next(name); + } + + override getSnapshot(): ITestBoardData { + return this._snapshot; + } + + override getRev(): number { + return 1; + } + + override incrementRev(): void { } + + override setRev(): void { } +} + describe('Test resources service', () => { let univer: Univer; @@ -232,6 +275,68 @@ describe('Test resources service', () => { expect(loads).toEqual([['slide-late-resource', 'late']]); }); + it('should load and unload board resources through the real unit lifecycle', () => { + const injector = univer.__getInjector(); + const resourceManagerService = injector.get(IResourceManagerService); + const resourceLoaderService = injector.get(IResourceLoaderService); + const univerInstanceService = injector.get(IUniverInstanceService); + const pluginName = 'BOARD_TEST_PLUGIN' as never; + const loads: Array<[string, string]> = []; + const unloads: string[] = []; + + univerInstanceService.registerCtorForType(UniverInstanceType.UNIVER_BOARD, MockBoardUnit); + resourceManagerService.registerPluginResource<{ kind: string }>({ + pluginName, + businesses: [UniverInstanceType.UNIVER_BOARD], + onLoad: (unitId, resource) => loads.push([unitId, resource.kind]), + onUnLoad: (unitId) => unloads.push(unitId), + toJson: (unitId) => JSON.stringify({ kind: `saved:${unitId}` }), + parseJson: (bytes) => JSON.parse(bytes), + }); + + const board = univer.createUnit(UniverInstanceType.UNIVER_BOARD, { + id: 'board-resource', + resources: [ + { name: pluginName, data: '{"kind":"loaded"}' }, + ], + }); + + expect(loads).toContainEqual(['board-resource', 'loaded']); + expect(resourceLoaderService.saveUnit('board-resource')?.resources).toEqual([ + { name: pluginName, data: '{"kind":"saved:board-resource"}' }, + ]); + + expect(univerInstanceService.disposeUnit(board.getUnitId())).toBe(true); + expect(unloads).toEqual(['board-resource']); + }); + + it('should load resources for existing board units when hooks register later', () => { + const injector = univer.__getInjector(); + const resourceManagerService = injector.get(IResourceManagerService); + const univerInstanceService = injector.get(IUniverInstanceService); + const pluginName = 'BOARD_LATE_PLUGIN' as never; + const loads: Array<[string, string]> = []; + + univerInstanceService.registerCtorForType(UniverInstanceType.UNIVER_BOARD, MockBoardUnit); + univer.createUnit(UniverInstanceType.UNIVER_BOARD, { + id: 'board-late-resource', + resources: [ + { name: pluginName, data: '{"kind":"late"}' }, + ], + }); + + resourceManagerService.registerPluginResource<{ kind: string }>({ + pluginName, + businesses: [UniverInstanceType.UNIVER_BOARD], + onLoad: (unitId, resource) => loads.push([unitId, resource.kind]), + onUnLoad: () => undefined, + toJson: () => '{}', + parseJson: (bytes) => JSON.parse(bytes), + }); + + expect(loads).toEqual([['board-late-resource', 'late']]); + }); + it('should load array-shaped slide plugin resources through the unit lifecycle', () => { const injector = univer.__getInjector(); const resourceManagerService = injector.get(IResourceManagerService); diff --git a/packages/core/src/services/resource-loader/resource-loader.service.ts b/packages/core/src/services/resource-loader/resource-loader.service.ts index 498a2584c1f5..ef1658d93de8 100644 --- a/packages/core/src/services/resource-loader/resource-loader.service.ts +++ b/packages/core/src/services/resource-loader/resource-loader.service.ts @@ -72,6 +72,12 @@ export class ResourceLoaderService extends Disposable implements IResourceLoader }); break; } + case UniverInstanceType.UNIVER_BOARD: { + this._univerInstanceService.getAllUnitsForType>(UniverInstanceType.UNIVER_BOARD).forEach((board) => { + loadHookResource(hook, board.getUnitId(), board.getSnapshot().resources, 'Board'); + }); + break; + } case UniverInstanceType.UNIVER_SHEET: { this._univerInstanceService.getAllUnitsForType(UniverInstanceType.UNIVER_SHEET).forEach((workbook) => { loadHookResource(hook, workbook.getUnitId(), workbook.getSnapshot().resources, 'Workbook'); @@ -108,6 +114,12 @@ export class ResourceLoaderService extends Disposable implements IResourceLoader this._resourceManagerService.loadResources(slide.getUnitId(), slide.getSnapshot().resources); }) ); + this.disposeWithMe( + this._univerInstanceService.getTypeOfUnitAdded$>(UniverInstanceType.UNIVER_BOARD).subscribe((event) => { + const { unit: board } = event; + this._resourceManagerService.loadResources(board.getUnitId(), board.getSnapshot().resources); + }) + ); this.disposeWithMe( this._univerInstanceService.getTypeOfUnitDisposed$(UniverInstanceType.UNIVER_SHEET).subscribe((workbook) => { @@ -125,6 +137,11 @@ export class ResourceLoaderService extends Disposable implements IResourceLoader this._resourceManagerService.unloadResources(slide.getUnitId(), UniverInstanceType.UNIVER_SLIDE); }) ); + this.disposeWithMe( + this._univerInstanceService.getTypeOfUnitDisposed$(UniverInstanceType.UNIVER_BOARD).subscribe((board) => { + this._resourceManagerService.unloadResources(board.getUnitId(), UniverInstanceType.UNIVER_BOARD); + }) + ); } saveUnit(unitId: string) { From c906c31e78ae6c4a3310d915e0673a262dd24d2d Mon Sep 17 00:00:00 2001 From: Univer Date: Thu, 25 Jun 2026 01:13:52 +0800 Subject: [PATCH 4/6] fix: support explicit locale exports --- .../shared/tsdown/utils/cleanup-pkg.spec.ts | 160 ++++++++++++++++++ common/shared/tsdown/utils/cleanup-pkg.ts | 57 +++++-- .../design/src/components/button/Button.tsx | 22 ++- .../design/src/components/switch/Switch.tsx | 6 +- .../switch/__tests__/index.spec.tsx | 11 +- 5 files changed, 237 insertions(+), 19 deletions(-) create mode 100644 common/shared/tsdown/utils/cleanup-pkg.spec.ts diff --git a/common/shared/tsdown/utils/cleanup-pkg.spec.ts b/common/shared/tsdown/utils/cleanup-pkg.spec.ts new file mode 100644 index 000000000000..0256a8852dc5 --- /dev/null +++ b/common/shared/tsdown/utils/cleanup-pkg.spec.ts @@ -0,0 +1,160 @@ +/** + * 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 + * + * https://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 { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import { afterEach, describe, expect, it } from 'vitest'; +import { cleanupPackageJson } from './cleanup-pkg'; + +function writeJson(filePath: string, value: unknown) { + writeFileSync(filePath, `${JSON.stringify(value, null, 4)}\n`); +} + +function readPackageJson(packageDir: string) { + return JSON.parse(readFileSync(path.join(packageDir, 'package.json'), 'utf8')) as { + exports?: Record; + publishConfig?: { exports?: Record }; + }; +} + +function createPackageDir(packageJson: Record) { + const packageDir = mkdtempSync(path.join(tmpdir(), 'univer-cleanup-pkg-')); + + mkdirSync(path.join(packageDir, 'src'), { recursive: true }); + writeFileSync(path.join(packageDir, 'src/index.ts'), 'export const value = 1;\n'); + writeJson(path.join(packageDir, 'package.json'), packageJson); + + return packageDir; +} + +describe('cleanupPackageJson publish manifest', () => { + const packageDirs: string[] = []; + + afterEach(() => { + while (packageDirs.length > 0) { + const packageDir = packageDirs.pop(); + if (packageDir) { + rmSync(packageDir, { force: true, recursive: true }); + } + } + }); + + it('keeps wildcard publish exports by default', () => { + const packageJson = { + name: '@univerjs-pro/default-wildcards', + version: '0.0.0', + dependencies: {}, + devDependencies: {}, + }; + const packageDir = createPackageDir(packageJson); + packageDirs.push(packageDir); + + cleanupPackageJson(packageDir, packageJson); + + const nextPackageJson = readPackageJson(packageDir); + + expect(nextPackageJson.publishConfig?.exports).toHaveProperty('./*'); + expect(nextPackageJson.publishConfig?.exports).toHaveProperty('./lib/*'); + }); + + it('omits wildcard publish exports when package opts out', () => { + const packageJson = { + name: '@univerjs-pro/explicit-entries', + version: '0.0.0', + dependencies: {}, + devDependencies: {}, + univerCli: { + publishWildcardExports: false, + }, + }; + const packageDir = createPackageDir(packageJson); + packageDirs.push(packageDir); + + mkdirSync(path.join(packageDir, 'src/facade'), { recursive: true }); + writeFileSync(path.join(packageDir, 'src/facade/index.ts'), 'export const facade = 1;\n'); + + cleanupPackageJson(packageDir, packageJson); + + const nextPackageJson = readPackageJson(packageDir); + + expect(nextPackageJson.publishConfig?.exports).toHaveProperty('.'); + expect(nextPackageJson.publishConfig?.exports).toHaveProperty('./facade'); + expect(nextPackageJson.publishConfig?.exports).toHaveProperty('./lib/facade'); + expect(Object.hasOwn(nextPackageJson.publishConfig?.exports ?? {}, './*')).toBe(false); + expect(Object.hasOwn(nextPackageJson.publishConfig?.exports ?? {}, './lib/*')).toBe(false); + }); + + it('keeps locale exports explicit when package opts out of wildcard publish exports', () => { + const packageJson = { + name: '@univerjs-pro/explicit-locale-entries', + version: '0.0.0', + dependencies: {}, + devDependencies: {}, + exports: { + '.': './src/index.ts', + './locale/en-US': './src/locale/en-US.ts', + './locale/zh-CN': './src/locale/zh-CN.ts', + }, + univerCli: { + publishWildcardExports: false, + }, + }; + const packageDir = createPackageDir(packageJson); + packageDirs.push(packageDir); + + mkdirSync(path.join(packageDir, 'src/locale'), { recursive: true }); + writeFileSync(path.join(packageDir, 'src/locale/en-US.ts'), 'export default {};\n'); + writeFileSync(path.join(packageDir, 'src/locale/zh-CN.ts'), 'export default {};\n'); + + cleanupPackageJson(packageDir, packageJson); + + const nextPackageJson = readPackageJson(packageDir); + + expect(nextPackageJson.exports).toHaveProperty('./locale/en-US'); + expect(nextPackageJson.exports).toHaveProperty('./locale/zh-CN'); + expect(nextPackageJson.publishConfig?.exports).toHaveProperty('./locale/en-US'); + expect(nextPackageJson.publishConfig?.exports).toHaveProperty('./locale/zh-CN'); + expect(Object.hasOwn(nextPackageJson.exports ?? {}, './locale/*')).toBe(false); + expect(Object.hasOwn(nextPackageJson.publishConfig?.exports ?? {}, './locale/*')).toBe(false); + }); + + it('removes stale locale wildcard source exports when opt-out packages no longer have locale files', () => { + const packageJson = { + name: '@univerjs-pro/stale-locale-wildcard', + version: '0.0.0', + dependencies: {}, + devDependencies: {}, + exports: { + '.': './src/index.ts', + './locale/*': './src/locale/*.ts', + }, + univerCli: { + publishWildcardExports: false, + }, + }; + const packageDir = createPackageDir(packageJson); + packageDirs.push(packageDir); + + cleanupPackageJson(packageDir, packageJson); + + const nextPackageJson = readPackageJson(packageDir); + + expect(nextPackageJson.exports).toHaveProperty('.'); + expect(Object.hasOwn(nextPackageJson.exports ?? {}, './locale/*')).toBe(false); + expect(Object.hasOwn(nextPackageJson.publishConfig?.exports ?? {}, './locale/*')).toBe(false); + }); +}); diff --git a/common/shared/tsdown/utils/cleanup-pkg.ts b/common/shared/tsdown/utils/cleanup-pkg.ts index 1196491cbf6e..3fd24565390d 100644 --- a/common/shared/tsdown/utils/cleanup-pkg.ts +++ b/common/shared/tsdown/utils/cleanup-pkg.ts @@ -20,6 +20,9 @@ type CleanupPackageJson = IPackageJson & { main: string; module: string; }; + univerCli?: { + publishWildcardExports?: boolean; + }; }; interface IDerivedDependencyGroups { dependencies: StringMap; @@ -357,8 +360,16 @@ function deriveDependencyGroups(packageDir: string, packageJson: IPackageJson): } function applyPublishManifest(pkg: CleanupPackageJson, packageDir: string) { - const hasLocales = fs.existsSync(path.resolve(packageDir, 'src/locale')); + const localeDir = path.resolve(packageDir, 'src/locale'); + const localeNames = fs.existsSync(localeDir) + ? fs.readdirSync(localeDir) + .filter((fileName) => path.extname(fileName) === '.ts') + .map((fileName) => path.basename(fileName, '.ts')) + .sort((left, right) => left.localeCompare(right)) + : []; + const hasLocales = localeNames.length > 0; const hasFacade = fs.existsSync(path.resolve(packageDir, 'src/facade/index.ts')); + const publishWildcardExports = pkg.univerCli?.publishWildcardExports !== false; pkg.publishConfig = { access: 'public', @@ -370,23 +381,41 @@ function applyPublishManifest(pkg: CleanupPackageJson, packageDir: string) { require: './lib/cjs/index.js', types: './lib/types/index.d.ts', }, - './*': { - import: './lib/es/*', - require: './lib/cjs/*', - types: './lib/types/index.d.ts', - }, }, }; + if (publishWildcardExports) { + pkg.publishConfig.exports['./*'] = { + import: './lib/es/*', + require: './lib/cjs/*', + types: './lib/types/index.d.ts', + }; + } + pkg.exports ||= {}; + if (!publishWildcardExports) { + delete pkg.exports['./locale/*']; + } + if (hasLocales) { - pkg.exports['./locale/*'] = './src/locale/*.ts'; - pkg.publishConfig.exports['./locale/*'] = { - import: './lib/es/locale/*.js', - require: './lib/cjs/locale/*.js', - types: './lib/types/locale/*.d.ts', - }; + if (publishWildcardExports) { + pkg.exports['./locale/*'] = './src/locale/*.ts'; + pkg.publishConfig.exports['./locale/*'] = { + import: './lib/es/locale/*.js', + require: './lib/cjs/locale/*.js', + types: './lib/types/locale/*.d.ts', + }; + } else { + for (const localeName of localeNames) { + pkg.exports[`./locale/${localeName}`] = `./src/locale/${localeName}.ts`; + pkg.publishConfig.exports[`./locale/${localeName}`] = { + import: `./lib/es/locale/${localeName}.js`, + require: `./lib/cjs/locale/${localeName}.js`, + types: `./lib/types/locale/${localeName}.d.ts`, + }; + } + } } if (hasFacade) { @@ -399,7 +428,9 @@ function applyPublishManifest(pkg: CleanupPackageJson, packageDir: string) { pkg.publishConfig.exports['./lib/facade'] = pkg.publishConfig.exports['./facade']; } - pkg.publishConfig.exports['./lib/*'] = './lib/*'; + if (publishWildcardExports) { + pkg.publishConfig.exports['./lib/*'] = './lib/*'; + } } function assignPeerDependencies(pkg: CleanupPackageJson, peerDeps: StringMap) { diff --git a/packages/design/src/components/button/Button.tsx b/packages/design/src/components/button/Button.tsx index 43246e684e11..8a28e6360ece 100644 --- a/packages/design/src/components/button/Button.tsx +++ b/packages/design/src/components/button/Button.tsx @@ -15,7 +15,7 @@ */ import type { VariantProps } from 'class-variance-authority'; -import type { ButtonHTMLAttributes } from 'react'; +import type { ButtonHTMLAttributes, ComponentPropsWithoutRef } from 'react'; import { Slot } from '@radix-ui/react-slot'; import { cva } from 'class-variance-authority'; import { forwardRef } from 'react'; @@ -94,10 +94,24 @@ export interface IButtonProps export const Button = forwardRef( ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : 'button'; + const buttonClassName = clsx(buttonVariants({ variant, size, className })); + + if (asChild) { + const slotProps = props as ComponentPropsWithoutRef; + + return ( + + ); + } + return ( - void; + inputProps?: Pick, 'aria-label' | 'aria-labelledby'>; } const Switch = (props: ISwitchProps) => { - const { defaultChecked = false, onChange } = props; + const { defaultChecked = false, inputProps, onChange } = props; const [checked, setChecked] = useState(defaultChecked); const handleChange = () => { @@ -39,7 +41,9 @@ const Switch = (props: ISwitchProps) => {