Expose renderScrollbars to AppState

This commit is contained in:
Jack Walsh 2025-04-16 12:28:06 +10:00
parent 58f7d33d80
commit c1be250493
7 changed files with 32 additions and 1 deletions

View file

@ -31,6 +31,7 @@ All `props` are _optional_.
| [`generateIdForFile`](#generateidforfile) | `function` | \_ | Allows you to override `id` generation for files added on canvas | | [`generateIdForFile`](#generateidforfile) | `function` | \_ | Allows you to override `id` generation for files added on canvas |
| [`validateEmbeddable`](#validateembeddable) | `string[]` \| `boolean` \| `RegExp` \| `RegExp[]` \| <code>((link: string) => boolean &#124; undefined)</code> | \_ | use for custom src url validation | | [`validateEmbeddable`](#validateembeddable) | `string[]` \| `boolean` \| `RegExp` \| `RegExp[]` \| <code>((link: string) => boolean &#124; undefined)</code> | \_ | use for custom src url validation |
| [`renderEmbeddable`](/docs/@excalidraw/excalidraw/api/props/render-props#renderEmbeddable) | `function` | \_ | Render function that can override the built-in `<iframe>` | | [`renderEmbeddable`](/docs/@excalidraw/excalidraw/api/props/render-props#renderEmbeddable) | `function` | \_ | Render function that can override the built-in `<iframe>` |
| [`renderScrollbars`] | `boolean`| | `false` | Indicates whether scrollbars will be shown
### Storing custom data on Excalidraw elements ### Storing custom data on Excalidraw elements

View file

@ -104,6 +104,7 @@ export default function ExampleApp({
const [viewModeEnabled, setViewModeEnabled] = useState(false); const [viewModeEnabled, setViewModeEnabled] = useState(false);
const [zenModeEnabled, setZenModeEnabled] = useState(false); const [zenModeEnabled, setZenModeEnabled] = useState(false);
const [gridModeEnabled, setGridModeEnabled] = useState(false); const [gridModeEnabled, setGridModeEnabled] = useState(false);
const [renderScrollbars, setRenderScrollbars] = useState(false);
const [blobUrl, setBlobUrl] = useState<string>(""); const [blobUrl, setBlobUrl] = useState<string>("");
const [canvasUrl, setCanvasUrl] = useState<string>(""); const [canvasUrl, setCanvasUrl] = useState<string>("");
const [exportWithDarkMode, setExportWithDarkMode] = useState(false); const [exportWithDarkMode, setExportWithDarkMode] = useState(false);
@ -192,6 +193,7 @@ export default function ExampleApp({
}) => setPointerData(payload), }) => setPointerData(payload),
viewModeEnabled, viewModeEnabled,
zenModeEnabled, zenModeEnabled,
renderScrollbars,
gridModeEnabled, gridModeEnabled,
theme, theme,
name: "Custom name of drawing", name: "Custom name of drawing",
@ -710,6 +712,14 @@ export default function ExampleApp({
/> />
Grid mode Grid mode
</label> </label>
<label>
<input
type="checkbox"
checked={renderScrollbars}
onChange={() => setRenderScrollbars(!renderScrollbars)}
/>
Render scrollbars
</label>
<label> <label>
<input <input
type="checkbox" type="checkbox"

View file

@ -81,6 +81,7 @@ export const getDefaultAppState = (): Omit<
openDialog: null, openDialog: null,
pasteDialog: { shown: false, data: null }, pasteDialog: { shown: false, data: null },
previousSelectedElementIds: {}, previousSelectedElementIds: {},
renderScrollbars: false,
resizingElement: null, resizingElement: null,
scrolledOutside: false, scrolledOutside: false,
scrollX: 0, scrollX: 0,
@ -208,6 +209,7 @@ const APP_STATE_STORAGE_CONF = (<
openDialog: { browser: false, export: false, server: false }, openDialog: { browser: false, export: false, server: false },
pasteDialog: { browser: false, export: false, server: false }, pasteDialog: { browser: false, export: false, server: false },
previousSelectedElementIds: { browser: true, export: false, server: false }, previousSelectedElementIds: { browser: true, export: false, server: false },
renderScrollbars: { browser: true, export: false, server: false },
resizingElement: { browser: false, export: false, server: false }, resizingElement: { browser: false, export: false, server: false },
scrolledOutside: { browser: true, export: false, server: false }, scrolledOutside: { browser: true, export: false, server: false },
scrollX: { browser: true, export: false, server: false }, scrollX: { browser: true, export: false, server: false },

View file

@ -721,6 +721,7 @@ class App extends React.Component<AppProps, AppState> {
zenModeEnabled = false, zenModeEnabled = false,
gridModeEnabled = false, gridModeEnabled = false,
objectsSnapModeEnabled = false, objectsSnapModeEnabled = false,
renderScrollbars = false,
theme = defaultAppState.theme, theme = defaultAppState.theme,
name = `${t("labels.untitled")}-${getDateTime()}`, name = `${t("labels.untitled")}-${getDateTime()}`,
} = props; } = props;
@ -731,6 +732,7 @@ class App extends React.Component<AppProps, AppState> {
...this.getCanvasOffsets(), ...this.getCanvasOffsets(),
viewModeEnabled, viewModeEnabled,
zenModeEnabled, zenModeEnabled,
renderScrollbars,
objectsSnapModeEnabled, objectsSnapModeEnabled,
gridModeEnabled: gridModeEnabled ?? defaultAppState.gridModeEnabled, gridModeEnabled: gridModeEnabled ?? defaultAppState.gridModeEnabled,
name, name,
@ -2206,6 +2208,7 @@ class App extends React.Component<AppProps, AppState> {
if (actionResult.appState || editingTextElement || this.state.contextMenu) { if (actionResult.appState || editingTextElement || this.state.contextMenu) {
let viewModeEnabled = actionResult?.appState?.viewModeEnabled || false; let viewModeEnabled = actionResult?.appState?.viewModeEnabled || false;
let zenModeEnabled = actionResult?.appState?.zenModeEnabled || false; let zenModeEnabled = actionResult?.appState?.zenModeEnabled || false;
let renderScrollbars = actionResult?.appState?.renderScrollbars || false;
const theme = const theme =
actionResult?.appState?.theme || this.props.theme || THEME.LIGHT; actionResult?.appState?.theme || this.props.theme || THEME.LIGHT;
const name = actionResult?.appState?.name ?? this.state.name; const name = actionResult?.appState?.name ?? this.state.name;
@ -2219,6 +2222,10 @@ class App extends React.Component<AppProps, AppState> {
zenModeEnabled = this.props.zenModeEnabled; zenModeEnabled = this.props.zenModeEnabled;
} }
if (typeof this.props.renderScrollbars !== "undefined") {
renderScrollbars = this.props.renderScrollbars;
}
editingTextElement = actionResult.appState?.editingTextElement || null; editingTextElement = actionResult.appState?.editingTextElement || null;
// make sure editingTextElement points to latest element reference // make sure editingTextElement points to latest element reference
@ -2252,6 +2259,7 @@ class App extends React.Component<AppProps, AppState> {
editingTextElement, editingTextElement,
viewModeEnabled, viewModeEnabled,
zenModeEnabled, zenModeEnabled,
renderScrollbars,
theme, theme,
name, name,
errorMessage, errorMessage,
@ -2863,6 +2871,10 @@ class App extends React.Component<AppProps, AppState> {
this.setState({ theme: this.props.theme }); this.setState({ theme: this.props.theme });
} }
if (prevProps.renderScrollbars !== this.props.renderScrollbars) {
this.setState({ renderScrollbars: !!this.props.renderScrollbars });
}
this.excalidrawContainerRef.current?.classList.toggle( this.excalidrawContainerRef.current?.classList.toggle(
"theme--dark", "theme--dark",
this.state.theme === THEME.DARK, this.state.theme === THEME.DARK,

View file

@ -143,7 +143,7 @@ const InteractiveCanvas = (props: InteractiveCanvasProps) => {
remotePointerUsernames, remotePointerUsernames,
remotePointerUserStates, remotePointerUserStates,
selectionColor, selectionColor,
renderScrollbars: false, renderScrollbars: props.appState.renderScrollbars,
}, },
device: props.device, device: props.device,
callback: props.renderInteractiveSceneCallback, callback: props.renderInteractiveSceneCallback,
@ -214,6 +214,7 @@ const getRelevantAppStateProps = (
isCropping: appState.isCropping, isCropping: appState.isCropping,
croppingElementId: appState.croppingElementId, croppingElementId: appState.croppingElementId,
searchMatches: appState.searchMatches, searchMatches: appState.searchMatches,
renderScrollbars: appState.renderScrollbars,
}); });
const areEqual = ( const areEqual = (

View file

@ -53,6 +53,7 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
renderEmbeddable, renderEmbeddable,
aiEnabled, aiEnabled,
showDeprecatedFonts, showDeprecatedFonts,
renderScrollbars,
} = props; } = props;
const canvasActions = props.UIOptions?.canvasActions; const canvasActions = props.UIOptions?.canvasActions;
@ -143,6 +144,7 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
renderEmbeddable={renderEmbeddable} renderEmbeddable={renderEmbeddable}
aiEnabled={aiEnabled !== false} aiEnabled={aiEnabled !== false}
showDeprecatedFonts={showDeprecatedFonts} showDeprecatedFonts={showDeprecatedFonts}
renderScrollbars={renderScrollbars}
> >
{children} {children}
</App> </App>

View file

@ -230,6 +230,7 @@ export type InteractiveCanvasAppState = Readonly<
croppingElementId: AppState["croppingElementId"]; croppingElementId: AppState["croppingElementId"];
// Search matches // Search matches
searchMatches: AppState["searchMatches"]; searchMatches: AppState["searchMatches"];
renderScrollbars: AppState["renderScrollbars"];
} }
>; >;
@ -428,6 +429,7 @@ export interface AppState {
croppingElementId: ExcalidrawElement["id"] | null; croppingElementId: ExcalidrawElement["id"] | null;
searchMatches: readonly SearchMatch[]; searchMatches: readonly SearchMatch[];
renderScrollbars: boolean;
} }
type SearchMatch = { type SearchMatch = {
@ -601,6 +603,7 @@ export interface ExcalidrawProps {
) => JSX.Element | null; ) => JSX.Element | null;
aiEnabled?: boolean; aiEnabled?: boolean;
showDeprecatedFonts?: boolean; showDeprecatedFonts?: boolean;
renderScrollbars?: boolean;
} }
export type SceneData = { export type SceneData = {