Compare commits

..

1 Commits

Author SHA1 Message Date
Ryan Di c0efc16270 fix: do not bind invisible part of an element to arrow 2023-07-19 18:15:04 +08:00
16 changed files with 69 additions and 859 deletions
-30
View File
@@ -1,30 +0,0 @@
name: "Bundle Size check @excalidraw/excalidraw"
on:
pull_request:
branches:
- master
jobs:
size:
runs-on: ubuntu-latest
env:
CI_JOB_NUMBER: 1
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup Node.js 18.x
uses: actions/setup-node@v3
with:
node-version: 18.x
- name: Install
run: yarn --frozen-lockfile
- name: Install in src/packages/excalidraw
run: yarn --frozen-lockfile
working-directory: src/packages/excalidraw
env:
CI: true
- uses: andresz1/size-limit-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
build_script: build:umd
skip_step: install
directory: src/packages/excalidraw
@@ -165,35 +165,3 @@ function App() {
| Prop | Type | Required | Default | Description | | Prop | Type | Required | Default | Description |
| --- | --- | :-: | :-: | --- | | --- | --- | :-: | :-: | --- |
| `children ` | `React.ReactNode` | Yes | - | The content of the `Menu Group` | | `children ` | `React.ReactNode` | Yes | - | The content of the `Menu Group` |
### MainMenu.Sub
The MainMenu component now supports submenus. To render a submenu, you can use `MainMenu.Sub`, `MainMenu.Sub.Trigger`, `MainMenu.Sub.Content` and `MainMenu.Sub.Item`. Note that `MainMenu.Sub.Trigger` and `MainMenu.Sub.Content` must be direct children of `MainMenu.Sub`.
```jsx live
function App() {
return (
<div style={{ height: "500px" }}>
<Excalidraw>
<MainMenu>
<MainMenu.Sub>
<MainMenu.Sub.Trigger>Submenu</MainMenu.Sub.Trigger>
<MainMenu.Sub.Content>
<MainMenu.Sub.Item
onSelect={() => window.alert("Submenu item 1")}
>
Submenu item 1
</MainMenu.Sub.Item>
<MainMenu.Sub.Item
onSelect={() => window.alert("Submenu item 2")}
>
Submenu item 2
</MainMenu.Sub.Item>
</MainMenu.Sub.Content>
</MainMenu.Sub>
</MainMenu>
</Excalidraw>
</div>
);
}
```
+5 -5
View File
@@ -65,7 +65,7 @@ export const actionChangeExportScale = register({
); );
const scaleButtonTitle = `${t( const scaleButtonTitle = `${t(
"imageExportDialog.label.scale", "buttons.scale",
)} ${s}x (${width}x${height})`; )} ${s}x (${width}x${height})`;
return ( return (
@@ -102,7 +102,7 @@ export const actionChangeExportBackground = register({
checked={appState.exportBackground} checked={appState.exportBackground}
onChange={(checked) => updateData(checked)} onChange={(checked) => updateData(checked)}
> >
{t("imageExportDialog.label.withBackground")} {t("labels.withBackground")}
</CheckboxItem> </CheckboxItem>
), ),
}); });
@@ -121,8 +121,8 @@ export const actionChangeExportEmbedScene = register({
checked={appState.exportEmbedScene} checked={appState.exportEmbedScene}
onChange={(checked) => updateData(checked)} onChange={(checked) => updateData(checked)}
> >
{t("imageExportDialog.label.embedScene")} {t("labels.exportEmbedScene")}
<Tooltip label={t("imageExportDialog.tooltip.embedScene")} long={true}> <Tooltip label={t("labels.exportEmbedScene_details")} long={true}>
<div className="excalidraw-tooltip-icon">{questionCircle}</div> <div className="excalidraw-tooltip-icon">{questionCircle}</div>
</Tooltip> </Tooltip>
</CheckboxItem> </CheckboxItem>
@@ -277,7 +277,7 @@ export const actionExportWithDarkMode = register({
onChange={(theme: Theme) => { onChange={(theme: Theme) => {
updateData(theme === THEME.DARK); updateData(theme === THEME.DARK);
}} }}
title={t("imageExportDialog.label.darkMode")} title={t("labels.toggleExportColorScheme")}
/> />
</div> </div>
), ),
@@ -8,7 +8,7 @@ import {
} from "./colorPickerUtils"; } from "./colorPickerUtils";
import HotkeyLabel from "./HotkeyLabel"; import HotkeyLabel from "./HotkeyLabel";
import { ColorPaletteCustom } from "../../colors"; import { ColorPaletteCustom } from "../../colors";
import { TranslationKeys, t } from "../../i18n"; import { t } from "../../i18n";
interface PickerColorListProps { interface PickerColorListProps {
palette: ColorPaletteCustom; palette: ColorPaletteCustom;
@@ -48,11 +48,7 @@ const PickerColorList = ({
(Array.isArray(value) ? value[activeShade] : value) || "transparent"; (Array.isArray(value) ? value[activeShade] : value) || "transparent";
const keybinding = colorPickerHotkeyBindings[index]; const keybinding = colorPickerHotkeyBindings[index];
const label = t( const label = t(`colors.${key.replace(/\d+/, "")}`, null, "");
`colors.${key.replace(/\d+/, "")}` as unknown as TranslationKeys,
null,
"",
);
return ( return (
<button <button
+3 -7
View File
@@ -1,6 +1,6 @@
import clsx from "clsx"; import clsx from "clsx";
import { Popover } from "./Popover"; import { Popover } from "./Popover";
import { t, TranslationKeys } from "../i18n"; import { t } from "../i18n";
import "./ContextMenu.scss"; import "./ContextMenu.scss";
import { import {
@@ -83,14 +83,10 @@ export const ContextMenu = React.memo(
if (item.contextItemLabel) { if (item.contextItemLabel) {
if (typeof item.contextItemLabel === "function") { if (typeof item.contextItemLabel === "function") {
label = t( label = t(
item.contextItemLabel( item.contextItemLabel(elements, appState, actionManager.app),
elements,
appState,
actionManager.app,
) as unknown as TranslationKeys,
); );
} else { } else {
label = t(item.contextItemLabel as unknown as TranslationKeys); label = t(item.contextItemLabel);
} }
} }
+1 -1
View File
@@ -3,7 +3,7 @@ import { t } from "../i18n";
import { useExcalidrawContainer } from "./App"; import { useExcalidrawContainer } from "./App";
export const Section: React.FC<{ export const Section: React.FC<{
heading: "canvasActions" | "selectedShapeActions" | "shapes"; heading: string;
children?: React.ReactNode | ((heading: React.ReactNode) => React.ReactNode); children?: React.ReactNode | ((heading: React.ReactNode) => React.ReactNode);
className?: string; className?: string;
}> = ({ heading, children, ...props }) => { }> = ({ heading, children, ...props }) => {
+5 -9
View File
@@ -3,7 +3,6 @@ import { render } from "@testing-library/react";
import fallbackLangData from "../locales/en.json"; import fallbackLangData from "../locales/en.json";
import Trans from "./Trans"; import Trans from "./Trans";
import { TranslationKeys } from "../i18n";
describe("Test <Trans/>", () => { describe("Test <Trans/>", () => {
it("should translate the the strings correctly", () => { it("should translate the the strings correctly", () => {
@@ -19,27 +18,24 @@ describe("Test <Trans/>", () => {
const { getByTestId } = render( const { getByTestId } = render(
<> <>
<div data-testid="test1"> <div data-testid="test1">
<Trans <Trans i18nKey="transTest.key1" audience="world" />
i18nKey={"transTest.key1" as unknown as TranslationKeys}
audience="world"
/>
</div> </div>
<div data-testid="test2"> <div data-testid="test2">
<Trans <Trans
i18nKey={"transTest.key2" as unknown as TranslationKeys} i18nKey="transTest.key2"
link={(el) => <a href="https://example.com">{el}</a>} link={(el) => <a href="https://example.com">{el}</a>}
/> />
</div> </div>
<div data-testid="test3"> <div data-testid="test3">
<Trans <Trans
i18nKey={"transTest.key3" as unknown as TranslationKeys} i18nKey="transTest.key3"
link={(el) => <a href="https://example.com">{el}</a>} link={(el) => <a href="https://example.com">{el}</a>}
location="the button" location="the button"
/> />
</div> </div>
<div data-testid="test4"> <div data-testid="test4">
<Trans <Trans
i18nKey={"transTest.key4" as unknown as TranslationKeys} i18nKey="transTest.key4"
link={(el) => <a href="https://example.com">{el}</a>} link={(el) => <a href="https://example.com">{el}</a>}
location="the button" location="the button"
bold={(el) => <strong>{el}</strong>} bold={(el) => <strong>{el}</strong>}
@@ -47,7 +43,7 @@ describe("Test <Trans/>", () => {
</div> </div>
<div data-testid="test5"> <div data-testid="test5">
<Trans <Trans
i18nKey={"transTest.key5" as unknown as TranslationKeys} i18nKey="transTest.key5"
connect-link={(el) => <a href="https://example.com">{el}</a>} connect-link={(el) => <a href="https://example.com">{el}</a>}
/> />
</div> </div>
+2 -2
View File
@@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { TranslationKeys, useI18n } from "../i18n"; import { useI18n } from "../i18n";
// Used for splitting i18nKey into tokens in Trans component // Used for splitting i18nKey into tokens in Trans component
// Example: // Example:
@@ -153,7 +153,7 @@ const Trans = ({
children, children,
...props ...props
}: { }: {
i18nKey: TranslationKeys; i18nKey: string;
[key: string]: React.ReactNode | ((el: React.ReactNode) => React.ReactNode); [key: string]: React.ReactNode | ((el: React.ReactNode) => React.ReactNode);
}) => { }) => {
const { t } = useI18n(); const { t } = useI18n();
+29 -4
View File
@@ -27,6 +27,7 @@ import { LinearElementEditor } from "./linearElementEditor";
import { arrayToMap, tupleToCoors } from "../utils"; import { arrayToMap, tupleToCoors } from "../utils";
import { KEYS } from "../keys"; import { KEYS } from "../keys";
import { getBoundTextElement, handleBindTextResize } from "./textElement"; import { getBoundTextElement, handleBindTextResize } from "./textElement";
import { getContainingFrame, isPointInFrame } from "../frame";
export type SuggestedBinding = export type SuggestedBinding =
| NonDeleted<ExcalidrawBindableElement> | NonDeleted<ExcalidrawBindableElement>
@@ -274,6 +275,18 @@ export const getHoveredElementForBinding = (
isBindableElement(element, false) && isBindableElement(element, false) &&
bindingBorderTest(element, pointerCoords), bindingBorderTest(element, pointerCoords),
); );
if (hoveredElement) {
const frame = getContainingFrame(hoveredElement);
if (frame) {
if (isPointInFrame(pointerCoords, frame)) {
return hoveredElement as NonDeleted<ExcalidrawBindableElement>;
}
return null;
}
}
return hoveredElement as NonDeleted<ExcalidrawBindableElement> | null; return hoveredElement as NonDeleted<ExcalidrawBindableElement> | null;
}; };
@@ -499,10 +512,22 @@ const getElligibleElementsForBindingElement = (
return [ return [
getElligibleElementForBindingElement(linearElement, "start"), getElligibleElementForBindingElement(linearElement, "start"),
getElligibleElementForBindingElement(linearElement, "end"), getElligibleElementForBindingElement(linearElement, "end"),
].filter( ].filter((element): element is NonDeleted<ExcalidrawBindableElement> => {
(element): element is NonDeleted<ExcalidrawBindableElement> => if (element != null) {
element != null, const frame = getContainingFrame(element);
); return frame
? isPointInFrame(
getLinearElementEdgeCoors(linearElement, "start"),
frame,
) ||
isPointInFrame(
getLinearElementEdgeCoors(linearElement, "end"),
frame,
)
: true;
}
return false;
});
}; };
const getElligibleElementForBindingElement = ( const getElligibleElementForBindingElement = (
+10
View File
@@ -1,6 +1,7 @@
import { import {
getCommonBounds, getCommonBounds,
getElementAbsoluteCoords, getElementAbsoluteCoords,
getElementBounds,
isTextElement, isTextElement,
} from "./element"; } from "./element";
import { import {
@@ -299,6 +300,15 @@ export const groupsAreCompletelyOutOfFrame = (
); );
}; };
export const isPointInFrame = (
{ x, y }: { x: number; y: number },
frame: ExcalidrawFrameElement,
) => {
const [x1, y1, x2, y2] = getElementBounds(frame);
return x >= x1 && x <= x2 && y >= y1 && y <= y2;
};
// --------------------------- Frame Utils ------------------------------------ // --------------------------- Frame Utils ------------------------------------
/** /**
+1 -4
View File
@@ -3,7 +3,6 @@ import percentages from "./locales/percentages.json";
import { ENV } from "./constants"; import { ENV } from "./constants";
import { jotaiScope, jotaiStore } from "./jotai"; import { jotaiScope, jotaiStore } from "./jotai";
import { atom, useAtomValue } from "jotai"; import { atom, useAtomValue } from "jotai";
import { NestedKeyOf } from "./utility-types";
const COMPLETION_THRESHOLD = 85; const COMPLETION_THRESHOLD = 85;
@@ -13,8 +12,6 @@ export interface Language {
rtl?: boolean; rtl?: boolean;
} }
export type TranslationKeys = NestedKeyOf<typeof fallbackLangData>;
export const defaultLang = { code: "en", label: "English" }; export const defaultLang = { code: "en", label: "English" };
export const languages: Language[] = [ export const languages: Language[] = [
@@ -126,7 +123,7 @@ const findPartsForData = (data: any, parts: string[]) => {
}; };
export const t = ( export const t = (
path: NestedKeyOf<typeof fallbackLangData>, path: string,
replacement?: { [key: string]: string | number } | null, replacement?: { [key: string]: string | number } | null,
fallback?: string, fallback?: string,
) => { ) => {
-16
View File
@@ -1,16 +0,0 @@
[
{
"path": "dist/excalidraw.production.min.js",
"limit": "285 kB"
},
{
"path": "dist/excalidraw-assets/locales",
"name": "dist/excalidraw-assets/locales",
"limit": "270 kB"
},
{
"path": "dist/excalidraw-assets/vendor-*.js",
"name": "dist/excalidraw-assets/vendor*.js",
"limit": "30 kB"
}
]
+1 -5
View File
@@ -52,7 +52,6 @@
"@babel/preset-env": "7.18.6", "@babel/preset-env": "7.18.6",
"@babel/preset-react": "7.18.6", "@babel/preset-react": "7.18.6",
"@babel/preset-typescript": "7.18.6", "@babel/preset-typescript": "7.18.6",
"@size-limit/preset-big-lib": "8.2.6",
"autoprefixer": "10.4.7", "autoprefixer": "10.4.7",
"babel-loader": "8.2.5", "babel-loader": "8.2.5",
"babel-plugin-transform-class-properties": "6.24.1", "babel-plugin-transform-class-properties": "6.24.1",
@@ -62,8 +61,6 @@
"mini-css-extract-plugin": "2.6.1", "mini-css-extract-plugin": "2.6.1",
"postcss-loader": "7.0.1", "postcss-loader": "7.0.1",
"sass-loader": "13.0.2", "sass-loader": "13.0.2",
"size-limit": "8.2.4",
"style-loader": "3.3.3",
"terser-webpack-plugin": "5.3.3", "terser-webpack-plugin": "5.3.3",
"ts-loader": "9.3.1", "ts-loader": "9.3.1",
"typescript": "4.9.4", "typescript": "4.9.4",
@@ -82,7 +79,6 @@
"pack": "yarn build:umd && yarn pack", "pack": "yarn build:umd && yarn pack",
"start": "webpack serve --config webpack.dev-server.config.js", "start": "webpack serve --config webpack.dev-server.config.js",
"install:deps": "yarn install --frozen-lockfile && yarn --cwd ../../../", "install:deps": "yarn install --frozen-lockfile && yarn --cwd ../../../",
"build:example": "EXAMPLE=true webpack --config webpack.dev-server.config.js && yarn gen:types", "build:example": "EXAMPLE=true webpack --config webpack.dev-server.config.js && yarn gen:types"
"size": "yarn build:umd && size-limit"
} }
} }
File diff suppressed because it is too large Load Diff
-4
View File
@@ -50,7 +50,3 @@ export type ExtractSetType<T extends Set<any>> = T extends Set<infer U>
export type SameType<T, U> = T extends U ? (U extends T ? true : false) : false; export type SameType<T, U> = T extends U ? (U extends T ? true : false) : false;
export type Assert<T extends true> = T; export type Assert<T extends true> = T;
export type NestedKeyOf<T, K = keyof T> = K extends keyof T & (string | number)
? `${K}` | (T[K] extends object ? `${K}.${NestedKeyOf<T[K]>}` : never)
: never;
+1 -11
View File
@@ -10267,7 +10267,7 @@ terser-webpack-plugin@^5.1.3, terser-webpack-plugin@^5.2.5:
serialize-javascript "^6.0.1" serialize-javascript "^6.0.1"
terser "^5.16.5" terser "^5.16.5"
terser@^5.0.0, terser@^5.10.0: terser@^5.0.0, terser@^5.10.0, terser@^5.16.5:
version "5.16.9" version "5.16.9"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.16.9.tgz#7a28cb178e330c484369886f2afd623d9847495f" resolved "https://registry.yarnpkg.com/terser/-/terser-5.16.9.tgz#7a28cb178e330c484369886f2afd623d9847495f"
integrity sha512-HPa/FdTB9XGI2H1/keLFZHxl6WNvAI4YalHGtDQTlMnJcoqSab1UwL4l1hGEhs6/GmLHBZIg/YgB++jcbzoOEg== integrity sha512-HPa/FdTB9XGI2H1/keLFZHxl6WNvAI4YalHGtDQTlMnJcoqSab1UwL4l1hGEhs6/GmLHBZIg/YgB++jcbzoOEg==
@@ -10277,16 +10277,6 @@ terser@^5.0.0, terser@^5.10.0:
commander "^2.20.0" commander "^2.20.0"
source-map-support "~0.5.20" source-map-support "~0.5.20"
terser@^5.16.5:
version "5.17.1"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.17.1.tgz#948f10830454761e2eeedc6debe45c532c83fd69"
integrity sha512-hVl35zClmpisy6oaoKALOpS0rDYLxRFLHhRuDlEGTKey9qHjS1w9GMORjuwIMt70Wan4lwsLYyWDVnWgF+KUEw==
dependencies:
"@jridgewell/source-map" "^0.3.2"
acorn "^8.5.0"
commander "^2.20.0"
source-map-support "~0.5.20"
test-exclude@^6.0.0: test-exclude@^6.0.0:
version "6.0.0" version "6.0.0"
resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e"