ant-design-vue/components/vc-virtual-list/List.tsx
tangjinzhou a2e50dc43e
Feat v4 (#6329)
* refactor(icon): remove style dir (#6215)

* refactor: rename locale

* refactor: locale-provider

* refactor: modal

* refactor: menu

* fix: custom class (#6217)

* refactor: tooltip

* refactor: grid (#6220)

* refactor: grid

* fix(grid): align & justify responsive

* chore: update demo and snapshot

* fix: row ts type not work

* doc: update demo

* refactor: ts

* refactor: spin (#6222)

* fix: typo (#6218)

* fix:  typo

* docs<upload>: docs update

* refactor: spin

* refactor: spin

* refactor: spin

* refactor: spinnn

* refactor: spin

---------

Co-authored-by: lyn <76365499@qq.com>

* fix: spin error #6222

* test: test case error (#6225)

* fix: inject value maybe undefined

* fix: tootip emit correct value

* fix: rollback warning suffix avoid test break

* doc(grid): remove unused type="flex"

* refactor: skeleton (#6224)

* refactor: skeleton

* refactor: skeleton style

* chore: modify skeleton demo style

* fix(button): link and text should not have wave (#6226)

* refactor: dropdown

* refactor: popover & popconfirm

* refactor(tag): less to cssinjs (#6227)

* refactor(empty): less to cssinjs (#6230)

* refactor(empty): less to cssinjs

* chore: remove unuse code

* fix: reactivity lose

* fix: empty props #6230

* refactor: progress style (#6234)

* refactor: progress

* refactor: progress style

* fix: progress attrs

* refactor: progress #6234

* refactor: switch (#6236)

* refactor: switch style

* refactor: delete switch style

* refactor:input (#6237)

* refactor:input

* fix inheritAttrs:false

* fix attrs.class

* feat: input add disabled

* refactor:comment (#6238)

* refactor:comment

* fix inheritAttrs: false & attrs.class

* refactor:pageheader (#6239)

* refactor:pageheader

* fix inheritAttrs: false & attrs.class

* refactor:statistic (#6240)

* refactor:statistic

* fix inheritAttrs: false & attrs.class

* refactor:list (#6241)

* refactor:list

* fix inheritAttrs: false & attrs.class

* feat: update type

* refactor(Space): less to cssinjs & add compact mode (#6229)

* refactor(Space): less to cssinjs & add compact mode

* chore(space): update md

* chore(space): add demo

* chore(space): add some demo

* feat(button): add compact mode

* fix: reactivity lose

* docs: fix props version

---------

Co-authored-by: tangjinzhou <415800467@qq.com>

* perf: space compact

* refactor:typography (#6244)

* refactor:typography

* fix return

* fix import type

* fix: typography #6244

* refactor:datepicker (#6245)

* refactor: datepicker type

* refactor: rate style (#6254)

* refactor(layout): less to cssinjs (#6249)

* doc: update layout cover

* refactor(result): less to cssinjs (#6246)

* refactor(result): less to cssinjs

* fix: class name is overridden

* docs: update result cover

* refactor:slider (#6250)

* feat: slider  deprecated tooltipVisible

* refactor(crad): less to cssinjs  (#6258)

* update

* switch

* Style adjustment

* refactor(Card): less to cssinjs

* Eliminate invalid code

* optimization and adjustment css

* Adjust the css

* Optimize each item

* adjustment css

* refactor: card #6258

* refactor:carousel (#6262)

* refactor:carousel

* docs:update & refactor: carousel type

---------

Co-authored-by: tangjinzhou <415800467@qq.com>

* refactor:transfer (#6247)

* refactor:transfer

* merge v4 branch & fix theme interface conflict

* docs:update & refactor: transfer type

* perf: transfer

* refactor:checkbox (#6248)

* refactor:checkbox

* docs:update & refactor: checkbox type

* feat: checkbox add disabled context

* refactor:pagination (#6251)

* refactor:pagination

* docs:update & refactor: pagination type

* style: update pagination props type

* refactor: mentions (#6255)

* refactor: mentions

* refactor: mentions menu provider

* doc: update mentions demo

* refcator:upload (#6261)

* refcator:upload

* docs:update & refactor: upload type

* Update style.ts

---------

Co-authored-by: tangjinzhou <415800467@qq.com>

* perf: upload motion

* refactor:timeline (#6263)

* refactor:timeline

* docs:update & refactor: timeline type

* perf: timeline

* refactor:steps (#6264)

* refactor:steps

* fix ...attrs

* fix StepsToken error

* docs:update & refactor: steps type

* fix: steps icon clss error

* refactor:collapse (#6266)

* refactor:collapse

* fix collapse props version

* docs:update & refactor: collapse type & fix collapsible

* feat: update collapse type

* refactor:inputnumber (#6265)

* refactor:inputnumber

* docs:update & refactor: inputnumber type

---------

Co-authored-by: tangjinzhou <415800467@qq.com>

* feat: number add compactSize & disabledContext

* refactor:table (#6267)

* refactor:table

* docs:update & refactor: table type

---------

Co-authored-by: tangjinzhou <415800467@qq.com>

* refactor: table

* feat: table add expandColumnTitle slot

* refactor:calendar (#6269)

* refactor:calendar

* docs:update

* refactor:timepicker (#6270)

* refactor:timepicker

* docs:update & refactor: timepicker type

* refactor:tree (#6276)

* Feat v4 fix type errors (#6285)

* fix compile type errors

* fix menuprops type import

* fix lint errors

* fix lint errors

* fix format error

* fix node version

* fix run dist error

* fix run lint

* fix as any

* fix string type

* refactor: rename locale file

* feat: tree add leafIcon

* [tabs] :less to cssinjs  (#6288)

* update

* switch

* Style adjustment

* refactor(Card): less to cssinjs

* tabs: less to cssinjs 开发ing

* add function cssinjs

* Eliminate irrelevant code

* Eliminate irrelevant code 2

* update components

* Eliminate irrelevant input  code

* refactor: tabs #6288

* feat: add segmented (#6286)

* refactor: segmented #6286

* refactor:select (#6295)

* refactor:select

* update doc

* delete useless

* feat: select add context size

* refactor: tree select (#6296)

* feat: tree-select add context size

* perf: table

* docs: update doc toc

* refactor: cascader

* refactor: auto-complete

* refactor: image

* refactor: drawer

* refactor:radio (#6299)

* refactor:radio

* fix attrs

* feat: radio add disabled context

* fix: some type & doc (#6292)

* fix: typo (#6218)

* fix:  typo

* docs<upload>: docs update

* fix: type of minute in props disabledDateTime of DatePicker (#6233)

* docs: typo (#6256)

* feat: tooltip added overlayInnerStyle attribute

* Update abstractTooltipProps.ts

* Update Tooltip.tsx

---------

Co-authored-by: lyn <76365499@qq.com>
Co-authored-by: H1mple <35363759+baohangxing@users.noreply.github.com>
Co-authored-by: tangjinzhou <415800467@qq.com>

* refactor: form

* fix: directive not work

* fix: use open, remove visible

* doc: update cover

* refactor: remove not use code

* chore: update build script

* doc: update doc

* doc: refactor doc

* chore: update token error

* chore: update style

* refactor: rename _style to style

* fix: tag warning

* fix(dropdown): open invalid (#6316)

* feat: add watermark  (#6300)

* feat: add watermark

* feat: add watermark demo

* feat: add mutationObserver

* feat: add watermark demo

* refactor: watermark type

* doc: add theme-editor

* fix: inject value maybe undefined && tag style invalid (#6320)

* fix: inject value maybe undefined

* fix(tag): style invalid

* feat: add qrcode (#6315)

* feat: add qrcode

* fix: qrcode bug

* fix: qrcode value required

* refactor: props  deconstruct

* Feat v4 floatbutton (#6294)

* feat: add float-button components

* fix type &  demo display

* fix components entry

* fix review bug

* fix bug

* fix .value

* refactor: qrcode #6315

* refactor: float-button

* fix: groupsize context error

* fix: floatbutton animation not work

* Feat v4 theme editor (#6348)

* feat: add theme editor container

* feat: add theme editor layout

* add left panel

* add vue-colorful & fix bug

* 修复hue组件抖动问题

* fix bug && add demo

* fix bug

* fix demo preview

* fix theme editor components demo

* fix: token effect error

* Feat v4 theme editor (#6349)

* feat: add theme editor container

* feat: add theme editor layout

* add left panel

* add vue-colorful & fix bug

* 修复hue组件抖动问题

* fix bug && add demo

* fix bug

* fix demo preview

* fix theme editor components demo

* add theme editor token drawer

* add theme editor token drawer

* fix bug

* open commment

* fix error demo

* fix theme editor bug

* fix: cssinjs effect error

* doc: format code

* fix: tag click event not trigger

* release 4.0.0-alpha.1

* fix: qrcode type

* fix: remove not use file

* doc: update doc site

* doc: update site

* doc: fix theme editor bgcolor (#6358)

* fix: motion not work

* release 4.0.0-alpha.2

* fix: qrcode ; error, close #6362

* fix docs dark theme & add docs coverDark (#6367)

* fix docs dark theme & add docs coverDark

* fix theme Editor edit

* fix: dropdown divider disappear, close  #6365 (#6369)

* doc: update baner

* fix: button wave not work

* fix: ant-piker-cell-range-hover-end style error (#6373)

* fix: ant-piker-cell-range-hover-end style error

* feat: be consistent with antd

* feat: be consistent with antd

* fix: ConfigProvider error for style, close #6368

* release 4.0.0-alpha.4

* style: add dark style for `pre` and `code` (#6382)

* docs: version menu (#6390)

* Feat(DatePicker): increase presets prop (#6387)

* feat(date-picker): add PresetDate type

* feat(date-picker): add usePresets hook

* feat(date-picker): add PresetPanel Component

* feat(date-picker): add PresetPanel Component

* feat(demo): update Preset Ranges Examples

* feat(docs): add new prop presets

* feat(docs): add new prop presets with english

* fix(RangePicker): footer is not managed by panels

* chore(Picker): prefixCls default rc-picker

* chore(date-picker): update presetted-ranges demo

* chore(date-picker): update rangePickerProps'presets

* feat(date-picker): presets reactively processing

* chore(date-picker): update type

* refactor(RangePicker): deprecated ranges prop

* chore(date-picker): update type

* chore(PickerPanel): del notuse panelRef

---------

Co-authored-by: tangjinzhou <415800467@qq.com>

* fix: datepicker presets error #6387

* docs: update datepicker doc #6387

* feat(Steps): add items prop and variants (#6406)

* refactor(steps): add items prop and variants

* feat(steps): add Label Placement and Inline Steps demo

* feat(steps): Label Placement and Inline Steps snap

* test(steps): Steps demo snap

* feat(Steps): update docs

* fix(Step): progressDot

* chore(useLegacyItems): change from warning to devWarning

* refactor(Steps): Remove useLegacyItems

* refactor(Steps): renderStep

* test(Steps): update test snapshot

* chore(Steps): filterEmpty

* feat(Steps): update docs

* docs: update site

* refactor: steps #6406

* test: update steps

* perf: shallowRef instead ref

* fix(Modal): fix modal locale (#6423)

* feat(StyleProvider): add StyleProvider handle cssinjs features (#6415)

* feat(StyleProvider): StyleProvider

* feat(StyleProvider): refactor to  use context

* chore(StyleProvider): update AStyleProviderProps type

* chore(App): reback

* chore(StyleProvider): export StyleProvider

* feat(StyleProvider): update StyleProvider docs

* feat(StyleProvider): update StyleProvider docs

* feat(StyleProvider): add StyleProvider docs routes

* chore(StyleProvider): with useStyleProvider

* docs: update compatiple #6415

* feat(Progress): enhance size prop and add variants (#6409)

* refactor(progress): Progress size and add variants

* feat(progress): add `getsize`

* refactor(progress): Progress size and add variants

* chore(progress): update props type

* chore(progress): update props type

* feat(progress): update demo

* feat(progress): update docs

* test(progress): update test snap

* fix(Circle): Merging classes

* test(progress): update test snap

* feat(progress): add size demo

* test(progress): add size snapshot

* chore(Progress): reback Circle svg class change

* fix: progress borderRadius reactive #6409

* fix(defaultConfigProvider): add getPopupContainer (#6425), close #6419

* fix: qrcode size error, close #6418

* release 4.0.0-alpha.4

* fix: picker import error

* test: add QRCode unit testing (#6441)

* fix

* fix compile type errors

* fix menuprops type import

* fix lint errors

* fix lint errors

* fix format error

* fix node version

* fix run dist error

* fix run lint

* fix as any

* fix string type

* fix steps error & fix docs version select option & fix theme editor error

* fix(badge): badge props count default value error (#6433)

* docs: update site responsive

* fix: modal api method i18n not work, close #6438

* release 4.0.0-alpha.5

* chore(docs): update docs (#6446)

* docs(space): update demo

* docs(affix): update docs

* fix: cssinjs compatibility (#6454)

* feat: add convertLegacyToken

* docs: v4 vuedocs (#6468)

* fix introduce doc

* fix getting-started doc

* add migration-v4 doc

* fix docs

* Update migration-v4.zh-CN.md

* Update migration-v4.zh-CN.md

* Update migration-v4.en-US.md

* Update migration-v4.zh-CN.md

* Update getting-started.en-US.md

* Update getting-started.zh-CN.md

* Update introduce.en-US.md

* Update introduce.zh-CN.md

---------

Co-authored-by: tangjinzhou <415800467@qq.com>

* feat: remove backtop

* feat(anchor): add direction action (#6447)

* refactor(anchor): direction show

* refactor(anchor): update anchor css

* feat(anchor): update demo

* test(anchor): update demo test snap

* feat(anchor): update docs

* Update index.zh-CN.md

* Update index.en-US.md

---------

Co-authored-by: tangjinzhou <415800467@qq.com>

* feat: anchor add customTitle slot #6447

* docs: update doc anchor

* feat(menu): icon support function components with items and update demo (#6457)

* fix(menu): icon do not show problem

* fix(menu): icon do not show problem

* feat(menu): update demo

* test(menu): update demo snap

* chore(Menu): update docs

* test(Menu): update demo

* Update MenuItem.tsx

* Update SubMenu.tsx

---------

Co-authored-by: tangjinzhou <415800467@qq.com>

* doc: update menu icon

* feat: menu items icon add arg

* fix: antd.min error

* release 4.0.0-alpha.6

* fix: table resizable not work && type error (#6514)

* Refactor(demo): change options to composition api (#6499)

* feat(demo): A-B

* feat(demo): update B-checkbox

* feat(demo): update CheckBox -DatePicker

* feat(demo): update DatePicker - Form

* feat(demo): update Form - List

* feat(demo): update  List-pagination

* feat(demo): update  List - skeleton

* feat(demo): update  skeleton - switch

* feat(demo): update  skeleton - switch

* feat(demo): update   switch - upload

* feat(demo): update  watermark

* fix(demo): del hashId

* fix: submenu type lose theme

* fix: dropdown menu hide error

* fix: dealing with switching topics modal, notification, message does not take effect close #6512 (#6518)

* fix: resolve dark mode not support

* fix: unified expression

* feat(modal): add useModal (#6517)

* feat(modal): add useModal hook

* feat(modal): add HookModal demo

* test(modal): update HookModal demo snap

* feat(modal): update modal docs

* chore(modal): update modal type

* perf: useModal #6517

* release 4.0.0-beta.1

* docs: fix tab demo error

* fix(config-provider): fix ConfigProvider.config is not function close #6528 (#6529)

* Feat(use): add useMessage useNotification (#6527)

* feat(Message): add useMessage hook

* feat(Notification): add useNotification hook

* feat(Message): add Hook demo

* feat(Notification): add Hook demo

* test(Message): update demo snap

* test(Notification): update demo snap

* docs(Message): update docs with FAQ

* docs(Notification): update docs with FAQ

* refactor: useMessage #6527

* refactor: useNotification #6527

* release 4.0.0-beta.2

* docs(button): update demo with space (#6536)

* feat(button): demo space

* test(button): update demo snap

* chore(button): disabled demo Ghost space

* test(button): update disabled demo snap

* docs(introduce): update docs (#6539)

* docs(introduce): update docs

* docs(introduce): add Dollar

* Update introduce.zh-CN.md

* Update introduce.en-US.md

---------

Co-authored-by: tangjinzhou <415800467@qq.com>

* docs(customize-theme): update docs (#6540)

* fix introduce doc

* fix getting-started doc

* add migration-v4 doc

* fix docs

* Update migration-v4.zh-CN.md

* Update migration-v4.zh-CN.md

* Update migration-v4.en-US.md

* Update migration-v4.zh-CN.md

* Update getting-started.en-US.md

* Update getting-started.zh-CN.md

* Update introduce.en-US.md

* Update introduce.zh-CN.md

* update customize-theme doc & fix migration-v4 error

* update customize-theme doc

* fix migration-v4 error

* remove SSR & shadowDom

* Update customize-theme.zh-CN.md

* Update customize-theme.en-US.md

---------

Co-authored-by: tangjinzhou <415800467@qq.com>

* fix: getPopupContainer not work

* release 4.0.0-beta.3

* release 4.0.0-beta.4

* docs: update grid docs (#6549)

Co-authored-by: zhuzhengjian <zhuzhengjian@hoteamsoft.com>

* test(alert): update demo with space (#6541)

* docs(alert): update demo with space

* docs(alert): update alert test snap

---------

Co-authored-by: zhuzhengjian <zhuzhengjian@hoteamsoft.com>

* fix: components bug & update docs (#6548)

* fix bug

* fix test case and update snapshot,fix space merge class

* docs(grid): update migrate docs && delete xxxl in grid docs (#6562)

* fix: segmentd disabled label is undefined (#6556)

* fix: segmentd disabled label is undefined

* fix: segmentd disabled label is undefined

* fix: segmentd disabled label is undefined

* fix(grid): remove grid xxxl attribute (#6572)

* fix: remove grid xxxl attribute

* docs: remove xxxl in grid docs

* fix: tooltip custom color error

* feat: remove Step __legacy

* feat: add tour (#6332)

* feat v4 add tour

* fix type error

* sync tour from antd5.4.6 & fix type error

* fix error

* refactor: tour #6332

* fix: tour center

* fix: picker support v-show

* test: update snap

* test: update tour test

* fix: tour-mask attrs pointer-events (#6577)

* fix: tour animated

* feat: support vue 3.3 slot type

* release 4.0.0-rc.1

* release 4.0.0-rc.2, close #6588

* 4.0.0-rc.3

* chore: remove vue private api

* fix: paginantion error, close #6590

* release 4.0.0-rc.4

* fix: checxbox style

* fix: pagination mini size style

* release 4.0.0-rc.5

* docs: update v4 tabs doc error(#6606) (#6607)

* docs: add ant-design-vue nuxt module (#6620)

* fix: layout-sider and menu transition style(#6637) (#6640)

* docs: fixed the style error of online demo (#6630)

* feat: checkbox label slot support use option label (#6642)

* docs: 📃change the default  setting of  "treeNodeFilterProp" from  "value" to "label"

* revert: ↩revert this config and create another pr to commit

* feat: checkbox label slot support use option label

* test: 🧪update checkbox *.snap file

---------

Co-authored-by: tangjinzhou <415800467@qq.com>

* fix: add disabledContext override with form components (#6618)

* fix: add disabledContext override with form components

* test: update snap

* fix: LabelWidth demo filename

* fix: fontsize spelling mistake

* fix(tour): target position (#6629)

* style: format lint

* docs(form): add form disabled demo (#6658)

* fix: comment node error

* release 4.0

* fix: portalWrapper add autoLock prop (#6687), close #6649

* fix: image animation & zindex, close #6675

* docs(QRCode): Synchronize QR code demonstration and add SVG (#6660)

* fix: Synchronize QR code demonstration and add SVG

* fix: responsive loss and invalid border style

* docs: synchronize antd5.6.3 QRCode color in dark mode

* feat:  calendar select support info.source param (#6697)

* docs: add ant-design-vue nuxt module

* feat: calendar select support info.source param

* docs: synchronous config-provider demo (#6706)

* revert: #6706

* docs: export space-compact types (#6716)

* release 4.0.0

---------

Co-authored-by: bqy_fe <1743369777@qq.com>
Co-authored-by: zkwolf <chenhao5866@gmail.com>
Co-authored-by: Zev Zhu <45655660+aibayanyu20@users.noreply.github.com>
Co-authored-by: lyn <76365499@qq.com>
Co-authored-by: 果冻橙 <shifeng199307@gmail.com>
Co-authored-by: songsong0707 <74165917+songsong0707@users.noreply.github.com>
Co-authored-by: yang <30883395+webvs2@users.noreply.github.com>
Co-authored-by: selicens <1244620067@qq.com>
Co-authored-by: 一堆菠萝 <53335668+JavanShen@users.noreply.github.com>
Co-authored-by: H1mple <35363759+baohangxing@users.noreply.github.com>
Co-authored-by: Cherry7 <79909910+CCherry07@users.noreply.github.com>
Co-authored-by: Konv Suu <2583695112@qq.com>
Co-authored-by: luoawai <32483950+luoawai@users.noreply.github.com>
Co-authored-by: 鱼见 <657715602@qq.com>
Co-authored-by: zhuzhengjian <zhuzhengjian@hoteamsoft.com>
Co-authored-by: Cupid Valentine <53572196+valcosmos@users.noreply.github.com>
Co-authored-by: 专业逮虾户aa <30494925+waldonUB@users.noreply.github.com>
Co-authored-by: PanStar <PanStar@users.noreply.github.com>
2023-07-14 11:58:27 +08:00

543 lines
15 KiB
Vue

import type { PropType, Component, CSSProperties } from 'vue';
import {
shallowRef,
toRaw,
onMounted,
onUpdated,
defineComponent,
watchEffect,
computed,
nextTick,
onBeforeUnmount,
reactive,
watch,
} from 'vue';
import type { Key } from '../_util/type';
import Filler from './Filler';
import Item from './Item';
import ScrollBar from './ScrollBar';
import useHeights from './hooks/useHeights';
import useScrollTo from './hooks/useScrollTo';
import useFrameWheel from './hooks/useFrameWheel';
import useMobileTouchMove from './hooks/useMobileTouchMove';
import useOriginScroll from './hooks/useOriginScroll';
import PropTypes from '../_util/vue-types';
import classNames from '../_util/classNames';
import type { RenderFunc, SharedConfig } from './interface';
import supportsPassive from '../_util/supportsPassive';
const EMPTY_DATA = [];
const ScrollStyle: CSSProperties = {
overflowY: 'auto',
overflowAnchor: 'none',
};
export type ScrollAlign = 'top' | 'bottom' | 'auto';
export type ScrollConfig =
| {
index: number;
align?: ScrollAlign;
offset?: number;
}
| {
key: Key;
align?: ScrollAlign;
offset?: number;
};
export type ScrollTo = (arg: number | ScrollConfig) => void;
function renderChildren<T>(
list: T[],
startIndex: number,
endIndex: number,
setNodeRef: (item: T, element: HTMLElement) => void,
renderFunc: RenderFunc<T>,
{ getKey }: SharedConfig<T>,
) {
return list.slice(startIndex, endIndex + 1).map((item, index) => {
const eleIndex = startIndex + index;
const node = renderFunc(item, eleIndex, {
// style: status === 'MEASURE_START' ? { visibility: 'hidden' } : {},
});
const key = getKey(item);
return (
<Item key={key} setRef={ele => setNodeRef(item, ele as HTMLElement)}>
{node}
</Item>
);
});
}
export interface ListState {
scrollTop: number;
scrollMoving: boolean;
}
const List = defineComponent({
compatConfig: { MODE: 3 },
name: 'List',
inheritAttrs: false,
props: {
prefixCls: String,
data: PropTypes.array,
height: Number,
itemHeight: Number,
/** If not match virtual scroll condition, Set List still use height of container. */
fullHeight: { type: Boolean, default: undefined },
itemKey: {
type: [String, Number, Function] as PropType<Key | ((item: Record<string, any>) => Key)>,
required: true,
},
component: {
type: [String, Object] as PropType<string | Component>,
},
/** Set `false` will always use real scroll instead of virtual one */
virtual: { type: Boolean, default: undefined },
children: Function,
onScroll: Function,
onMousedown: Function,
onMouseenter: Function,
onVisibleChange: Function as PropType<(visibleList: any[], fullList: any[]) => void>,
},
setup(props, { expose }) {
// ================================= MISC =================================
const useVirtual = computed(() => {
const { height, itemHeight, virtual } = props;
return !!(virtual !== false && height && itemHeight);
});
const inVirtual = computed(() => {
const { height, itemHeight, data } = props;
return useVirtual.value && data && itemHeight * data.length > height;
});
const state = reactive<ListState>({
scrollTop: 0,
scrollMoving: false,
});
const data = computed(() => {
return props.data || EMPTY_DATA;
});
const mergedData = shallowRef([]);
watch(
data,
() => {
mergedData.value = toRaw(data.value).slice();
},
{ immediate: true },
);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const itemKey = shallowRef((_item: Record<string, any>) => undefined);
watch(
() => props.itemKey,
val => {
if (typeof val === 'function') {
itemKey.value = val;
} else {
itemKey.value = item => item?.[val];
}
},
{ immediate: true },
);
const componentRef = shallowRef<HTMLDivElement>();
const fillerInnerRef = shallowRef<HTMLDivElement>();
const scrollBarRef = shallowRef<any>(); // Hack on scrollbar to enable flash call
// =============================== Item Key ===============================
const getKey = (item: Record<string, any>) => {
return itemKey.value(item);
};
const sharedConfig = {
getKey,
};
// ================================ Scroll ================================
function syncScrollTop(newTop: number | ((prev: number) => number)) {
let value: number;
if (typeof newTop === 'function') {
value = newTop(state.scrollTop);
} else {
value = newTop;
}
const alignedTop = keepInRange(value);
if (componentRef.value) {
componentRef.value.scrollTop = alignedTop;
}
state.scrollTop = alignedTop;
}
// ================================ Height ================================
const [setInstance, collectHeight, heights, updatedMark] = useHeights(
mergedData,
getKey,
null,
null,
);
const calRes = reactive<{
scrollHeight?: number;
start?: number;
end?: number;
offset?: number;
}>({
scrollHeight: undefined,
start: 0,
end: 0,
offset: undefined,
});
const offsetHeight = shallowRef(0);
onMounted(() => {
nextTick(() => {
offsetHeight.value = fillerInnerRef.value?.offsetHeight || 0;
});
});
onUpdated(() => {
nextTick(() => {
offsetHeight.value = fillerInnerRef.value?.offsetHeight || 0;
});
});
watch(
[useVirtual, mergedData],
() => {
if (!useVirtual.value) {
Object.assign(calRes, {
scrollHeight: undefined,
start: 0,
end: mergedData.value.length - 1,
offset: undefined,
});
}
},
{ immediate: true },
);
watch(
[useVirtual, mergedData, offsetHeight, inVirtual],
() => {
// Always use virtual scroll bar in avoid shaking
if (useVirtual.value && !inVirtual.value) {
Object.assign(calRes, {
scrollHeight: offsetHeight.value,
start: 0,
end: mergedData.value.length - 1,
offset: undefined,
});
}
if (componentRef.value) {
state.scrollTop = componentRef.value.scrollTop;
}
},
{
immediate: true,
},
);
watch(
[
inVirtual,
useVirtual,
() => state.scrollTop,
mergedData,
updatedMark,
() => props.height,
offsetHeight,
],
() => {
if (!useVirtual.value || !inVirtual.value) {
return;
}
let itemTop = 0;
let startIndex: number | undefined;
let startOffset: number | undefined;
let endIndex: number | undefined;
const dataLen = mergedData.value.length;
const data = mergedData.value;
const scrollTop = state.scrollTop;
const { itemHeight, height } = props;
const scrollTopHeight = scrollTop + height;
for (let i = 0; i < dataLen; i += 1) {
const item = data[i];
const key = getKey(item);
let cacheHeight = heights.get(key);
if (cacheHeight === undefined) {
cacheHeight = itemHeight;
}
const currentItemBottom = itemTop + cacheHeight;
if (startIndex === undefined && currentItemBottom >= scrollTop) {
startIndex = i;
startOffset = itemTop;
}
// Check item bottom in the range. We will render additional one item for motion usage
if (endIndex === undefined && currentItemBottom > scrollTopHeight) {
endIndex = i;
}
itemTop = currentItemBottom;
}
// When scrollTop at the end but data cut to small count will reach this
if (startIndex === undefined) {
startIndex = 0;
startOffset = 0;
endIndex = Math.ceil(height / itemHeight);
}
if (endIndex === undefined) {
endIndex = dataLen - 1;
}
// Give cache to improve scroll experience
endIndex = Math.min(endIndex + 1, dataLen);
Object.assign(calRes, {
scrollHeight: itemTop,
start: startIndex,
end: endIndex,
offset: startOffset,
});
},
{ immediate: true },
);
// =============================== In Range ===============================
const maxScrollHeight = computed(() => calRes.scrollHeight! - props.height!);
function keepInRange(newScrollTop: number) {
let newTop = newScrollTop;
if (!Number.isNaN(maxScrollHeight.value)) {
newTop = Math.min(newTop, maxScrollHeight.value);
}
newTop = Math.max(newTop, 0);
return newTop;
}
const isScrollAtTop = computed(() => state.scrollTop <= 0);
const isScrollAtBottom = computed(() => state.scrollTop >= maxScrollHeight.value);
const originScroll = useOriginScroll(isScrollAtTop, isScrollAtBottom);
// ================================ Scroll ================================
function onScrollBar(newScrollTop: number) {
const newTop = newScrollTop;
syncScrollTop(newTop);
}
// When data size reduce. It may trigger native scroll event back to fit scroll position
function onFallbackScroll(e: UIEvent) {
const { scrollTop: newScrollTop } = e.currentTarget as Element;
if (newScrollTop !== state.scrollTop) {
syncScrollTop(newScrollTop);
}
// Trigger origin onScroll
props.onScroll?.(e);
}
// Since this added in global,should use ref to keep update
const [onRawWheel, onFireFoxScroll] = useFrameWheel(
useVirtual,
isScrollAtTop,
isScrollAtBottom,
offsetY => {
syncScrollTop(top => {
const newTop = top + offsetY;
return newTop;
});
},
);
// Mobile touch move
useMobileTouchMove(useVirtual, componentRef, (deltaY, smoothOffset) => {
if (originScroll(deltaY, smoothOffset)) {
return false;
}
onRawWheel({ preventDefault() {}, deltaY } as WheelEvent);
return true;
});
// Firefox only
function onMozMousePixelScroll(e: MouseEvent) {
if (useVirtual.value) {
e.preventDefault();
}
}
const removeEventListener = () => {
if (componentRef.value) {
componentRef.value.removeEventListener(
'wheel',
onRawWheel,
supportsPassive ? ({ passive: false } as EventListenerOptions) : false,
);
componentRef.value.removeEventListener('DOMMouseScroll', onFireFoxScroll as any);
componentRef.value.removeEventListener('MozMousePixelScroll', onMozMousePixelScroll as any);
}
};
watchEffect(() => {
nextTick(() => {
if (componentRef.value) {
removeEventListener();
componentRef.value.addEventListener(
'wheel',
onRawWheel,
supportsPassive ? ({ passive: false } as EventListenerOptions) : false,
);
componentRef.value.addEventListener('DOMMouseScroll', onFireFoxScroll as any);
componentRef.value.addEventListener('MozMousePixelScroll', onMozMousePixelScroll as any);
}
});
});
onBeforeUnmount(() => {
removeEventListener();
});
// ================================= Ref ==================================
const scrollTo = useScrollTo(
componentRef,
mergedData,
heights,
props,
getKey,
collectHeight,
syncScrollTop,
() => {
scrollBarRef.value?.delayHidden();
},
);
expose({
scrollTo,
});
const componentStyle = computed(() => {
let cs: CSSProperties | null = null;
if (props.height) {
cs = { [props.fullHeight ? 'height' : 'maxHeight']: props.height + 'px', ...ScrollStyle };
if (useVirtual.value) {
cs!.overflowY = 'hidden';
if (state.scrollMoving) {
cs!.pointerEvents = 'none';
}
}
}
return cs;
});
// ================================ Effect ================================
/** We need told outside that some list not rendered */
watch(
[() => calRes.start, () => calRes.end, mergedData],
() => {
if (props.onVisibleChange) {
const renderList = mergedData.value.slice(calRes.start, calRes.end + 1);
props.onVisibleChange(renderList, mergedData.value);
}
},
{ flush: 'post' },
);
return {
state,
mergedData,
componentStyle,
onFallbackScroll,
onScrollBar,
componentRef,
useVirtual,
calRes,
collectHeight,
setInstance,
sharedConfig,
scrollBarRef,
fillerInnerRef,
};
},
render() {
const {
prefixCls = 'rc-virtual-list',
height,
itemHeight,
// eslint-disable-next-line no-unused-vars
fullHeight,
data,
itemKey,
virtual,
component: Component = 'div',
onScroll,
children = this.$slots.default,
style,
class: className,
...restProps
} = { ...this.$props, ...this.$attrs } as any;
const mergedClassName = classNames(prefixCls, className);
const { scrollTop } = this.state;
const { scrollHeight, offset, start, end } = this.calRes;
const {
componentStyle,
onFallbackScroll,
onScrollBar,
useVirtual,
collectHeight,
sharedConfig,
setInstance,
mergedData,
} = this;
return (
<div
style={{
...style,
position: 'relative',
}}
class={mergedClassName}
{...restProps}
>
<Component
class={`${prefixCls}-holder`}
style={componentStyle}
ref="componentRef"
onScroll={onFallbackScroll}
>
<Filler
prefixCls={prefixCls}
height={scrollHeight}
offset={offset}
onInnerResize={collectHeight}
ref="fillerInnerRef"
v-slots={{
default: () =>
renderChildren(mergedData, start, end, setInstance, children, sharedConfig),
}}
></Filler>
</Component>
{useVirtual && (
<ScrollBar
ref="scrollBarRef"
prefixCls={prefixCls}
scrollTop={scrollTop}
height={height}
scrollHeight={scrollHeight}
count={mergedData.length}
onScroll={onScrollBar}
onStartMove={() => {
this.state.scrollMoving = true;
}}
onStopMove={() => {
this.state.scrollMoving = false;
}}
/>
)}
</div>
);
},
});
export default List;