Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(indexes): 增加 indexes 组件的sticky属性 #117

Merged
merged 2 commits into from
May 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/common.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/** Vue3 特有全局变量 */
/** Vue3 特有全局类型 */
type VNode = import('vue').VNode;
export type ScopedSlot = () => SlotReturnValue;
export type SlotReturnValue = VNode | string | boolean | null | undefined | SlotReturnArray;
Expand All @@ -16,7 +16,7 @@ export type TNode<T = undefined> = T extends undefined
export type AttachNodeReturnValue = HTMLElement | Element | Document;
export type AttachNode = CSSSelector | ((triggerNode?: HTMLElement) => AttachNodeReturnValue);

// 与滚动相关的容器类型,因为 document 上没有 scroll 相关属性, 因此排除document
// 与滚动相关的容器类型,因为 document 上没有 scroll 相关属性, 因此排除 document
export type ScrollContainerElement = Window | HTMLElement;
export type ScrollContainer = (() => ScrollContainerElement) | CSSSelector;

Expand All @@ -27,7 +27,7 @@ export type FormSubmitEvent = Event;
export interface Styles {
[css: string]: string | number;
}
/** 通用全局变量 */
/** 通用全局类型 */

export type OptionData = {
label?: string;
Expand Down
3 changes: 3 additions & 0 deletions src/indexes/demos/mobile.vue
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,7 @@ export default defineComponent({
margin-bottom: 16px;
}
}
.tdesign-demo-topnav {
z-index: 99;
}
</style>
46 changes: 27 additions & 19 deletions src/indexes/indexes-anchor.vue
Original file line number Diff line number Diff line change
@@ -1,36 +1,44 @@
<template>
<t-cell-group :class="state.componentName" :data-index="state.index" :title="state.title" />
<div ref="boxRef" :class="boxClasses" :style="boxStyles">
<div ref="contentRef" class="t-indexes__anchor" :style="anchorStyle">
<t-node :content="stickyContent"></t-node>
</div>
</div>
</template>

<script lang="ts">
import { reactive, defineComponent } from 'vue';
import { computed, getCurrentInstance, defineComponent } from 'vue';
import { useElementBounding, templateRef } from '@vueuse/core';
import config from '../config';
import TCellGroup from '../cell-group';
import { renderContent, TNode } from '../shared';

const { prefix } = config;
const componentName = `${prefix}-indexes-anchor`;
const name = `${config.prefix}-indexes-anchor`;

export default defineComponent({
name: componentName,
components: { TCellGroup },
name,
components: { TNode },
props: {
index: {
type: String,
default: '',
},
title: {
anchorStyle: {
type: String,
default: '',
},
},
setup(props) {
const state = reactive({
componentName,
index: props.index || '',
title: props.title ? props.title : props.index,
});
setup(props, context) {
const boxClasses = name;
const stickyContent = computed(() => renderContent(getCurrentInstance(), 'default', ''));

// box 用于占位和记录边界
// content 用于实际定位
const boxRef = templateRef('boxRef');
const contentRef = templateRef('contentRef');
const { height } = useElementBounding(contentRef);

const boxStyles = computed(() => `height:${height.value}px;`);

return {
state,
boxClasses,
boxStyles,
stickyContent,
};
},
});
Expand Down
3 changes: 2 additions & 1 deletion src/indexes/indexes.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
名称 | 类型 | 默认值 | 说明 | 必传
-- | -- | -- | -- | --
height | Number | - | 列表高度,未设置默认占满设备高度 | N
list | Array | [] | 必需。索引列表的列表数据。每个元素包含三个字元素,index(string):索引值,例如1,2,3,...或A,B,C等;title(string): 索引标题,可不填将默认设为索引值;children(Array<{title: string}>): 子元素列表,title为子元素的展示文案。。TS 类型:`ListItem[] ` `interface ListItem { title: string; index: string; children: { title: string; [key: string]: any} [] }`。[详细类型定义](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/indexes/type.ts) | Y
list | Array | [] | 必需。索引列表的列表数据。每个元素包含三个子元素,index(string):索引值,例如1,2,3,...或A,B,C等;title(string): 索引标题,可不填将默认设为索引值;children(Array<{title: string}>): 子元素列表,title为子元素的展示文案。。TS 类型:`ListItem[] ` `interface ListItem { title: string; index: string; children: { title: string; [key: string]: any} [] }`。[详细类型定义](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/indexes/type.ts) | Y
sticky | Boolean | true | 索引是否吸顶,默认为true。TS 类型:`Boolean` | N
onSelect | Function | | TS 类型:`(indexes: { groupIndex: string; childrenIndex: number }) => void`<br/>点击行元素时触发事件 | N

### Indexes Events
Expand Down
61 changes: 49 additions & 12 deletions src/indexes/indexes.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@
</div>
</div>

<div v-for="(item, index) in list" :ref="setAnchorRefs(index)" :key="item.index" :data-index="item.index">
<div :class="[`${componentName}__anchor`]">
<div v-for="(item, index) in list" :ref="setAnchorGroupRefs(index)" :key="item.index" :data-index="item.index">
<indexes-anchor :ref="setAnchorRefs(index)" :anchor-style="anchorStyle[index]">
{{ item.title ?? item.index }}
</div>
</indexes-anchor>
<div :class="[`${componentName}__group`]">
<t-indexes-cell
v-for="(child, childrenIndex) in item.children"
Expand Down Expand Up @@ -61,6 +61,7 @@ import config from '../config';
import { ListItem } from './type';
import IndexesProps from './props';
import { useEmitEvent } from '../shared';
import indexesAnchor from './indexes-anchor.vue';

const { prefix } = config;

Expand All @@ -77,6 +78,7 @@ interface State {
list: ListItem[];
showSidebarTip: boolean;
activeSidebar: string;
activeIndex: number;
}

const touch: Touch = {
Expand All @@ -92,6 +94,7 @@ const componentName = `${prefix}-indexes`;

export default defineComponent({
name: componentName,
components: { indexesAnchor },
props: IndexesProps,
emits: ['select'],
setup(props, context: SetupContext) {
Expand All @@ -103,9 +106,18 @@ export default defineComponent({
list: props.list,
showSidebarTip: false,
activeSidebar: '',
activeIndex: -1,
});
const anchor = ref<HTMLElement[]>([]);

const anchorGroup = ref<HTMLElement[]>([]);
const setAnchorGroupRefs = (index: number) => {
return (el: any) => {
anchorGroup.value[index] = el as HTMLElement;
};
};

const anchor = ref<any[]>([]);
const anchorStyle = reactive<string[]>([]);
const setAnchorRefs = (index: number) => {
return (el: any) => {
anchor.value[index] = el as HTMLElement;
Expand All @@ -121,27 +133,46 @@ export default defineComponent({
});

const scrollToView = (): void => {
const children = anchor.value;
const children = anchorGroup.value;
const targets = children.filter((ele) => {
const { dataset } = ele;
return dataset && dataset.index === state.activeSidebar;
});
targets[0]?.scrollIntoView();
};

const calcSticky = (indexesRootTop: number) => {
const children = anchorGroup.value;
for (let i = 0; i < children.length; i++) {
const { top: childTop, width } = children[i].getBoundingClientRect();
anchorStyle[i] = `z-index: ${i + 1};`;
if (childTop < indexesRootTop && i === state.activeIndex) {
anchorStyle[i] += `position:fixed;top:${indexesRootTop}px;width: ${width}px;`;
} else {
anchorStyle[i] += '';
}
const anchorHeight = anchor.value[i - 1]?.$el.getBoundingClientRect()?.height;
const diff = childTop - indexesRootTop - anchorHeight;
if (i - 1 === state.activeIndex && diff < 0) {
anchorStyle[i - 1] += `transform: translateY(${diff}px)`;
}
}
};

const calcChildPosition = (scrollTop: number) => {
const children = anchor.value;
let currentIndex = '';
const children = anchorGroup.value;
let currentIndex = -1;
for (let i = 0; i < children.length - 1; i++) {
if (scrollTop < children[i + 1].offsetTop) {
currentIndex = children[i].dataset.index ?? '';
currentIndex = i;
break;
}
}
if (scrollTop >= children[children.length - 1].offsetTop) {
currentIndex = children[children.length - 1].dataset.index ?? '';
currentIndex = children.length - 1;
}
state.activeSidebar = currentIndex;
state.activeIndex = currentIndex;
state.activeSidebar = children[currentIndex].dataset.index ?? '';
};

const setActiveSidebarAndTip = (index: string) => {
Expand Down Expand Up @@ -185,6 +216,10 @@ export default defineComponent({
const handleRootScroll = (event: UIEvent) => {
if (indexesRoot.value) {
calcChildPosition(indexesRoot.value.scrollTop);
if (props.sticky) {
const indexesRootTop = indexesRoot.value?.getBoundingClientRect()?.top ?? 0;
calcSticky(indexesRootTop);
}
}
};

Expand All @@ -202,7 +237,7 @@ export default defineComponent({
};

onMounted(() => {
const children = anchor.value;
const children = anchorGroup.value;
if (children.length > 0) {
const { index } = children[0].dataset;
if (index !== undefined) {
Expand All @@ -218,7 +253,9 @@ export default defineComponent({
...toRefs(state),
indexesRoot,
indexesRootStyle,
anchor,
anchorGroup,
anchorStyle,
setAnchorGroupRefs,
setAnchorRefs,
handleSidebarItemClick,
handleSidebarTouchmove,
Expand Down
7 changes: 6 additions & 1 deletion src/indexes/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,17 @@ export default {
height: {
type: Number,
},
/** 索引列表的列表数据。每个元素包含三个字元素,index(string):索引值,例如1,2,3,...或A,B,C等;title(string): 索引标题,可不填将默认设为索引值;children(Array<{title: string}>): 子元素列表,title为子元素的展示文案。 */
/** 索引列表的列表数据。每个元素包含三个子元素,index(string):索引值,例如1,2,3,...或A,B,C等;title(string): 索引标题,可不填将默认设为索引值;children(Array<{title: string}>): 子元素列表,title为子元素的展示文案。 */
list: {
type: Array as PropType<TdIndexesProps['list']>,
default: (): TdIndexesProps['list'] => [],
required: true,
},
/** 索引是否吸顶,默认为true */
sticky: {
type: Boolean,
default: true,
},
/** 点击行元素时触发事件 */
onSelect: Function as PropType<TdIndexesProps['onSelect']>,
};
15 changes: 12 additions & 3 deletions src/indexes/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,23 @@ export interface TdIndexesProps {
*/
height?: number;
/**
* 索引列表的列表数据。每个元素包含三个字元素,index(string):索引值,例如1,2,3,...或A,B,C等;title(string): 索引标题,可不填将默认设为索引值;children(Array<{title: string}>): 子元素列表,title为子元素的展示文案。
* 索引列表的列表数据。每个元素包含三个子元素,index(string):索引值,例如1,2,3,...或A,B,C等;title(string): 索引标题,可不填将默认设为索引值;children(Array<{title: string}>): 子元素列表,title为子元素的展示文案。
* @default []
*/
list: ListItem[] ;
list: ListItem[];
/**
* 索引是否吸顶,默认为true
* @default true
*/
sticky?: Boolean;
/**
* 点击行元素时触发事件
*/
onSelect?: (indexes: { groupIndex: string; childrenIndex: number }) => void;
}

export interface ListItem { title: string; index: string; children: { title: string; [key: string]: any} [] };
export interface ListItem {
title: string;
index: string;
children: { title: string; [key: string]: any }[];
}
Loading