Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions docs/app/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,6 @@ body {
box-shadow: unset !important;
}

.demo {
overflow: none;
}

.demo .bn-container {
position: relative;
}

.demo .bn-container:not(.bn-comment-editor),
.demo .bn-editor {
height: 100%;
Expand Down
12 changes: 12 additions & 0 deletions packages/core/src/extensions/SideMenu/SideMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -784,5 +784,17 @@ export const SideMenuExtension = createExtension(({ editor }) => {
view!.state!.show = false;
view!.emitUpdate(view!.state!);
},

/**
* Hides the side menu unless it is currently frozen (e.g. the drag
* handle menu is open). Used to dismiss the menu on scroll without
* interfering with open submenus.
*/
hideMenuIfNotFrozen() {
if (!view!.menuFrozen && view!.state!.show) {
view!.state!.show = false;
view!.emitUpdate(view!.state!);
}
},
} as const;
});
14 changes: 14 additions & 0 deletions packages/core/src/extensions/TableHandles/TableHandles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,20 @@ export const TableHandlesExtension = createExtension(({ editor }) => {
view!.menuFrozen = false;
},

/**
* Hides the table handles unless they are currently frozen (e.g. a
* handle menu is open). Used to dismiss the handles on scroll without
* interfering with open submenus.
*/
hideHandlesIfNotFrozen() {
if (!view!.menuFrozen && view!.state?.show) {
view!.state.show = false;
view!.state.showAddOrRemoveRowsButton = false;
view!.state.showAddOrRemoveColumnsButton = false;
view!.emitUpdate();
}
},

getCellsAtRowHandle(
block: BlockFromConfigNoChildren<DefaultBlockSchema["table"], any, any>,
relativeRowIndex: RelativeCellIndices["row"],
Expand Down
38 changes: 36 additions & 2 deletions packages/react/src/components/Popovers/GenericPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
FloatingFocusManager,
useDismiss,
useFloating,
UseFloatingOptions,
useHover,
useInteractions,
useMergeRefs,
Expand Down Expand Up @@ -77,15 +78,48 @@ export function getMountedBoundingClientRectCache(
};
}

/**
* Merges two `whileElementsMounted` handlers into one. Both run when elements
* mount, and both cleanup functions are called on unmount.
*/
function mergeWhileElementsMounted(
a: UseFloatingOptions["whileElementsMounted"],
b: UseFloatingOptions["whileElementsMounted"],
): UseFloatingOptions["whileElementsMounted"] {
if (!a) {
return b;
}
if (!b) {
return a;
}

return (reference, floating, update) => {
const cleanupA = a(reference, floating, update);
const cleanupB = b(reference, floating, update);
return () => {
cleanupA?.();
cleanupB?.();
};
};
}

export const GenericPopover = (
props: FloatingUIOptions & {
reference?: GenericPopoverReference;
children: ReactNode;
},
) => {
const {
whileElementsMounted: _whileElementsMounted,
...restFloatingOptions
} = props.useFloatingOptions ?? {};

const { refs, floatingStyles, context } = useFloating<HTMLDivElement>({
whileElementsMounted: autoUpdate,
...props.useFloatingOptions,
whileElementsMounted: mergeWhileElementsMounted(
autoUpdate,
props.useFloatingOptions?.whileElementsMounted,
),
...restFloatingOptions,
});

const { isMounted, styles } = useTransitionStyles(
Expand Down
40 changes: 38 additions & 2 deletions packages/react/src/components/SideMenu/SideMenuController.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { SideMenuExtension } from "@blocknote/core/extensions";
import { FC, useMemo } from "react";
import { autoUpdate, ReferenceElement } from "@floating-ui/react";
import { FC, useCallback, useMemo } from "react";

import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor.js";
import { useExtensionState } from "../../hooks/useExtension.js";
import { BlockPopover } from "../Popovers/BlockPopover.js";
import { FloatingUIOptions } from "../Popovers/FloatingUIOptions.js";
Expand All @@ -11,6 +13,7 @@ export const SideMenuController = (props: {
sideMenu?: FC<SideMenuProps>;
floatingUIOptions?: Partial<FloatingUIOptions>;
}) => {
const editor = useBlockNoteEditor();
const state = useExtensionState(SideMenuExtension, {
selector: (state) => {
return state !== undefined
Expand All @@ -24,12 +27,45 @@ export const SideMenuController = (props: {

const { show, block } = state || {};

// Hides the side menu on ancestor scroll so it doesn't overflow outside
// the editor's scroll container.
const whileElementsMounted = useCallback(
(
reference: ReferenceElement,
floating: HTMLElement,
_update: () => void,
) => {
let initialized = false;
return autoUpdate(
reference,
floating,
() => {
if (!initialized) {
// autoUpdate calls this function once when the floating element is mounted
// we don't want to hide the menu in that case
initialized = true;
return;
}
editor.getExtension(SideMenuExtension)?.hideMenuIfNotFrozen();
},
{
ancestorScroll: true,
ancestorResize: false,
elementResize: false,
layoutShift: false,
},
);
},
[editor],
);

const floatingUIOptions = useMemo<FloatingUIOptions>(
() => ({
...props.floatingUIOptions,
useFloatingOptions: {
open: show,
placement: "left-start",
whileElementsMounted,
...props.floatingUIOptions?.useFloatingOptions,
},
useDismissProps: {
Expand All @@ -47,7 +83,7 @@ export const SideMenuController = (props: {
...props.floatingUIOptions?.elementProps,
},
}),
[props.floatingUIOptions, show],
[props.floatingUIOptions, show, whileElementsMounted],
);

const Component = props.sideMenu || SideMenu;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import {
StyleSchema,
} from "@blocknote/core";
import { TableHandlesExtension } from "@blocknote/core/extensions";
import { FC, useMemo, useState } from "react";
import { FC, useCallback, useMemo, useState } from "react";

import { offset, size } from "@floating-ui/react";
import { autoUpdate, offset, ReferenceElement, size } from "@floating-ui/react";
import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor.js";
import { useExtensionState } from "../../hooks/useExtension.js";
import { FloatingUIOptions } from "../Popovers/FloatingUIOptions.js";
Expand Down Expand Up @@ -137,6 +137,36 @@ export const TableHandlesController = <
return references;
}, [editor, state]);

// Hides the table handles on ancestor scroll so they don't overflow
// outside the editor's scroll container.
const whileElementsMounted = useCallback(
(
reference: ReferenceElement,
floating: HTMLElement,
_update: () => void,
) => {
let initialized = false;
return autoUpdate(
reference,
floating,
() => {
if (!initialized) {
initialized = true;
return;
}
editor.getExtension(TableHandlesExtension)?.hideHandlesIfNotFrozen();
},
{
ancestorScroll: true,
ancestorResize: false,
elementResize: false,
layoutShift: false,
},
);
},
[editor],
);

const floatingUIOptions = useMemo<
| {
rowTableHandle: FloatingUIOptions;
Expand All @@ -158,6 +188,7 @@ export const TableHandlesController = <
(!onlyShownElement || onlyShownElement === "rowTableHandle"),
placement: "left",
middleware: [offset(-10)],
whileElementsMounted,
},
focusManagerProps: {
disabled: true,
Expand All @@ -177,6 +208,7 @@ export const TableHandlesController = <
onlyShownElement === "columnTableHandle"),
placement: "top",
middleware: [offset(-12)],
whileElementsMounted,
},
focusManagerProps: {
disabled: true,
Expand All @@ -196,6 +228,7 @@ export const TableHandlesController = <
(!onlyShownElement || onlyShownElement === "tableCellHandle"),
placement: "top-end",
middleware: [offset({ mainAxis: -15, crossAxis: -1 })],
whileElementsMounted,
},
focusManagerProps: {
disabled: true,
Expand All @@ -214,6 +247,7 @@ export const TableHandlesController = <
(!onlyShownElement ||
onlyShownElement === "extendRowsButton"),
placement: "bottom",
whileElementsMounted,
middleware: [
size({
apply({ rects, elements }) {
Expand Down Expand Up @@ -241,6 +275,7 @@ export const TableHandlesController = <
(!onlyShownElement ||
onlyShownElement === "extendColumnsButton"),
placement: "right",
whileElementsMounted,
middleware: [
size({
apply({ rects, elements }) {
Expand All @@ -262,7 +297,7 @@ export const TableHandlesController = <
},
}
: undefined,
[onlyShownElement, state],
[onlyShownElement, state, whileElementsMounted],
);

if (!state) {
Expand Down
Loading