Composer redesign: Misc bug fixes (#40596)

This commit is contained in:
Echo
2026-09-19 13:10:55 +00:00
committed by GitHub
parent 2c92a56e5d
commit 3cdcf93dd3
14 changed files with 61 additions and 25 deletions

View File

@@ -46,6 +46,7 @@ const meta = {
const { onTextChange, suggestProps, sourceProps } = useAutosuggestMenu({
suggestions,
onSelect: selectCb,
sourceRef: textareaRef,
onFetch(token) {
const newSuggestions = tokenToSuggestions(token);

View File

@@ -19,6 +19,7 @@ export type OnSuggestionSelect = (
interface UseAutosuggestMenuOptions {
suggestions: Suggestion[];
sourceRef: Source;
onSelect: OnSuggestionSelect;
onFetch?: (token: string) => void;
onClear?: () => void;
@@ -31,6 +32,7 @@ type SourceProps = React.DetailedHTMLProps<
export function useAutosuggestMenu({
suggestions,
sourceRef,
onSelect,
onFetch,
onClear,
@@ -108,6 +110,7 @@ export function useAutosuggestMenu({
suggestProps: {
suggestions,
onSuggestionClick,
source: sourceToElement(sourceRef),
listRef,
tokenCb,
} satisfies AutosuggestMenuProps,
@@ -122,13 +125,11 @@ export function useAutosuggestMenu({
interface UseAutosuggestFloatingMenuOptions extends UseAutosuggestMenuOptions {
text?: string;
sourceRef: Source;
className?: string;
}
export function useAutosuggestFloatingMenu({
text,
sourceRef,
className,
...suggestOptions
}: UseAutosuggestFloatingMenuOptions) {
@@ -138,9 +139,9 @@ export function useAutosuggestFloatingMenu({
const { getToken, ...autosuggestProps } = useAutosuggestMenu(suggestOptions);
const { onClear } = suggestOptions;
const source = autosuggestProps.suggestProps.source;
const source = sourceToElement(sourceRef);
const { onClear } = suggestOptions;
// Update the popover on scroll or select.
const onUpdate = useCallback(() => {

View File

@@ -12,12 +12,13 @@ import { Popover } from '../popover';
import { AutosuggestItem } from './items';
import classes from './styles.module.scss';
import type { Suggestion } from './types';
import type { AutosuggestSourceElements, Suggestion } from './types';
export interface AutosuggestMenuProps {
suggestions: Suggestion[];
tokenCb: () => string | null;
onSuggestionClick: React.MouseEventHandler;
source: AutosuggestSourceElements | null;
children?: React.ReactNode;
listRef?: React.Ref<HTMLDivElement>;
reference?: HTMLElement | null;
@@ -60,6 +61,7 @@ type AutosuggestMenuListProps = Omit<
const AutosuggestMenuList: React.FC<AutosuggestMenuListProps> = ({
children,
listRef,
source,
tokenCb,
suggestions,
reference,
@@ -72,10 +74,15 @@ const AutosuggestMenuList: React.FC<AutosuggestMenuListProps> = ({
const { popover, menuListProps } = useMenuContext();
useEffect(() => {
if (!popover.isMenuOpen && token !== lastToken && suggestions.length > 0) {
if (
source === document.activeElement &&
!popover.isMenuOpen &&
token !== lastToken &&
suggestions.length > 0
) {
popover.openMenu();
}
}, [lastToken, popover, suggestions.length, token]);
}, [lastToken, popover, suggestions.length, token, source]);
const mergedRef = useMergedRefs(menuListProps.ref, listRef);

View File

@@ -36,7 +36,7 @@
&:focus-visible,
&:focus-within {
outline: 2px solid var(--color-bg-brand-base);
outline: var(--outline-focus-default);
outline-offset: 2px;
}

View File

@@ -64,7 +64,7 @@ input.input {
}
&:focus {
outline: 2px solid var(--color-border-brand);
outline: var(--outline-focus-default);
}
&:disabled {

View File

@@ -8,6 +8,7 @@ import { createBrowserHistory } from 'history';
import { WithOptionalRouterPropTypes, withOptionalRouter } from 'mastodon/utils/react_router';
import { IGNORE_FOCUS_ON_OPEN } from '../reducers/modal';
import { normalizeKey } from './hotkeys/utils';
class ModalRoot extends PureComponent {
@@ -31,15 +32,12 @@ class ModalRoot extends PureComponent {
activeElement = this.props.children ? document.activeElement : null;
handleKeyUp = (e) => {
if ((e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27)
&& !!this.props.children) {
this.props.onClose();
}
};
/**
* @param {KeyboardEvent} e Event
*/
handleKeyDown = (e) => {
if (e.key === 'Tab') {
const key = normalizeKey(e.key);
if (key === 'tab') {
const focusable = Array.from(this.node.querySelectorAll('button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])')).filter((x) => window.getComputedStyle(x).display !== 'none');
const index = focusable.indexOf(e.target);
@@ -56,11 +54,12 @@ class ModalRoot extends PureComponent {
e.stopPropagation();
e.preventDefault();
}
} else if (key === 'enter' && !e.defaultPrevented && !!this.props.children) {
this.props.onClose();
}
};
componentDidMount () {
window.addEventListener('keyup', this.handleKeyUp, false);
window.addEventListener('keydown', this.handleKeyDown, false);
this.history = this.props.history || createBrowserHistory();
}
@@ -96,7 +95,6 @@ class ModalRoot extends PureComponent {
}
componentWillUnmount () {
window.removeEventListener('keyup', this.handleKeyUp);
window.removeEventListener('keydown', this.handleKeyDown);
}

View File

@@ -175,14 +175,15 @@ export const Popover: React.FC<PopoverProps> = ({
function closeOnEscape(event: KeyboardEvent) {
if (event.key === 'Escape') {
event.preventDefault();
onClose(event);
}
}
document.addEventListener('keyup', closeOnEscape);
document.addEventListener('keydown', closeOnEscape);
return () => {
document.removeEventListener('keyup', closeOnEscape);
document.removeEventListener('keydown', closeOnEscape);
};
}, [isOpen, onClose]);

View File

@@ -33,8 +33,12 @@ const selectIsFollowersReply = createAppSelector(
state,
state.compose.get('in_reply_to') as null | string,
),
(state) => state.meta.get('me') as string | null,
],
(status) => (status?.visibility === 'private' ? status.account.acct : null),
(status, me) =>
status?.visibility === 'private' && status.account.acct !== me
? status.account.acct
: null,
);
export const ComposeHints = () => {

View File

@@ -14,8 +14,10 @@ import {
} from '@/mastodon/actions/compose';
import { ToggleButton } from '@/mastodon/components/button/redesign';
import { TextInputField } from '@/mastodon/components/form_fields/redesign';
import { normalizeKey } from '@/mastodon/components/hotkeys/utils';
import { Icon, useIconWeight } from '@/mastodon/components/icon';
import {
closeComposer,
getComposerTextarea,
requestComposerFocus,
submitComposer,
@@ -141,6 +143,25 @@ function useComposeHandlers(redirectOnSuccess?: boolean) {
const dispatch = useAppDispatch();
const isModalOpen = useAppSelector((state) => state.modal.stack.size > 0);
useEffect(() => {
function escapeComposer(event: KeyboardEvent) {
const key = normalizeKey(event.key);
if (key !== 'escape' || isModalOpen) {
return;
}
if (!event.defaultPrevented) {
dispatch(closeComposer());
}
}
document.addEventListener('keydown', escapeComposer);
return () => {
document.removeEventListener('keydown', escapeComposer);
};
}, [dispatch, isModalOpen]);
// Sensitive handling
const isSensitive = useAppSelector((state) => !!state.compose.get('spoiler'));
useEffect(() => {

View File

@@ -132,9 +132,10 @@
width: 100%;
outline: none;
background: none;
min-height: 3lh;
&:focus {
outline: 2px solid var(--color-border-brand);
outline: var(--outline-focus-default);
outline-offset: -2px;
&::placeholder {

View File

@@ -157,6 +157,7 @@ export const ComposeTextarea: React.FC<ComposeTextareaProps> = ({
event.preventDefault();
onSuggestionClear();
} else if (key === 'escape') {
event.preventDefault();
// Dismiss the suggestions if we're displaying any.
if (suggestions.length > 0) {
onSuggestionClear();

View File

@@ -19,6 +19,7 @@
.composer {
width: 100%;
top: env(safe-area-inset-top);
padding-block-end: max(env(safe-area-inset-bottom), var(--space-4));
// Set from the visualViewport API.
height: var(--viewport-height, 100vh);

View File

@@ -64,7 +64,7 @@ export function useStatusFetch(
}
if (!status) {
dispatch(fetchStatus(id));
} else if (withAccount && !account) {
} else if (withAccount && status.account && !account) {
dispatch(fetchAccount(status.account));
} else if (withReblog && status.reblog && !reblog) {
dispatch(fetchStatus(status.reblog));

View File

@@ -1491,7 +1491,7 @@ body > [data-popover-placement] {
.focusable {
&:focus-visible {
outline: 2px solid var(--color-border-brand);
outline: var(--outline-focus-default);
outline-offset: -2px;
background: var(--color-bg-brand-softest);
}