mirror of
https://github.com/mastodon/mastodon.git
synced 2026-09-27 11:08:03 -05:00
Composer redesign: Misc bug fixes (#40596)
This commit is contained in:
@@ -46,6 +46,7 @@ const meta = {
|
||||
const { onTextChange, suggestProps, sourceProps } = useAutosuggestMenu({
|
||||
suggestions,
|
||||
onSelect: selectCb,
|
||||
sourceRef: textareaRef,
|
||||
onFetch(token) {
|
||||
const newSuggestions = tokenToSuggestions(token);
|
||||
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
&:focus-visible,
|
||||
&:focus-within {
|
||||
outline: 2px solid var(--color-bg-brand-base);
|
||||
outline: var(--outline-focus-default);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ input.input {
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: 2px solid var(--color-border-brand);
|
||||
outline: var(--outline-focus-default);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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]);
|
||||
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user