refactor(core): adjust modal animation (#7606)

<div class='graphite__hidden'>
          <div>🎥 Video uploaded on Graphite:</div>
            <a href="https://app.graphite.dev/media/video/g3jz87HxbjOJpXV3FPT7/529d6c3f-4b23-43ac-84cc-171713d3dc72.mp4">
              <img src="https://app.graphite.dev/api/v1/graphite/video/thumbnail/g3jz87HxbjOJpXV3FPT7/529d6c3f-4b23-43ac-84cc-171713d3dc72.mp4">
            </a>
          </div>
<video src="https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/g3jz87HxbjOJpXV3FPT7/529d6c3f-4b23-43ac-84cc-171713d3dc72.mp4">CleanShot 2024-07-25 at 20.04.01.mp4</video>

When a modal is closed, sometimes its components are completely unmounted from the component tree, making it difficult to animate. This pr defining a custom element as the container of ReactDOM.portal, rewriting the `removeChild` function, and use `startViewTransition` when ReactDOM calls it to implement the animation.

# Save Input

Some inputs use blur event to save data, but when they are unmounted, blur event will not be triggered at all. This pr changes blur event to native addEventListener, which will be called after the DOM element is unmounted, so as to save data in time.
This commit is contained in:
EYHN
2024-07-26 08:39:34 +00:00
parent 3eb09cde5e
commit 6bc5337307
12 changed files with 257 additions and 199 deletions

View File

@@ -96,8 +96,8 @@ export const EditPropertyNameMenuItem = ({
[onBlur]
);
const handleBlur = useCallback(
(e: React.FocusEvent<HTMLInputElement>) => {
onBlur(e.target.value);
(e: FocusEvent & { currentTarget: HTMLInputElement }) => {
onBlur(e.currentTarget.value);
},
[onBlur]
);

View File

@@ -8,7 +8,7 @@ import { i18nTime, useI18n } from '@affine/i18n';
import { DocService, useService } from '@toeverything/infra';
import { noop } from 'lodash-es';
import type { ChangeEventHandler } from 'react';
import { useCallback, useContext, useEffect, useState } from 'react';
import { useCallback, useContext, useEffect, useRef, useState } from 'react';
import { managerContext } from './common';
import * as styles from './styles.css';
@@ -87,14 +87,24 @@ export const TextValue = ({ property }: PropertyRowValueProps) => {
const handleClick = useCallback((e: React.MouseEvent) => {
e.stopPropagation();
}, []);
const ref = useRef<HTMLTextAreaElement>(null);
const handleBlur = useCallback(
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
(e: FocusEvent) => {
manager.updateCustomProperty(property.id, {
value: e.target.value.trim(),
value: (e.currentTarget as HTMLTextAreaElement).value.trim(),
});
},
[manager, property.id]
);
// use native blur event to get event after unmount
// don't use useLayoutEffect here, cause the cleanup function will be called before unmount
useEffect(() => {
ref.current?.addEventListener('blur', handleBlur);
return () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
ref.current?.removeEventListener('blur', handleBlur);
};
}, [handleBlur]);
const handleOnChange: ChangeEventHandler<HTMLTextAreaElement> = useCallback(
e => {
setValue(e.target.value);
@@ -109,11 +119,11 @@ export const TextValue = ({ property }: PropertyRowValueProps) => {
return (
<div onClick={handleClick} className={styles.propertyRowValueTextCell}>
<textarea
ref={ref}
className={styles.propertyRowValueTextarea}
value={value || ''}
onChange={handleOnChange}
onClick={handleClick}
onBlur={handleBlur}
data-empty={!value}
placeholder={t[
'com.affine.page-properties.property-value-placeholder'

View File

@@ -49,6 +49,10 @@ export const EditCollectionModal = ({
onOpenChange(false);
}, [onOpenChange]);
if (!(open && init)) {
return null;
}
return (
<Modal
open={open}
@@ -59,16 +63,14 @@ export const EditCollectionModal = ({
contentOptions={contentOptions}
persistent
>
{open && init ? (
<EditCollection
title={title}
onConfirmText={t['com.affine.editCollection.save']()}
init={init}
mode={mode}
onCancel={onCancel}
onConfirm={onConfirmOnCollection}
/>
) : null}
<EditCollection
title={title}
onConfirmText={t['com.affine.editCollection.save']()}
init={init}
mode={mode}
onCancel={onCancel}
onConfirm={onConfirmOnCollection}
/>
</Modal>
);
};

View File

@@ -159,6 +159,7 @@ export const Component = () => {
open: true,
}}
items={<UserWithWorkspaceList />}
noPortal
contentOptions={{
style: {
width: 300,