Skip to content

Commit d4ea59e

Browse files
committed
feat(ui): add listener for scrollview change
1 parent f102c5a commit d4ea59e

10 files changed

Lines changed: 157 additions & 44 deletions

File tree

.eslintrc.json

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"root": true,
33
"ignorePatterns": ["**/*"],
4-
"plugins": ["@nrwl/nx", "import", "markdown", "jsdoc"],
5-
"extends": ["plugin:prettier/recommended", "plugin:jsdoc/recommended"],
4+
"plugins": ["@nrwl/nx", "import", "markdown", "jsdoc", "eslint-plugin-tsdoc"],
5+
"extends": ["plugin:prettier/recommended"],
66
"overrides": [
77
{
88
"files": ["**/*.md"],
@@ -43,14 +43,14 @@
4343
},
4444
"warnOnUnassignedImports": true
4545
}
46-
],
47-
"jsdoc/require-jsdoc": "off"
46+
]
4847
}
4948
},
5049
{
5150
"files": ["*.ts", "*.tsx"],
5251
"extends": ["plugin:@nrwl/nx/typescript"],
5352
"rules": {
53+
"tsdoc/syntax": "warn",
5454
"no-unreachable": "error",
5555
"@typescript-eslint/array-type": "error",
5656
"@typescript-eslint/ban-types": [
@@ -118,8 +118,10 @@
118118
},
119119
{
120120
"files": ["*.js", "*.jsx"],
121-
"extends": ["plugin:@nrwl/nx/javascript"],
122-
"rules": {}
121+
"extends": ["plugin:@nrwl/nx/javascript", "plugin:jsdoc/recommended"],
122+
"rules": {
123+
"jsdoc/require-jsdoc": "off"
124+
}
123125
},
124126
{
125127
"files": ["**/*.md/*.ts", "**/*.md/*.tsx"],

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"regenerator-runtime": "^0.13.9",
4242
"rfs": "^9.0.6",
4343
"rxjs": "^7.5.2",
44+
"scrollview-resize": "^1.0.0",
4445
"tslib": "^2.3.1"
4546
},
4647
"devDependencies": {
@@ -81,6 +82,7 @@
8182
"eslint-plugin-prettier": "^4.0.0",
8283
"eslint-plugin-react": "^7.28.0",
8384
"eslint-plugin-react-hooks": "^4.3.0",
85+
"eslint-plugin-tsdoc": "^0.2.14",
8486
"fs-extra": "^10.0.0",
8587
"husky": "^7.0.4",
8688
"jest": "^27.4.7",

packages/site/src/app/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export function App() {
9696
);
9797

9898
return (
99-
<DRoot theme={theme} i18n={{ lang: i18n.language as DLang }} icons={icons} contentSelector="main .app-route-article">
99+
<DRoot theme={theme} i18n={{ lang: i18n.language as DLang }} icons={icons} contentSelector="main">
100100
<AppContext.Provider value={contextValue}>
101101
<AppHeader />
102102
<AppSidebar />

packages/ui/src/components/_popup/Popup.tsx

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ import {
1313
useRefSelector,
1414
useImmer,
1515
useRefCallback,
16-
useContentRefConfig,
1716
useDTransition,
1817
useMaxIndex,
18+
useContentSVChangeConfig,
1919
} from '../../hooks';
2020
import { getClassName, getPopupPlacementStyle, mergeStyle } from '../../utils';
2121
import { checkOutEl } from './utils';
@@ -93,7 +93,7 @@ const Popup: React.ForwardRefRenderFunction<DPopupRef, DPopupProps> = (props, re
9393

9494
//#region Context
9595
const dPrefix = usePrefixConfig();
96-
const rootContentRef = useContentRefConfig();
96+
const contentSVChange = useContentSVChangeConfig();
9797
//#endregion
9898

9999
//#region Ref
@@ -508,19 +508,16 @@ const Popup: React.ForwardRefRenderFunction<DPopupRef, DPopupProps> = (props, re
508508
}, [asyncCapture, dEscClosable, dVisible, changeVisible]);
509509

510510
useEffect(() => {
511-
const [asyncGroup, asyncId] = asyncCapture.createGroup();
512511
if (popupRendered) {
512+
const [asyncGroup, asyncId] = asyncCapture.createGroup();
513513
if (popupEl) {
514514
asyncGroup.onResize(popupEl, updatePosition);
515515
}
516516
if (triggerRef.current) {
517517
asyncGroup.onResize(triggerRef.current, updatePosition);
518518
}
519-
if (!isFixed && rootContentRef.current) {
520-
asyncGroup.onResize(rootContentRef.current, updatePosition);
521-
}
522519

523-
asyncGroup.onGlobalScroll(updatePosition, () => {
520+
const skipUpdate = () => {
524521
if (triggerRef.current) {
525522
const { top, left } = triggerRef.current.getBoundingClientRect();
526523

@@ -530,12 +527,22 @@ const Popup: React.ForwardRefRenderFunction<DPopupRef, DPopupProps> = (props, re
530527
}
531528

532529
return false;
530+
};
531+
const ob = contentSVChange?.subscribe({
532+
next: () => {
533+
if (!skipUpdate()) {
534+
updatePosition();
535+
}
536+
},
533537
});
538+
asyncGroup.onGlobalScroll(updatePosition, skipUpdate);
539+
540+
return () => {
541+
ob?.unsubscribe();
542+
asyncCapture.deleteGroup(asyncId);
543+
};
534544
}
535-
return () => {
536-
asyncCapture.deleteGroup(asyncId);
537-
};
538-
}, [asyncCapture, isFixed, popupEl, popupRendered, rootContentRef, triggerRef, updatePosition]);
545+
}, [asyncCapture, popupEl, popupRendered, contentSVChange, triggerRef, updatePosition]);
539546

540547
useImperativeHandle(
541548
ref,

packages/ui/src/components/affix/Affix.tsx

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,15 @@ import { isString, isUndefined } from 'lodash';
44
import React, { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
55
import ReactDOM from 'react-dom';
66

7-
import { usePrefixConfig, useComponentConfig, useAsync, useRefSelector, useImmer, useRefCallback, useContentRefConfig } from '../../hooks';
7+
import {
8+
usePrefixConfig,
9+
useComponentConfig,
10+
useAsync,
11+
useRefSelector,
12+
useImmer,
13+
useRefCallback,
14+
useContentSVChangeConfig,
15+
} from '../../hooks';
816
import { getClassName, toPx, mergeStyle, generateComponentMate } from '../../utils';
917

1018
export interface DAffixRef {
@@ -36,7 +44,7 @@ const Affix: React.ForwardRefRenderFunction<DAffixRef, DAffixProps> = (props, re
3644

3745
//#region Context
3846
const dPrefix = usePrefixConfig();
39-
const rootContentRef = useContentRefConfig();
47+
const contentSVChange = useContentSVChangeConfig();
4048
//#endregion
4149

4250
//#region Ref
@@ -149,10 +157,7 @@ const Affix: React.ForwardRefRenderFunction<DAffixRef, DAffixProps> = (props, re
149157

150158
useEffect(() => {
151159
const [asyncGroup, asyncId] = asyncCapture.createGroup();
152-
if (rootContentRef.current) {
153-
asyncGroup.onResize(rootContentRef.current, updatePosition);
154-
}
155-
asyncGroup.onGlobalScroll(updatePosition, () => {
160+
const skipUpdate = () => {
156161
const el = fixed ? referenceEl : affixEl;
157162
if (el) {
158163
const { top, left } = el.getBoundingClientRect();
@@ -163,11 +168,20 @@ const Affix: React.ForwardRefRenderFunction<DAffixRef, DAffixProps> = (props, re
163168
}
164169

165170
return false;
171+
};
172+
const ob = contentSVChange?.subscribe({
173+
next: () => {
174+
if (!skipUpdate()) {
175+
updatePosition();
176+
}
177+
},
166178
});
179+
asyncGroup.onGlobalScroll(updatePosition, skipUpdate);
167180
return () => {
181+
ob?.unsubscribe();
168182
asyncCapture.deleteGroup(asyncId);
169183
};
170-
}, [affixEl, asyncCapture, fixed, referenceEl, rootContentRef, updatePosition]);
184+
}, [affixEl, asyncCapture, fixed, referenceEl, contentSVChange, updatePosition]);
171185

172186
useEffect(() => {
173187
updatePosition();

packages/ui/src/components/anchor/Anchor.tsx

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,15 @@ import type { DElementSelector } from '../../hooks/element-ref';
33
import { isUndefined } from 'lodash';
44
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
55

6-
import { usePrefixConfig, useComponentConfig, useRefSelector, useImmer, useAsync, useRefCallback, useContentRefConfig } from '../../hooks';
6+
import {
7+
usePrefixConfig,
8+
useComponentConfig,
9+
useRefSelector,
10+
useImmer,
11+
useAsync,
12+
useRefCallback,
13+
useContentSVChangeConfig,
14+
} from '../../hooks';
715
import { getClassName, CustomScroll, generateComponentMate } from '../../utils';
816

917
export interface DAnchorContextData {
@@ -40,7 +48,7 @@ export const DAnchor = (props: DAnchorProps) => {
4048

4149
//#region Context
4250
const dPrefix = usePrefixConfig();
43-
const rootContentRef = useContentRefConfig();
51+
const contentSVChange = useContentSVChangeConfig();
4452
//#endregion
4553

4654
//#region Ref
@@ -151,10 +159,12 @@ export const DAnchor = (props: DAnchorProps) => {
151159

152160
useEffect(() => {
153161
const [asyncGroup, asyncId] = asyncCapture.createGroup();
154-
if (rootContentRef.current) {
155-
asyncGroup.onResize(rootContentRef.current, updateAnchor);
156-
}
157-
asyncGroup.onGlobalScroll(updateAnchor, () => {
162+
const ob = contentSVChange?.subscribe({
163+
next: () => {
164+
updateAnchor();
165+
},
166+
});
167+
const skipUpdate = () => {
158168
const data = Array.from(links.values())[0];
159169
if (data) {
160170
const el = document.getElementById(data.href.slice(1));
@@ -168,11 +178,13 @@ export const DAnchor = (props: DAnchorProps) => {
168178
}
169179

170180
return false;
171-
});
181+
};
182+
asyncGroup.onGlobalScroll(updateAnchor, skipUpdate);
172183
return () => {
184+
ob?.unsubscribe();
173185
asyncCapture.deleteGroup(asyncId);
174186
};
175-
}, [asyncCapture, links, rootContentRef, updateAnchor]);
187+
}, [asyncCapture, links, contentSVChange, updateAnchor]);
176188

177189
const stateBackflow = useMemo<Pick<DAnchorContextData, 'updateLinks' | 'removeLinks'>>(
178190
() => ({

packages/ui/src/components/root/Root.tsx

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,30 @@
11
import type { DConfigContextData } from '../../hooks/d-config';
2+
import type { DElementSelector } from '../../hooks/element-ref';
23

4+
import { isUndefined } from 'lodash';
35
import { useEffect } from 'react';
6+
import { useState } from 'react';
7+
import { Subject } from 'rxjs';
8+
import { SVResizeObserver } from 'scrollview-resize';
49

10+
import { useRefSelector } from '../../hooks';
511
import { DConfigContext } from '../../hooks/d-config';
612
import { Notification } from './Notification';
713
import { Toast } from './Toast';
814

9-
export interface DRootProps extends DConfigContextData {
15+
export interface DRootProps extends Omit<DConfigContextData, 'scrollViewChange'> {
16+
contentSelector?: DElementSelector;
1017
children: React.ReactNode;
1118
}
1219

1320
export function DRoot(props: DRootProps) {
14-
const { theme, i18n, children, ...restProps } = props;
21+
const { contentSelector, children, theme, i18n, ...restProps } = props;
1522

1623
const lang = i18n?.lang ?? 'en-US';
1724

25+
const [scrollViewChange] = useState(() => new Subject<void>());
26+
const contentRef = useRefSelector(contentSelector ?? null);
27+
1828
useEffect(() => {
1929
document.body.classList.toggle('CJK', lang === 'zh-Hant');
2030
}, [lang]);
@@ -30,11 +40,32 @@ export function DRoot(props: DRootProps) {
3040
}
3141
}, [theme]);
3242

43+
useEffect(() => {
44+
if (isUndefined(contentSelector)) {
45+
const observer = new ResizeObserver(() => {
46+
scrollViewChange.next();
47+
});
48+
observer.observe(document.documentElement);
49+
return () => {
50+
observer.disconnect();
51+
};
52+
} else if (contentRef.current) {
53+
const observer = new SVResizeObserver(() => {
54+
scrollViewChange.next();
55+
});
56+
observer.observe(contentRef.current);
57+
return () => {
58+
observer.disconnect();
59+
};
60+
}
61+
}, [contentRef, contentSelector, scrollViewChange]);
62+
3363
return (
3464
<DConfigContext.Provider
3565
value={{
3666
theme,
3767
i18n,
68+
scrollViewChange,
3869
...restProps,
3970
}}
4071
>

packages/ui/src/hooks/d-config.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,12 @@ import type {
4747
DTooltipProps,
4848
} from '../components';
4949
import type { DBreakpoints } from '../components/grid';
50+
import type { Subject } from 'rxjs';
5051

5152
import { isUndefined } from 'lodash';
5253
import React, { useContext, useMemo } from 'react';
5354

5455
import { getFragmentChildren } from '../utils';
55-
import { useRefSelector } from './element-ref';
5656

5757
interface Resources {
5858
[index: string]: string | Resources;
@@ -128,7 +128,7 @@ export interface DConfigContextData {
128128
type?: string;
129129
}[];
130130
}[];
131-
contentSelector?: string;
131+
scrollViewChange?: Subject<void>;
132132
}
133133
export const DConfigContext = React.createContext<DConfigContextData>({});
134134

@@ -187,9 +187,6 @@ export function useComponentConfig<T>(component: keyof DComponentConfig, props:
187187
return { ...customConfig, ...(noUndefinedProps as T), children };
188188
}
189189

190-
export function useContentRefConfig() {
191-
const contentSelector = useContext(DConfigContext).contentSelector ?? null;
192-
const contentRef = useRefSelector(contentSelector);
193-
194-
return contentRef;
190+
export function useContentSVChangeConfig() {
191+
return useContext(DConfigContext).scrollViewChange;
195192
}

packages/ui/src/hooks/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ export { useAsync } from './async';
22
export { useTranslation } from './i18n';
33
export { useDTransition, useDCollapseTransition } from './transition';
44
export { useCustomContext } from './context';
5-
export { usePrefixConfig, useThemeConfig, useGridConfig, useContentRefConfig, useComponentConfig } from './d-config';
5+
export { usePrefixConfig, useThemeConfig, useGridConfig, useContentSVChangeConfig, useComponentConfig } from './d-config';
66
export { useRefSelector } from './element-ref';
77
export { useGeneralState, DGeneralStateContext } from './general-state';
88
export { useImmer, useImmerReducer } from './immer';

0 commit comments

Comments
 (0)