From af9a734806a1541ac8c32b7ea789df0ed313ef3f Mon Sep 17 00:00:00 2001 From: "lixuefei.1313" Date: Tue, 21 Jul 2026 21:07:09 +0800 Subject: [PATCH 1/5] perf: optimize shared state legend mount --- .../electron/legend/discrete.test.ts | 66 +++++++++++++ .../src/legend/discrete/discrete.ts | 94 +++++++++++++++---- .../unit/graphic/graphic-state.test.ts | 49 ++++++++++ .../graphic/shared-state-batch-mount.test.ts | 91 ++++++++++++++++++ packages/vrender-core/src/graphic/graphic.ts | 73 ++++++++++++-- packages/vrender-core/src/graphic/group.ts | 70 +++++++++++--- .../vrender-core/src/graphic/node-tree.ts | 7 +- packages/vrender-core/src/graphic/richtext.ts | 5 +- .../vrender-core/src/interface/graphic.ts | 6 +- .../vrender-core/src/interface/node-tree.ts | 7 +- 10 files changed, 423 insertions(+), 45 deletions(-) create mode 100644 packages/vrender-core/__tests__/unit/graphic/shared-state-batch-mount.test.ts diff --git a/packages/vrender-components/__tests__/electron/legend/discrete.test.ts b/packages/vrender-components/__tests__/electron/legend/discrete.test.ts index 44a504d49..1fc0e3150 100644 --- a/packages/vrender-components/__tests__/electron/legend/discrete.test.ts +++ b/packages/vrender-components/__tests__/electron/legend/discrete.test.ts @@ -73,6 +73,72 @@ describe('DiscreteLegend', () => { // }); }); + it('should reuse static state definitions without coupling item selection', () => { + const legend = new DiscreteLegend({ + select: true, + defaultSelected: ['A'], + item: { + shape: { + visible: false + }, + label: { + style: { + fill: 'black' + }, + state: { + selected: { + fill: 'red' + }, + unSelected: { + fill: 'gray' + } + } + } + }, + items: [ + { label: 'A', shape: { fill: 'red' } }, + { label: 'B', shape: { fill: 'blue' } } + ] + }); + + stage.defaultLayer.add(legend as unknown as IGraphic); + stage.render(); + + const items = ((legend as any)._itemsContainer.getChildren() as IGroup[]).slice(0, 2); + const labels = items.map( + item => (item.getChildren()[0] as IGroup).find(node => node.name === 'legendItemLabel', false) as IGraphic + ); + + expect(labels[0].states).toBe(labels[1].states); + expect((labels[0] as any).compiledStateDefinitions).toBe((labels[1] as any).compiledStateDefinitions); + expect(labels[0].attribute.fill).toBe('red'); + expect(labels[1].attribute.fill).toBe('gray'); + + legend.setSelected(['B']); + + expect(labels[0].attribute.fill).toBe('gray'); + expect(labels[1].attribute.fill).toBe('red'); + + legend.setAttributes({ + item: { + label: { + state: { + selected: { fill: 'green' }, + unSelected: { fill: 'silver' } + } + } + } + } as any); + + const rerenderedItems = ((legend as any)._itemsContainer.getChildren() as IGroup[]).slice(0, 2); + const rerenderedLabels = rerenderedItems.map( + item => (item.getChildren()[0] as IGroup).find(node => node.name === 'legendItemLabel', false) as IGraphic + ); + + expect(rerenderedLabels[0].attribute.fill).toBe('green'); + expect(rerenderedLabels[1].attribute.fill).toBe('silver'); + }); + it('should return its own width when its own width does not exceed maxWidth', () => { const legend = new DiscreteLegend({ layout: 'vertical', diff --git a/packages/vrender-components/src/legend/discrete/discrete.ts b/packages/vrender-components/src/legend/discrete/discrete.ts index 3ee786c8e..8ea3e8e11 100644 --- a/packages/vrender-components/src/legend/discrete/discrete.ts +++ b/packages/vrender-components/src/legend/discrete/discrete.ts @@ -33,6 +33,7 @@ import type { IRect } from '@visactor/vrender-core'; // eslint-disable-next-line no-duplicate-imports +import { StateDefinitionCompiler } from '@visactor/vrender-core'; import { graphicCreator } from '../../util/graphic-creator'; import { LegendBase } from '../base'; import { Pager } from '../../pager'; @@ -62,6 +63,11 @@ import { createTextGraphicByType } from '../../util'; import { ScrollBar, type ScrollBarAttributes } from '../../scrollbar'; import { commitUpdateAnimationTarget } from '../../animation/static-truth'; +type StateDefinitionsCacheEntry = { + definitions: Record; + compiledDefinitions: Map; +}; + const DEFAULT_STATES = { [LegendStateValue.focus]: {}, [LegendStateValue.selected]: {}, @@ -105,6 +111,8 @@ export class DiscreteLegend extends LegendBase { startStops: ILinearGradient['stops']; endStops: ILinearGradient['stops']; }; + // Weak keys avoid retaining caller-owned state definitions after a legend is released. + private _stateDefinitionsCache = new WeakMap(); static defaultAttributes: Partial = { layout: 'horizontal', @@ -200,6 +208,7 @@ export class DiscreteLegend extends LegendBase { } render() { + this._stateDefinitionsCache = new WeakMap(); super.render(); this._lastActiveItem = null; } @@ -562,7 +571,14 @@ export class DiscreteLegend extends LegendBase { y: 0, ...backgroundStyle.style }); - this._appendDataToShape(itemGroup, LEGEND_ELEMENT_NAME.item, item, itemGroup, backgroundStyle.state); + this._appendDataToShape( + itemGroup, + LEGEND_ELEMENT_NAME.item, + item, + itemGroup, + backgroundStyle.state, + backgroundStyle.reuseStateDefinitions + ); } itemGroup.id = `${id ?? label}-${index}`; @@ -607,7 +623,7 @@ export class DiscreteLegend extends LegendBase { (shapeStyle.state[key] as ISymbolGraphicAttribute).stroke = color as string; } }); - this._appendDataToShape(itemShape, LEGEND_ELEMENT_NAME.itemShape, item, itemGroup, shapeStyle.state); + this._appendDataToShape(itemShape, LEGEND_ELEMENT_NAME.itemShape, item, itemGroup, shapeStyle.state, false); itemShape.addState(isSelected ? LegendStateValue.selected : LegendStateValue.unSelected); innerGroup.add(itemShape); @@ -645,7 +661,14 @@ export class DiscreteLegend extends LegendBase { const labelShape = createTextGraphicByType(labelAttributes); - this._appendDataToShape(labelShape, LEGEND_ELEMENT_NAME.itemLabel, item, itemGroup, labelStyle.state); + this._appendDataToShape( + labelShape, + LEGEND_ELEMENT_NAME.itemLabel, + item, + itemGroup, + labelStyle.state, + labelStyle.reuseStateDefinitions + ); labelShape.addState(isSelected ? LegendStateValue.selected : LegendStateValue.unSelected); innerGroup.add(labelShape); const labelSpace = get(labelAttr, 'space', DEFAULT_LABEL_SPACE); @@ -665,7 +688,14 @@ export class DiscreteLegend extends LegendBase { const valueShape = createTextGraphicByType(valueAttributes); - this._appendDataToShape(valueShape, LEGEND_ELEMENT_NAME.itemValue, item, itemGroup, valueStyle.state); + this._appendDataToShape( + valueShape, + LEGEND_ELEMENT_NAME.itemValue, + item, + itemGroup, + valueStyle.state, + valueStyle.reuseStateDefinitions + ); valueShape.addState(isSelected ? LegendStateValue.selected : LegendStateValue.unSelected); if (this._itemWidthByUser) { @@ -1566,11 +1596,38 @@ export class DiscreteLegend extends LegendBase { return selectedData; } - private _appendDataToShape(shape: any, name: string, data: any, delegateShape: any, states: any = {}) { + private _appendDataToShape( + shape: any, + name: string, + data: any, + delegateShape: any, + states?: Record, + reuseStateDefinitions: boolean = true + ) { shape.name = name; shape.data = data; shape.delegate = delegateShape; - shape.states = merge({}, DEFAULT_STATES, states); + if (!reuseStateDefinitions) { + shape.states = merge({}, DEFAULT_STATES, states); + return; + } + + const source = states ?? DEFAULT_STATES; + const cached = this._stateDefinitionsCache.get(source); + if (cached) { + shape.states = cached.definitions; + (shape as any).setStateDefinitionsWithCompiled(cached.definitions, cached.compiledDefinitions); + return; + } + + const definitions = states ? merge({}, DEFAULT_STATES, states) : DEFAULT_STATES; + const entry = { + definitions, + compiledDefinitions: new StateDefinitionCompiler().compile(definitions) + }; + this._stateDefinitionsCache.set(source, entry); + shape.states = entry.definitions; + (shape as any).setStateDefinitionsWithCompiled(entry.definitions, entry.compiledDefinitions); } private _dispatchLegendEvent(eventName: string, legendItem: any, event: FederatedPointerEvent) { @@ -1609,23 +1666,28 @@ export class DiscreteLegend extends LegendBase { } if (config.state) { - newConfig.state = {}; - - Object.keys(config.state).forEach(key => { - if (config.state[key]) { - if (isFunction(config.state[key])) { - newConfig.state[key] = config.state[key](item, isSelected, index, items); - } else { - newConfig.state[key] = config.state[key]; + const stateKeys = Object.keys(config.state); + const hasStateFunction = stateKeys.some(key => isFunction(config.state[key])); + newConfig.reuseStateDefinitions = !hasStateFunction; + if (!hasStateFunction) { + newConfig.state = config.state; + } else { + newConfig.state = {}; + stateKeys.forEach(key => { + if (config.state[key]) { + newConfig.state[key] = isFunction(config.state[key]) + ? config.state[key](item, isSelected, index, items) + : config.state[key]; } - } - }); + }); + } } return newConfig; } release(): void { + this._stateDefinitionsCache = new WeakMap(); super.release(); this.removeAllEventListeners(); } diff --git a/packages/vrender-core/__tests__/unit/graphic/graphic-state.test.ts b/packages/vrender-core/__tests__/unit/graphic/graphic-state.test.ts index d3500a628..ab6d69eca 100644 --- a/packages/vrender-core/__tests__/unit/graphic/graphic-state.test.ts +++ b/packages/vrender-core/__tests__/unit/graphic/graphic-state.test.ts @@ -1,7 +1,12 @@ import { createRect } from '../../../src/graphic/rect'; import { AttributeUpdateType } from '../../../src/common/enums'; +import { StateDefinitionCompiler } from '../../../src/graphic/state/state-definition-compiler'; describe('Graphic useStates', () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + const createGraphic = () => { const graphic = createRect({ x: 0, @@ -51,6 +56,50 @@ describe('Graphic useStates', () => { expect(graphic.normalAttrs).toEqual((graphic as any).baseAttributes); }); + test('should reuse compiled local definitions for graphics with the same state source', () => { + const states = { + selected: { + fill: 'red' + } + } as any; + const first = createGraphic(); + const second = createGraphic(); + const compile = jest.spyOn(StateDefinitionCompiler.prototype, 'compile'); + const compiledDefinitions = new StateDefinitionCompiler().compile(states); + (first as any).setStateDefinitionsWithCompiled(states, compiledDefinitions); + (second as any).setStateDefinitionsWithCompiled(states, compiledDefinitions); + + first.useStates(['selected'], false); + second.useStates(['selected'], false); + + expect(compile).toHaveBeenCalledTimes(1); + expect(first.attribute.fill).toBe('red'); + expect(second.attribute.fill).toBe('red'); + + first.useStates([], false); + + expect(first.attribute.fill).toBe('blue'); + expect(second.attribute.fill).toBe('red'); + }); + + test('should compile local definitions per graphic without a precompiled definition', () => { + const states = { + selected: { + fill: 'red' + } + } as any; + const first = createGraphic(); + const second = createGraphic(); + first.states = states; + second.states = states; + const compile = jest.spyOn(StateDefinitionCompiler.prototype, 'compile'); + + first.useStates(['selected'], false); + second.useStates(['selected'], false); + + expect(compile).toHaveBeenCalledTimes(2); + }); + test('should sync target states through a single optimized call', () => { const graphic = createGraphic(); graphic.states = { diff --git a/packages/vrender-core/__tests__/unit/graphic/shared-state-batch-mount.test.ts b/packages/vrender-core/__tests__/unit/graphic/shared-state-batch-mount.test.ts new file mode 100644 index 000000000..72f7ea414 --- /dev/null +++ b/packages/vrender-core/__tests__/unit/graphic/shared-state-batch-mount.test.ts @@ -0,0 +1,91 @@ +import { Group, createGroup } from '../../../src/graphic/group'; +import { createRect } from '../../../src/graphic/rect'; +import { createSharedStateTestStage } from './shared-state-test-utils'; + +describe('shared state batch mount', () => { + test('should preserve custom parent-tree synchronization hooks', () => { + const stage = createSharedStateTestStage(); + const group = createGroup({}); + const inheritedHandler = group.onParentSharedStateTreeChanged; + const customHandler = jest.fn((nextStage, nextLayer, nextScope) => { + inheritedHandler.call(group, nextStage, nextLayer, nextScope); + }); + group.onParentSharedStateTreeChanged = customHandler; + + stage.appendChild(group); + + expect(customHandler).toHaveBeenCalledTimes(1); + expect(group.stage).toBe(stage); + }); + + test('should bind a detached multi-level subtree once while preserving inherited states and later tree edits', () => { + const stage = createSharedStateTestStage(); + const stateScope = createGroup({}); + const itemsContainer = createGroup({}); + const stateDefinitions = { + selected: { fill: 'red', opacity: 0.5 } + }; + const itemGroups = [] as ReturnType[]; + const itemShapes = [] as ReturnType[]; + + (stateScope as any).sharedStateDefinitions = stateDefinitions; + stateScope.appendChild(itemsContainer); + + for (let index = 0; index < 100; index++) { + const itemGroup = createGroup({}); + const contentGroup = createGroup({}); + const shape = createRect({ x: index, y: 0, width: 1, height: 1, fill: 'black', opacity: 1 }); + + shape.states = { selected: { fill: 'local-selected' } }; + itemGroup.currentStates = ['selected']; + shape.currentStates = ['selected']; + contentGroup.appendChild(shape); + itemGroup.appendChild(contentGroup); + itemsContainer.appendChild(itemGroup); + itemGroups.push(itemGroup); + itemShapes.push(shape); + } + + const syncChildSharedStateTreeBinding = jest.spyOn(Group.prototype as any, 'syncChildSharedStateTreeBinding'); + const onParentSharedStateTreeChanged = jest.spyOn(Group.prototype, 'onParentSharedStateTreeChanged'); + + stage.appendChild(stateScope); + itemShapes[0].refreshSharedStateBeforeRender(); + + expect(syncChildSharedStateTreeBinding).toHaveBeenCalledTimes(1); + expect(onParentSharedStateTreeChanged).not.toHaveBeenCalled(); + expect((itemShapes[0] as any).boundSharedStateScope).toBe((stateScope as any).sharedStateScope); + expect(itemShapes[0].attribute.fill).toBe('red'); + expect(itemShapes[0].attribute.opacity).toBe(0.5); + + (stateScope as any).sharedStateDefinitions = { + selected: { fill: 'green', opacity: 0.75 } + }; + itemShapes[0].refreshSharedStateBeforeRender(); + + expect(itemShapes[0].attribute.fill).toBe('green'); + expect(itemShapes[0].attribute.opacity).toBe(0.75); + + const addedItem = createGroup({}); + const addedContent = createGroup({}); + const addedShape = createRect({ x: 101, y: 0, width: 1, height: 1, fill: 'black', opacity: 1 }); + addedShape.states = { selected: { fill: 'local-selected' } }; + addedItem.currentStates = ['selected']; + addedShape.currentStates = ['selected']; + addedContent.appendChild(addedShape); + addedItem.appendChild(addedContent); + + itemsContainer.appendChild(addedItem); + addedShape.refreshSharedStateBeforeRender(); + + expect((addedShape as any).boundSharedStateScope).toBe((stateScope as any).sharedStateScope); + expect(addedShape.attribute.fill).toBe('green'); + expect(addedShape.attribute.opacity).toBe(0.75); + + itemsContainer.removeChild(itemGroups[0]); + + expect(itemGroups[0].stage).toBeNull(); + expect(itemShapes[0].stage).toBeNull(); + expect((itemShapes[0] as any).boundSharedStateScope).toBeUndefined(); + }); +}); diff --git a/packages/vrender-core/src/graphic/graphic.ts b/packages/vrender-core/src/graphic/graphic.ts index f7456939c..f6755ad6f 100644 --- a/packages/vrender-core/src/graphic/graphic.ts +++ b/packages/vrender-core/src/graphic/graphic.ts @@ -530,8 +530,10 @@ abstract class GraphicImpl = Partial> | undefined; } - protected syncSharedStateScopeBindingFromTree(markDirty: boolean = true): boolean { - const nextScope = this.resolveBoundSharedStateScope(); + protected syncSharedStateScopeBinding( + nextScope: SharedStateScope | SharedStateScope> | undefined, + markDirty: boolean = true + ): boolean { if (this.boundSharedStateScope === nextScope) { this.syncSharedStateActiveRegistrations(); return false; @@ -552,7 +554,21 @@ abstract class GraphicImpl = Partial> | null + ): boolean { + const nextScope = + inheritedSharedStateScope === undefined + ? this.resolveBoundSharedStateScope() + : inheritedSharedStateScope ?? undefined; + return this.syncSharedStateScopeBinding(nextScope, markDirty); + } + + protected syncSharedStateScopeBindingOnTreeChange( + markDirty: boolean = true, + inheritedSharedStateScope?: SharedStateScope> | null + ): boolean { if ( !this.currentStates?.length && !this.boundSharedStateScope && @@ -562,7 +578,7 @@ abstract class GraphicImpl = Partial = Partial { @@ -594,6 +614,21 @@ abstract class GraphicImpl = Partial>): boolean { + let scope = this.boundSharedStateScope; + let scopeCount = 0; + + while (scope) { + if (!previousScopes.has(scope)) { + return false; + } + scopeCount += 1; + scope = scope.parentScope as SharedStateScope | undefined; + } + + return scopeCount === previousScopes.size; + } + protected clearSharedStateActiveRegistrations(): void { const previousScopes = this.registeredActiveScopes; if (previousScopes) { @@ -611,13 +646,17 @@ abstract class GraphicImpl = Partial> | null + ): void { if (this.stage !== stage || this.layer !== layer) { - this.setStage(stage, layer); + this.setStage(stage, layer, inheritedSharedStateScope); return; } - this.syncSharedStateScopeBindingOnTreeChange(); + this.syncSharedStateScopeBindingOnTreeChange(true, inheritedSharedStateScope); } refreshSharedStateBeforeRender(): void { @@ -2025,6 +2064,20 @@ abstract class GraphicImpl = Partial, + compiledDefinitions: Map> + ): void { + this.states = definitions; + if (this.localStateDefinitionsSource !== definitions) { + this.localStateDefinitionsSource = definitions; + this.localStateDefinitionsVersion = (this.localStateDefinitionsVersion ?? 0) + 1; + } + this.compiledStateDefinitions = compiledDefinitions; + this.compiledStateDefinitionsCacheKey = `local:${this.localStateDefinitionsVersion}`; + } + protected getStateResolveBaseAttrs(): Partial { return (this.baseAttributes ?? this.attribute) as Partial; } @@ -2766,7 +2819,7 @@ abstract class GraphicImpl = Partial> | null) { const graphicService = stage?.graphicService ?? this.stage?.graphicService ?? application.graphicService; const previousStage = this.stage; if (this.stage !== stage || this.layer !== layer) { @@ -2778,7 +2831,7 @@ abstract class GraphicImpl = Partial = Partial implements IGroup { this.addUpdateBoundTag(); } - setStage(stage?: IStage, layer?: ILayer) { + setStage(stage?: IStage, layer?: ILayer, inheritedSharedStateScope?: SharedStateScope> | null) { const graphicService = stage?.graphicService ?? this.stage?.graphicService ?? application.graphicService; const needsSharedStateTreeSync = this.hasSharedStateDefinitions() || @@ -369,12 +369,12 @@ export class Group extends Graphic implements IGroup { this.layer = layer; if (needsSharedStateTreeSync) { this.ensureSharedStateScopeBound(); - this.syncSharedStateScopeBindingOnTreeChange(true); + this.syncSharedStateScopeBindingOnTreeChange(true, inheritedSharedStateScope); } this.setStageToShadowRoot(stage, layer); this._onSetStage && this._onSetStage(this, stage, layer); graphicService?.onSetStage?.(this, stage); - this.notifyChildrenSharedStateTreeChanged(); + this.notifyChildrenSharedStateTreeChanged(inheritedSharedStateScope); return; } @@ -384,10 +384,10 @@ export class Group extends Graphic implements IGroup { } if (needsSharedStateTreeSync) { this.ensureSharedStateScopeBound(); - this.syncSharedStateScopeBindingOnTreeChange(true); - this.notifyChildrenSharedStateTreeChanged(); + this.syncSharedStateScopeBindingOnTreeChange(true, inheritedSharedStateScope); + this.notifyChildrenSharedStateTreeChanged(inheritedSharedStateScope); } else if (layerChanged) { - this.notifyChildrenSharedStateTreeChanged(); + this.notifyChildrenSharedStateTreeChanged(inheritedSharedStateScope); } } /** @@ -520,25 +520,67 @@ export class Group extends Graphic implements IGroup { return !!this._sharedStateDefinitions && Object.keys(this._sharedStateDefinitions).length > 0; } - protected notifyChildrenSharedStateTreeChanged(): void { + protected resolveChildSharedStateScope( + inheritedSharedStateScope?: SharedStateScope> | null + ): SharedStateScope> | null { + if (this.sharedStateScope) { + return this.sharedStateScope; + } + if (inheritedSharedStateScope !== undefined) { + return inheritedSharedStateScope; + } + return this.resolveBoundSharedStateScope() ?? null; + } + + protected notifyChildrenSharedStateTreeChanged( + inheritedSharedStateScope?: SharedStateScope> | null + ): void { + const childSharedStateScope = this.resolveChildSharedStateScope(inheritedSharedStateScope); this.forEachChildren(item => { - this.syncChildSharedStateTreeBinding(item); + this.setStageToChild(item, childSharedStateScope); }); } - protected syncChildSharedStateTreeBinding(child: INode): void { - child.onParentSharedStateTreeChanged(this.stage, this.layer); + protected setStageToChild( + child: INode, + inheritedSharedStateScope?: SharedStateScope> | null + ): void { + const graphic = child as IGraphic; + if ( + typeof graphic.setStage === 'function' && + (child.onParentSharedStateTreeChanged === Graphic.prototype.onParentSharedStateTreeChanged || + child.onParentSharedStateTreeChanged === Group.prototype.onParentSharedStateTreeChanged) && + (graphic.stage !== this.stage || graphic.layer !== this.layer) + ) { + graphic.setStage(this.stage, this.layer, inheritedSharedStateScope); + return; + } + child.onParentSharedStateTreeChanged(this.stage, this.layer, inheritedSharedStateScope); + } + + protected syncChildSharedStateTreeBinding( + child: INode, + inheritedSharedStateScope?: SharedStateScope> | null + ): void { + if (inheritedSharedStateScope === undefined) { + inheritedSharedStateScope = this.resolveChildSharedStateScope(); + } + this.setStageToChild(child, inheritedSharedStateScope); } - onParentSharedStateTreeChanged(stage?: IStage, layer?: ILayer): void { + onParentSharedStateTreeChanged( + stage?: IStage, + layer?: ILayer, + inheritedSharedStateScope?: SharedStateScope> | null + ): void { if (this.stage !== stage || this.layer !== layer) { - this.setStage(stage, layer); + this.setStage(stage, layer, inheritedSharedStateScope); return; } this.ensureSharedStateScopeBound(); - this.syncSharedStateScopeBindingOnTreeChange(!!this.currentStates?.length); - this.notifyChildrenSharedStateTreeChanged(); + this.syncSharedStateScopeBindingOnTreeChange(!!this.currentStates?.length, inheritedSharedStateScope); + this.notifyChildrenSharedStateTreeChanged(inheritedSharedStateScope); } } diff --git a/packages/vrender-core/src/graphic/node-tree.ts b/packages/vrender-core/src/graphic/node-tree.ts index 14061ad46..d5301b77a 100644 --- a/packages/vrender-core/src/graphic/node-tree.ts +++ b/packages/vrender-core/src/graphic/node-tree.ts @@ -1,6 +1,7 @@ import { EventEmitter, Logger, isBoolean, isFunction, isObject, type Dict, type LooseFunction } from '@visactor/vutils'; import { Generator } from '../common/generator'; import type { ILayer, INode, IGroup, IStage } from '../interface'; +import type { SharedStateScope } from './state/shared-state-scope'; export class Node extends EventEmitter implements INode { parent: any; @@ -59,7 +60,11 @@ export class Node extends EventEmitter implements INode { this._count = 1; } - onParentSharedStateTreeChanged(_stage?: IStage, _layer?: ILayer): void { + onParentSharedStateTreeChanged( + _stage?: IStage, + _layer?: ILayer, + _inheritedSharedStateScope?: SharedStateScope> | null + ): void { return; } diff --git a/packages/vrender-core/src/graphic/richtext.ts b/packages/vrender-core/src/graphic/richtext.ts index 7cc11baf0..212a20354 100644 --- a/packages/vrender-core/src/graphic/richtext.ts +++ b/packages/vrender-core/src/graphic/richtext.ts @@ -27,6 +27,7 @@ import { RichTextIcon } from './richtext/icon'; import type { FederatedMouseEvent } from '../event'; import { application } from '../application'; import { RICHTEXT_NUMBER_TYPE } from './constants'; +import type { SharedStateScope } from './state/shared-state-scope'; let supportIntl = false; try { @@ -618,8 +619,8 @@ export class RichText extends Graphic implements IRic return new RichText({ ...this.attribute }); } - setStage(stage?: IStage, layer?: ILayer) { - super.setStage(stage, layer); + setStage(stage?: IStage, layer?: ILayer, inheritedSharedStateScope?: SharedStateScope> | null) { + super.setStage(stage, layer, inheritedSharedStateScope); const frameCache = this.getFrameCache(); // for (let i = 0; i < frameCache.icons.length; i++) { // const icon = frameCache.icons[i]; diff --git a/packages/vrender-core/src/interface/graphic.ts b/packages/vrender-core/src/interface/graphic.ts index d0e38b19a..a3f57d80c 100644 --- a/packages/vrender-core/src/interface/graphic.ts +++ b/packages/vrender-core/src/interface/graphic.ts @@ -853,7 +853,11 @@ export interface IGraphic = Partial void; - setStage: (stage?: IStage, layer?: ILayer) => void; + setStage: ( + stage?: IStage, + layer?: ILayer, + inheritedSharedStateScope?: SharedStateScope> | null + ) => void; detachStageForRelease: () => void; onSetStage: (cb: (g: IGraphic, stage: IStage) => void) => void; diff --git a/packages/vrender-core/src/interface/node-tree.ts b/packages/vrender-core/src/interface/node-tree.ts index 091bbb6ab..488d5ffb1 100644 --- a/packages/vrender-core/src/interface/node-tree.ts +++ b/packages/vrender-core/src/interface/node-tree.ts @@ -1,6 +1,7 @@ import type { IEventElement, Releaseable } from './common'; import type { ILayer } from './layer'; import type { IStage } from './stage'; +import type { SharedStateScope } from '../graphic/state/shared-state-scope'; export interface INode extends Releaseable, IEventElement { _prev?: INode; @@ -69,7 +70,11 @@ export interface INode extends Releaseable, IEventElement { /** * Rebinds stage/layer derived state when this node is attached to a different parent tree. */ - onParentSharedStateTreeChanged: (stage?: IStage, layer?: ILayer) => void; + onParentSharedStateTreeChanged: ( + stage?: IStage, + layer?: ILayer, + inheritedSharedStateScope?: SharedStateScope> | null + ) => void; /** * 从当前节点的父节点删除当前节点 */ From e97dd184d992102ef230500143f7643065a3752f Mon Sep 17 00:00:00 2001 From: "lixuefei.1313" Date: Mon, 3 Aug 2026 19:08:33 +0800 Subject: [PATCH 2/5] fix: respect lynx env in browser shared entry --- .../vrender/__tests__/types/entries-typing.ts | 14 +++ .../unit/shared-browser-entry.test.ts | 65 ++++++++--- .../vrender/src/entries/shared-browser.ts | 107 +++++++++++++++--- packages/vrender/src/entries/shared.ts | 51 ++++++++- 4 files changed, 203 insertions(+), 34 deletions(-) diff --git a/packages/vrender/__tests__/types/entries-typing.ts b/packages/vrender/__tests__/types/entries-typing.ts index 7977d4ef2..53a99b2c5 100644 --- a/packages/vrender/__tests__/types/entries-typing.ts +++ b/packages/vrender/__tests__/types/entries-typing.ts @@ -6,10 +6,21 @@ import { type TVRenderSharedAppHandle, type TVRenderSharedAppOptions } from '../../src/entries/shared'; +import { + acquireSharedVRenderApp as acquireBrowserConditionSharedVRenderApp, + type TVRenderSharedBrowserAppHandle +} from '../../src/entries/shared-browser'; const browserFactory: (options?: IEntryOptions) => IApp = createBrowserVRenderApp; const nodeFactory: (options?: IEntryOptions) => IApp = createNodeVRenderApp; const browserSharedHandle: TVRenderSharedAppHandle<'browser'> = acquireSharedVRenderApp({ env: 'browser' }); +const positionalLynxSharedHandle: TVRenderSharedAppHandle<'lynx'> = acquireSharedVRenderApp(undefined, 'lynx'); +const browserConditionDefaultHandle: TVRenderSharedBrowserAppHandle<'browser'> = + acquireBrowserConditionSharedVRenderApp(); +const browserConditionLynxHandle: TVRenderSharedBrowserAppHandle<'lynx'> = acquireBrowserConditionSharedVRenderApp( + undefined, + 'lynx' +); const lynxSharedOptions: TVRenderSharedAppOptions<'lynx'> = { env: 'lynx', key: 'main', @@ -21,4 +32,7 @@ const lynxSharedOptions: TVRenderSharedAppOptions<'lynx'> = { void browserFactory; void nodeFactory; void browserSharedHandle; +void positionalLynxSharedHandle; +void browserConditionDefaultHandle; +void browserConditionLynxHandle; void lynxSharedOptions; diff --git a/packages/vrender/__tests__/unit/shared-browser-entry.test.ts b/packages/vrender/__tests__/unit/shared-browser-entry.test.ts index b9eb0b685..2aa373b7e 100644 --- a/packages/vrender/__tests__/unit/shared-browser-entry.test.ts +++ b/packages/vrender/__tests__/unit/shared-browser-entry.test.ts @@ -6,27 +6,62 @@ declare const require: any; export {}; const emptyArray = (): never[] => []; +const SHARED_APP_REGISTRY_KEY = Symbol.for('visactor.vrender.sharedAppRegistry'); -describe('browser-only shared app entry', () => { +describe('browser-condition shared app entry', () => { beforeEach(() => { jest.resetModules(); + delete (globalThis as any)[SHARED_APP_REGISTRY_KEY]; }); - test('does not load non-browser env, full custom animate, or gif modules', () => { + test('routes an explicit lynx env to the Lynx app without initializing the browser environment', () => { + jest.isolateModules(() => { + const lynxApp = { + env: 'lynx', + release: jest.fn() + }; + const createBrowserApp = jest.fn(() => { + throw new Error('the Lynx shared app must not initialize the browser environment'); + }); + const createLynxVRenderApp = jest.fn(() => lynxApp); + + jest.doMock('@visactor/vrender-core/entries/browser', () => ({ + BrowserEntry: class BrowserEntry {}, + createBrowserApp + })); + jest.doMock('../../src/entries/miniapp', () => ({ createLynxVRenderApp })); + + const { acquireSharedVRenderApp } = require('../../src/entries/shared-browser'); + const handle = (acquireSharedVRenderApp as any)(undefined, 'lynx'); + + expect(handle.env).toBe('lynx'); + expect(handle.app).toBe(lynxApp); + expect(createLynxVRenderApp).toHaveBeenCalledTimes(1); + expect(createBrowserApp).not.toHaveBeenCalled(); + + handle.release(); + }); + }); + + test('keeps browser as the default and isolates browser and Lynx shared apps', () => { jest.isolateModules(() => { const legacyBindingContextMock = { getAll: jest.fn(emptyArray) }; - const forbiddenLoads: string[] = []; - const createBrowserApp = jest.fn(() => ({ + const browserApp = { + env: 'browser', registry: { renderer: { getAll: jest.fn(emptyArray), clear: jest.fn(), register: jest.fn() }, picker: { getAll: jest.fn(emptyArray), clear: jest.fn(), register: jest.fn() } }, release: jest.fn() - })); + }; + const lynxApp = { env: 'lynx', release: jest.fn() }; + const createBrowserApp = jest.fn(() => browserApp); + const createLynxVRenderApp = jest.fn(() => lynxApp); jest.doMock('@visactor/vrender-core/entries/browser', () => ({ createBrowserApp })); + jest.doMock('../../src/entries/miniapp', () => ({ createLynxVRenderApp })); jest.doMock('@visactor/vrender-core/legacy/bootstrap', () => ({ getLegacyBindingContext: jest.fn(() => legacyBindingContextMock) })); @@ -90,26 +125,30 @@ describe('browser-only shared app entry', () => { [ '@visactor/vrender-kits/env/node', '@visactor/vrender-kits/env/wx', - '@visactor/vrender-kits/env/lynx', '@visactor/vrender-kits/env/harmony', '@visactor/vrender-kits/env/browser', '@visactor/vrender-kits/register/register-gif', '@visactor/vrender-animate/custom/register' ].forEach(moduleName => { jest.doMock(moduleName, () => { - forbiddenLoads.push(moduleName); - return {}; + throw new Error(`${moduleName} should not be loaded by shared-browser`); }); }); - const { acquireSharedBrowserVRenderApp } = require('../../src/entries/shared-browser'); - const handle = acquireSharedBrowserVRenderApp({ env: 'browser', key: Symbol('browser-only') }); + const { acquireSharedVRenderApp } = require('../../src/entries/shared-browser'); + const browserHandle = acquireSharedVRenderApp(); + const lynxHandle = (acquireSharedVRenderApp as any)(undefined, 'lynx'); expect(createBrowserApp).toHaveBeenCalledTimes(1); expect(createBrowserApp).toHaveBeenCalledWith({}); - expect(handle.env).toBe('browser'); - expect(forbiddenLoads).toEqual([]); - handle.release(); + expect(browserHandle.env).toBe('browser'); + expect(browserHandle.app).toBe(browserApp); + expect(lynxHandle.env).toBe('lynx'); + expect(lynxHandle.app).toBe(lynxApp); + expect(browserHandle.app).not.toBe(lynxHandle.app); + + browserHandle.release(); + lynxHandle.release(); }); }); }); diff --git a/packages/vrender/src/entries/shared-browser.ts b/packages/vrender/src/entries/shared-browser.ts index 3817189e5..6eb67362b 100644 --- a/packages/vrender/src/entries/shared-browser.ts +++ b/packages/vrender/src/entries/shared-browser.ts @@ -1,6 +1,7 @@ import type { IApp, IEntryOptions, IEnvParamsMap } from '@visactor/vrender-core'; import { createBrowserApp } from '@visactor/vrender-core/entries/browser'; import { bootstrapVRenderSharedBrowserApp } from './bootstrap-browser'; +import { createLynxVRenderApp } from './miniapp'; import { installPendingRuntimeContributionModulesToApp } from './runtime-contribution'; import { acquireSharedApp, @@ -10,11 +11,11 @@ import { type TVRenderSharedAppKey } from './shared-registry'; -const SHARED_BROWSER_REGISTRY_ENV = 'browser-shared'; +export type TVRenderSharedBrowserAppEnv = 'browser' | 'lynx'; -export type TVRenderSharedBrowserAppOptions = IEntryOptions & { - env?: 'browser'; - envParams?: IEnvParamsMap['browser']; +export type TVRenderSharedBrowserAppOptions = IEntryOptions & { + env?: TEnv; + envParams?: IEnvParamsMap[TEnv]; /** * Shared identity inside the current JS runtime. Use a stable app/page/container key * when multiple products, such as VChart and VTable, should share one VRender app. @@ -22,36 +23,106 @@ export type TVRenderSharedBrowserAppOptions = IEntryOptions & { key?: TVRenderSharedAppKey; }; -export type TVRenderSharedBrowserAppHandle = TVRenderSharedAppHandle<'browser'>; +export type TVRenderSharedBrowserAppHandle = + TVRenderSharedAppHandle; -function createSharedBrowserApp(options: TVRenderSharedBrowserAppOptions): IApp { - const { envParams } = options; +type TVRenderSharedBrowserAppArgument = + | TVRenderSharedBrowserAppOptions + | IEnvParamsMap[TVRenderSharedBrowserAppEnv]; + +function isSharedBrowserAppOptions( + options: TVRenderSharedBrowserAppArgument +): options is TVRenderSharedBrowserAppOptions { + return 'context' in options || 'env' in options || 'envParams' in options || 'key' in options; +} + +function resolveSharedBrowserAppOptions( + optionsOrEnvParams?: TVRenderSharedBrowserAppArgument, + env?: TVRenderSharedBrowserAppEnv +): TVRenderSharedBrowserAppOptions { + if (env !== undefined) { + return { + env, + envParams: optionsOrEnvParams as IEnvParamsMap[TVRenderSharedBrowserAppEnv] | undefined + }; + } + + if (optionsOrEnvParams === undefined) { + return { env: 'browser' }; + } + + if (isSharedBrowserAppOptions(optionsOrEnvParams)) { + return { + ...optionsOrEnvParams, + env: optionsOrEnvParams.env ?? 'browser' + }; + } + + return { + env: 'browser', + envParams: optionsOrEnvParams + }; +} + +function createSharedBrowserApp(options: TVRenderSharedBrowserAppOptions): IApp { + const { env, envParams } = options; const entryOptions = { ...options }; delete entryOptions.env; delete entryOptions.envParams; delete entryOptions.key; - const app = bootstrapVRenderSharedBrowserApp(createBrowserApp(entryOptions as any) as unknown as IApp, envParams); + + if (env === 'lynx') { + return createLynxVRenderApp({ + ...(entryOptions as IEntryOptions), + envParams: envParams as IEnvParamsMap['lynx'] + }); + } + + const app = bootstrapVRenderSharedBrowserApp( + createBrowserApp(entryOptions as any) as unknown as IApp, + envParams as IEnvParamsMap['browser'] + ); installPendingRuntimeContributionModulesToApp(app); return app; } export function acquireSharedBrowserVRenderApp( - options: TVRenderSharedBrowserAppOptions = {} -): TVRenderSharedBrowserAppHandle { - return acquireSharedApp(SHARED_BROWSER_REGISTRY_ENV, options, createSharedBrowserApp, 'browser'); + options?: TVRenderSharedBrowserAppOptions<'browser'> +): TVRenderSharedBrowserAppHandle<'browser'>; +export function acquireSharedBrowserVRenderApp( + envParams?: IEnvParamsMap['browser'], + env?: 'browser' +): TVRenderSharedBrowserAppHandle<'browser'>; +export function acquireSharedBrowserVRenderApp( + optionsOrEnvParams?: TVRenderSharedBrowserAppOptions<'browser'> | IEnvParamsMap['browser'], + env?: 'browser' +): TVRenderSharedBrowserAppHandle<'browser'> { + const options = resolveSharedBrowserAppOptions(optionsOrEnvParams, env) as TVRenderSharedBrowserAppOptions<'browser'>; + return acquireSharedApp('browser-shared', options, createSharedBrowserApp, 'browser'); } export function getSharedBrowserVRenderApp(key?: TVRenderSharedAppKey): IApp | null { - return getSharedApp(SHARED_BROWSER_REGISTRY_ENV, key); + return getSharedApp('browser-shared', key); } export function releaseSharedBrowserVRenderApp(key?: TVRenderSharedAppKey): void { - releaseSharedApp(SHARED_BROWSER_REGISTRY_ENV, key); + releaseSharedApp('browser-shared', key); } -export { - acquireSharedBrowserVRenderApp as acquireSharedVRenderApp, - getSharedBrowserVRenderApp as getSharedVRenderApp, - releaseSharedBrowserVRenderApp as releaseSharedVRenderApp -}; +export function acquireSharedVRenderApp( + options?: TVRenderSharedBrowserAppOptions +): TVRenderSharedBrowserAppHandle; +export function acquireSharedVRenderApp( + envParams?: IEnvParamsMap[TEnv], + env?: TEnv +): TVRenderSharedBrowserAppHandle; +export function acquireSharedVRenderApp( + optionsOrEnvParams?: TVRenderSharedBrowserAppArgument, + env?: TVRenderSharedBrowserAppEnv +): TVRenderSharedBrowserAppHandle { + const options = resolveSharedBrowserAppOptions(optionsOrEnvParams, env); + return acquireSharedApp(`${options.env}-shared`, options, createSharedBrowserApp, options.env); +} + +export { getSharedBrowserVRenderApp as getSharedVRenderApp, releaseSharedBrowserVRenderApp as releaseSharedVRenderApp }; diff --git a/packages/vrender/src/entries/shared.ts b/packages/vrender/src/entries/shared.ts index a550105ba..147e10e05 100644 --- a/packages/vrender/src/entries/shared.ts +++ b/packages/vrender/src/entries/shared.ts @@ -1,4 +1,4 @@ -import type { IApp } from '@visactor/vrender-core'; +import type { IApp, IEnvParamsMap } from '@visactor/vrender-core'; import { createBrowserVRenderApp, type TVRenderBrowserAppEntryOptions } from './browser'; import { createFeishuVRenderApp, @@ -45,6 +45,42 @@ export type TVRenderSharedAppOptions = TSharedAppHandle; +type TVRenderSharedAppArgument = TVRenderSharedAppOptions | IEnvParamsMap[TVRenderSharedAppEnv]; + +function isSharedAppOptions( + options: TVRenderSharedAppArgument +): options is TVRenderSharedAppOptions { + return 'context' in options || 'env' in options || 'envParams' in options || 'key' in options; +} + +function resolveSharedAppOptions( + optionsOrEnvParams?: TVRenderSharedAppArgument, + env?: TVRenderSharedAppEnv +): TVRenderSharedAppOptions { + if (env !== undefined) { + return { + env, + envParams: optionsOrEnvParams as IEnvParamsMap[TVRenderSharedAppEnv] | undefined + } as TVRenderSharedAppOptions; + } + + if (optionsOrEnvParams === undefined) { + return { env: 'browser' } as TVRenderSharedAppOptions<'browser'>; + } + + if (isSharedAppOptions(optionsOrEnvParams)) { + return { + ...optionsOrEnvParams, + env: optionsOrEnvParams.env ?? 'browser' + } as TVRenderSharedAppOptions; + } + + return { + env: 'browser', + envParams: optionsOrEnvParams + } as TVRenderSharedAppOptions<'browser'>; +} + function createAppForSharedEnv(options: TVRenderSharedAppOptions): IApp { const { env } = options; const entryOptions = { ...options } as Record; @@ -76,8 +112,17 @@ function createAppForSharedEnv(options: TVRen } export function acquireSharedVRenderApp( - options: TVRenderSharedAppOptions -): TVRenderSharedAppHandle { + options?: TVRenderSharedAppOptions +): TVRenderSharedAppHandle; +export function acquireSharedVRenderApp( + envParams?: IEnvParamsMap[TEnv], + env?: TEnv +): TVRenderSharedAppHandle; +export function acquireSharedVRenderApp( + optionsOrEnvParams?: TVRenderSharedAppArgument, + env?: TVRenderSharedAppEnv +): TVRenderSharedAppHandle { + const options = resolveSharedAppOptions(optionsOrEnvParams, env); return acquireSharedApp(options.env, options, createAppForSharedEnv, options.env); } From f5a637868fd4c7bd781cf73d2ad2cf66237cdc9f Mon Sep 17 00:00:00 2001 From: "lixuefei.1313" Date: Fri, 7 Aug 2026 15:37:24 +0800 Subject: [PATCH 3/5] fix: normalize Lynx text metrics --- multi-platform/lynx-vrender/src/App.tsx | 4 +- .../lynx-vrender/src/smoke-scenes.ts | 200 ++++++++++++++++++ .../__tests__/unit/lynx-context.test.ts | 32 +++ .../src/canvas/contributions/lynx/context.ts | 22 +- 4 files changed, 248 insertions(+), 10 deletions(-) create mode 100644 packages/vrender-kits/__tests__/unit/lynx-context.test.ts diff --git a/multi-platform/lynx-vrender/src/App.tsx b/multi-platform/lynx-vrender/src/App.tsx index a3e146146..72fc7f92e 100644 --- a/multi-platform/lynx-vrender/src/App.tsx +++ b/multi-platform/lynx-vrender/src/App.tsx @@ -31,7 +31,7 @@ const LIFECYCLE_MODES: Array<{ key: LifecycleMode; label: string }> = [ let activeController: ReturnType | null = null; let activeControllerSceneKey: SceneKey | null = null; -let requestedSceneKey: SceneKey = 'primitives'; +let requestedSceneKey: SceneKey = 'tooltip'; let lastCanvasTouchAt = 0; let canvasTouchActive = false; let canvasMouseActive = false; @@ -285,7 +285,7 @@ function getLifecycleModeLabel(mode: LifecycleMode) { } export function App() { - const [sceneKey, setSceneKey] = useState('primitives'); + const [sceneKey, setSceneKey] = useState('tooltip'); const [lifecycleMode, setLifecycleMode] = useState('reuse-stage'); const [hasCanvasBridge, setHasCanvasBridge] = useState( diff --git a/multi-platform/lynx-vrender/src/smoke-scenes.ts b/multi-platform/lynx-vrender/src/smoke-scenes.ts index e5bb94bb1..122c6b56a 100644 --- a/multi-platform/lynx-vrender/src/smoke-scenes.ts +++ b/multi-platform/lynx-vrender/src/smoke-scenes.ts @@ -43,6 +43,8 @@ export const SCENES = [ { key: 'states', label: '状态' }, { key: 'transform', label: '变换' }, { key: 'components', label: '组件' }, + { key: 'tooltip', label: 'Tooltip' }, + { key: 'baseline', label: '基准矩阵' }, { key: 'events', label: '事件' }, { key: 'stress', label: '批量' }, { key: 'geometry', label: '几何' }, @@ -81,6 +83,14 @@ export const SCENE_META: Record = { title: 'VRender Components', hint: '首屏挂载 Tag、Segment、Axis、Legend、Slider、DataZoom、ScrollBar 和输入控件,失败组件显示 fallback。' }, + tooltip: { + title: 'Tooltip 文字对齐', + hint: '红色横线是内容行中心,应同时穿过 symbol 中心和文字视觉中心。' + }, + baseline: { + title: 'Baseline Matrix(PROTOTYPE)', + hint: '10/16/24px × 五种 correction;横线是传给 alphabetic baseline 的原始 y。' + }, events: { title: '事件拾取边界', hint: 'touch 转发到 stage.window.dispatchEvent 后,验证重叠、zIndex、旋转组和裁剪子节点拾取。' @@ -338,6 +348,10 @@ class LynxSmokeController { this.drawTransformScene(); } else if (sceneKey === 'components') { this.drawComponentsScene(); + } else if (sceneKey === 'tooltip') { + this.drawTooltipScene(); + } else if (sceneKey === 'baseline') { + this.drawBaselineMatrixScene(); } else if (sceneKey === 'events') { this.drawEventsScene(); } else if (sceneKey === 'stress') { @@ -1861,6 +1875,192 @@ class LynxSmokeController { this.mountDeferredComponentControls(); } + private drawTooltipScene() { + const tooltip = new (componentCtor(Tooltip))({ + x: 16, + y: 18, + visible: true, + autoMeasure: false, + autoCalculatePosition: false, + padding: [18, 20], + panel: { + visible: true, + width: 294, + height: 172, + fill: '#FFFFFF', + stroke: '#98A2B3', + lineWidth: 1, + cornerRadius: 10, + shadow: false + }, + title: { + visible: true, + height: 30, + value: { text: 'Tooltip alignment' } + }, + titleStyle: { + value: { + fill: '#101828', + fontFamily: 'Arial', + fontSize: 20, + lineHeight: 30, + textBaseline: 'middle' + }, + spaceRow: 8 + }, + content: [ + { + height: 18, + key: { text: 'Alpha' }, + value: { text: '12' }, + shape: { visible: true, fill: '#276EF1' } + }, + { + height: 18, + key: { text: 'Beta' }, + value: { text: '34' }, + shape: { visible: true, fill: '#11A579' } + } + ], + contentStyle: { + shape: { visible: true, size: 12, spacing: 12 }, + key: { + fill: '#101828', + fontFamily: 'Arial', + fontSize: 18, + lineHeight: 18, + spacing: 52 + }, + value: { + fill: '#101828', + fontFamily: 'Arial', + fontSize: 18, + lineHeight: 18, + spacing: 0 + }, + spaceRow: 8 + }, + hasContentShape: true, + keyWidth: 65, + valueWidth: 24 + }); + this.stage.defaultLayer.add(tooltip); + + for (const y of [83, 109]) { + this.stage.defaultLayer.add( + createLine({ + points: [ + { x: 30, y }, + { x: 296, y } + ], + stroke: '#D92D20', + lineWidth: 1, + lineDash: [4, 3], + zIndex: 501, + pickable: false + }) + ); + } + } + + private drawBaselineMatrixScene() { + const context = this.stage.window.getContext(); + const fontSizes = [10, 16, 24]; + const columns = [112, 210, 308]; + const variants = [ + { label: 'A none', offset: () => 0 }, + { label: 'B −0.5em', offset: (fontSize: number) => -0.5 * fontSize }, + { + label: 'C −fontAsc', + offset: (fontSize: number, metrics: any) => + -(metrics.fontBoundingBoxAscent ?? 0.79 * fontSize) + }, + { label: 'D −0.79em', offset: (fontSize: number) => -0.79 * fontSize }, + { + label: 'E −actualAsc', + offset: (fontSize: number, metrics: any) => + -(metrics.actualBoundingBoxAscent ?? 0.79 * fontSize) + } + ]; + + this.stage.defaultLayer.add( + createText({ + x: 18, + y: 24, + text: 'baseline matrix (PROTOTYPE)', + fontSize: 14, + fill: '#667085' + }) + ); + + fontSizes.forEach((fontSize, index) => { + this.stage.defaultLayer.add( + createText({ + x: columns[index], + y: 52, + text: `${fontSize}px`, + fontSize: 11, + fill: '#667085', + textAlign: 'center', + textBaseline: 'middle' + }) + ); + }); + + variants.forEach((variant, rowIndex) => { + const baselineY = 94 + rowIndex * 72; + this.stage.defaultLayer.add( + createText({ + x: 16, + y: baselineY - 20, + text: variant.label, + fontSize: 10, + fill: '#475467' + }) + ); + this.stage.defaultLayer.add( + createLine({ + points: [ + { x: 76, y: baselineY }, + { x: 348, y: baselineY } + ], + stroke: '#98A2B3', + lineWidth: 1, + pickable: false + }) + ); + + fontSizes.forEach((fontSize, columnIndex) => { + context.setTextStyleWithoutAlignBaseline({ + fontFamily: 'Arial', + fontSize + }); + const metrics = context.measureText('Hg') as any; + const x = columns[columnIndex]; + this.stage.defaultLayer.add( + createCircle({ + x, + y: baselineY, + radius: 3, + fill: '#D92D20', + pickable: false + }) + ); + this.stage.defaultLayer.add( + createText({ + x: x + 8, + y: baselineY + variant.offset(fontSize, metrics), + text: 'Hg', + fontFamily: 'Arial', + fontSize, + fill: '#101828', + textBaseline: 'alphabetic' + }) + ); + }); + }); + } + private mountDeferredComponentControls() { const nodes = this.componentNodes; if (!nodes || nodes.deferredControlsMounted) { diff --git a/packages/vrender-kits/__tests__/unit/lynx-context.test.ts b/packages/vrender-kits/__tests__/unit/lynx-context.test.ts new file mode 100644 index 000000000..3670de49f --- /dev/null +++ b/packages/vrender-kits/__tests__/unit/lynx-context.test.ts @@ -0,0 +1,32 @@ +import { application } from '@visactor/vrender-core/application'; +import { LynxContext2d } from '../../src/canvas/contributions/lynx/context'; + +test('lynx context normalizes device-scaled font metrics', () => { + const metrics = { + width: 80, + actualBoundingBoxAscent: 12, + actualBoundingBoxDescent: 4, + fontBoundingBoxAscent: 28, + fontBoundingBoxDescent: 10 + }; + const context = new LynxContext2d( + { + nativeCanvas: { + getContext: () => ({ measureText: () => metrics, setTransform: (): void => undefined }) + } + } as any, + 2 + ); + const global = application.global; + application.global = { devicePixelRatio: 1 } as any; + + try { + expect(context.measureText('VRender', 'native')).toEqual({ + ...metrics, + fontBoundingBoxAscent: 14, + fontBoundingBoxDescent: 5 + }); + } finally { + application.global = global; + } +}); diff --git a/packages/vrender-kits/src/canvas/contributions/lynx/context.ts b/packages/vrender-kits/src/canvas/contributions/lynx/context.ts index bf2df8658..46d6b80c6 100644 --- a/packages/vrender-kits/src/canvas/contributions/lynx/context.ts +++ b/packages/vrender-kits/src/canvas/contributions/lynx/context.ts @@ -75,15 +75,21 @@ export class LynxContext2d extends BrowserContext2d implements IContext2d { text: string, method: 'native' | 'simple' | 'quick' = application.global.measureTextMethod ): { width: number } { - this.setTransform(1, 0, 0, 1, 0, 0, true, application.global.devicePixelRatio); - const data: any = super.measureText(text, method); - // lynx环境中的fontBoundingBoxDescent和fontBoundingBoxAscent有严重偏移,暂时规避 + const dpr = this.dpr || 1; + this.setTransform(1, 0, 0, 1, 0, 0, true, dpr); + const metrics = super.measureText(text, method) as TextMetrics; + if (dpr === 1 || metrics.fontBoundingBoxAscent == null || metrics.fontBoundingBoxDescent == null) { + return metrics; + } return { - width: data.width, - fontBoundingBoxDescent: undefined, - fontBoundingBoxAscent: undefined, - actualBoundingBoxAscent: undefined, - actualBoundingBoxDescent: undefined + ...metrics, + width: metrics.width, + actualBoundingBoxLeft: metrics.actualBoundingBoxLeft, + actualBoundingBoxRight: metrics.actualBoundingBoxRight, + actualBoundingBoxAscent: metrics.actualBoundingBoxAscent, + actualBoundingBoxDescent: metrics.actualBoundingBoxDescent, + fontBoundingBoxAscent: metrics.fontBoundingBoxAscent / dpr, + fontBoundingBoxDescent: metrics.fontBoundingBoxDescent / dpr } as any; } From 0f5ead77a45500c01999ce2f810f58b156483fe5 Mon Sep 17 00:00:00 2001 From: "lixuefei.1313" Date: Fri, 7 Aug 2026 19:43:23 +0800 Subject: [PATCH 4/5] perf(core): skip empty state attribute commits --- .../unit/graphic/graphic-state.test.ts | 32 +++++++++++++++++++ .../unit/graphic/shared-state-refresh.test.ts | 19 +++++++++++ packages/vrender-core/src/graphic/graphic.ts | 26 +++++++++++++++ 3 files changed, 77 insertions(+) diff --git a/packages/vrender-core/__tests__/unit/graphic/graphic-state.test.ts b/packages/vrender-core/__tests__/unit/graphic/graphic-state.test.ts index ab6d69eca..d2eab99d6 100644 --- a/packages/vrender-core/__tests__/unit/graphic/graphic-state.test.ts +++ b/packages/vrender-core/__tests__/unit/graphic/graphic-state.test.ts @@ -255,6 +255,38 @@ describe('Graphic useStates', () => { expect(graphic.currentStates).toEqual(['hover']); }); + test('should retain an empty state without submitting attribute updates', () => { + const graphics = Array.from({ length: 100 }, createGraphic); + const onAttributeUpdate = jest.fn(); + const afterStateUpdate = jest.fn(); + + graphics[0].addEventListener('afterStateUpdate', afterStateUpdate as any); + + graphics.forEach(graphic => { + graphic.states = { selected: {} } as any; + jest.spyOn(graphic as any, 'onAttributeUpdate').mockImplementation(onAttributeUpdate); + graphic.addState('selected', false, false); + }); + + graphics.forEach(graphic => { + expect(graphic.currentStates).toEqual(['selected']); + expect(graphic.attribute).toBe((graphic as any).baseAttributes); + expect(graphic.resolvedStatePatch).toEqual({}); + }); + expect(onAttributeUpdate).not.toHaveBeenCalled(); + expect(afterStateUpdate).toHaveBeenCalledTimes(1); + + graphics.forEach(graphic => graphic.clearStates(false)); + + graphics.forEach(graphic => { + expect(graphic.currentStates).toEqual([]); + expect(graphic.attribute).toBe((graphic as any).baseAttributes); + expect(graphic.resolvedStatePatch).toBeUndefined(); + }); + expect(onAttributeUpdate).not.toHaveBeenCalled(); + expect(afterStateUpdate).toHaveBeenCalledTimes(2); + }); + test('should emit beforeStateUpdate with previous and next state info', () => { const graphic = createGraphic(); graphic.states = { diff --git a/packages/vrender-core/__tests__/unit/graphic/shared-state-refresh.test.ts b/packages/vrender-core/__tests__/unit/graphic/shared-state-refresh.test.ts index 007816dc5..b8fecb39d 100644 --- a/packages/vrender-core/__tests__/unit/graphic/shared-state-refresh.test.ts +++ b/packages/vrender-core/__tests__/unit/graphic/shared-state-refresh.test.ts @@ -3,6 +3,25 @@ import { createGroup } from '../../../src/graphic/group'; import { createSharedStateTestStage } from './shared-state-test-utils'; describe('shared state refresh contract', () => { + test('should refresh an active empty state when its shared definition gains attrs', () => { + const stage = createSharedStateTestStage(); + const group = createGroup({}); + const rect = createRect({ x: 0, y: 0, width: 10, height: 10, fill: 'black' }); + + (group as any).sharedStateDefinitions = { selected: {} }; + stage.appendChild(group); + group.appendChild(rect); + + rect.addState('selected', false, false); + expect(rect.attribute).toBe((rect as any).baseAttributes); + + (group as any).sharedStateDefinitions = { selected: { fill: 'red' } }; + stage.hooks.beforeRender.call(stage); + + expect(rect.currentStates).toEqual(['selected']); + expect(rect.attribute.fill).toBe('red'); + }); + test('should refresh active descendants before next render when group shared definitions change', () => { const stage = createSharedStateTestStage(); const outer = createGroup({}); diff --git a/packages/vrender-core/src/graphic/graphic.ts b/packages/vrender-core/src/graphic/graphic.ts index 4b3984443..d87ea1fd5 100644 --- a/packages/vrender-core/src/graphic/graphic.ts +++ b/packages/vrender-core/src/graphic/graphic.ts @@ -2298,6 +2298,20 @@ abstract class GraphicImpl = Partial): boolean { + if (!patch) { + return false; + } + + for (const key in patch) { + if (Object.prototype.hasOwnProperty.call(patch, key)) { + return true; + } + } + + return false; + } + protected commitSameStatePatchRefresh( states: string[], hasAnimation?: boolean, @@ -2466,6 +2480,10 @@ abstract class GraphicImpl = Partial = Partial Date: Wed, 12 Aug 2026 07:29:23 +0000 Subject: [PATCH 5/5] build: prelease version 1.1.8 --- common/config/rush/pnpm-lock.yaml | 36 ++++++++++----------- common/config/rush/version-policies.json | 2 +- docs/package.json | 2 +- packages/react-vrender-utils/CHANGELOG.json | 6 ++++ packages/react-vrender-utils/CHANGELOG.md | 7 +++- packages/react-vrender-utils/package.json | 6 ++-- packages/react-vrender/CHANGELOG.json | 6 ++++ packages/react-vrender/CHANGELOG.md | 7 +++- packages/react-vrender/package.json | 4 +-- packages/vrender-animate/CHANGELOG.json | 6 ++++ packages/vrender-animate/CHANGELOG.md | 7 +++- packages/vrender-animate/package.json | 4 +-- packages/vrender-components/CHANGELOG.json | 6 ++++ packages/vrender-components/CHANGELOG.md | 7 +++- packages/vrender-components/package.json | 8 ++--- packages/vrender-core/CHANGELOG.json | 6 ++++ packages/vrender-core/CHANGELOG.md | 7 +++- packages/vrender-core/package.json | 2 +- packages/vrender-kits/CHANGELOG.json | 6 ++++ packages/vrender-kits/CHANGELOG.md | 7 +++- packages/vrender-kits/package.json | 4 +-- packages/vrender/CHANGELOG.json | 6 ++++ packages/vrender/CHANGELOG.md | 7 +++- packages/vrender/package.json | 10 +++--- tools/bugserver-trigger/package.json | 10 +++--- 25 files changed, 128 insertions(+), 51 deletions(-) diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 0df543148..5ca07ce2f 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -22,7 +22,7 @@ importers: specifier: ~0.5.7 version: 0.5.7 '@visactor/vrender': - specifier: workspace:1.1.7 + specifier: workspace:1.1.8 version: link:../packages/vrender '@visactor/vutils': specifier: ~1.0.12 @@ -95,7 +95,7 @@ importers: ../../packages/react-vrender: dependencies: '@visactor/vrender': - specifier: workspace:1.1.7 + specifier: workspace:1.1.8 version: link:../vrender '@visactor/vutils': specifier: ~1.0.12 @@ -168,10 +168,10 @@ importers: ../../packages/react-vrender-utils: dependencies: '@visactor/react-vrender': - specifier: workspace:1.1.7 + specifier: workspace:1.1.8 version: link:../react-vrender '@visactor/vrender': - specifier: workspace:1.1.7 + specifier: workspace:1.1.8 version: link:../vrender '@visactor/vutils': specifier: ~1.0.12 @@ -241,16 +241,16 @@ importers: ../../packages/vrender: dependencies: '@visactor/vrender-animate': - specifier: workspace:1.1.7 + specifier: workspace:1.1.8 version: link:../vrender-animate '@visactor/vrender-components': - specifier: workspace:1.1.7 + specifier: workspace:1.1.8 version: link:../vrender-components '@visactor/vrender-core': - specifier: workspace:1.1.7 + specifier: workspace:1.1.8 version: link:../vrender-core '@visactor/vrender-kits': - specifier: workspace:1.1.7 + specifier: workspace:1.1.8 version: link:../vrender-kits devDependencies: '@internal/bundler': @@ -320,7 +320,7 @@ importers: ../../packages/vrender-animate: dependencies: '@visactor/vrender-core': - specifier: workspace:1.1.7 + specifier: workspace:1.1.8 version: link:../vrender-core '@visactor/vutils': specifier: ~1.0.12 @@ -393,13 +393,13 @@ importers: ../../packages/vrender-components: dependencies: '@visactor/vrender-animate': - specifier: workspace:1.1.7 + specifier: workspace:1.1.8 version: link:../vrender-animate '@visactor/vrender-core': - specifier: workspace:1.1.7 + specifier: workspace:1.1.8 version: link:../vrender-core '@visactor/vrender-kits': - specifier: workspace:1.1.7 + specifier: workspace:1.1.8 version: link:../vrender-kits '@visactor/vscale': specifier: ~1.0.12 @@ -518,7 +518,7 @@ importers: specifier: 2.4.1 version: 2.4.1 '@visactor/vrender-core': - specifier: workspace:1.1.7 + specifier: workspace:1.1.8 version: link:../vrender-core '@visactor/vutils': specifier: ~1.0.12 @@ -649,19 +649,19 @@ importers: ../../tools/bugserver-trigger: dependencies: '@visactor/vrender': - specifier: workspace:1.1.7 + specifier: workspace:1.1.8 version: link:../../packages/vrender '@visactor/vrender-animate': - specifier: workspace:1.1.7 + specifier: workspace:1.1.8 version: link:../../packages/vrender-animate '@visactor/vrender-components': - specifier: workspace:1.1.7 + specifier: workspace:1.1.8 version: link:../../packages/vrender-components '@visactor/vrender-core': - specifier: workspace:1.1.7 + specifier: workspace:1.1.8 version: link:../../packages/vrender-core '@visactor/vrender-kits': - specifier: workspace:1.1.7 + specifier: workspace:1.1.8 version: link:../../packages/vrender-kits devDependencies: '@internal/bundler': diff --git a/common/config/rush/version-policies.json b/common/config/rush/version-policies.json index 21357d4e1..2eacbf861 100644 --- a/common/config/rush/version-policies.json +++ b/common/config/rush/version-policies.json @@ -1 +1 @@ -[{"definitionName":"lockStepVersion","policyName":"vrenderMain","version":"1.1.7","nextBump":"patch"}] +[{"definitionName":"lockStepVersion","policyName":"vrenderMain","version":"1.1.8","nextBump":"patch"}] diff --git a/docs/package.json b/docs/package.json index 5089b2c57..978165dbe 100644 --- a/docs/package.json +++ b/docs/package.json @@ -13,7 +13,7 @@ "@visactor/vchart": "1.3.0", "@visactor/vutils": "~1.0.12", "@visactor/vgrammar": "~0.5.7", - "@visactor/vrender": "workspace:1.1.7", + "@visactor/vrender": "workspace:1.1.8", "markdown-it": "^13.0.0", "highlight.js": "^11.8.0", "axios": "^1.4.0", diff --git a/packages/react-vrender-utils/CHANGELOG.json b/packages/react-vrender-utils/CHANGELOG.json index 06508e324..c1092a9cc 100644 --- a/packages/react-vrender-utils/CHANGELOG.json +++ b/packages/react-vrender-utils/CHANGELOG.json @@ -1,6 +1,12 @@ { "name": "@visactor/react-vrender-utils", "entries": [ + { + "version": "1.1.8", + "tag": "@visactor/react-vrender-utils_v1.1.8", + "date": "Wed, 12 Aug 2026 07:23:35 GMT", + "comments": {} + }, { "version": "1.1.7", "tag": "@visactor/react-vrender-utils_v1.1.7", diff --git a/packages/react-vrender-utils/CHANGELOG.md b/packages/react-vrender-utils/CHANGELOG.md index bde7968d3..eb4cb1ca7 100644 --- a/packages/react-vrender-utils/CHANGELOG.md +++ b/packages/react-vrender-utils/CHANGELOG.md @@ -1,6 +1,11 @@ # Change Log - @visactor/react-vrender-utils -This log was last generated on Sun, 02 Aug 2026 07:21:46 GMT and should not be manually modified. +This log was last generated on Wed, 12 Aug 2026 07:23:35 GMT and should not be manually modified. + +## 1.1.8 +Wed, 12 Aug 2026 07:23:35 GMT + +_Version update only_ ## 1.1.7 Sun, 02 Aug 2026 07:21:46 GMT diff --git a/packages/react-vrender-utils/package.json b/packages/react-vrender-utils/package.json index e74d19840..542d637ce 100644 --- a/packages/react-vrender-utils/package.json +++ b/packages/react-vrender-utils/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/react-vrender-utils", - "version": "1.1.7", + "version": "1.1.8", "description": "", "sideEffects": false, "main": "cjs/index.js", @@ -27,8 +27,8 @@ "react-dom": "^18.2.0" }, "dependencies": { - "@visactor/vrender": "workspace:1.1.7", - "@visactor/react-vrender": "workspace:1.1.7", + "@visactor/vrender": "workspace:1.1.8", + "@visactor/react-vrender": "workspace:1.1.8", "@visactor/vutils": "~1.0.12", "react-reconciler": "^0.29.0", "tslib": "^2.3.1" diff --git a/packages/react-vrender/CHANGELOG.json b/packages/react-vrender/CHANGELOG.json index 1a0d7a02b..2485c0c2f 100644 --- a/packages/react-vrender/CHANGELOG.json +++ b/packages/react-vrender/CHANGELOG.json @@ -1,6 +1,12 @@ { "name": "@visactor/react-vrender", "entries": [ + { + "version": "1.1.8", + "tag": "@visactor/react-vrender_v1.1.8", + "date": "Wed, 12 Aug 2026 07:23:35 GMT", + "comments": {} + }, { "version": "1.1.7", "tag": "@visactor/react-vrender_v1.1.7", diff --git a/packages/react-vrender/CHANGELOG.md b/packages/react-vrender/CHANGELOG.md index f8fb7d41e..15262856a 100644 --- a/packages/react-vrender/CHANGELOG.md +++ b/packages/react-vrender/CHANGELOG.md @@ -1,6 +1,11 @@ # Change Log - @visactor/react-vrender -This log was last generated on Sun, 02 Aug 2026 07:21:46 GMT and should not be manually modified. +This log was last generated on Wed, 12 Aug 2026 07:23:35 GMT and should not be manually modified. + +## 1.1.8 +Wed, 12 Aug 2026 07:23:35 GMT + +_Version update only_ ## 1.1.7 Sun, 02 Aug 2026 07:21:46 GMT diff --git a/packages/react-vrender/package.json b/packages/react-vrender/package.json index ddf802916..ed2c93fba 100644 --- a/packages/react-vrender/package.json +++ b/packages/react-vrender/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/react-vrender", - "version": "1.1.7", + "version": "1.1.8", "description": "", "sideEffects": false, "main": "cjs/index.js", @@ -26,7 +26,7 @@ "react": "^18.2.0" }, "dependencies": { - "@visactor/vrender": "workspace:1.1.7", + "@visactor/vrender": "workspace:1.1.8", "@visactor/vutils": "~1.0.12", "react-reconciler": "^0.29.0", "tslib": "^2.3.1" diff --git a/packages/vrender-animate/CHANGELOG.json b/packages/vrender-animate/CHANGELOG.json index bc3f382c0..e751a04ba 100644 --- a/packages/vrender-animate/CHANGELOG.json +++ b/packages/vrender-animate/CHANGELOG.json @@ -1,6 +1,12 @@ { "name": "@visactor/vrender-animate", "entries": [ + { + "version": "1.1.8", + "tag": "@visactor/vrender-animate_v1.1.8", + "date": "Wed, 12 Aug 2026 07:23:35 GMT", + "comments": {} + }, { "version": "1.1.7", "tag": "@visactor/vrender-animate_v1.1.7", diff --git a/packages/vrender-animate/CHANGELOG.md b/packages/vrender-animate/CHANGELOG.md index 6ab9e5acb..55eaa49ab 100644 --- a/packages/vrender-animate/CHANGELOG.md +++ b/packages/vrender-animate/CHANGELOG.md @@ -1,6 +1,11 @@ # Change Log - @visactor/vrender-animate -This log was last generated on Sun, 02 Aug 2026 07:21:46 GMT and should not be manually modified. +This log was last generated on Wed, 12 Aug 2026 07:23:35 GMT and should not be manually modified. + +## 1.1.8 +Wed, 12 Aug 2026 07:23:35 GMT + +_Version update only_ ## 1.1.7 Sun, 02 Aug 2026 07:21:46 GMT diff --git a/packages/vrender-animate/package.json b/packages/vrender-animate/package.json index 1a603a49d..222190f7d 100644 --- a/packages/vrender-animate/package.json +++ b/packages/vrender-animate/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vrender-animate", - "version": "1.1.7", + "version": "1.1.8", "description": "", "sideEffects": false, "main": "cjs/index.js", @@ -37,7 +37,7 @@ }, "dependencies": { "@visactor/vutils": "~1.0.12", - "@visactor/vrender-core": "workspace:1.1.7" + "@visactor/vrender-core": "workspace:1.1.8" }, "devDependencies": { "@internal/bundler": "workspace:*", diff --git a/packages/vrender-components/CHANGELOG.json b/packages/vrender-components/CHANGELOG.json index 81aa21cc9..078ad0d5e 100644 --- a/packages/vrender-components/CHANGELOG.json +++ b/packages/vrender-components/CHANGELOG.json @@ -1,6 +1,12 @@ { "name": "@visactor/vrender-components", "entries": [ + { + "version": "1.1.8", + "tag": "@visactor/vrender-components_v1.1.8", + "date": "Wed, 12 Aug 2026 07:23:35 GMT", + "comments": {} + }, { "version": "1.1.7", "tag": "@visactor/vrender-components_v1.1.7", diff --git a/packages/vrender-components/CHANGELOG.md b/packages/vrender-components/CHANGELOG.md index 410ef5d09..b343ab23d 100644 --- a/packages/vrender-components/CHANGELOG.md +++ b/packages/vrender-components/CHANGELOG.md @@ -1,6 +1,11 @@ # Change Log - @visactor/vrender-components -This log was last generated on Sun, 02 Aug 2026 07:21:46 GMT and should not be manually modified. +This log was last generated on Wed, 12 Aug 2026 07:23:35 GMT and should not be manually modified. + +## 1.1.8 +Wed, 12 Aug 2026 07:23:35 GMT + +_Version update only_ ## 1.1.7 Sun, 02 Aug 2026 07:21:46 GMT diff --git a/packages/vrender-components/package.json b/packages/vrender-components/package.json index 6226e854c..fc37b858b 100644 --- a/packages/vrender-components/package.json +++ b/packages/vrender-components/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vrender-components", - "version": "1.1.7", + "version": "1.1.8", "description": "components library for dp visualization", "sideEffects": false, "main": "cjs/index.js", @@ -69,9 +69,9 @@ "dependencies": { "@visactor/vutils": "~1.0.12", "@visactor/vscale": "~1.0.12", - "@visactor/vrender-core": "workspace:1.1.7", - "@visactor/vrender-kits": "workspace:1.1.7", - "@visactor/vrender-animate": "workspace:1.1.7" + "@visactor/vrender-core": "workspace:1.1.8", + "@visactor/vrender-kits": "workspace:1.1.8", + "@visactor/vrender-animate": "workspace:1.1.8" }, "devDependencies": { "@internal/bundler": "workspace:*", diff --git a/packages/vrender-core/CHANGELOG.json b/packages/vrender-core/CHANGELOG.json index 6e53b0513..1aa6872b5 100644 --- a/packages/vrender-core/CHANGELOG.json +++ b/packages/vrender-core/CHANGELOG.json @@ -1,6 +1,12 @@ { "name": "@visactor/vrender-core", "entries": [ + { + "version": "1.1.8", + "tag": "@visactor/vrender-core_v1.1.8", + "date": "Wed, 12 Aug 2026 07:23:35 GMT", + "comments": {} + }, { "version": "1.1.7", "tag": "@visactor/vrender-core_v1.1.7", diff --git a/packages/vrender-core/CHANGELOG.md b/packages/vrender-core/CHANGELOG.md index 8d8381c71..70e7cea59 100644 --- a/packages/vrender-core/CHANGELOG.md +++ b/packages/vrender-core/CHANGELOG.md @@ -1,6 +1,11 @@ # Change Log - @visactor/vrender-core -This log was last generated on Sun, 02 Aug 2026 07:21:46 GMT and should not be manually modified. +This log was last generated on Wed, 12 Aug 2026 07:23:35 GMT and should not be manually modified. + +## 1.1.8 +Wed, 12 Aug 2026 07:23:35 GMT + +_Version update only_ ## 1.1.7 Sun, 02 Aug 2026 07:21:46 GMT diff --git a/packages/vrender-core/package.json b/packages/vrender-core/package.json index 4a3347806..8a77b42f5 100644 --- a/packages/vrender-core/package.json +++ b/packages/vrender-core/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vrender-core", - "version": "1.1.7", + "version": "1.1.8", "description": "", "sideEffects": [ "./src/modules.ts", diff --git a/packages/vrender-kits/CHANGELOG.json b/packages/vrender-kits/CHANGELOG.json index 132271af8..863bc625b 100644 --- a/packages/vrender-kits/CHANGELOG.json +++ b/packages/vrender-kits/CHANGELOG.json @@ -1,6 +1,12 @@ { "name": "@visactor/vrender-kits", "entries": [ + { + "version": "1.1.8", + "tag": "@visactor/vrender-kits_v1.1.8", + "date": "Wed, 12 Aug 2026 07:23:35 GMT", + "comments": {} + }, { "version": "1.1.7", "tag": "@visactor/vrender-kits_v1.1.7", diff --git a/packages/vrender-kits/CHANGELOG.md b/packages/vrender-kits/CHANGELOG.md index 7fad3ed40..ff41813c7 100644 --- a/packages/vrender-kits/CHANGELOG.md +++ b/packages/vrender-kits/CHANGELOG.md @@ -1,6 +1,11 @@ # Change Log - @visactor/vrender-kits -This log was last generated on Sun, 02 Aug 2026 07:21:46 GMT and should not be manually modified. +This log was last generated on Wed, 12 Aug 2026 07:23:35 GMT and should not be manually modified. + +## 1.1.8 +Wed, 12 Aug 2026 07:23:35 GMT + +_Version update only_ ## 1.1.7 Sun, 02 Aug 2026 07:21:46 GMT diff --git a/packages/vrender-kits/package.json b/packages/vrender-kits/package.json index 382bd26e4..b9abab14d 100644 --- a/packages/vrender-kits/package.json +++ b/packages/vrender-kits/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vrender-kits", - "version": "1.1.7", + "version": "1.1.8", "description": "", "sideEffects": false, "main": "cjs/index-node.js", @@ -37,7 +37,7 @@ }, "dependencies": { "@visactor/vutils": "~1.0.12", - "@visactor/vrender-core": "workspace:1.1.7", + "@visactor/vrender-core": "workspace:1.1.8", "@resvg/resvg-js": "2.4.1", "roughjs": "4.6.6", "gifuct-js": "2.1.2", diff --git a/packages/vrender/CHANGELOG.json b/packages/vrender/CHANGELOG.json index efaea660a..4c84bf38b 100644 --- a/packages/vrender/CHANGELOG.json +++ b/packages/vrender/CHANGELOG.json @@ -1,6 +1,12 @@ { "name": "@visactor/vrender", "entries": [ + { + "version": "1.1.8", + "tag": "@visactor/vrender_v1.1.8", + "date": "Wed, 12 Aug 2026 07:23:35 GMT", + "comments": {} + }, { "version": "1.1.7", "tag": "@visactor/vrender_v1.1.7", diff --git a/packages/vrender/CHANGELOG.md b/packages/vrender/CHANGELOG.md index 4eed5e83f..d1fb527ba 100644 --- a/packages/vrender/CHANGELOG.md +++ b/packages/vrender/CHANGELOG.md @@ -1,6 +1,11 @@ # Change Log - @visactor/vrender -This log was last generated on Sun, 02 Aug 2026 07:21:46 GMT and should not be manually modified. +This log was last generated on Wed, 12 Aug 2026 07:23:35 GMT and should not be manually modified. + +## 1.1.8 +Wed, 12 Aug 2026 07:23:35 GMT + +_Version update only_ ## 1.1.7 Sun, 02 Aug 2026 07:21:46 GMT diff --git a/packages/vrender/package.json b/packages/vrender/package.json index 20d9fef8a..6da672727 100644 --- a/packages/vrender/package.json +++ b/packages/vrender/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vrender", - "version": "1.1.7", + "version": "1.1.8", "description": "", "sideEffects": true, "main": "cjs/index.js", @@ -31,10 +31,10 @@ "test-watch": "cross-env DEBUG_MODE=1 jest --watch -c jest.config.js" }, "dependencies": { - "@visactor/vrender-core": "workspace:1.1.7", - "@visactor/vrender-kits": "workspace:1.1.7", - "@visactor/vrender-animate": "workspace:1.1.7", - "@visactor/vrender-components": "workspace:1.1.7" + "@visactor/vrender-core": "workspace:1.1.8", + "@visactor/vrender-kits": "workspace:1.1.8", + "@visactor/vrender-animate": "workspace:1.1.8", + "@visactor/vrender-components": "workspace:1.1.8" }, "devDependencies": { "@internal/bundler": "workspace:*", diff --git a/tools/bugserver-trigger/package.json b/tools/bugserver-trigger/package.json index 083c32645..ad659c296 100644 --- a/tools/bugserver-trigger/package.json +++ b/tools/bugserver-trigger/package.json @@ -8,11 +8,11 @@ "ci": "ts-node --transpileOnly --skipProject ./scripts/trigger-test.ts" }, "dependencies": { - "@visactor/vrender": "workspace:1.1.7", - "@visactor/vrender-core": "workspace:1.1.7", - "@visactor/vrender-kits": "workspace:1.1.7", - "@visactor/vrender-components": "workspace:1.1.7", - "@visactor/vrender-animate": "workspace:1.1.7" + "@visactor/vrender": "workspace:1.1.8", + "@visactor/vrender-core": "workspace:1.1.8", + "@visactor/vrender-kits": "workspace:1.1.8", + "@visactor/vrender-components": "workspace:1.1.8", + "@visactor/vrender-animate": "workspace:1.1.8" }, "devDependencies": { "@rushstack/eslint-patch": "~1.1.4",