diff --git a/CHANGELOG.md b/CHANGELOG.md
index 47291bd9..481cc85e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,12 @@ All notable changes to the Reactodia will be documented in this document.
The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]
+#### 🚀 New Features
+- Add `EditorController.applyAuthoringChanges()` method to apply current authoring changes to the diagram (i.e. change entity data, delete relations, etc) and reset the change state to be empty.
+
+#### 💅 Polish
+- Make dialogs fill the available viewport when the viewport width is small:
+ * This is controlled by new CSS property `--reactodia-dialog-viewport-breakpoint-s` with default value `600px` which makes dialog fill the viewport if the available width is less or equal to that value.
## [0.30.1] - 2025-06-27
#### 🐛 Fixed
diff --git a/examples/resources/template.ejs b/examples/resources/template.ejs
index 3474ce28..85438778 100644
--- a/examples/resources/template.ejs
+++ b/examples/resources/template.ejs
@@ -37,6 +37,9 @@
:root[data-border-radius-zero] .reactodia-workspace {
--reactodia-border-radius-base: unset;
}
+ .reactodia-paper-area {
+ outline: none;
+ }
();
+
private _openedDialog: OpenedDialog | undefined;
private _tasks = new Set();
private _taskError: { error: unknown } | undefined;
@@ -107,6 +109,7 @@ export class OverlayController {
view.setCanvasWidget('selectionHandler', {
element: (
@@ -324,8 +327,14 @@ export class OverlayController {
onClose,
};
+ const canvas = this.view.findAnyCanvas();
+ const breakpoint = readOverlayProperty(this.overlays, reader => reader.getDialogViewportBreakpoint());
+ const isSmallViewport = Boolean(
+ canvas && breakpoint !== undefined && canvas.metrics.area.clientWidth <= breakpoint
+ );
+
const onHide = () => this.hideDialog();
- if (target) {
+ if (target && !isSmallViewport) {
this.view.setCanvasWidget('dialog', {
element: (
),
- attachment: 'overElements'
+ attachment: 'overElements',
});
} else {
this.view.setCanvasWidget('dialog', {
element: (
{content}
@@ -446,7 +456,6 @@ function ViewportDialog(props: DialogProps) {
className='reactodia-viewport-dialog-overlay'>
@@ -469,19 +478,65 @@ function useViewportSize() {
return size;
}
+interface OverlayReader {
+ getDialogViewportBreakpoint(): number | undefined;
+}
+
function CanvasOverlayHandler(props: {
+ overlays: Set;
onCanvasPointerUp: (event: CanvasPointerUpEvent) => void;
onCanvasKeydown: (event: CanvasKeyboardEvent) => void;
}) {
- const {onCanvasPointerUp, onCanvasKeydown} = props;
+ const {overlays, onCanvasPointerUp, onCanvasKeydown} = props;
const {canvas} = useCanvas();
+
+ const ref = React.useRef(null);
+ React.useLayoutEffect(() => {
+ const overlay = ref.current;
+ if (overlay) {
+ const reader: OverlayReader = {
+ getDialogViewportBreakpoint: () => {
+ const style = getComputedStyle(overlay);
+ // Use parseFloat to ignore trailing "px" unit at the end
+ return style.maxWidth ? parseFloat(style.maxWidth) : undefined;
+ },
+ };
+ overlays.add(reader);
+ return () => {
+ overlays.delete(reader);
+ };
+ }
+ }, [overlays]);
+
React.useEffect(() => {
const listener = new EventObserver();
listener.listen(canvas.events, 'pointerUp', onCanvasPointerUp);
listener.listen(canvas.events, 'keydown', onCanvasKeydown);
return () => listener.stopListening();
}, [onCanvasPointerUp, onCanvasKeydown]);
- return null;
+
+ return (
+
+ );
+}
+
+function readOverlayProperty(
+ readers: Iterable,
+ getProperty: (reader: OverlayReader) => T | undefined
+): T | undefined {
+ for (const reader of readers) {
+ const value = getProperty(reader);
+ if (value !== undefined) {
+ return value;
+ }
+ }
+ return undefined;
}
function getErrorMessage(error: unknown): string | undefined {
diff --git a/src/widgets/dialog.tsx b/src/widgets/dialog.tsx
index 328a9247..48bad7e8 100644
--- a/src/widgets/dialog.tsx
+++ b/src/widgets/dialog.tsx
@@ -14,7 +14,7 @@ import { DraggableHandle } from './utility/draggableHandle';
export interface DialogProps extends DialogStyleProps {
target?: DialogTarget;
onHide: () => void;
- centered?: boolean;
+ mode?: 'centered' | 'fillViewport';
children: React.ReactNode;
}
@@ -263,7 +263,8 @@ export class Dialog extends React.Component {
}
private onDragHandle = (e: MouseEvent, dx: number, dy: number) => {
- const {dock = DEFAULT_DOCK, centered} = this.props;
+ const {dock = DEFAULT_DOCK, mode} = this.props;
+ const centered = mode === 'centered';
let factorX = centered ? 2 : 1;
let factorY = centered ? 2 : 1;
switch (dock) {
@@ -286,12 +287,14 @@ export class Dialog extends React.Component {
render() {
const {
+ mode,
dock = DEFAULT_DOCK,
caption,
- resizableBy = 'all',
+ resizableBy: baseResizableBy = 'all',
closable = true,
} = this.props;
+ const resizableBy = mode === 'fillViewport' ? 'none' : baseResizableBy;
const size = this.getCurrentSize();
const position = this.calculatePosition(size);
const style: React.CSSProperties = {
@@ -302,10 +305,14 @@ export class Dialog extends React.Component {
};
return (
-
+ style={mode === 'fillViewport' ? undefined : style}>