diff --git a/examples/with-script-in-browser/components/ExampleApp.scss b/examples/with-script-in-browser/components/ExampleApp.scss index e41a77ccc..77b921ea8 100644 --- a/examples/with-script-in-browser/components/ExampleApp.scss +++ b/examples/with-script-in-browser/components/ExampleApp.scss @@ -52,7 +52,7 @@ transform: none; } -.excalidraw .panelColumn { +.excalidraw .selected-shape-actions { text-align: left; } diff --git a/packages/common/package.json b/packages/common/package.json index 32cffc717..8fedd6742 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -13,7 +13,7 @@ "default": "./dist/prod/index.js" }, "./*": { - "types": "./../common/dist/types/common/src/*.d.ts" + "types": "./dist/types/common/src/*.d.ts" } }, "files": [ diff --git a/packages/common/src/constants.ts b/packages/common/src/constants.ts index 88d9c8f11..b9e5661a9 100644 --- a/packages/common/src/constants.ts +++ b/packages/common/src/constants.ts @@ -10,6 +10,7 @@ export const isDarwin = /Mac|iPod|iPhone|iPad/.test(navigator.platform); export const isWindows = /^Win/.test(navigator.platform); export const isAndroid = /\b(android)\b/i.test(navigator.userAgent); export const isFirefox = + typeof window !== "undefined" && "netscape" in window && navigator.userAgent.indexOf("rv:") > 1 && navigator.userAgent.indexOf("Gecko") > 1; @@ -255,7 +256,7 @@ export const EXPORT_DATA_TYPES = { excalidrawClipboardWithAPI: "excalidraw-api/clipboard", } as const; -export const EXPORT_SOURCE = +export const getExportSource = () => window.EXCALIDRAW_EXPORT_SOURCE || window.location.origin; // time in milliseconds diff --git a/packages/common/src/font-metadata.ts b/packages/common/src/font-metadata.ts index f6005ee45..7382aa70f 100644 --- a/packages/common/src/font-metadata.ts +++ b/packages/common/src/font-metadata.ts @@ -46,7 +46,7 @@ export const FONT_METADATA: Record = { unitsPerEm: 1000, ascender: 1011, descender: -353, - lineHeight: 1.35, + lineHeight: 1.25, }, }, [FONT_FAMILY["Lilita One"]]: { @@ -116,7 +116,7 @@ export const FONT_METADATA: Record = { unitsPerEm: 1000, ascender: 880, descender: -144, - lineHeight: 1.15, + lineHeight: 1.25, }, fallback: true, }, diff --git a/packages/element/package.json b/packages/element/package.json index 1eec60742..16b9a49e7 100644 --- a/packages/element/package.json +++ b/packages/element/package.json @@ -13,7 +13,7 @@ "default": "./dist/prod/index.js" }, "./*": { - "types": "./../element/dist/types/element/src/*.d.ts" + "types": "./dist/types/element/src/*.d.ts" } }, "files": [ diff --git a/packages/element/src/embeddable.ts b/packages/element/src/embeddable.ts index e6d6b4af3..78dc26fe2 100644 --- a/packages/element/src/embeddable.ts +++ b/packages/element/src/embeddable.ts @@ -33,6 +33,8 @@ const RE_GH_GIST = /^https:\/\/gist\.github\.com\/([\w_-]+)\/([\w_-]+)/; const RE_GH_GIST_EMBED = /^ twitter embeds const RE_TWITTER = /(?:https?:\/\/)?(?:(?:w){3}\.)?(?:twitter|x)\.com\/[^/]+\/status\/(\d+)/; @@ -69,6 +71,7 @@ const ALLOWED_DOMAINS = new Set([ "val.town", "giphy.com", "reddit.com", + "forms.microsoft.com", ]); const ALLOW_SAME_ORIGIN = new Set([ @@ -82,6 +85,7 @@ const ALLOW_SAME_ORIGIN = new Set([ "*.simplepdf.eu", "stackblitz.com", "reddit.com", + "forms.microsoft.com", ]); export const createSrcDoc = (body: string) => { @@ -206,6 +210,10 @@ export const getEmbedLink = ( }; } + if (RE_MSFORMS.test(link) && !link.includes("embed=true")) { + link += link.includes("?") ? "&embed=true" : "?embed=true"; + } + if (RE_TWITTER.test(link)) { const postId = link.match(RE_TWITTER)![1]; // the embed srcdoc still supports twitter.com domain only. diff --git a/packages/element/src/renderElement.ts b/packages/element/src/renderElement.ts index c8091e8ed..2786f3f84 100644 --- a/packages/element/src/renderElement.ts +++ b/packages/element/src/renderElement.ts @@ -351,12 +351,20 @@ const generateElementCanvas = ( export const DEFAULT_LINK_SIZE = 14; -const IMAGE_PLACEHOLDER_IMG = document.createElement("img"); +const IMAGE_PLACEHOLDER_IMG = + typeof document !== "undefined" + ? document.createElement("img") + : ({ src: "" } as HTMLImageElement); // mock image element outside of browser + IMAGE_PLACEHOLDER_IMG.src = `data:${MIME_TYPES.svg},${encodeURIComponent( ``, )}`; -const IMAGE_ERROR_PLACEHOLDER_IMG = document.createElement("img"); +const IMAGE_ERROR_PLACEHOLDER_IMG = + typeof document !== "undefined" + ? document.createElement("img") + : ({ src: "" } as HTMLImageElement); // mock image element outside of browser + IMAGE_ERROR_PLACEHOLDER_IMG.src = `data:${MIME_TYPES.svg},${encodeURIComponent( ``, )}`; diff --git a/packages/excalidraw/actions/actionProperties.tsx b/packages/excalidraw/actions/actionProperties.tsx index b116595ff..8d2c1a073 100644 --- a/packages/excalidraw/actions/actionProperties.tsx +++ b/packages/excalidraw/actions/actionProperties.tsx @@ -78,7 +78,7 @@ import type { Scene } from "@excalidraw/element"; import type { CaptureUpdateActionType } from "@excalidraw/element"; import { trackEvent } from "../analytics"; -import { ButtonIconSelect } from "../components/ButtonIconSelect"; +import { RadioSelection } from "../components/RadioSelection"; import { ColorPicker } from "../components/ColorPicker/ColorPicker"; import { FontPicker } from "../components/FontPicker/FontPicker"; import { IconPicker } from "../components/IconPicker"; @@ -457,50 +457,52 @@ export const actionChangeFillStyle = register({ return (
{t("labels.fill")} - element.fillStyle, - (element) => element.hasOwnProperty("fillStyle"), - (hasSelection) => - hasSelection ? null : appState.currentItemFillStyle, - )} - onClick={(value, event) => { - const nextValue = - event.altKey && - value === "hachure" && - selectedElements.every((el) => el.fillStyle === "hachure") - ? "zigzag" - : value; +
+ element.fillStyle, + (element) => element.hasOwnProperty("fillStyle"), + (hasSelection) => + hasSelection ? null : appState.currentItemFillStyle, + )} + onClick={(value, event) => { + const nextValue = + event.altKey && + value === "hachure" && + selectedElements.every((el) => el.fillStyle === "hachure") + ? "zigzag" + : value; - updateData(nextValue); - }} - /> + updateData(nextValue); + }} + /> +
); }, @@ -524,38 +526,40 @@ export const actionChangeStrokeWidth = register({ PanelComponent: ({ elements, appState, updateData, app }) => (
{t("labels.strokeWidth")} - element.strokeWidth, - (element) => element.hasOwnProperty("strokeWidth"), - (hasSelection) => - hasSelection ? null : appState.currentItemStrokeWidth, - )} - onChange={(value) => updateData(value)} - /> +
+ element.strokeWidth, + (element) => element.hasOwnProperty("strokeWidth"), + (hasSelection) => + hasSelection ? null : appState.currentItemStrokeWidth, + )} + onChange={(value) => updateData(value)} + /> +
), }); @@ -579,35 +583,37 @@ export const actionChangeSloppiness = register({ PanelComponent: ({ elements, appState, updateData, app }) => (
{t("labels.sloppiness")} - element.roughness, - (element) => element.hasOwnProperty("roughness"), - (hasSelection) => - hasSelection ? null : appState.currentItemRoughness, - )} - onChange={(value) => updateData(value)} - /> +
+ element.roughness, + (element) => element.hasOwnProperty("roughness"), + (hasSelection) => + hasSelection ? null : appState.currentItemRoughness, + )} + onChange={(value) => updateData(value)} + /> +
), }); @@ -630,35 +636,37 @@ export const actionChangeStrokeStyle = register({ PanelComponent: ({ elements, appState, updateData, app }) => (
{t("labels.strokeStyle")} - element.strokeStyle, - (element) => element.hasOwnProperty("strokeStyle"), - (hasSelection) => - hasSelection ? null : appState.currentItemStrokeStyle, - )} - onChange={(value) => updateData(value)} - /> +
+ element.strokeStyle, + (element) => element.hasOwnProperty("strokeStyle"), + (hasSelection) => + hasSelection ? null : appState.currentItemStrokeStyle, + )} + onChange={(value) => updateData(value)} + /> +
), }); @@ -697,63 +705,65 @@ export const actionChangeFontSize = register({ PanelComponent: ({ elements, appState, updateData, app }) => (
{t("labels.fontSize")} - { - if (isTextElement(element)) { - return element.fontSize; - } - const boundTextElement = getBoundTextElement( - element, - app.scene.getNonDeletedElementsMap(), - ); - if (boundTextElement) { - return boundTextElement.fontSize; - } - return null; - }, - (element) => - isTextElement(element) || - getBoundTextElement( - element, - app.scene.getNonDeletedElementsMap(), - ) !== null, - (hasSelection) => - hasSelection - ? null - : appState.currentItemFontSize || DEFAULT_FONT_SIZE, - )} - onChange={(value) => updateData(value)} - /> +
+ { + if (isTextElement(element)) { + return element.fontSize; + } + const boundTextElement = getBoundTextElement( + element, + app.scene.getNonDeletedElementsMap(), + ); + if (boundTextElement) { + return boundTextElement.fontSize; + } + return null; + }, + (element) => + isTextElement(element) || + getBoundTextElement( + element, + app.scene.getNonDeletedElementsMap(), + ) !== null, + (hasSelection) => + hasSelection + ? null + : appState.currentItemFontSize || DEFAULT_FONT_SIZE, + )} + onChange={(value) => updateData(value)} + /> +
), }); @@ -1225,52 +1235,54 @@ export const actionChangeTextAlign = register({ return (
{t("labels.textAlign")} - - group="text-align" - options={[ - { - value: "left", - text: t("labels.left"), - icon: TextAlignLeftIcon, - testId: "align-left", - }, - { - value: "center", - text: t("labels.center"), - icon: TextAlignCenterIcon, - testId: "align-horizontal-center", - }, - { - value: "right", - text: t("labels.right"), - icon: TextAlignRightIcon, - testId: "align-right", - }, - ]} - value={getFormValue( - elements, - app, - (element) => { - if (isTextElement(element)) { - return element.textAlign; - } - const boundTextElement = getBoundTextElement( - element, - elementsMap, - ); - if (boundTextElement) { - return boundTextElement.textAlign; - } - return null; - }, - (element) => - isTextElement(element) || - getBoundTextElement(element, elementsMap) !== null, - (hasSelection) => - hasSelection ? null : appState.currentItemTextAlign, - )} - onChange={(value) => updateData(value)} - /> +
+ + group="text-align" + options={[ + { + value: "left", + text: t("labels.left"), + icon: TextAlignLeftIcon, + testId: "align-left", + }, + { + value: "center", + text: t("labels.center"), + icon: TextAlignCenterIcon, + testId: "align-horizontal-center", + }, + { + value: "right", + text: t("labels.right"), + icon: TextAlignRightIcon, + testId: "align-right", + }, + ]} + value={getFormValue( + elements, + app, + (element) => { + if (isTextElement(element)) { + return element.textAlign; + } + const boundTextElement = getBoundTextElement( + element, + elementsMap, + ); + if (boundTextElement) { + return boundTextElement.textAlign; + } + return null; + }, + (element) => + isTextElement(element) || + getBoundTextElement(element, elementsMap) !== null, + (hasSelection) => + hasSelection ? null : appState.currentItemTextAlign, + )} + onChange={(value) => updateData(value)} + /> +
); }, @@ -1313,54 +1325,56 @@ export const actionChangeVerticalAlign = register({ PanelComponent: ({ elements, appState, updateData, app }) => { return (
- - group="text-align" - options={[ - { - value: VERTICAL_ALIGN.TOP, - text: t("labels.alignTop"), - icon: , - testId: "align-top", - }, - { - value: VERTICAL_ALIGN.MIDDLE, - text: t("labels.centerVertically"), - icon: , - testId: "align-middle", - }, - { - value: VERTICAL_ALIGN.BOTTOM, - text: t("labels.alignBottom"), - icon: , - testId: "align-bottom", - }, - ]} - value={getFormValue( - elements, - app, - (element) => { - if (isTextElement(element) && element.containerId) { - return element.verticalAlign; - } - const boundTextElement = getBoundTextElement( - element, - app.scene.getNonDeletedElementsMap(), - ); - if (boundTextElement) { - return boundTextElement.verticalAlign; - } - return null; - }, - (element) => - isTextElement(element) || - getBoundTextElement( - element, - app.scene.getNonDeletedElementsMap(), - ) !== null, - (hasSelection) => (hasSelection ? null : VERTICAL_ALIGN.MIDDLE), - )} - onChange={(value) => updateData(value)} - /> +
+ + group="text-align" + options={[ + { + value: VERTICAL_ALIGN.TOP, + text: t("labels.alignTop"), + icon: , + testId: "align-top", + }, + { + value: VERTICAL_ALIGN.MIDDLE, + text: t("labels.centerVertically"), + icon: , + testId: "align-middle", + }, + { + value: VERTICAL_ALIGN.BOTTOM, + text: t("labels.alignBottom"), + icon: , + testId: "align-bottom", + }, + ]} + value={getFormValue( + elements, + app, + (element) => { + if (isTextElement(element) && element.containerId) { + return element.verticalAlign; + } + const boundTextElement = getBoundTextElement( + element, + app.scene.getNonDeletedElementsMap(), + ); + if (boundTextElement) { + return boundTextElement.verticalAlign; + } + return null; + }, + (element) => + isTextElement(element) || + getBoundTextElement( + element, + app.scene.getNonDeletedElementsMap(), + ) !== null, + (hasSelection) => (hasSelection ? null : VERTICAL_ALIGN.MIDDLE), + )} + onChange={(value) => updateData(value)} + /> +
); }, @@ -1408,34 +1422,39 @@ export const actionChangeRoundness = register({ return (
{t("labels.edges")} - - hasLegacyRoundness ? null : element.roundness ? "round" : "sharp", - (element) => - !isArrowElement(element) && element.hasOwnProperty("roundness"), - (hasSelection) => - hasSelection ? null : appState.currentItemRoundness, - )} - onChange={(value) => updateData(value)} - > +
+ + hasLegacyRoundness + ? null + : element.roundness + ? "round" + : "sharp", + (element) => + !isArrowElement(element) && element.hasOwnProperty("roundness"), + (hasSelection) => + hasSelection ? null : appState.currentItemRoundness, + )} + onChange={(value) => updateData(value)} + /> {renderAction("togglePolygon")} - +
); }, @@ -1798,48 +1817,50 @@ export const actionChangeArrowType = register({ return (
{t("labels.arrowtypes")} - { - if (isArrowElement(element)) { - return element.elbowed - ? ARROW_TYPE.elbow - : element.roundness - ? ARROW_TYPE.round - : ARROW_TYPE.sharp; - } +
+ { + if (isArrowElement(element)) { + return element.elbowed + ? ARROW_TYPE.elbow + : element.roundness + ? ARROW_TYPE.round + : ARROW_TYPE.sharp; + } - return null; - }, - (element) => isArrowElement(element), - (hasSelection) => - hasSelection ? null : appState.currentItemArrowType, - )} - onChange={(value) => updateData(value)} - /> + return null; + }, + (element) => isArrowElement(element), + (hasSelection) => + hasSelection ? null : appState.currentItemArrowType, + )} + onChange={(value) => updateData(value)} + /> +
); }, diff --git a/packages/excalidraw/components/Actions.tsx b/packages/excalidraw/components/Actions.tsx index 4f3782048..60dab78f4 100644 --- a/packages/excalidraw/components/Actions.tsx +++ b/packages/excalidraw/components/Actions.tsx @@ -154,7 +154,7 @@ export const SelectedShapeActions = ({ !isSingleElementBoundContainer && alignActionsPredicate(appState, app); return ( -
+
{canChangeStrokeColor(appState, targetElements) && renderAction("changeStrokeColor")} diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 29de62cf4..b0c43359b 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -7276,8 +7276,13 @@ class App extends React.Component { }); // If we click on something } else if (hitElement != null) { + // == deep selection == // on CMD/CTRL, drill down to hit element regardless of groups etc. if (event[KEYS.CTRL_OR_CMD]) { + if (event.altKey) { + // ctrl + alt means we're lasso selecting + return false; + } if (!this.state.selectedElementIds[hitElement.id]) { pointerDownState.hit.wasAddedToSelection = true; } @@ -8636,17 +8641,19 @@ class App extends React.Component { pointerDownState.lastCoords.x = pointerCoords.x; pointerDownState.lastCoords.y = pointerCoords.y; if (event.altKey) { - this.setActiveTool( - { type: "lasso", fromSelection: true }, - event.shiftKey, - ); - this.lassoTrail.startPath( - pointerDownState.origin.x, - pointerDownState.origin.y, - event.shiftKey, - ); - this.setAppState({ - selectionElement: null, + flushSync(() => { + this.setActiveTool( + { type: "lasso", fromSelection: true }, + event.shiftKey, + ); + this.lassoTrail.startPath( + pointerDownState.origin.x, + pointerDownState.origin.y, + event.shiftKey, + ); + this.setAppState({ + selectionElement: null, + }); }); } else { this.maybeDragNewGenericElement(pointerDownState, event); @@ -9521,7 +9528,10 @@ class App extends React.Component { // if we're editing a line, pointerup shouldn't switch selection if // box selected (!this.state.editingLinearElement || - !pointerDownState.boxSelection.hasOccurred) + !pointerDownState.boxSelection.hasOccurred) && + // hitElement can be set when alt + ctrl to toggle lasso and we will + // just respect the selected elements from lasso instead + this.state.activeTool.type !== "lasso" ) { // when inside line editor, shift selects points instead if (childEvent.shiftKey && !this.state.editingLinearElement) { diff --git a/packages/excalidraw/components/ButtonSelect.tsx b/packages/excalidraw/components/ButtonSelect.tsx deleted file mode 100644 index c47ff65e7..000000000 --- a/packages/excalidraw/components/ButtonSelect.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import clsx from "clsx"; - -export const ButtonSelect = ({ - options, - value, - onChange, - group, -}: { - options: { value: T; text: string }[]; - value: T | null; - onChange: (value: T) => void; - group: string; -}) => ( -
- {options.map((option) => ( - - ))} -
-); diff --git a/packages/excalidraw/components/FontPicker/FontPicker.tsx b/packages/excalidraw/components/FontPicker/FontPicker.tsx index 546e1fa34..118c6fac3 100644 --- a/packages/excalidraw/components/FontPicker/FontPicker.tsx +++ b/packages/excalidraw/components/FontPicker/FontPicker.tsx @@ -6,7 +6,7 @@ import { FONT_FAMILY } from "@excalidraw/common"; import type { FontFamilyValues } from "@excalidraw/element/types"; import { t } from "../../i18n"; -import { ButtonIconSelect } from "../ButtonIconSelect"; +import { RadioSelection } from "../RadioSelection"; import { ButtonSeparator } from "../ButtonSeparator"; import { FontFamilyCodeIcon, @@ -82,12 +82,14 @@ export const FontPicker = React.memo( return (
- - type="button" - options={defaultFonts} - value={selectedFontFamily} - onClick={onSelectCallback} - /> +
+ + type="button" + options={defaultFonts} + value={selectedFontFamily} + onClick={onSelectCallback} + /> +
diff --git a/packages/excalidraw/components/PublishLibrary.tsx b/packages/excalidraw/components/PublishLibrary.tsx index 580b909d4..076b303d7 100644 --- a/packages/excalidraw/components/PublishLibrary.tsx +++ b/packages/excalidraw/components/PublishLibrary.tsx @@ -5,10 +5,10 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { EDITOR_LS_KEYS, EXPORT_DATA_TYPES, - EXPORT_SOURCE, MIME_TYPES, VERSIONS, chunk, + getExportSource, } from "@excalidraw/common"; import { EditorLocalStorage } from "../data/EditorLocalStorage"; @@ -281,7 +281,7 @@ const PublishLibrary = ({ const libContent: ExportedLibraryData = { type: EXPORT_DATA_TYPES.excalidrawLibrary, version: VERSIONS.excalidrawLibrary, - source: EXPORT_SOURCE, + source: getExportSource(), libraryItems: clonedLibItems, }; const content = JSON.stringify(libContent, null, 2); diff --git a/packages/excalidraw/components/ButtonIconSelect.tsx b/packages/excalidraw/components/RadioSelection.tsx similarity index 85% rename from packages/excalidraw/components/ButtonIconSelect.tsx rename to packages/excalidraw/components/RadioSelection.tsx index 6d64396a9..9cdc47e48 100644 --- a/packages/excalidraw/components/ButtonIconSelect.tsx +++ b/packages/excalidraw/components/RadioSelection.tsx @@ -4,8 +4,7 @@ import { ButtonIcon } from "./ButtonIcon"; import type { JSX } from "react"; -// TODO: It might be "clever" to add option.icon to the existing component -export const ButtonIconSelect = ( +export const RadioSelection = ( props: { options: { value: T; @@ -17,7 +16,6 @@ export const ButtonIconSelect = ( }[]; value: T | null; type?: "radio" | "button"; - children?: React.ReactNode; } & ( | { type?: "radio"; group: string; onChange: (value: T) => void } | { @@ -29,7 +27,7 @@ export const ButtonIconSelect = ( } ), ) => ( -
+ <> {props.options.map((option) => props.type === "button" ? ( ( ), )} - {props.children} -
+ ); diff --git a/packages/excalidraw/components/hyperlink/helpers.ts b/packages/excalidraw/components/hyperlink/helpers.ts index bdabbfc44..7d39b7ff7 100644 --- a/packages/excalidraw/components/hyperlink/helpers.ts +++ b/packages/excalidraw/components/hyperlink/helpers.ts @@ -4,8 +4,6 @@ import { MIME_TYPES } from "@excalidraw/common"; import { getElementAbsoluteCoords } from "@excalidraw/element"; import { hitElementBoundingBox } from "@excalidraw/element"; -import { DEFAULT_LINK_SIZE } from "@excalidraw/element"; - import type { GlobalPoint, Radians } from "@excalidraw/math"; import type { Bounds } from "@excalidraw/element"; @@ -16,9 +14,11 @@ import type { import type { AppState, UIAppState } from "../../types"; +export const DEFAULT_LINK_SIZE = 12; + export const EXTERNAL_LINK_IMG = document.createElement("img"); EXTERNAL_LINK_IMG.src = `data:${MIME_TYPES.svg}, ${encodeURIComponent( - ``, + ``, )}`; export const ELEMENT_LINK_IMG = document.createElement("img"); @@ -32,13 +32,14 @@ export const getLinkHandleFromCoords = ( appState: Pick, ): Bounds => { const size = DEFAULT_LINK_SIZE; - const linkWidth = size / appState.zoom.value; - const linkHeight = size / appState.zoom.value; - const linkMarginY = size / appState.zoom.value; + const zoom = appState.zoom.value > 1 ? appState.zoom.value : 1; + const linkWidth = size / zoom; + const linkHeight = size / zoom; + const linkMarginY = size / zoom; const centerX = (x1 + x2) / 2; const centerY = (y1 + y2) / 2; - const centeringOffset = (size - 8) / (2 * appState.zoom.value); - const dashedLineMargin = 4 / appState.zoom.value; + const centeringOffset = (size - 8) / (2 * zoom); + const dashedLineMargin = 4 / zoom; // Same as `ne` resize handle const x = x2 + dashedLineMargin - centeringOffset; diff --git a/packages/excalidraw/css/styles.scss b/packages/excalidraw/css/styles.scss index 6f1d9cd48..f8486083e 100644 --- a/packages/excalidraw/css/styles.scss +++ b/packages/excalidraw/css/styles.scss @@ -140,7 +140,7 @@ body.excalidraw-cursor-resize * { justify-content: space-between; } - .panelColumn { + .selected-shape-actions { display: flex; flex-direction: column; row-gap: 0.75rem; @@ -245,10 +245,6 @@ body.excalidraw-cursor-resize * { left: 0; right: 0; --bar-padding: calc(4 * var(--space-factor)); - padding-top: #{"max(var(--bar-padding), var(--sat,0))"}; - padding-right: var(--sar, 0); - padding-bottom: var(--sab, 0); - padding-left: var(--sal, 0); z-index: 4; display: flex; align-items: flex-end; @@ -263,10 +259,6 @@ body.excalidraw-cursor-resize * { display: flex; flex-direction: column; pointer-events: var(--ui-pointerEvents); - - .panelColumn { - padding: 8px 8px 0 8px; - } } } @@ -302,6 +294,10 @@ body.excalidraw-cursor-resize * { overflow-y: auto; box-sizing: border-box; margin-bottom: var(--bar-padding); + + .selected-shape-actions { + padding: 8px 8px 0 8px; + } } .App-menu { diff --git a/packages/excalidraw/data/json.ts b/packages/excalidraw/data/json.ts index 527c9e56e..52cbf9958 100644 --- a/packages/excalidraw/data/json.ts +++ b/packages/excalidraw/data/json.ts @@ -1,7 +1,7 @@ import { DEFAULT_FILENAME, EXPORT_DATA_TYPES, - EXPORT_SOURCE, + getExportSource, MIME_TYPES, VERSIONS, } from "@excalidraw/common"; @@ -56,7 +56,7 @@ export const serializeAsJSON = ( const data: ExportedDataState = { type: EXPORT_DATA_TYPES.excalidraw, version: VERSIONS.excalidraw, - source: EXPORT_SOURCE, + source: getExportSource(), elements: type === "local" ? clearElementsForExport(elements) @@ -142,7 +142,7 @@ export const serializeLibraryAsJSON = (libraryItems: LibraryItems) => { const data: ExportedLibraryData = { type: EXPORT_DATA_TYPES.excalidrawLibrary, version: VERSIONS.excalidrawLibrary, - source: EXPORT_SOURCE, + source: getExportSource(), libraryItems, }; return JSON.stringify(data, null, 2); diff --git a/packages/excalidraw/lasso/index.ts b/packages/excalidraw/lasso/index.ts index 41e6b2080..163a8b7a9 100644 --- a/packages/excalidraw/lasso/index.ts +++ b/packages/excalidraw/lasso/index.ts @@ -17,7 +17,7 @@ import { selectGroupsForSelectedElements } from "@excalidraw/element"; import { getContainerElement } from "@excalidraw/element"; -import { arrayToMap, easeOut } from "@excalidraw/common"; +import { arrayToMap, easeOut, isShallowEqual } from "@excalidraw/common"; import type { ExcalidrawElement, @@ -33,11 +33,18 @@ import { getLassoSelectedElementIds } from "./utils"; import type App from "../components/App"; +type CanvasTranslate = { + scrollX: number; + scrollY: number; + zoom: number; +}; + export class LassoTrail extends AnimatedTrail { private intersectedElements: Set = new Set(); private enclosedElements: Set = new Set(); private elementsSegments: Map[]> | null = null; + private canvasTranslate: CanvasTranslate | null = null; private keepPreviousSelection: boolean = false; constructor(animationFrameHandler: AnimationFrameHandler, app: App) { @@ -169,7 +176,17 @@ export class LassoTrail extends AnimatedTrail { .getCurrentTrail() ?.originalPoints?.map((p) => pointFrom(p[0], p[1])); - if (!this.elementsSegments) { + const currentCanvasTranslate: CanvasTranslate = { + scrollX: this.app.state.scrollX, + scrollY: this.app.state.scrollY, + zoom: this.app.state.zoom.value, + }; + + if ( + !this.elementsSegments || + !isShallowEqual(currentCanvasTranslate, this.canvasTranslate ?? {}) + ) { + this.canvasTranslate = currentCanvasTranslate; this.elementsSegments = new Map(); const visibleElementsMap = arrayToMap(this.app.visibleElements); for (const element of this.app.visibleElements) { diff --git a/packages/excalidraw/renderer/staticScene.ts b/packages/excalidraw/renderer/staticScene.ts index d63779464..18cf0092d 100644 --- a/packages/excalidraw/renderer/staticScene.ts +++ b/packages/excalidraw/renderer/staticScene.ts @@ -188,7 +188,7 @@ const renderLinkIcon = ( window.devicePixelRatio * appState.zoom.value, window.devicePixelRatio * appState.zoom.value, ); - linkCanvasCacheContext.fillStyle = "#fff"; + linkCanvasCacheContext.fillStyle = appState.viewBackgroundColor || "#fff"; linkCanvasCacheContext.fillRect(0, 0, width, height); if (canvasKey === "elementLink") { diff --git a/packages/excalidraw/snapping.ts b/packages/excalidraw/snapping.ts index 635ba065d..8dd1bd59a 100644 --- a/packages/excalidraw/snapping.ts +++ b/packages/excalidraw/snapping.ts @@ -170,10 +170,11 @@ export const isSnappingEnabled = ({ }) => { if (event) { return ( - (app.state.objectsSnapModeEnabled && !event[KEYS.CTRL_OR_CMD]) || - (!app.state.objectsSnapModeEnabled && - event[KEYS.CTRL_OR_CMD] && - !isGridModeEnabled(app)) + app.state.activeTool.type !== "lasso" && + ((app.state.objectsSnapModeEnabled && !event[KEYS.CTRL_OR_CMD]) || + (!app.state.objectsSnapModeEnabled && + event[KEYS.CTRL_OR_CMD] && + !isGridModeEnabled(app))) ); } diff --git a/packages/excalidraw/wysiwyg/textWysiwyg.test.tsx b/packages/excalidraw/wysiwyg/textWysiwyg.test.tsx index fb142057f..e7cd97509 100644 --- a/packages/excalidraw/wysiwyg/textWysiwyg.test.tsx +++ b/packages/excalidraw/wysiwyg/textWysiwyg.test.tsx @@ -1324,7 +1324,7 @@ describe("textWysiwyg", () => { ).toEqual(FONT_FAMILY.Nunito); expect( (h.elements[1] as ExcalidrawTextElementWithContainer).lineHeight, - ).toEqual(1.35); + ).toEqual(1.25); }); describe("should align correctly", () => { diff --git a/packages/math/package.json b/packages/math/package.json index e9f5fd8da..5fac47bef 100644 --- a/packages/math/package.json +++ b/packages/math/package.json @@ -13,7 +13,7 @@ "default": "./dist/prod/index.js" }, "./*": { - "types": "./../math/dist/types/math/src/*.d.ts" + "types": "./dist/types/math/src/*.d.ts" } }, "files": [ diff --git a/packages/utils/package.json b/packages/utils/package.json index 2dc54c59c..6dc400c65 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -13,7 +13,7 @@ "default": "./dist/prod/index.js" }, "./*": { - "types": "./../utils/dist/types/utils/src/*.d.ts" + "types": "./dist/types/utils/src/*.d.ts" } }, "files": [