Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
## [Unreleased]
#### 🐛 Fixed
- Fix stale/non-saved data when calling `applyChanges()` immediately after `updateData()` in `EntityEditor` and `RelationEditor`.
- Fix `SelectionAction*` components not updating on selection change when provided as children to `Halo`.

## [0.34.0] - 2026-03-25
#### 🚀 New Features
Expand Down
13 changes: 2 additions & 11 deletions src/widgets/halo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { flushSync } from 'react-dom';
import cx from 'clsx';

import { AnyListener, EventObserver } from '../coreUtils/events';
import { useObservedProperty } from '../coreUtils/hooks';
import { TranslatedText } from '../coreUtils/i18n';

import { TemplateProperties } from '../data/schema';
Expand All @@ -20,7 +19,7 @@ import { ResizableBox, type ResizableBoxOperation } from './utility/resizableBox
import {
SelectionActionRemove, SelectionActionExpand, SelectionActionAnchor,
SelectionActionConnections, SelectionActionAddToFilter, SelectionActionGroup,
SelectionActionEstablishLink, SelectionActionAnnotate,
SelectionActionEstablishLink, SelectionActionAnnotate, useSingleSelectedElement,
} from './selectionAction';

/**
Expand Down Expand Up @@ -63,15 +62,7 @@ export interface HaloProps {
export function Halo(props: HaloProps) {
const {model, canvas} = useCanvas();

const singleTarget = useObservedProperty(
model.events,
'changeSelection',
() => {
const target = model.selection.length === 1 ? model.selection[0] : undefined;
return target instanceof Element ? target : undefined;
}
);

const singleTarget = useSingleSelectedElement(model);
if (singleTarget) {
return (
<CanvasPlaceAt layer='overElements'>
Expand Down
51 changes: 30 additions & 21 deletions src/widgets/selectionAction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,17 @@ function getDockStyle(
return dockStyle;
}

export function useSingleSelectedElement(model: DiagramModel): Element | undefined {
return useObservedProperty(
model.events,
'changeSelection',
() => {
const target = model.selection.length === 1 ? model.selection[0] : undefined;
return target instanceof Element ? target : undefined;
}
);
}

/**
* Props for {@link SelectionActionSpinner} component.
*
Expand Down Expand Up @@ -206,7 +217,8 @@ export function SelectionActionRemove(props: SelectionActionRemoveProps) {
const {canvas} = useCanvas();
const {model, editor} = useWorkspace();
const t = useTranslation();
const elements = model.selection.filter((cell): cell is Element => cell instanceof Element);
const selection = useObservedProperty(model.events, 'changeSelection', () => model.selection);
const elements = selection.filter(item => item instanceof Element);

let newEntities = 0;
let totalEntities = 0;
Expand Down Expand Up @@ -258,7 +270,8 @@ export function SelectionActionZoomToFit(props: SelectionActionZoomToFitProps) {
const {className, title, ...otherProps} = props;
const {model, canvas} = useCanvas();
const t = useTranslation();
const elements = model.selection.filter((cell): cell is Element => cell instanceof Element);
const selection = useObservedProperty(model.events, 'changeSelection', () => model.selection);
const elements = selection.filter(item => item instanceof Element);
if (elements.length <= 1) {
return null;
}
Expand Down Expand Up @@ -300,7 +313,8 @@ export function SelectionActionLayout(props: SelectionActionLayoutProps) {
const {model, canvas} = useCanvas();
const {performLayout} = useWorkspace();
const t = useTranslation();
const elements = model.selection.filter((cell): cell is Element => cell instanceof Element);
const selection = useObservedProperty(model.events, 'changeSelection', () => model.selection);
const elements = selection.filter(item => item instanceof Element);
if (elements.length <= 1) {
return null;
}
Expand Down Expand Up @@ -345,7 +359,8 @@ export function SelectionActionExpand(props: SelectionActionExpandProps) {
const {model} = useWorkspace();
const t = useTranslation();

const elements = model.selection.filter((cell): cell is Element => cell instanceof Element);
const selection = useObservedProperty(model.events, 'changeSelection', () => model.selection);
const elements = selection.filter(item => item instanceof Element);
const elementExpandedStore = useElementExpandedStore(model, elements);

const canExpand = (element: Element) => {
Expand Down Expand Up @@ -444,11 +459,7 @@ export function SelectionActionAnchor(props: SelectionActionAnchorProps) {
const {dock, dockRow, dockColumn, className, title, anchorProps, onSelect} = props;
const {model} = useWorkspace();
const t = useTranslation();
const elements = model.selection.filter((cell): cell is Element => cell instanceof Element);
if (elements.length !== 1) {
return null;
}
const [target] = elements;
const target = useSingleSelectedElement(model);
if (!(target instanceof EntityElement)) {
return null;
}
Expand Down Expand Up @@ -502,7 +513,8 @@ export function SelectionActionConnections(props: SelectionActionConnectionsProp
() => overlay.openedDialog?.knownType === BuiltinDialogType.connectionsMenu
);

const elements = model.selection.filter((cell): cell is Element => cell instanceof Element);
const selection = useObservedProperty(model.events, 'changeSelection', () => model.selection);
const elements = selection.filter(item => item instanceof Element);

let entityCount = 0;
for (const element of elements) {
Expand Down Expand Up @@ -558,16 +570,12 @@ export function SelectionActionAddToFilter(props: SelectionActionAddToFilterProp
const {model, getCommandBus} = useWorkspace();
const t = useTranslation();

const elements = model.selection.filter((cell): cell is Element => cell instanceof Element);
const target = useSingleSelectedElement(model);
const commands = getCommandBus(InstancesSearchTopic);
const event: InstancesSearchCommands['findCapabilities'] = {capabilities: []};
commands.trigger('findCapabilities', event);

if (!(event.capabilities.length > 0 && elements.length === 1)) {
return null;
}
const [target] = elements;
if (!(target instanceof EntityElement)) {
if (!(target instanceof EntityElement && event.capabilities.length > 0)) {
return null;
}
return (
Expand Down Expand Up @@ -602,7 +610,7 @@ export interface SelectionActionGroupProps extends SelectionActionStyleProps {
* are selected, the elements can be ungrouped if only {@link EntityGroup entity groups}
* are selected.
*
* Grouping or ungrouping the elements adds a command to the command history.
* Grouping or un-grouping the elements adds a command to the command history.
*
* @category Components
*/
Expand All @@ -613,7 +621,8 @@ export function SelectionActionGroup(props: SelectionActionGroupProps) {
const t = useTranslation();
const {model} = workspace;

const elements = model.selection.filter((cell): cell is Element => cell instanceof Element);
const selection = useObservedProperty(model.events, 'changeSelection', () => model.selection);
const elements = selection.filter(item => item instanceof Element);

const canGroup = elements.length > 0 && elements.every(element => element instanceof EntityElement);
const canUngroup = elements.length > 0 && elements.every(element => element instanceof EntityGroup);
Expand Down Expand Up @@ -682,8 +691,7 @@ export interface SelectionActionEstablishLinkProps extends SelectionActionStyleP
export function SelectionActionEstablishLink(props: SelectionActionEstablishLinkProps) {
const {model} = useCanvas();

const elements = model.selection.filter((cell): cell is Element => cell instanceof Element);
const target = elements.length === 1 ? elements[0] : undefined;
const target = useSingleSelectedElement(model);

if (target instanceof EntityElement) {
return <SelectionActionEstablishRelation {...props} target={target} />;
Expand Down Expand Up @@ -854,7 +862,8 @@ export function SelectionActionAnnotate(props: SelectionActionAnnotateProps) {
const {model, getCommandBus} = useWorkspace();
const t = useTranslation();

const elements = model.selection.filter((cell): cell is Element => cell instanceof Element);
const selection = useObservedProperty(model.events, 'changeSelection', () => model.selection);
const elements = selection.filter(item => item instanceof Element);

const commands = getCommandBus(AnnotationTopic);
const event: AnnotationCommands['findCapabilities'] = {capabilities: []};
Expand Down
Loading