Skip to content
Draft
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
30 changes: 27 additions & 3 deletions packages/ariakit/src/input/Form.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,36 @@
import { FormProvider as AriakitFormProvider } from "@ariakit/react";

import { assertEmpty } from "@blocknote/core";
import { ComponentProps } from "@blocknote/react";
import { ComponentProps, useDictionary } from "@blocknote/react";

export const Form = (props: ComponentProps["Generic"]["Form"]["Root"]) => {
const { children, ...rest } = props;
const { children, onSubmit, omitSubmitButton, ...rest } = props;
const dict = useDictionary();

assertEmpty(rest);

return <AriakitFormProvider>{children}</AriakitFormProvider>;
return (
<AriakitFormProvider>
<form
onSubmit={(event) => {
// These forms have no action — a real submission would navigate.
event.preventDefault();
onSubmit?.();
}}
>
{children}
{/*
Gives the form a submit button, which is what makes Enter submit it at
all once a caller renders more than one field (see the `onSubmit`
contract in `ComponentsContext`). Visually hidden rather than absent,
so assistive technology still has a labelled control to activate.
*/}
{!omitSubmitButton && (
<button className={"bn-form-submit"} tabIndex={-1} type={"submit"}>
{dict.generic.form_submit}
</button>
)}
</form>
</AriakitFormProvider>
);
};
22 changes: 16 additions & 6 deletions packages/ariakit/src/input/TextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import {
} from "@ariakit/react";

import { assertEmpty, mergeCSSClasses } from "@blocknote/core";
import { ComponentProps } from "@blocknote/react";
import { forwardRef } from "react";
import { ComponentProps, useMergeRefs } from "@blocknote/react";
import { forwardRef, useEffect, useRef } from "react";

export const TextInput = forwardRef<
HTMLInputElement,
Expand All @@ -23,7 +23,6 @@ export const TextInput = forwardRef<
disabled,
onKeyDown,
onChange,
onSubmit,
autoComplete,
"aria-activedescendant": ariaActivedescendant,
rightSection,
Expand All @@ -32,6 +31,19 @@ export const TextInput = forwardRef<

assertEmpty(rest);

// Focus with `preventScroll`, rather than the native `autofocus`: these
// inputs live in popovers that floating-ui positions *after* mount, so the
// browser's scroll-into-view runs while the popover is still at its
// pre-positioned spot and yanks the page (on mobile, right out from under
// the block being edited).
const inputRef = useRef<HTMLInputElement | null>(null);
const setRefs = useMergeRefs([inputRef, ref]);
useEffect(() => {
if (autoFocus) {
inputRef.current?.focus({ preventScroll: true });
}
}, [autoFocus]);

return (
<>
{props.label && <AriakitFormLabel name={name}>{label}</AriakitFormLabel>}
Expand All @@ -43,15 +55,13 @@ export const TextInput = forwardRef<
className || "",
variant === "large" ? "bn-ak-input-large" : "",
)}
ref={ref}
ref={setRefs}
name={name}
value={value}
autoFocus={autoFocus}
placeholder={placeholder}
disabled={disabled}
onKeyDown={onKeyDown}
onChange={onChange}
onSubmit={onSubmit}
autoComplete={autoComplete}
aria-activedescendant={ariaActivedescendant}
/>
Expand Down
20 changes: 20 additions & 0 deletions packages/ariakit/src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -433,3 +433,23 @@
.bn-ariakit .bn-thread.selected .bn-ak-expand-sections-prompt {
color: var(--bn-colors-selected-text);
}

