UNPKG

hongluan-ui

Version:
1 lines 17.3 kB
{"version":3,"file":"tab-nav.mjs","sources":["../../../../../../packages/components/tabs/src/tab-nav.tsx"],"sourcesContent":["import {\n computed,\n defineComponent,\n inject,\n nextTick,\n onMounted,\n onUpdated,\n ref,\n watch,\n} from 'vue'\nimport {\n useDocumentVisibility,\n useResizeObserver,\n useWindowFocus,\n} from '@vueuse/core'\nimport {\n buildProps,\n capitalize,\n definePropType,\n mutable,\n throwError,\n} from '@hongluan-ui/utils'\nimport { EVENT_CODE } from '@hongluan-ui/constants'\nimport HlIcon from '@hongluan-ui/components/icon'\nimport { SystemClose, SystemArrowLeft, SystemArrowRight } from '@hongluan-ui/components/system-icon'\nimport { tabsRootContextKey } from '@hongluan-ui/tokens'\nimport TabBar from './tab-bar.vue'\nimport type { CSSProperties, ExtractPropTypes } from 'vue'\nimport type { TabsPaneContext } from '@hongluan-ui/tokens'\nimport type { TabPaneName } from './tabs'\n\ninterface Scrollable {\n next?: boolean\n prev?: number\n}\ntype KeyOfOffset = 'offsetWidth' | 'offsetHeight'\n\nexport const tabNavProps = buildProps({\n panes: {\n type: definePropType<TabsPaneContext[]>(Array),\n default: () => mutable([] as const),\n },\n currentName: {\n type: [String, Number],\n default: '',\n },\n editable: Boolean,\n type: {\n type: String,\n values: ['line', 'button', ''],\n default: '',\n },\n stretch: Boolean,\n} as const)\n\nexport const tabNavEmits = {\n tabClick: (tab: TabsPaneContext, tabName: TabPaneName, ev: Event) =>\n ev instanceof Event,\n tabRemove: (tab: TabsPaneContext, ev: Event) => ev instanceof Event,\n}\n\nexport type TabNavProps = ExtractPropTypes<typeof tabNavProps>\nexport type TabNavEmits = typeof tabNavEmits\n\nconst COMPONENT_NAME = 'TabNav'\nconst TabNav = defineComponent({\n name: COMPONENT_NAME,\n props: tabNavProps,\n emits: tabNavEmits,\n setup(props, { expose, emit }) {\n\n const rootTabs = inject(tabsRootContextKey)\n if (!rootTabs) throwError(COMPONENT_NAME, `<hl-tabs><tab-nav /></hl-tabs>`)\n\n const visibility = useDocumentVisibility()\n const focused = useWindowFocus()\n\n const navScroll$ = ref<HTMLDivElement>()\n const nav$ = ref<HTMLDivElement>()\n const el$ = ref<HTMLDivElement>()\n const tabBarRef = ref<InstanceType<typeof TabBar>>()\n\n const scrollable = ref<false | Scrollable>(false)\n const navOffset = ref(0)\n const isFocus = ref(false)\n const focusable = ref(true)\n\n const sizeName = computed(() =>\n ['top', 'bottom'].includes(rootTabs.props.tabPosition)\n ? 'width'\n : 'height',\n )\n const navStyle = computed<CSSProperties>(() => {\n const dir = sizeName.value === 'width' ? 'X' : 'Y'\n return {\n transform: `translate${dir}(-${navOffset.value}px)`,\n }\n })\n\n const scrollPrev = () => {\n if (!navScroll$.value) return\n\n const containerSize =\n navScroll$.value[`offset${capitalize(sizeName.value)}` as KeyOfOffset]\n const currentOffset = navOffset.value\n\n if (!currentOffset) return\n\n const newOffset =\n currentOffset > containerSize ? currentOffset - containerSize : 0\n\n navOffset.value = newOffset\n }\n\n const scrollNext = () => {\n if (!navScroll$.value || !nav$.value) return\n\n const navSize = nav$.value[`offset${capitalize(sizeName.value)}` as KeyOfOffset]\n const containerSize =\n navScroll$.value[`offset${capitalize(sizeName.value)}` as KeyOfOffset]\n const currentOffset = navOffset.value\n\n if (navSize - currentOffset <= containerSize) return\n\n const newOffset =\n navSize - currentOffset > containerSize * 2\n ? currentOffset + containerSize\n : navSize - containerSize\n\n navOffset.value = newOffset\n }\n\n const scrollToActiveTab = async () => {\n const nav = nav$.value\n if (!scrollable.value || !el$.value || !navScroll$.value || !nav) return\n\n await nextTick()\n\n const activeTab = el$.value.querySelector('.is-active')\n if (!activeTab) return\n\n const navScroll = navScroll$.value\n const isHorizontal = ['top', 'bottom'].includes(\n rootTabs.props.tabPosition,\n )\n const activeTabBounding = activeTab.getBoundingClientRect()\n const navScrollBounding = navScroll.getBoundingClientRect()\n const maxOffset = isHorizontal\n ? nav.offsetWidth - navScrollBounding.width\n : nav.offsetHeight - navScrollBounding.height\n const currentOffset = navOffset.value\n let newOffset = currentOffset\n\n if (isHorizontal) {\n if (activeTabBounding.left < navScrollBounding.left) {\n newOffset =\n currentOffset - (navScrollBounding.left - activeTabBounding.left)\n }\n if (activeTabBounding.right > navScrollBounding.right) {\n newOffset =\n currentOffset + activeTabBounding.right - navScrollBounding.right\n }\n } else {\n if (activeTabBounding.top < navScrollBounding.top) {\n newOffset =\n currentOffset - (navScrollBounding.top - activeTabBounding.top)\n }\n if (activeTabBounding.bottom > navScrollBounding.bottom) {\n newOffset =\n currentOffset +\n (activeTabBounding.bottom - navScrollBounding.bottom)\n }\n }\n newOffset = Math.max(newOffset, 0)\n navOffset.value = Math.min(newOffset, maxOffset)\n }\n\n const update = () => {\n if (!nav$.value || !navScroll$.value) return\n\n props.stretch && tabBarRef.value?.update()\n\n const navSize = nav$.value[`offset${capitalize(sizeName.value)}` as KeyOfOffset]\n const containerSize =\n navScroll$.value[`offset${capitalize(sizeName.value)}` as KeyOfOffset]\n const currentOffset = navOffset.value\n\n if (containerSize < navSize) {\n scrollable.value = scrollable.value || {}\n scrollable.value.prev = currentOffset\n scrollable.value.next = currentOffset + containerSize < navSize\n if (navSize - currentOffset < containerSize) {\n navOffset.value = navSize - containerSize\n }\n } else {\n scrollable.value = false\n if (currentOffset > 0) {\n navOffset.value = 0\n }\n }\n }\n\n const changeTab = (e: KeyboardEvent) => {\n const code = e.code\n\n const { up, down, left, right } = EVENT_CODE\n if (![up, down, left, right].includes(code)) return\n\n // 左右上下键更换tab\n const tabList: HTMLDivElement[] = Array.from(\n (e.currentTarget as HTMLDivElement).querySelectorAll<HTMLDivElement>(\n '[role=tab]:not(.is-disabled)',\n ),\n )\n const currentIndex = tabList.indexOf(e.target as HTMLDivElement)\n\n let nextIndex: number\n if (code === left || code === up) {\n // left\n if (currentIndex === 0) {\n // first\n nextIndex = tabList.length - 1\n } else {\n nextIndex = currentIndex - 1\n }\n } else {\n // right\n if (currentIndex < tabList.length - 1) {\n // not last\n nextIndex = currentIndex + 1\n } else {\n nextIndex = 0\n }\n }\n tabList[nextIndex].focus({ preventScroll: true }) // 改变焦点元素\n tabList[nextIndex].click() // 选中下一个tab\n setFocus()\n }\n\n const setFocus = () => {\n if (focusable.value) isFocus.value = true\n }\n const removeFocus = () => (isFocus.value = false)\n\n watch(visibility, visibility => {\n if (visibility === 'hidden') {\n focusable.value = false\n } else if (visibility === 'visible') {\n setTimeout(() => (focusable.value = true), 50)\n }\n })\n watch(focused, focused => {\n if (focused) {\n setTimeout(() => (focusable.value = true), 50)\n } else {\n focusable.value = false\n }\n })\n\n useResizeObserver(el$, update)\n\n onMounted(() => setTimeout(() => scrollToActiveTab(), 0))\n onUpdated(() => update())\n\n expose({\n scrollToActiveTab,\n removeFocus,\n })\n\n return () => {\n const scrollBtn = scrollable.value\n ? [\n <span\n class={[\n 'prev',\n scrollable.value.prev ? '' : 'is-disabled',\n ]}\n onClick={scrollPrev}\n >\n <HlIcon>\n <SystemArrowLeft />\n </HlIcon>\n </span>,\n <span\n class={[\n 'next',\n scrollable.value.next ? '' : 'is-disabled',\n ]}\n onClick={scrollNext}\n >\n <HlIcon>\n <SystemArrowRight />\n </HlIcon>\n </span>,\n ]\n : null\n\n const tabs = props.panes.map((pane, index) => {\n const uid = pane.uid\n const disabled = pane.props.disabled\n const tabName = pane.props.name ?? pane.index ?? `${index}`\n const closable = !disabled && (pane.isClosable || props.editable)\n pane.index = `${index}`\n\n const btnClose = closable ? (\n <HlIcon\n class=\"tab-close\"\n // `onClick` not exist when generate dts\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n onClick={(ev: MouseEvent) => emit('tabRemove', pane, ev)}\n >\n <SystemClose />\n </HlIcon>\n ) : null\n\n const tabLabelContent = pane.slots.label?.() || pane.props.label\n const tabindex = !disabled && pane.active ? 0 : -1\n\n return (\n <div\n ref={`tab-${uid}`}\n class={{\n 'tabs-item': true,\n [`at-${rootTabs.props.tabPosition}`]: true,\n 'is-active': pane.active,\n 'is-disabled': disabled,\n 'is-closable': closable,\n 'is-focus': isFocus,\n }}\n id={`tab-${tabName}`}\n key={`tab-${uid}`}\n aria-controls={`pane-${tabName}`}\n role=\"tab\"\n aria-selected={pane.active}\n tabindex={tabindex}\n onFocus={() => setFocus()}\n onBlur={() => removeFocus()}\n onClick={(ev: MouseEvent) => {\n removeFocus()\n emit('tabClick', pane, tabName, ev)\n }}\n onKeydown={(ev: KeyboardEvent) => {\n if (\n closable &&\n (ev.code === EVENT_CODE.delete ||\n ev.code === EVENT_CODE.backspace)\n ) {\n emit('tabRemove', pane, ev)\n }\n }}\n >\n {...[tabLabelContent, btnClose]}\n </div>\n )\n })\n\n return (\n <div\n ref={el$}\n class={[\n 'tabs-wrap',\n scrollable.value ? 'is-scrollable' : '',\n `at-${rootTabs.props.tabPosition}`,\n ]}\n >\n {scrollBtn}\n <div class={'tabs-scroll'} ref={navScroll$}>\n <div\n class={[\n 'tabs-nav',\n `at-${rootTabs.props.tabPosition}`,\n props.stretch &&\n ['top', 'bottom'].includes(rootTabs.props.tabPosition)\n ? 'is-stretch'\n : '',\n ]}\n ref={nav$}\n style={navStyle.value}\n role=\"tablist\"\n onKeydown={changeTab}\n >\n {...[\n props.type ? <TabBar ref={tabBarRef} tabs={[...props.panes]} /> : null,\n tabs,\n ]}\n </div>\n </div>\n </div>\n )\n }\n },\n})\n\nexport type TabNavInstance = InstanceType<typeof TabNav> & {\n scrollToActiveTab: () => Promise<void>\n removeFocus: () => void\n}\nexport default TabNav\n"],"names":["tabNavProps","buildProps","panes","type","definePropType","Array","default","mutable","currentName","String","Number","editable","Boolean","values","stretch","tabNavEmits","tabClick","tab","tabName","ev","Event","tabRemove","COMPONENT_NAME","TabNav","defineComponent","name","props","emits","setup","expose","emit","visibility","useDocumentVisibility","focused","useWindowFocus","navScroll$","ref","nav$","el$","tabBarRef","scrollable","navOffset","isFocus","focusable","sizeName","computed","includes","rootTabs","tabPosition","navStyle","dir","value","transform","scrollPrev","containerSize","capitalize","currentOffset","newOffset","scrollNext","navScroll","navScrollBounding","maxOffset","isHorizontal","offsetWidth","activeTabBounding","right","top","bottom","update","navSize","prev","code","up","down","left","tabList","currentIndex","nextIndex","preventScroll","setFocus","watch","setTimeout","useResizeObserver","onMounted","onUpdated","scrollToActiveTab","removeFocus","index","pane","closable","active"],"mappings":";;;;;;;;;;;;;;;;;;MAqCaA,WAAW,GAAGC,UAAU,CAAC;AACpCC,EAAAA,KAAK,EAAE;AACLC,IAAAA,IAAI,EAAEC,cAAc,CAAoBC,KAApB,CADf;AAELC,IAAAA,OAAO,EAAE,MAAMC,OAAO,CAAC,EAAD;AAFjB,GAD6B;AAKpCC,EAAAA,WAAW,EAAE;AACXL,IAAAA,IAAI,EAAE,CAACM,MAAD,EAASC,MAAT,CADK;AAEXJ,IAAAA,OAAO,EAAE;AAFE,GALuB;AASpCK,EAAAA,QAAQ,EAAEC,OAT0B;AAUpCT,EAAAA,IAAI,EAAE;AACJA,IAAAA,IAAI,EAAEM,MADF;AAEJI,IAAAA,MAAM,EAAE,CAAC,MAAD,EAAS,QAAT,EAAmB,EAAnB,CAFJ;AAGJP,IAAAA,OAAO,EAAE;AAHL,GAV8B;AAepCQ,EAAAA,OAAO,EAAEF;AAf2B,CAAD;MAkBxBG,WAAW,GAAG;AACzBC,EAAAA,QAAQ,EAAE,CAACC,GAAD,EAAuBC,OAAvB,EAA6CC,EAA7C,KACRA,EAAE,YAAYC,KAFS;AAGzBC,EAAAA,SAAS,EAAE,CAACJ,GAAD,EAAuBE,EAAvB,KAAqCA,EAAE,YAAYC;AAHrC;AAS3B,MAAME,cAAc,GAAG,QAAvB;MACMC,MAAM,GAAGC,eAAe,CAAC;AAC7BC,EAAAA,IAAI,EAAEH,cADuB;AAE7BI,EAAAA,KAAK,EAAE1B,WAFsB;AAG7B2B,EAAAA,KAAK,EAAEZ,WAHsB;;AAI7Ba;AAAeC,IAAAA;AAAQC;AAAV;AAEX;AACA,iBAAA;AAEA,UAAMC,UAAU,GAAGC,qBAAqB,EAAxC;AACA,UAAMC,OAAO,GAAGC,cAAc,EAA9B;AAEA,UAAMC,UAAU,GAAGC,GAAG,EAAtB;AACA,UAAMC,IAAI,GAAGD,GAAG,EAAhB;AACA,UAAME,GAAG,GAAGF,GAAG,EAAf;AACA,UAAMG,SAAS,GAAGH,GAAG,EAArB;AAEA,UAAMI,UAAU,GAAGJ,GAAG,CAAqB,KAArB,CAAtB;AACA,UAAMK,SAAS,GAAGL,GAAG,CAAC,CAAD,CAArB;AACA,UAAMM,OAAO,GAAGN,GAAG,CAAC,KAAD,CAAnB;AACA,UAAMO,SAAS,GAAGP,GAAG,CAAC,IAAD,CAArB;AAEA,UAAMQ,QAAQ,GAAGC,QAAQ,CAAC,MACxB,CAAC,KAAD,EAAQ,QAAR,EAAkBC,QAAlB,CAA2BC,QAAQ,CAACrB,KAAT,CAAesB,WAA1C,IACI,OADJ,GAEI,QAHmB,CAAzB;AAKA,UAAMC,QAAQ,GAAGJ,QAAQ,CAAgB,MAAM;AAC7C,YAAMK,GAAG,GAAGN,QAAQ,CAACO,KAAT,KAAmB,OAAnB,GAA6B,GAA7B,GAAmC,GAA/C;AACA,aAAO;AACLC,QAAAA,SAAS,EAAG,YAAWF,GAAI,KAAIT,SAAS,CAACU,KAAM;AAD1C,OAAP;AAGD,KALwB,CAAzB;;AAOA,UAAME;AACJ;AAEA,YAAMC,aAAa,GACjBnB,UAAU,CAACgB,KAAX,CAAkB,SAAQI,UAAU,CAACX,QAAQ,CAACO,KAAV,CAAiB,EAArD,CADF;AAEA,YAAMK,aAAa,GAAGf,SAAS,CAACU,KAAhC;AAEA,UAAI,CAACK,aAAL;AAEA;AAGAf,MAAAA,eAAA,GAAkBgB;AACnB;;AAED,UAAMC,UAAU,GAAG,MAAM;AACvB,UAAI,CAACvB,UAAU,CAACgB,KAAZ,IAAqB,CAACd,IAAI,CAACc,KAA/B;AAEA;AACA,YAAMG;AAEN,YAAME,aAAa,GAAGf,UAAUU;AAEhC,qCAA+BG;AAE/B,kDAC4BA;AAI5Bb,eAAS;AACV;;AAED;AACE;AACA,sBAAgBU;AAEhB,sBAAA;AAEA;AACA,oBAAA,EAAgB;AAEhB,YAAMQ,SAAS,GAAGxB,UAAU;AAC5B;AAGA;AACA,YAAMyB,6BAA6B;AACnC,YAAMC,YAAYC,mBACVC,WAAJ,0BAD0B;AAG9B,YAAMP;AACN;;AAEA;AACE;AACEC,UAAAA,YACED;AACH;;AACD;AACEC,mCACe,GAAGO,iBAAiB,CAACC,KAAlC;AACH;AACF;AACC;AACER,yDACqCS;AACtC;;AACD,YAAIF,iBAAiB,CAACG,MAAlB,GAA2BP,iBAAiB,CAACO,MAAjD,EAAyD;AACvDV,UAAAA,SAAS,GACPD,aAAa,IACZQ,iBAAiB,CAACG,MAAlB,GAA2BP,iBAAiB,CAACO,MADjC,CADf;AAGD;AACF;;AACDV,MAAAA,SAAS;AACThB,MAAAA;AACD;;AAED,UAAM2B,MAAM,SAAS;AACnB,eAAS;AAET1C,MAAAA,KAAK,CAACZ,OAAN,qBAAkCsD;AAElC,YAAMC,OAAO,GAAGhC,IAAI,CAACc,KAAL,CAAY,SAAQI,UAAU,CAACX,QAAQ,CAACO,KAAV,CAAiB,EAA/C,CAAhB;AACA,YAAMG,aAAa,GACjBnB,UAAU,CAACgB,KAAX,CAAkB,SAAQI,UAAU,CAACX,QAAQ,CAACO,KAAV,CAAiB,EAArD,CADF;AAEA,YAAMK,aAAa,GAAGf,SAAS,CAACU,KAAhC;;AAEA;AACEX,QAAAA,UAAU,CAACW,KAAX;AACAX,QAAAA,UAAU,CAACW,KAAX,CAAiBmB,IAAjB,GAAwBd,aAAxB;AACAhB,QAAAA,WAAWW;;AACX;AACEV;AACD;AACF;AACCD,mBAAWW;;AACX;AACEV;AACD;AACF;AACF;;AAED;AACE,YAAM8B;AAEN;AAAQC,UAAF;AAAMC,YAAN;AAAYC,QAAAA;AAAMT;AAAlB;AACN,UAAI,UAAC;;AAGL,YAAMU,oBAA4B;AAKlC;AAEA;;AACA;AACE;AACA,YAAIC,YAAY;AACd;AACAC;AACD;AACCA;AACD;AACF,aAAM;AACL;AACA;AACE;AACAA;AACD;AACCA,UAAAA;AACD;AACF;;AACDF,wBAAA,MAAA;AAA2BG,uBAAe;AAAjB,UAAyB;;AAClDH,aAAO,oBAjC+B;;AAkCtCI;AACD,KAnCD;;AAqCA,UAAMA,QAAQ;AACZ,mBAAa,MAAb,SAA4B,CAAC5B,KAAR,GAAgB,IAAhB;AACtB;;AACD;;AAEA6B,IAAAA,kBAAkBjD;AAChB,oBAAc;AACZY,0BAAkB;AACnB;AACCsC;AACD;AACF,KANI,CAAL;AAOAD,IAAAA,aAAK;AACH;AACEC,QAAAA,UAAU,yBAA0B,IAA1B,KAAV;AACD;AACCtC,sBAAA,QAAA;AACD;AACF;AAEDuC;AAEAC;AACAC,4BAAA;AAEAvD;AACEwD;AACAC,MAAAA;AAFK;AAKP,iBAAa;AACX,mCAA6BnC;AACzB,wBAES,YAEK,WAAV,qBAFK;AAFT;AAAA;AAAA;AAAA;AAAA,wBAaS,uBAEL,QAA6B;AAfjC;AAAA;AAAA;AAAA,cAwBA;AAEJ,yBAAmBjD,SAAN,QAAuBqF,UAAU;AAC5C,2BAAA;AACA,sBAAc,OAAO;AACrB;AACA,yBAAiB;AACjBC,YAAI,CAACD;AAEL,yBAAiBE;AAAQ;AAAA,2BAMQ3D;AANR;AAAA;AAAA,iBAAzB;AAYA,6BAAqB;AACrB,yBAAiB,iBAAiB,CAAC4D;AAEnC;AAAA,2BAEoB;AAFpB;AAIM;AACA;AACA,6BAAiB;AACjB;AACA,2BAAeD,QALV;AAML;AANK;AAHX,gBAWS,gBAXT;AAAA;AAAA;AAAA;AAAA,+BAeuB;AAfvB;AAAA;AAAA;AAAA;AAoBMH;AACAxD,iBAAK;AACN;AAtBL,sBAuBgBX;AACV,+BAEK,KAAF,eAAsB,WACrBA,EAAE,oBAAoB;AAExBW,mBAAK;AACN;AACF;AA/BL;AAoCD,OA1DY;AA4Db;AAAA;AAAA,iBAGW,YAAA,EAELU;AALN;AAAA;AAAA;;;;"}