fix: clip frame children completely out of bounds
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
# AGENTS.md
|
||||
|
||||
## Project Structure
|
||||
|
||||
Excalidraw is a **monorepo** with a clear separation between the core library and the application:
|
||||
|
||||
- **`packages/excalidraw/`** - Main React component library published to npm as `@excalidraw/excalidraw`
|
||||
- **`excalidraw-app/`** - Full-featured web application (excalidraw.com) that uses the library
|
||||
- **`packages/`** - Core packages: `@excalidraw/common`, `@excalidraw/element`, `@excalidraw/math`, `@excalidraw/utils`
|
||||
- **`examples/`** - Integration examples (NextJS, browser script)
|
||||
|
||||
## Development Workflow
|
||||
|
||||
1. **Package Development**: Work in `packages/*` for editor features
|
||||
2. **App Development**: Work in `excalidraw-app/` for app-specific features
|
||||
3. **Testing**: Always run `yarn test:update` before committing
|
||||
4. **Type Safety**: Use `yarn test:typecheck` to verify TypeScript
|
||||
|
||||
## Development Commands
|
||||
|
||||
```bash
|
||||
yarn test:typecheck # TypeScript type checking
|
||||
yarn test:update # Run all tests (with snapshot updates)
|
||||
yarn fix # Auto-fix formatting and linting issues
|
||||
```
|
||||
|
||||
## Architecture Notes
|
||||
|
||||
### Package System
|
||||
|
||||
- Uses Yarn workspaces for monorepo management
|
||||
- Internal packages use path aliases (see `vitest.config.mts`)
|
||||
- Build system uses esbuild for packages, Vite for the app
|
||||
- TypeScript throughout with strict configuration
|
||||
@@ -872,6 +872,19 @@ export const shouldApplyFrameClip = (
|
||||
return true;
|
||||
}
|
||||
|
||||
// Elements that belong to a frame should still render through that frame's
|
||||
// clip, even when fully outside the frame bounds (e.g. generated content).
|
||||
if (
|
||||
!appState.selectedElementsAreBeingDragged &&
|
||||
element.frameId === frame.id
|
||||
) {
|
||||
for (const groupId of element.groupIds) {
|
||||
checkedGroups?.set(groupId, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// if an element is outside the frame, but is part of a group that has some elements
|
||||
// "in" the frame, we should clip the element
|
||||
if (
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
convertToExcalidrawElements,
|
||||
Excalidraw,
|
||||
} from "@excalidraw/excalidraw";
|
||||
import { arrayToMap } from "@excalidraw/common";
|
||||
|
||||
import { API } from "@excalidraw/excalidraw/tests/helpers/api";
|
||||
import { Keyboard, Pointer } from "@excalidraw/excalidraw/tests/helpers/ui";
|
||||
@@ -10,6 +11,8 @@ import {
|
||||
render,
|
||||
} from "@excalidraw/excalidraw/tests/test-utils";
|
||||
|
||||
import { shouldApplyFrameClip } from "../src/frame";
|
||||
|
||||
import type { ExcalidrawElement } from "../src/types";
|
||||
|
||||
const { h } = window;
|
||||
@@ -561,3 +564,78 @@ describe("adding elements to frames", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("frame clipping", () => {
|
||||
const getAppStateForFrameClip = () =>
|
||||
({
|
||||
frameRendering: {
|
||||
enabled: true,
|
||||
clip: true,
|
||||
},
|
||||
selectedElementsAreBeingDragged: false,
|
||||
selectedElementIds: {},
|
||||
frameToHighlight: null,
|
||||
editingGroupId: null,
|
||||
} as any);
|
||||
|
||||
it("clips a frame child even when fully outside the frame bounds", () => {
|
||||
const frame = API.createElement({
|
||||
type: "frame",
|
||||
id: "frame",
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 100,
|
||||
height: 100,
|
||||
});
|
||||
const outsideChild = API.createElement({
|
||||
type: "rectangle",
|
||||
id: "outside-child",
|
||||
x: 250,
|
||||
y: 250,
|
||||
width: 50,
|
||||
height: 50,
|
||||
frameId: frame.id,
|
||||
});
|
||||
|
||||
const elementsMap = arrayToMap([outsideChild, frame]);
|
||||
|
||||
expect(
|
||||
shouldApplyFrameClip(
|
||||
outsideChild,
|
||||
frame,
|
||||
getAppStateForFrameClip(),
|
||||
elementsMap,
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("does not clip an outside element that does not belong to the frame", () => {
|
||||
const frame = API.createElement({
|
||||
type: "frame",
|
||||
id: "frame",
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 100,
|
||||
height: 100,
|
||||
});
|
||||
const outsideElement = API.createElement({
|
||||
type: "rectangle",
|
||||
id: "outside",
|
||||
x: 250,
|
||||
y: 250,
|
||||
width: 50,
|
||||
height: 50,
|
||||
});
|
||||
|
||||
const elementsMap = arrayToMap([outsideElement, frame]);
|
||||
|
||||
expect(
|
||||
shouldApplyFrameClip(
|
||||
outsideElement,
|
||||
frame,
|
||||
getAppStateForFrameClip(),
|
||||
elementsMap,
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user