/* The submit button `Form.Root` renders so that Enter reaches the form
* regardless of how many fields a popover has. It carries no visual design of
* its own - the popovers commit on Enter - but it stays in the accessibility
* tree with a real label, so screen readers and voice control have a submit
* control to operate. It is out of the tab order: keeping a control nobody can
* see as a tab stop would strand sighted keyboard users on invisible focus,
* and Enter already submits for them.
*/
.bn-form-submit {
border: 0;
clip-path: inset(50%);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
white-space: nowrap;
width: 1px;
}
21 changes: 20 additions & 1 deletion packages/core/src/editor/managers/StyleManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,26 @@ export class StyleManager<
*/
public getSelectedLinkUrl() {
return this.editor.transact((tr) => {
return this.getLinkMarkAtPos(tr.selection.from)?.href;
const { from, to, empty } = tr.selection;
if (empty) {
return this.getLinkMarkAtPos(from)?.href;
}
// For a non-empty selection, probing a single boundary position is
// fragile twice over: `marks()` excludes a link at its left edge, and
// browsers disagree by a position on where a selection over a link
// starts. Scan the selected range for the first link mark instead.
let href: string | undefined;
tr.doc.nodesBetween(from, to, (node) => {
if (href !== undefined) {
return false;
}
const linkMark = node.marks.find((mark) => mark.type.name === "link");
if (linkMark) {
href = linkMark.attrs.href;
}
return href === undefined;
});
return href;
});
}

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/ar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,5 +406,6 @@ export const ar: Dictionary = {
},
generic: {
ctrl_shortcut: "Ctrl",
form_submit: "إرسال",
},
};
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,5 +440,6 @@ export const de: Dictionary = {
},
generic: {
ctrl_shortcut: "Strg",
form_submit: "Absenden",
},
};
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,5 +421,6 @@ export const en = {
},
generic: {
ctrl_shortcut: "Ctrl",
form_submit: "Submit",
},
};
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -419,5 +419,6 @@ export const es: Dictionary = {
},
generic: {
ctrl_shortcut: "Ctrl",
form_submit: "Enviar",
},
};
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/fa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,5 +390,6 @@ export const fa = {
},
generic: {
ctrl_shortcut: "Ctrl",
form_submit: "ارسال",
},
};
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -467,5 +467,6 @@ export const fr: Dictionary = {
},
generic: {
ctrl_shortcut: "Ctrl",
form_submit: "Envoyer",
},
};
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/he.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,5 +421,6 @@ export const he: Dictionary = {
},
generic: {
ctrl_shortcut: "Ctrl",
form_submit: "שליחה",
},
};
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/hr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -435,5 +435,6 @@ export const hr: Dictionary = {
},
generic: {
ctrl_shortcut: "Ctrl",
form_submit: "Pošalji",
},
};
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/is.ts
Original file line number Diff line number Diff line change
Expand Up @@ -435,5 +435,6 @@ export const is: Dictionary = {
},
generic: {
ctrl_shortcut: "Ctrl",
form_submit: "Senda",
},
};
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -443,5 +443,6 @@ export const it: Dictionary = {
},
generic: {
ctrl_shortcut: "Ctrl",
form_submit: "Invia",
},
};
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -461,5 +461,6 @@ export const ja: Dictionary = {
},
generic: {
ctrl_shortcut: "Ctrl",
form_submit: "送信",
},
};
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -434,5 +434,6 @@ export const ko: Dictionary = {
},
generic: {
ctrl_shortcut: "Ctrl",
form_submit: "제출",
},
};
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -422,5 +422,6 @@ export const nl: Dictionary = {
},
generic: {
ctrl_shortcut: "Ctrl",
form_submit: "Verzenden",
},
};
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/no.ts
Original file line number Diff line number Diff line change
Expand Up @@ -439,5 +439,6 @@ export const no: Dictionary = {
},
generic: {
ctrl_shortcut: "Ctrl",
form_submit: "Send inn",
},
};
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -412,5 +412,6 @@ export const pl: Dictionary = {
},
generic: {
ctrl_shortcut: "Ctrl",
form_submit: "Wyślij",
},
};
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/pt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,5 +414,6 @@ export const pt: Dictionary = {
},
generic: {
ctrl_shortcut: "Ctrl",
form_submit: "Enviar",
},
};
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -465,5 +465,6 @@ export const ru: Dictionary = {
},
generic: {
ctrl_shortcut: "Ctrl",
form_submit: "Отправить",
},
};
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/sk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -419,5 +419,6 @@ export const sk = {
},
generic: {
ctrl_shortcut: "Ctrl",
form_submit: "Odoslať",
},
};
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/uk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -445,5 +445,6 @@ export const uk: Dictionary = {
},
generic: {
ctrl_shortcut: "Ctrl",
form_submit: "Надіслати",
},
};
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/uz.ts
Original file line number Diff line number Diff line change
Expand Up @@ -455,5 +455,6 @@ export const uz: Dictionary = {
},
generic: {
ctrl_shortcut: "Ctrl",
form_submit: "Yuborish",
},
};
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/vi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,5 +420,6 @@ export const vi: Dictionary = {
},
generic: {
ctrl_shortcut: "Ctrl",
form_submit: "Gửi",
},
};
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/zh-tw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -462,5 +462,6 @@ export const zhTW: Dictionary = {
},
generic: {
ctrl_shortcut: "Ctrl",
form_submit: "提交",
},
};
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -462,5 +462,6 @@ export const zh: Dictionary = {
},
generic: {
ctrl_shortcut: "Ctrl",
form_submit: "提交",
},
};
33 changes: 33 additions & 0 deletions packages/mantine/src/blocknoteStyles.css
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,19 @@ on touch devices (e.g. the mobile formatting toolbar). */
font-size: 12px;
}

/* On touch devices, enlarge the form-popover inputs (e.g. the link popover's
URL field). The 16px font-size is load-bearing: iOS Safari auto-zooms the
page when focusing an input with a smaller computed font-size, and that zoom
perturbs the visual viewport the mobile toolbar positions itself from. The
taller min-height also gives a comfortable tap target. */
@media (pointer: coarse) {
.bn-form-popover .mantine-TextInput-input,
.bn-form-popover .mantine-FileInput-input {
font-size: 16px;
min-height: 40px;
}
}

.bn-form-popover .mantine-FileInput-input:hover {
background-color: var(--bn-colors-hovered-background);
}
Expand Down Expand Up @@ -806,3 +819,23 @@ we just don't display it in CSS instead. */
.bn-mantine .bn-badge .mantine-Chip-iconWrapper {
display: none;
}

/* The submit button `Form.Root` renders so that Enter reaches the form
* regardless of how many fields a popover has. It carries no visual design of
* its own - the popovers commit on Enter - but it stays in the accessibility
* tree with a real label, so screen readers and voice control have a submit
* control to operate. It is out of the tab order: keeping a control nobody can
* see as a tab stop would strand sighted keyboard users on invisible focus,
* and Enter already submits for them.
*/
.bn-form-submit {
border: 0;
clip-path: inset(50%);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
white-space: nowrap;
width: 1px;
}
3 changes: 2 additions & 1 deletion packages/mantine/src/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Badge, BadgeGroup } from "./badge/Badge.js";
import { Card, CardSection, ExpandSectionsPrompt } from "./comments/Card.js";
import { Comment } from "./comments/Comment.js";
import { Editor } from "./comments/Editor.js";
import { Form } from "./form/Form.js";
import { TextInput } from "./form/TextInput.js";
import {
Menu,
Expand Down Expand Up @@ -89,7 +90,7 @@ export const components: Components = {
Group: BadgeGroup,
},
Form: {
Root: (props) => <div>{props.children}</div>,
Root: Form,
TextInput: TextInput,
},
Menu: {
Expand Down
Loading
Loading