Repository: tusen-ai/naive-ui Branch: main Commit: dbb66f5ca4cc Files: 3559 Total size: 7.2 MB Directory structure: gitextract_o125x3e3/ ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yml │ │ ├── bug_report.zh-CN.yml │ │ ├── config.yml │ │ ├── feature_request.yml │ │ └── feature_request.zh-CN.yml │ ├── dependabot.yml │ ├── pull_request_template.md │ └── workflows/ │ ├── node.js.yml │ └── publish-to-pkg.pr.new.yml ├── .gitignore ├── .husky/ │ └── pre-commit ├── .npmrc ├── .nvmrc ├── .pnpmfile.cjs ├── .prettierignore ├── .prettierrc ├── CHANGELOG.en-US.md ├── CHANGELOG.zh-CN.md ├── CONTRIBUTING.md ├── CONTRIBUTING.zh-CN.md ├── LICENSE ├── README.md ├── README.zh-CN.md ├── babel.config.js ├── build/ │ ├── loaders/ │ │ ├── ComponentDemoTemplate.vue │ │ ├── convert-md-to-doc.ts │ │ ├── convert-vue-to-demo.ts │ │ ├── md-renderer.ts │ │ ├── naive-ui-demo-loader.ts │ │ ├── naive-ui-doc-loader.ts │ │ ├── project-path.ts │ │ └── test/ │ │ ├── basic.test.md │ │ ├── component.test.md │ │ ├── test.md │ │ ├── testDemoLoader.js │ │ └── testMdLoader.js │ ├── utils/ │ │ ├── get-demo-by-path.ts │ │ ├── handle-merge-code.ts │ │ ├── terse-cssr.ts │ │ └── tsToJs.ts │ ├── vite-plugin-css-render.ts │ ├── vite-plugin-demo.ts │ └── vite-plugin-index-tranform.ts ├── demo/ │ ├── Caveat.md │ ├── Site.vue │ ├── SiteHeader.vue │ ├── SiteRoot.vue │ ├── index.dev.js │ ├── index.prod.js │ ├── pages/ │ │ ├── Layout.vue │ │ ├── docs/ │ │ │ ├── changelog/ │ │ │ │ ├── enUS/ │ │ │ │ │ └── index.vue │ │ │ │ └── zhCN/ │ │ │ │ └── index.vue │ │ │ ├── common-issues/ │ │ │ │ ├── enUS/ │ │ │ │ │ └── index.md │ │ │ │ └── zhCN/ │ │ │ │ └── index.md │ │ │ ├── community/ │ │ │ │ ├── enUS/ │ │ │ │ │ └── index.md │ │ │ │ └── zhCN/ │ │ │ │ └── index.md │ │ │ ├── controlled-uncontrolled/ │ │ │ │ ├── enUS/ │ │ │ │ │ └── index.md │ │ │ │ └── zhCN/ │ │ │ │ └── index.md │ │ │ ├── customize-theme/ │ │ │ │ ├── enUS/ │ │ │ │ │ └── index.md │ │ │ │ └── zhCN/ │ │ │ │ └── index.md │ │ │ ├── experimental-features/ │ │ │ │ ├── enUS/ │ │ │ │ │ └── index.md │ │ │ │ └── zhCN/ │ │ │ │ └── index.md │ │ │ ├── fonts/ │ │ │ │ ├── enUS/ │ │ │ │ │ └── index.md │ │ │ │ └── zhCN/ │ │ │ │ └── index.md │ │ │ ├── i18n/ │ │ │ │ ├── enUS/ │ │ │ │ │ └── index.md │ │ │ │ └── zhCN/ │ │ │ │ └── index.md │ │ │ ├── import-on-demand/ │ │ │ │ ├── enUS/ │ │ │ │ │ └── index.md │ │ │ │ └── zhCN/ │ │ │ │ └── index.md │ │ │ ├── installation/ │ │ │ │ ├── enUS/ │ │ │ │ │ └── index.md │ │ │ │ └── zhCN/ │ │ │ │ └── index.md │ │ │ ├── introduction/ │ │ │ │ ├── enUS/ │ │ │ │ │ └── index.md │ │ │ │ └── zhCN/ │ │ │ │ └── index.md │ │ │ ├── jsx/ │ │ │ │ ├── enUS/ │ │ │ │ │ └── index.md │ │ │ │ └── zhCN/ │ │ │ │ └── index.md │ │ │ ├── nuxtjs/ │ │ │ │ ├── enUS/ │ │ │ │ │ └── index.md │ │ │ │ └── zhCN/ │ │ │ │ └── index.md │ │ │ ├── ssr/ │ │ │ │ ├── enUS/ │ │ │ │ │ └── index.md │ │ │ │ └── zhCN/ │ │ │ │ └── index.md │ │ │ ├── style-conflict/ │ │ │ │ ├── enUS/ │ │ │ │ │ └── index.md │ │ │ │ └── zhCN/ │ │ │ │ └── index.md │ │ │ ├── supported-platforms/ │ │ │ │ ├── enUS/ │ │ │ │ │ └── index.md │ │ │ │ └── zhCN/ │ │ │ │ └── index.md │ │ │ ├── theme/ │ │ │ │ ├── enUS/ │ │ │ │ │ ├── element.demo.vue │ │ │ │ │ ├── index.demo-entry.md │ │ │ │ │ ├── provide-theme.demo.vue │ │ │ │ │ └── use-theme-vars.demo.vue │ │ │ │ └── zhCN/ │ │ │ │ ├── element.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── provide-theme.demo.vue │ │ │ │ └── use-theme-vars.demo.vue │ │ │ ├── umd/ │ │ │ │ ├── enUS/ │ │ │ │ │ └── index.md │ │ │ │ └── zhCN/ │ │ │ │ └── index.md │ │ │ ├── usage-sfc/ │ │ │ │ ├── enUS/ │ │ │ │ │ └── index.md │ │ │ │ └── zhCN/ │ │ │ │ └── index.md │ │ │ ├── vite-ssge/ │ │ │ │ ├── enUS/ │ │ │ │ │ └── index.md │ │ │ │ └── zhCN/ │ │ │ │ └── index.md │ │ │ ├── vitepress/ │ │ │ │ ├── enUS/ │ │ │ │ │ └── index.md │ │ │ │ └── zhCN/ │ │ │ │ └── index.md │ │ │ └── vue3/ │ │ │ ├── enUS/ │ │ │ │ └── index.vue │ │ │ └── zhCN/ │ │ │ └── index.vue │ │ └── home/ │ │ ├── Footer.vue │ │ ├── Left.vue │ │ ├── Right.vue │ │ └── index.vue │ ├── routes/ │ │ ├── router.js │ │ └── routes.js │ ├── setup.js │ ├── store/ │ │ ├── hljs.js │ │ ├── index.js │ │ └── menu-options.js │ ├── styles/ │ │ ├── Metropolis.css │ │ └── demo.css │ └── utils/ │ ├── ComponentDemo.vue │ ├── ComponentDemos.tsx │ ├── CopyCodeButton.vue │ ├── EditInCodeSandboxButton.vue │ ├── EditInPlaygroundButton.vue │ ├── EditOnGithubButton.vue │ ├── EditOnGithubHeader.vue │ ├── codesandbox.js │ ├── composables.js │ ├── composables.ts │ ├── github-url.js │ ├── playground.js │ └── route.js ├── design-notes/ │ ├── design-token-status.md │ ├── how-to-name-a-style-var.md │ ├── maintaining.md │ ├── think.md │ └── todo.md ├── eslint.config.mjs ├── esm-test/ │ └── index.spec.js ├── generic/ │ ├── AvatarGroup.vue │ └── index.ts ├── index.html ├── package.json ├── playground/ │ ├── collect-vars.js │ ├── ssr/ │ │ ├── app.js │ │ ├── build.sh │ │ ├── client.js │ │ ├── pre-build.sh │ │ ├── readme.md │ │ ├── server.js │ │ └── webpack.config.server.js │ ├── testAsyncValidator.js │ ├── testColor.js │ └── uploadServer.js ├── postcss.config.js ├── rollup.config.mjs ├── scripts/ │ ├── gen-component-declaration.ts │ ├── gen-css-vars-dts.ts │ ├── gen-version.ts │ ├── md-to-vue.ts │ ├── post-build/ │ │ ├── complete-path.ts │ │ ├── gen-web-types.ts │ │ ├── index.ts │ │ └── terse-cssr.ts │ ├── post-build-site/ │ │ └── post-build-site.sh │ ├── pre-build/ │ │ └── pre-cjs-build.ts │ ├── pre-build-site/ │ │ └── pre-build-site.sh │ ├── release-changelog.ts │ └── utils/ │ ├── collect-vars.ts │ ├── index.ts │ ├── loader.ts │ └── replace-define.ts ├── src/ │ ├── _internal/ │ │ ├── README.md │ │ ├── clear/ │ │ │ ├── index.ts │ │ │ └── src/ │ │ │ ├── Clear.tsx │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── close/ │ │ │ ├── index.ts │ │ │ └── src/ │ │ │ ├── Close.tsx │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── fade-in-expand-transition/ │ │ │ ├── index.ts │ │ │ └── src/ │ │ │ └── FadeInExpandTransition.ts │ │ ├── focus-detector/ │ │ │ ├── index.tsx │ │ │ └── src/ │ │ │ └── FocusDetector.tsx │ │ ├── icon/ │ │ │ ├── index.ts │ │ │ └── src/ │ │ │ ├── Icon.tsx │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── icon-switch-transition/ │ │ │ ├── index.ts │ │ │ └── src/ │ │ │ └── IconSwitchTransition.tsx │ │ ├── icons/ │ │ │ ├── Add.tsx │ │ │ ├── ArrowBack.tsx │ │ │ ├── ArrowDown.tsx │ │ │ ├── ArrowUp.tsx │ │ │ ├── Attach.tsx │ │ │ ├── Backward.tsx │ │ │ ├── Cancel.tsx │ │ │ ├── Checkmark.tsx │ │ │ ├── ChevronDown.tsx │ │ │ ├── ChevronDownFilled.tsx │ │ │ ├── ChevronLeft.tsx │ │ │ ├── ChevronRight.tsx │ │ │ ├── Clear.tsx │ │ │ ├── Close.tsx │ │ │ ├── Date.tsx │ │ │ ├── Download.tsx │ │ │ ├── Empty.tsx │ │ │ ├── Error.tsx │ │ │ ├── Eye.tsx │ │ │ ├── EyeOff.tsx │ │ │ ├── FastBackward.tsx │ │ │ ├── FastForward.tsx │ │ │ ├── File.tsx │ │ │ ├── Filter.tsx │ │ │ ├── Forward.tsx │ │ │ ├── Info.tsx │ │ │ ├── More.tsx │ │ │ ├── Photo.tsx │ │ │ ├── Remove.tsx │ │ │ ├── ResizeSmall.tsx │ │ │ ├── Retry.tsx │ │ │ ├── RotateClockwise.tsx │ │ │ ├── RotateCounterclockwise.tsx │ │ │ ├── Search.tsx │ │ │ ├── Success.tsx │ │ │ ├── Switcher.tsx │ │ │ ├── Time.tsx │ │ │ ├── To.tsx │ │ │ ├── Trash.tsx │ │ │ ├── Warning.tsx │ │ │ ├── ZoomIn.tsx │ │ │ ├── ZoomOut.tsx │ │ │ ├── index.ts │ │ │ └── replaceable.tsx │ │ ├── index.ts │ │ ├── loading/ │ │ │ ├── index.ts │ │ │ └── src/ │ │ │ ├── Loading.tsx │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── menu-mask/ │ │ │ ├── index.ts │ │ │ └── src/ │ │ │ ├── MenuMask.tsx │ │ │ ├── interface.ts │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── scrollbar/ │ │ │ ├── index.ts │ │ │ ├── src/ │ │ │ │ ├── Scrollbar.tsx │ │ │ │ └── styles/ │ │ │ │ ├── index.cssr.ts │ │ │ │ └── rtl.cssr.ts │ │ │ ├── styles/ │ │ │ │ ├── common.ts │ │ │ │ ├── dark.ts │ │ │ │ ├── index.ts │ │ │ │ ├── light.ts │ │ │ │ └── rtl.ts │ │ │ └── tests/ │ │ │ ├── Scrollbar.spec.ts │ │ │ └── server.spec.tsx │ │ ├── select-menu/ │ │ │ ├── index.ts │ │ │ ├── src/ │ │ │ │ ├── SelectGroupHeader.tsx │ │ │ │ ├── SelectMenu.tsx │ │ │ │ ├── SelectOption.tsx │ │ │ │ ├── interface.ts │ │ │ │ └── styles/ │ │ │ │ ├── index.cssr.ts │ │ │ │ └── rtl.cssr.ts │ │ │ └── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ ├── selection/ │ │ │ ├── index.ts │ │ │ ├── src/ │ │ │ │ ├── Selection.tsx │ │ │ │ ├── interface.ts │ │ │ │ └── styles/ │ │ │ │ ├── index.cssr.ts │ │ │ │ └── rtl.cssr.ts │ │ │ └── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ ├── slot-machine/ │ │ │ ├── index.ts │ │ │ └── src/ │ │ │ ├── SlotMachine.tsx │ │ │ ├── SlotMachineNumber.tsx │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── suffix/ │ │ │ ├── index.ts │ │ │ └── src/ │ │ │ └── Suffix.tsx │ │ └── wave/ │ │ ├── index.ts │ │ └── src/ │ │ ├── Wave.tsx │ │ └── styles/ │ │ └── index.cssr.ts │ ├── _mixins/ │ │ ├── common.ts │ │ ├── index.ts │ │ ├── use-config.ts │ │ ├── use-css-vars-class.ts │ │ ├── use-form-item.ts │ │ ├── use-hljs.ts │ │ ├── use-locale.ts │ │ ├── use-rtl.ts │ │ ├── use-style.ts │ │ └── use-theme.ts │ ├── _styles/ │ │ ├── common/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ ├── global/ │ │ │ └── index.cssr.ts │ │ └── transitions/ │ │ ├── fade-down.cssr.ts │ │ ├── fade-in-height-expand.cssr.ts │ │ ├── fade-in-scale-up.cssr.ts │ │ ├── fade-in-width-expand.cssr.ts │ │ ├── fade-in.cssr.ts │ │ ├── fade-up-width-expand.cssr.ts │ │ ├── icon-switch.cssr.ts │ │ ├── slide-in-from-bottom.ts │ │ ├── slide-in-from-left.ts │ │ ├── slide-in-from-right.ts │ │ └── slide-in-from-top.ts │ ├── _utils/ │ │ ├── color/ │ │ │ └── index.ts │ │ ├── composable/ │ │ │ ├── index.ts │ │ │ ├── use-adjusted-to.ts │ │ │ ├── use-browser-location.ts │ │ │ ├── use-collection.ts │ │ │ ├── use-deferred-true.ts │ │ │ ├── use-houdini.ts │ │ │ ├── use-is-composing.ts │ │ │ ├── use-lock-html-scroll.ts │ │ │ ├── use-reactivated.ts │ │ │ └── use-resize.ts │ │ ├── css/ │ │ │ ├── color-to-class.ts │ │ │ ├── format-length.ts │ │ │ ├── index.ts │ │ │ └── rtl-inset.ts │ │ ├── cssr/ │ │ │ └── index.ts │ │ ├── dom/ │ │ │ ├── download.ts │ │ │ ├── index.ts │ │ │ └── is-document.ts │ │ ├── env/ │ │ │ ├── browser.ts │ │ │ ├── is-browser.ts │ │ │ ├── is-jsdom.ts │ │ │ └── is-native-lazy-load.ts │ │ ├── event/ │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── naive/ │ │ │ ├── attribute.ts │ │ │ ├── extract-public-props.ts │ │ │ ├── index.ts │ │ │ ├── mutable.ts │ │ │ ├── prop.ts │ │ │ ├── value.ts │ │ │ └── warn.ts │ │ ├── tests/ │ │ │ └── index.spec.ts │ │ ├── ts/ │ │ │ └── ts.ts │ │ └── vue/ │ │ ├── call.ts │ │ ├── create-data-key.ts │ │ ├── create-injection-key.ts │ │ ├── create-ref-setter.ts │ │ ├── flatten.ts │ │ ├── get-first-slot-vnode.ts │ │ ├── get-slot.ts │ │ ├── get-v-node-children.ts │ │ ├── index.ts │ │ ├── is-node-v-show-false.ts │ │ ├── keep.ts │ │ ├── keysOf.ts │ │ ├── merge-handlers.ts │ │ ├── omit.ts │ │ ├── render.ts │ │ ├── resolve-slot.ts │ │ └── wrapper.tsx │ ├── affix/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ └── position.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ └── position.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Affix.tsx │ │ │ ├── styles/ │ │ │ │ └── index.cssr.ts │ │ │ └── utils.ts │ │ └── tests/ │ │ ├── Affix.spec.ts │ │ └── server.spec.tsx │ ├── alert/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── bordered.demo.vue │ │ │ │ ├── closable.demo.vue │ │ │ │ ├── icon.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── marquee.demo.vue │ │ │ │ └── no-icon.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── bordered.demo.vue │ │ │ ├── closable.demo.vue │ │ │ ├── empty-debug.demo.vue │ │ │ ├── icon.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── marquee.demo.vue │ │ │ ├── no-icon.demo.vue │ │ │ └── rtl-debug.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Alert.tsx │ │ │ └── styles/ │ │ │ ├── index.cssr.ts │ │ │ └── rtl.cssr.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── Alert.spec.ts │ │ ├── __snapshots__/ │ │ │ └── Alert.spec.ts.snap │ │ └── server.spec.tsx │ ├── anchor/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── affix.demo.vue │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── ignore-gap.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ └── scrollto.demo.vue │ │ │ └── zhCN/ │ │ │ ├── affix.demo.vue │ │ │ ├── basic.demo.vue │ │ │ ├── ignore-gap.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── max-height-debug.demo.vue │ │ │ └── scrollto.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── AnchorAdapter.tsx │ │ │ ├── BaseAnchor.tsx │ │ │ ├── Link.tsx │ │ │ ├── public-types.tsx │ │ │ ├── styles/ │ │ │ │ └── index.cssr.ts │ │ │ └── utils.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Anchor.spec.ts │ │ └── server.spec.tsx │ ├── auto-complete/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── after-select.demo.vue │ │ │ │ ├── append.demo.vue │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── custom-input.demo.vue │ │ │ │ ├── customized-rendering.demo.vue │ │ │ │ ├── group.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── show-options-by-value.demo.vue │ │ │ │ ├── size.demo.vue │ │ │ │ └── status.demo.vue │ │ │ └── zhCN/ │ │ │ ├── after-select.demo.vue │ │ │ ├── append.demo.vue │ │ │ ├── basic.demo.vue │ │ │ ├── custom-input.demo.vue │ │ │ ├── customized-rendering.demo.vue │ │ │ ├── group.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── show-options-by-value.demo.vue │ │ │ ├── size.demo.vue │ │ │ └── status.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── AutoComplete.tsx │ │ │ ├── interface.ts │ │ │ ├── public-types.ts │ │ │ ├── styles/ │ │ │ │ └── index.cssr.ts │ │ │ └── utils.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── AutoComplete.spec.ts │ │ ├── __snapshots__/ │ │ │ └── AutoComplete.spec.ts.snap │ │ └── server.spec.tsx │ ├── avatar/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── badge.demo.vue │ │ │ │ ├── color.demo.vue │ │ │ │ ├── fallback.demo.vue │ │ │ │ ├── group.demo.vue │ │ │ │ ├── icon.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── lazy.demo.vue │ │ │ │ ├── name-size.demo.vue │ │ │ │ ├── shape.demo.vue │ │ │ │ ├── size.demo.vue │ │ │ │ └── v-show-debug.demo.vue │ │ │ └── zhCN/ │ │ │ ├── badge.demo.vue │ │ │ ├── color.demo.vue │ │ │ ├── error-placeholder-debug.demo.vue │ │ │ ├── fallback.demo.vue │ │ │ ├── group.demo.vue │ │ │ ├── icon.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── lazy.demo.vue │ │ │ ├── name-size.demo.vue │ │ │ ├── rtl-debug.demo.vue │ │ │ ├── shape.demo.vue │ │ │ ├── size.demo.vue │ │ │ └── v-show-debug.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Avatar.tsx │ │ │ ├── context.ts │ │ │ ├── interface.ts │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Avatar.spec.tsx │ │ ├── __snapshots__/ │ │ │ └── Avatar.spec.tsx.snap │ │ └── server.spec.tsx │ ├── avatar-group/ │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── AvatarGroup.tsx │ │ │ ├── generic-public-types.ts │ │ │ ├── public-types.ts │ │ │ └── styles/ │ │ │ ├── avatar-group-rtl.cssr.ts │ │ │ └── avatar-group.cssr.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── Avatar.spec.tsx │ │ └── server.spec.tsx │ ├── back-top/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── change-position.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── target-container-selector.demo.vue │ │ │ │ └── visibility-height.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── change-position.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── target-container-selector.demo.vue │ │ │ └── visibility-height.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── BackTop.tsx │ │ │ ├── BackTopIcon.tsx │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── BackTop.spec.ts │ │ └── server.spec.tsx │ ├── badge/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── color.demo.vue │ │ │ │ ├── custom-content.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── manual.demo.vue │ │ │ │ ├── offset.demo.vue │ │ │ │ ├── overflow.demo.vue │ │ │ │ ├── processing.demo.vue │ │ │ │ ├── raw.demo.vue │ │ │ │ ├── show-zero.demo.vue │ │ │ │ └── type.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── color.demo.vue │ │ │ ├── custom-content.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── manual.demo.vue │ │ │ ├── offset.demo.vue │ │ │ ├── overflow.demo.vue │ │ │ ├── processing.demo.vue │ │ │ ├── raw.demo.vue │ │ │ ├── rtl-debug.demo.vue │ │ │ ├── show-zero.demo.vue │ │ │ └── type.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Badge.tsx │ │ │ └── styles/ │ │ │ ├── index.cssr.ts │ │ │ └── rtl.cssr.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── Badge.spec.ts │ │ └── server.spec.tsx │ ├── breadcrumb/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── custom.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── separator-per-item.demo.vue │ │ │ │ └── separator.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── custom.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── separator-per-item.demo.vue │ │ │ └── separator.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Breadcrumb.tsx │ │ │ ├── BreadcrumbItem.tsx │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Breadcrumb.spec.ts │ │ └── server.spec.tsx │ ├── button/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── color.demo.vue │ │ │ │ ├── dashed.demo.vue │ │ │ │ ├── disabled.demo.vue │ │ │ │ ├── events.demo.vue │ │ │ │ ├── ghost.demo.vue │ │ │ │ ├── group.demo.vue │ │ │ │ ├── icon-button.demo.vue │ │ │ │ ├── icon.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── loading.demo.vue │ │ │ │ ├── popover.demo.vue │ │ │ │ ├── quaternary.demo.vue │ │ │ │ ├── secondary.demo.vue │ │ │ │ ├── shape.demo.vue │ │ │ │ ├── size.demo.vue │ │ │ │ ├── tag.demo.vue │ │ │ │ ├── tertiary.demo.vue │ │ │ │ └── text.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── color.demo.vue │ │ │ ├── dashed.demo.vue │ │ │ ├── debug.demo.vue │ │ │ ├── disabled.demo.vue │ │ │ ├── events.demo.vue │ │ │ ├── ghost.demo.vue │ │ │ ├── group.demo.vue │ │ │ ├── icon-button.demo.vue │ │ │ ├── icon.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── loading.demo.vue │ │ │ ├── popover.demo.vue │ │ │ ├── quaternary.demo.vue │ │ │ ├── rtl-debug.demo.vue │ │ │ ├── secondary.demo.vue │ │ │ ├── shape.demo.vue │ │ │ ├── size.demo.vue │ │ │ ├── tag.demo.vue │ │ │ ├── tertiary.demo.vue │ │ │ └── text.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Button.tsx │ │ │ ├── public-types.ts │ │ │ └── styles/ │ │ │ ├── index.cssr.ts │ │ │ └── rtl.cssr.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── Button.spec.tsx │ │ ├── __snapshots__/ │ │ │ └── Button.spec.tsx.snap │ │ └── server.spec.tsx │ ├── button-group/ │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── ButtonGroup.tsx │ │ │ ├── context.ts │ │ │ └── styles/ │ │ │ ├── index.cssr.ts │ │ │ └── rtl.cssr.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── ButtonGroup.spec.tsx │ │ └── server.spec.tsx │ ├── calendar/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ └── index.demo-entry.md │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ └── index.demo-entry.md │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Calendar.tsx │ │ │ ├── interface.ts │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Calendar.spec.tsx │ │ └── server.spec.tsx │ ├── card/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── border.demo.vue │ │ │ │ ├── closable.demo.vue │ │ │ │ ├── content-scrollable.demo.vue │ │ │ │ ├── cover.demo.vue │ │ │ │ ├── custom-style.demo.vue │ │ │ │ ├── embedded.demo.vue │ │ │ │ ├── hoverable.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── loading.demo.vue │ │ │ │ ├── no-title.demo.vue │ │ │ │ ├── segment.demo.vue │ │ │ │ ├── size.demo.vue │ │ │ │ └── slots.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── border.demo.vue │ │ │ ├── closable.demo.vue │ │ │ ├── content-scrollable.demo.vue │ │ │ ├── cover.demo.vue │ │ │ ├── custom-style.demo.vue │ │ │ ├── embedded-debug.demo.vue │ │ │ ├── embedded.demo.vue │ │ │ ├── hoverable.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── loading.demo.vue │ │ │ ├── no-title.demo.vue │ │ │ ├── rtl-debug.demo.vue │ │ │ ├── segment.demo.vue │ │ │ ├── size.demo.vue │ │ │ └── slots.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Card.tsx │ │ │ ├── public-types.ts │ │ │ └── styles/ │ │ │ ├── index.cssr.ts │ │ │ └── rtl.cssr.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── Card.spec.ts │ │ ├── __snapshots__/ │ │ │ └── Card.spec.ts.snap │ │ └── server.spec.tsx │ ├── carousel/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── arrow.demo.vue │ │ │ │ ├── autoplay.demo.vue │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── centered.demo.vue │ │ │ │ ├── custom-arrow-and-dots.demo.vue │ │ │ │ ├── custom-card.demo.vue │ │ │ │ ├── custom-dots.demo.vue │ │ │ │ ├── dots.demo.vue │ │ │ │ ├── effect.demo.vue │ │ │ │ ├── hover.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── keyboard.demo.vue │ │ │ │ ├── mousewheel.demo.vue │ │ │ │ ├── simulate-drag.demo.vue │ │ │ │ ├── slides-per-view-auto.demo.vue │ │ │ │ ├── slides-per-view.demo.vue │ │ │ │ ├── space-between.demo.vue │ │ │ │ ├── transition-name.demo.vue │ │ │ │ └── vertical.demo.vue │ │ │ └── zhCN/ │ │ │ ├── arrow.demo.vue │ │ │ ├── autoplay.demo.vue │ │ │ ├── basic.demo.vue │ │ │ ├── centered.demo.vue │ │ │ ├── custom-arrow-and-dots.demo.vue │ │ │ ├── custom-card.demo.vue │ │ │ ├── custom-dots.demo.vue │ │ │ ├── dots.demo.vue │ │ │ ├── effect.demo.vue │ │ │ ├── hover.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── keyboard.demo.vue │ │ │ ├── mousewheel.demo.vue │ │ │ ├── simulate-drag.demo.vue │ │ │ ├── slides-per-view-auto.demo.vue │ │ │ ├── slides-per-view.demo.vue │ │ │ ├── space-between.demo.vue │ │ │ ├── transition-name.demo.vue │ │ │ └── vertical.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Carousel.tsx │ │ │ ├── CarouselArrow.tsx │ │ │ ├── CarouselContext.ts │ │ │ ├── CarouselDots.tsx │ │ │ ├── CarouselItem.tsx │ │ │ ├── interface.ts │ │ │ ├── styles/ │ │ │ │ └── index.cssr.ts │ │ │ └── utils/ │ │ │ ├── duplicatedLogic.ts │ │ │ ├── event.ts │ │ │ └── index.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Carousel.spec.tsx │ │ └── server.spec.tsx │ ├── cascader/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── action.demo.vue │ │ │ │ ├── check-strategy.demo.vue │ │ │ │ ├── custom-field.demo.vue │ │ │ │ ├── custom-render.demo.vue │ │ │ │ ├── focus.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── multiple-lazy.demo.vue │ │ │ │ ├── multiple.demo.vue │ │ │ │ ├── single-lazy.demo.vue │ │ │ │ ├── single.demo.vue │ │ │ │ ├── size.demo.vue │ │ │ │ ├── status.demo.vue │ │ │ │ └── virtual.demo.vue │ │ │ └── zhCN/ │ │ │ ├── action.demo.vue │ │ │ ├── check-strategy.demo.vue │ │ │ ├── custom-field.demo.vue │ │ │ ├── custom-render.demo.vue │ │ │ ├── default-value-debug.demo.vue │ │ │ ├── focus.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── multiple-lazy.demo.vue │ │ │ ├── multiple.demo.vue │ │ │ ├── single-lazy.demo.vue │ │ │ ├── single.demo.vue │ │ │ ├── size.demo.vue │ │ │ ├── status.demo.vue │ │ │ └── virtual.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Cascader.tsx │ │ │ ├── CascaderMenu.tsx │ │ │ ├── CascaderOption.tsx │ │ │ ├── CascaderSelectMenu.tsx │ │ │ ├── CascaderSubmenu.tsx │ │ │ ├── interface.ts │ │ │ ├── public-types.ts │ │ │ ├── styles/ │ │ │ │ └── index.cssr.ts │ │ │ └── utils.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Cascader.spec.ts │ │ ├── __snapshots__/ │ │ │ └── Cascader.spec.ts.snap │ │ └── server.spec.tsx │ ├── checkbox/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── controlled.demo.vue │ │ │ │ ├── customize-value.demo.vue │ │ │ │ ├── event.demo.vue │ │ │ │ ├── focus.demo.vue │ │ │ │ ├── grid.demo.vue │ │ │ │ ├── group.demo.vue │ │ │ │ ├── indeterminate.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ └── size.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── controlled.demo.vue │ │ │ ├── customize-value.demo.vue │ │ │ ├── event.demo.vue │ │ │ ├── focus.demo.vue │ │ │ ├── grid.demo.vue │ │ │ ├── group.demo.vue │ │ │ ├── indeterminate.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── rtl-debug.demo.vue │ │ │ └── size.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── CheckMark.tsx │ │ │ ├── Checkbox.tsx │ │ │ ├── CheckboxGroup.tsx │ │ │ ├── LineMark.tsx │ │ │ ├── interface.ts │ │ │ ├── public-types.ts │ │ │ └── styles/ │ │ │ ├── index.cssr.ts │ │ │ └── rtl.cssr.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── Checkbox.spec.tsx │ │ ├── __snapshots__/ │ │ │ └── Checkbox.spec.tsx.snap │ │ └── server.spec.tsx │ ├── code/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── inline.demo.vue │ │ │ │ ├── line-numbers.demo.vue │ │ │ │ └── softwrap.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── inline.demo.vue │ │ │ ├── line-numbers.demo.vue │ │ │ ├── loop-debug.demo.vue │ │ │ └── softwrap.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Code.tsx │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Code.spec.tsx │ │ └── server.spec.tsx │ ├── collapse/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── accordion.demo.vue │ │ │ │ ├── arrow-placement.demo.vue │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── customize-icon.demo.vue │ │ │ │ ├── default-expanded.demo.vue │ │ │ │ ├── disabled.demo.vue │ │ │ │ ├── display-directive.demo.vue │ │ │ │ ├── header-extra.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── item-header-click.demo.vue │ │ │ │ ├── nested.demo.vue │ │ │ │ └── trigger-areas.demo.vue │ │ │ └── zhCN/ │ │ │ ├── accordion.demo.vue │ │ │ ├── arrow-placement.demo.vue │ │ │ ├── basic.demo.vue │ │ │ ├── customize-icon.demo.vue │ │ │ ├── default-expanded.demo.vue │ │ │ ├── disabled.demo.vue │ │ │ ├── display-directive.demo.vue │ │ │ ├── header-extra.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── item-header-click.demo.vue │ │ │ ├── nested.demo.vue │ │ │ ├── rtl-debug.demo.vue │ │ │ └── trigger-areas.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Collapse.tsx │ │ │ ├── CollapseItem.tsx │ │ │ ├── CollapseItemContent.tsx │ │ │ ├── interface.ts │ │ │ └── styles/ │ │ │ ├── index.cssr.ts │ │ │ └── rtl.cssr.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── Collapse.spec.tsx │ │ └── server.spec.tsx │ ├── collapse-transition/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ └── index.demo-entry.md │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ └── rtl-debug.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── CollapseTransition.tsx │ │ │ └── styles/ │ │ │ ├── index.cssr.ts │ │ │ └── rtl.cssr.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── CollapseTransition.spec.tsx │ │ └── server.spec.tsx │ ├── color-picker/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── actions.demo.vue │ │ │ │ ├── alpha.demo.vue │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── disabled.demo.vue │ │ │ │ ├── form.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── modes.demo.vue │ │ │ │ ├── native.demo.vue │ │ │ │ ├── size.demo.vue │ │ │ │ ├── swatches.demo.vue │ │ │ │ └── trigger.demo.vue │ │ │ └── zhCN/ │ │ │ ├── actions.demo.vue │ │ │ ├── alpha.demo.vue │ │ │ ├── basic.demo.vue │ │ │ ├── close-debug.demo.vue │ │ │ ├── disabled.demo.vue │ │ │ ├── form.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── modes.demo.vue │ │ │ ├── native.demo.vue │ │ │ ├── size.demo.vue │ │ │ ├── swatches.demo.vue │ │ │ └── trigger.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── AlphaSlider.tsx │ │ │ ├── ColorInput.tsx │ │ │ ├── ColorInputUnit.tsx │ │ │ ├── ColorPicker.tsx │ │ │ ├── ColorPickerSwatches.tsx │ │ │ ├── ColorPickerTrigger.tsx │ │ │ ├── ColorPreview.tsx │ │ │ ├── HueSlider.tsx │ │ │ ├── Pallete.tsx │ │ │ ├── context.ts │ │ │ ├── interface.ts │ │ │ ├── public-types.ts │ │ │ ├── styles/ │ │ │ │ └── index.cssr.ts │ │ │ └── utils.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── ColorPicker.spec.tsx │ │ └── server.spec.tsx │ ├── components.ts │ ├── composables/ │ │ ├── index.ts │ │ └── use-theme-vars.ts │ ├── config-consumer/ │ │ └── demos/ │ │ ├── enUS/ │ │ │ └── index.demo-entry.md │ │ └── zhCN/ │ │ └── index.demo-entry.md │ ├── config-provider/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── inherit-theme.demo.vue │ │ │ │ ├── inline-theme-disabled.demo.vue │ │ │ │ ├── language.demo.vue │ │ │ │ ├── namespace.demo.vue │ │ │ │ ├── os-theme.demo.vue │ │ │ │ ├── size.demo.vue │ │ │ │ ├── theme.demo.vue │ │ │ │ └── transparent.demo.vue │ │ │ └── zhCN/ │ │ │ ├── index.demo-entry.md │ │ │ ├── inherit-theme.demo.vue │ │ │ ├── inline-theme-disabled.demo.vue │ │ │ ├── language.demo.vue │ │ │ ├── namespace.demo.vue │ │ │ ├── os-theme.demo.vue │ │ │ ├── overrides-inherit-debug.demo.vue │ │ │ ├── size.demo.vue │ │ │ ├── theme.demo.vue │ │ │ └── transparent.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── ConfigProvider.ts │ │ │ ├── config.ts │ │ │ ├── context.ts │ │ │ ├── interface.ts │ │ │ ├── internal-interface.ts │ │ │ └── katex.ts │ │ └── tests/ │ │ ├── ComponentSize.spec.tsx │ │ ├── ConfigProvider.spec.ts │ │ └── server.spec.tsx │ ├── countdown/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── precision.demo.vue │ │ │ │ ├── render.demo.vue │ │ │ │ └── reset.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── finish-debug.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── precision.demo.vue │ │ │ ├── render.demo.vue │ │ │ └── reset.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ └── Countdown.tsx │ │ └── tests/ │ │ ├── Countdown.spec.ts │ │ └── server.spec.tsx │ ├── create.ts │ ├── data-table/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── ajax-usage.demo.vue │ │ │ │ ├── async-expand.demo.vue │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── border.demo.vue │ │ │ │ ├── column-draggable.demo.vue │ │ │ │ ├── context-menu.demo.vue │ │ │ │ ├── controlled-filter.demo.vue │ │ │ │ ├── controlled-multiple-sorter.demo.vue │ │ │ │ ├── controlled-page.demo.vue │ │ │ │ ├── controlled-sorter.demo.vue │ │ │ │ ├── custom-filter-menu.demo.vue │ │ │ │ ├── custom-select.demo.vue │ │ │ │ ├── custom-sorter.demo.vue │ │ │ │ ├── custom-style.demo.vue │ │ │ │ ├── ellipsis-tooltip.demo.vue │ │ │ │ ├── ellipsis.demo.vue │ │ │ │ ├── empty.demo.vue │ │ │ │ ├── expand.demo.vue │ │ │ │ ├── export-csv.demo.vue │ │ │ │ ├── filter-and-sorter.demo.vue │ │ │ │ ├── fixed-header-column.demo.vue │ │ │ │ ├── fixed-header.demo.vue │ │ │ │ ├── flex-height.demo.vue │ │ │ │ ├── group-header.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── merge-cell.demo.vue │ │ │ │ ├── multiple-sorter.demo.vue │ │ │ │ ├── pagination-behavior-on-filter.demo.vue │ │ │ │ ├── render-cell.demo.vue │ │ │ │ ├── render-header.demo.vue │ │ │ │ ├── row-props.demo.vue │ │ │ │ ├── select-single.demo.vue │ │ │ │ ├── select.demo.vue │ │ │ │ ├── simple-editable.demo.vue │ │ │ │ ├── size.demo.vue │ │ │ │ ├── striped.demo.vue │ │ │ │ ├── summary.demo.vue │ │ │ │ ├── switchable-editable.demo.vue │ │ │ │ ├── tree.demo.vue │ │ │ │ ├── virtual-x.demo.vue │ │ │ │ └── virtual.demo.vue │ │ │ └── zhCN/ │ │ │ ├── ajax-usage.demo.vue │ │ │ ├── async-expand.demo.vue │ │ │ ├── basic.demo.vue │ │ │ ├── border.demo.vue │ │ │ ├── column-draggable.demo.vue │ │ │ ├── context-menu.demo.vue │ │ │ ├── controlled-filter.demo.vue │ │ │ ├── controlled-multiple-sorter.demo.vue │ │ │ ├── controlled-page.demo.vue │ │ │ ├── controlled-sorter.demo.vue │ │ │ ├── custom-expand-icon-debug.demo.vue │ │ │ ├── custom-filter-menu.demo.vue │ │ │ ├── custom-select.demo.vue │ │ │ ├── custom-sorter.demo.vue │ │ │ ├── custom-style.demo.vue │ │ │ ├── debug.demo.vue │ │ │ ├── ellipsis-debug.demo.vue │ │ │ ├── ellipsis-tooltip.demo.vue │ │ │ ├── ellipsis.demo.vue │ │ │ ├── empty-scroll-debug.demo.vue │ │ │ ├── empty.demo.vue │ │ │ ├── expand.demo.vue │ │ │ ├── expandable-debug.demo.vue │ │ │ ├── export-csv.demo.vue │ │ │ ├── filter-and-sorter.demo.vue │ │ │ ├── fixed-column-debug.demo.vue │ │ │ ├── fixed-column2-debug.demo.vue │ │ │ ├── fixed-header-column.demo.vue │ │ │ ├── fixed-header.demo.vue │ │ │ ├── flex-height.demo.vue │ │ │ ├── group-header.demo.vue │ │ │ ├── height-debug.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── keep-alive-debug.demo.vue │ │ │ ├── merge-cell.demo.vue │ │ │ ├── multiple-sorter.demo.vue │ │ │ ├── pagination-behavior-on-filter.demo.vue │ │ │ ├── render-cell.demo.vue │ │ │ ├── render-header.demo.vue │ │ │ ├── row-props.demo.vue │ │ │ ├── rtl-debug.demo.vue │ │ │ ├── scroll-debug.demo.vue │ │ │ ├── select-single.demo.vue │ │ │ ├── select.demo.vue │ │ │ ├── simple-editable.demo.vue │ │ │ ├── size.demo.vue │ │ │ ├── striped.demo.vue │ │ │ ├── summary-debug.demo.vue │ │ │ ├── summary.demo.vue │ │ │ ├── switchable-editable.demo.vue │ │ │ ├── tree.demo.vue │ │ │ ├── virtual-x.demo.vue │ │ │ └── virtual.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── DataTable.tsx │ │ │ ├── HeaderButton/ │ │ │ │ ├── FilterButton.tsx │ │ │ │ ├── FilterMenu.tsx │ │ │ │ ├── RenderFilter.ts │ │ │ │ ├── RenderSorter.ts │ │ │ │ ├── ResizeButton.tsx │ │ │ │ └── SortButton.tsx │ │ │ ├── MainTable.tsx │ │ │ ├── TableParts/ │ │ │ │ ├── Body.tsx │ │ │ │ ├── BodyCheckbox.tsx │ │ │ │ ├── BodyRadio.tsx │ │ │ │ ├── Cell.tsx │ │ │ │ ├── ExpandTrigger.tsx │ │ │ │ ├── Header.tsx │ │ │ │ └── SelectionMenu.tsx │ │ │ ├── interface.ts │ │ │ ├── public-types.ts │ │ │ ├── styles/ │ │ │ │ ├── index.cssr.ts │ │ │ │ └── rtl.cssr.ts │ │ │ ├── use-check.ts │ │ │ ├── use-expand.ts │ │ │ ├── use-group-header.ts │ │ │ ├── use-resizable.ts │ │ │ ├── use-scroll.ts │ │ │ ├── use-sorter.ts │ │ │ ├── use-table-data.ts │ │ │ └── utils.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── DataTable.spec.tsx │ │ ├── __snapshots__/ │ │ │ └── DataTable.spec.tsx.snap │ │ └── server.spec.tsx │ ├── date-picker/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── actions.demo.vue │ │ │ │ ├── close-panel-on-select.demo.vue │ │ │ │ ├── date.demo.vue │ │ │ │ ├── daterange.demo.vue │ │ │ │ ├── datetime.demo.vue │ │ │ │ ├── datetimeformat.demo.vue │ │ │ │ ├── datetimerange.demo.vue │ │ │ │ ├── default-time.demo.vue │ │ │ │ ├── disabled-time.demo.vue │ │ │ │ ├── disabled.demo.vue │ │ │ │ ├── events.demo.vue │ │ │ │ ├── focus.demo.vue │ │ │ │ ├── footerslot.demo.vue │ │ │ │ ├── format.demo.vue │ │ │ │ ├── icon.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── month.demo.vue │ │ │ │ ├── monthrange.demo.vue │ │ │ │ ├── panel.demo.vue │ │ │ │ ├── quarter.demo.vue │ │ │ │ ├── quarterrange.demo.vue │ │ │ │ ├── shortcuts.demo.vue │ │ │ │ ├── size.demo.vue │ │ │ │ ├── status.demo.vue │ │ │ │ ├── update-on-close.demo.vue │ │ │ │ ├── week.demo.vue │ │ │ │ ├── year.demo.vue │ │ │ │ └── yearrange.demo.vue │ │ │ └── zhCN/ │ │ │ ├── actions.demo.vue │ │ │ ├── close-panel-on-select.demo.vue │ │ │ ├── date.demo.vue │ │ │ ├── daterange.demo.vue │ │ │ ├── datetime.demo.vue │ │ │ ├── datetimeformat.demo.vue │ │ │ ├── datetimerange.demo.vue │ │ │ ├── default-time.demo.vue │ │ │ ├── disabled-time.demo.vue │ │ │ ├── disabled.demo.vue │ │ │ ├── events.demo.vue │ │ │ ├── focus.demo.vue │ │ │ ├── footerslot.demo.vue │ │ │ ├── form-debug.demo.vue │ │ │ ├── format.demo.vue │ │ │ ├── icon.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── month.demo.vue │ │ │ ├── monthrange.demo.vue │ │ │ ├── panel-debug.demo.vue │ │ │ ├── panel.demo.vue │ │ │ ├── quarter.demo.vue │ │ │ ├── quarterrange.demo.vue │ │ │ ├── shortcuts.demo.vue │ │ │ ├── size.demo.vue │ │ │ ├── status.demo.vue │ │ │ ├── update-on-close.demo.vue │ │ │ ├── week.demo.vue │ │ │ ├── year.demo.vue │ │ │ └── yearrange.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── DatePicker.tsx │ │ │ ├── config.ts │ │ │ ├── interface.ts │ │ │ ├── panel/ │ │ │ │ ├── date.tsx │ │ │ │ ├── daterange.tsx │ │ │ │ ├── datetime.tsx │ │ │ │ ├── datetimerange.tsx │ │ │ │ ├── month.tsx │ │ │ │ ├── monthrange.tsx │ │ │ │ ├── panelHeader.tsx │ │ │ │ ├── use-calendar.ts │ │ │ │ ├── use-dual-calendar.ts │ │ │ │ └── use-panel-common.ts │ │ │ ├── props.ts │ │ │ ├── public-types.ts │ │ │ ├── styles/ │ │ │ │ └── index.cssr.ts │ │ │ ├── utils.ts │ │ │ └── validation-utils.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── DatePicker.spec.tsx │ │ ├── __snapshots__/ │ │ │ └── DatePicker.spec.tsx.snap │ │ └── server.spec.tsx │ ├── descriptions/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── bordered.demo.vue │ │ │ │ ├── columns.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── placement.demo.vue │ │ │ │ ├── size.demo.vue │ │ │ │ └── span.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── bordered.demo.vue │ │ │ ├── columns.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── placement.demo.vue │ │ │ ├── single-line-debug.demo.vue │ │ │ ├── size.demo.vue │ │ │ └── span.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Descriptions.tsx │ │ │ ├── DescriptionsItem.ts │ │ │ ├── public-types.ts │ │ │ ├── styles/ │ │ │ │ └── index.cssr.ts │ │ │ └── utils.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Descriptions.spec.ts │ │ └── server.spec.tsx │ ├── dialog/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── action.demo.vue │ │ │ │ ├── async.demo.vue │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── mask.demo.vue │ │ │ │ ├── use-component.demo.vue │ │ │ │ └── use-dialog-reactive-list.demo.vue │ │ │ └── zhCN/ │ │ │ ├── action.demo.vue │ │ │ ├── async.demo.vue │ │ │ ├── basic.demo.vue │ │ │ ├── focus-debug.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── mask.demo.vue │ │ │ ├── rtl-debug.demo.vue │ │ │ ├── use-component.demo.vue │ │ │ └── use-dialog-reactive-list.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Dialog.tsx │ │ │ ├── DialogEnvironment.tsx │ │ │ ├── DialogProvider.ts │ │ │ ├── composables.ts │ │ │ ├── context.ts │ │ │ ├── dialogProps.ts │ │ │ ├── interface.ts │ │ │ └── styles/ │ │ │ ├── index.cssr.ts │ │ │ └── rtl.cssr.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── Dialog.spec.tsx │ │ └── server.spec.tsx │ ├── discrete/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ └── index.demo-entry.md │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ └── index.demo-entry.md │ │ ├── index.ts │ │ └── src/ │ │ ├── InjectionExtractor.tsx │ │ ├── discrete.ts │ │ ├── discreteApp.ts │ │ └── interface.ts │ ├── divider/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── content.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ └── vertical.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── content.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ └── vertical.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Divider.tsx │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Divider.spec.ts │ │ └── server.spec.tsx │ ├── drawer/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── closable.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── multiple.demo.vue │ │ │ │ ├── resizable.demo.vue │ │ │ │ ├── scroll.demo.vue │ │ │ │ ├── slot.demo.vue │ │ │ │ └── target.demo.vue │ │ │ └── zhCN/ │ │ │ ├── a11y-debug.demo.vue │ │ │ ├── basic.demo.vue │ │ │ ├── closable.demo.vue │ │ │ ├── custom-style-debug.demo.vue │ │ │ ├── dark-1-debug.demo.vue │ │ │ ├── dark-2-debug.demo.vue │ │ │ ├── dark-3-debug.demo.vue │ │ │ ├── dark-4-debug.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── multiple.demo.vue │ │ │ ├── resizable.demo.vue │ │ │ ├── rtl-debug.demo.vue │ │ │ ├── scroll.demo.vue │ │ │ ├── slot.demo.vue │ │ │ └── target.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Drawer.tsx │ │ │ ├── DrawerBodyWrapper.tsx │ │ │ ├── DrawerContent.tsx │ │ │ ├── interface.ts │ │ │ └── styles/ │ │ │ ├── index.cssr.ts │ │ │ └── rtl.cssr.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── Drawer.spec.tsx │ │ └── server.spec.tsx │ ├── dropdown/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── arrow.demo.vue │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── batch-render.demo.vue │ │ │ │ ├── cascade.demo.vue │ │ │ │ ├── icon.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── manual-position.demo.vue │ │ │ │ ├── option-props.demo.vue │ │ │ │ ├── placement.demo.vue │ │ │ │ ├── render-option.demo.vue │ │ │ │ ├── render.demo.vue │ │ │ │ ├── size.demo.vue │ │ │ │ └── trigger.demo.vue │ │ │ └── zhCN/ │ │ │ ├── arrow.demo.vue │ │ │ ├── basic.demo.vue │ │ │ ├── batch-render.demo.vue │ │ │ ├── cascade.demo.vue │ │ │ ├── group-debug.demo.vue │ │ │ ├── icon.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── manual-position.demo.vue │ │ │ ├── option-props.demo.vue │ │ │ ├── placement.demo.vue │ │ │ ├── render-option.demo.vue │ │ │ ├── render.demo.vue │ │ │ ├── scrollable-debug.demo.vue │ │ │ ├── size.demo.vue │ │ │ └── trigger.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Dropdown.tsx │ │ │ ├── DropdownDivider.tsx │ │ │ ├── DropdownGroup.tsx │ │ │ ├── DropdownGroupHeader.tsx │ │ │ ├── DropdownMenu.tsx │ │ │ ├── DropdownOption.tsx │ │ │ ├── DropdownRenderOption.tsx │ │ │ ├── context.ts │ │ │ ├── interface.ts │ │ │ ├── public-types.ts │ │ │ ├── styles/ │ │ │ │ └── index.cssr.ts │ │ │ └── utils.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Dropdown.spec.tsx │ │ ├── __snapshots__/ │ │ │ └── Dropdown.spec.tsx.snap │ │ └── server.spec.tsx │ ├── dynamic-input/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── custom-action.demo.vue │ │ │ │ ├── custom.demo.vue │ │ │ │ ├── form.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── move.demo.vue │ │ │ │ └── pair.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── create-debug.demo.vue │ │ │ ├── custom-action.demo.vue │ │ │ ├── custom.demo.vue │ │ │ ├── form.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── move.demo.vue │ │ │ ├── pair.demo.vue │ │ │ └── rtl-debug.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── DynamicInput.tsx │ │ │ ├── InputPreset.tsx │ │ │ ├── PairPreset.tsx │ │ │ ├── interface.ts │ │ │ └── styles/ │ │ │ ├── index.cssr.ts │ │ │ └── rtl.cssr.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── DynamicInput.spec.ts │ │ └── server.spec.tsx │ ├── dynamic-tags/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── form.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── max.demo.vue │ │ │ │ ├── on-create.demo.vue │ │ │ │ ├── option-format.demo.vue │ │ │ │ ├── render-tag.demo.vue │ │ │ │ └── slot.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── form.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── max.demo.vue │ │ │ ├── on-create.demo.vue │ │ │ ├── option-format.demo.vue │ │ │ ├── render-tag.demo.vue │ │ │ └── slot.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── DynamicTags.tsx │ │ │ ├── interface.ts │ │ │ ├── public-types.ts │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── DynamicTags.spec.ts │ │ ├── __snapshots__/ │ │ │ └── DynamicTags.spec.ts.snap │ │ └── server.spec.tsx │ ├── element/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ └── index.demo-entry.md │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ └── index.demo-entry.md │ │ ├── index.ts │ │ ├── src/ │ │ │ └── Element.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Element.spec.ts │ │ └── server.spec.tsx │ ├── ellipsis/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── custom-tooltip.demo.vue │ │ │ │ ├── expand-trigger.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── line-clamp.demo.vue │ │ │ │ └── performant-ellipsis.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── custom-tooltip.demo.vue │ │ │ ├── dynamic-debug.demo.vue │ │ │ ├── expand-trigger.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── line-clamp.demo.vue │ │ │ ├── performant-ellipsis.demo.vue │ │ │ └── width-debug.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Ellipsis.tsx │ │ │ ├── PerformantEllipsis.tsx │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Ellipsis.spec.tsx │ │ └── server.spec.tsx │ ├── empty/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── icon.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── locale-debug.demo.vue │ │ │ │ └── size.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── icon.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── locale-debug.demo.vue │ │ │ └── size.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Empty.tsx │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Empty.spec.ts │ │ ├── __snapshots__/ │ │ │ └── Empty.spec.ts.snap │ │ └── server.spec.tsx │ ├── equation/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ └── katex-options.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ └── katex-options.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ └── Equation.tsx │ │ └── styles/ │ │ ├── dark.ts │ │ ├── index.ts │ │ └── light.ts │ ├── flex/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── center.demo.vue │ │ │ │ ├── from-end.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── space-around.demo.vue │ │ │ │ ├── space-between.demo.vue │ │ │ │ └── vertical.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── center.demo.vue │ │ │ ├── from-end.demo.vue │ │ │ ├── grid-debug.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── rtl-debug.demo.vue │ │ │ ├── space-around.demo.vue │ │ │ ├── space-between.demo.vue │ │ │ └── vertical.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Flex.tsx │ │ │ ├── styles/ │ │ │ │ └── rtl.cssr.ts │ │ │ └── type.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── Flex.spec.tsx │ │ ├── __snapshots__/ │ │ │ └── Flex.spec.tsx.snap │ │ └── server.spec.tsx │ ├── float-button/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── badge.demo.vue │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── custom.demo.vue │ │ │ │ ├── group.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── menu.demo.vue │ │ │ │ └── tooltip.demo.vue │ │ │ └── zhCN/ │ │ │ ├── badge.demo.vue │ │ │ ├── basic.demo.vue │ │ │ ├── custom.demo.vue │ │ │ ├── group.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── menu.demo.vue │ │ │ └── tooltip.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── FloatButton.tsx │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── FloatButton.spec.tsx │ │ ├── __snapshots__/ │ │ │ └── FloatButton.spec.tsx.snap │ │ └── server.spec.tsx │ ├── float-button-group/ │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── FloatButtonGroup.tsx │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── NFloatButtonGroup.spec.tsx │ │ ├── __snapshots__/ │ │ │ └── NFloatButtonGroup.spec.tsx.snap │ │ └── server.spec.tsx │ ├── form/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── abnormal-warning.demo.vue │ │ │ │ ├── async.demo.vue │ │ │ │ ├── custom-messages.demo.vue │ │ │ │ ├── custom-rule.demo.vue │ │ │ │ ├── custom-validation.demo.vue │ │ │ │ ├── disabled.demo.vue │ │ │ │ ├── dynamic.demo.vue │ │ │ │ ├── feedback-style.demo.vue │ │ │ │ ├── i18n.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── inline.demo.vue │ │ │ │ ├── item-only.demo.vue │ │ │ │ ├── left.demo.vue │ │ │ │ ├── partially-apply-rules.demo.vue │ │ │ │ ├── show-label.demo.vue │ │ │ │ └── top.demo.vue │ │ │ └── zhCN/ │ │ │ ├── abnormal-warning.demo.vue │ │ │ ├── async.demo.vue │ │ │ ├── custom-messages.demo.vue │ │ │ ├── custom-rule.demo.vue │ │ │ ├── custom-validation.demo.vue │ │ │ ├── disabled.demo.vue │ │ │ ├── dynamic.demo.vue │ │ │ ├── feedback-style.demo.vue │ │ │ ├── i18n.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── inline.demo.vue │ │ │ ├── item-only.demo.vue │ │ │ ├── left.demo.vue │ │ │ ├── partially-apply-rules.demo.vue │ │ │ ├── render-feedback.demo.vue │ │ │ ├── show-label.demo.vue │ │ │ └── top.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Form.tsx │ │ │ ├── FormItem.tsx │ │ │ ├── FormItemCol.ts │ │ │ ├── FormItemGridItem.ts │ │ │ ├── FormItemRow.ts │ │ │ ├── context.ts │ │ │ ├── interface.ts │ │ │ ├── public-types.ts │ │ │ ├── styles/ │ │ │ │ ├── form-item.cssr.ts │ │ │ │ └── form.cssr.ts │ │ │ └── utils.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Form.spec.tsx │ │ └── server.spec.tsx │ ├── global-style/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ └── index.demo-entry.md │ │ │ └── zhCN/ │ │ │ └── index.demo-entry.md │ │ ├── index.ts │ │ ├── src/ │ │ │ └── GlobalStyle.ts │ │ └── tests/ │ │ └── server.spec.tsx │ ├── global.d.ts │ ├── gradient-text/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── custom.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ └── size.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── custom.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ └── size.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── GradientText.tsx │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── GradientText.spec.ts │ │ ├── __snapshots__/ │ │ │ └── GradientText.spec.ts.snap │ │ └── server.spec.tsx │ ├── grid/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── collapse.demo.vue │ │ │ │ ├── gap.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── layout-shift-disabled.demo.vue │ │ │ │ ├── offset.demo.vue │ │ │ │ ├── responsive-item.demo.vue │ │ │ │ └── responsive.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── collapse.demo.vue │ │ │ ├── gap.demo.vue │ │ │ ├── grid-basic-debug.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── layout-shift-disabled.demo.vue │ │ │ ├── offset.demo.vue │ │ │ ├── responsive-item.demo.vue │ │ │ ├── responsive.demo.vue │ │ │ └── vshow-debug.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Grid.tsx │ │ │ ├── GridItem.tsx │ │ │ └── config.ts │ │ └── tests/ │ │ ├── Grid.spec.tsx │ │ └── server.spec.tsx │ ├── heatmap/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── colors.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── slots.demo.vue │ │ │ │ └── themes.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── colors.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── slots.demo.vue │ │ │ └── themes.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── ColorIndicator.tsx │ │ │ ├── Heatmap.tsx │ │ │ ├── Rect.tsx │ │ │ ├── animationStyle.ts │ │ │ ├── interface.ts │ │ │ ├── public-types.ts │ │ │ ├── styles/ │ │ │ │ ├── index.cssr.ts │ │ │ │ └── rtl.cssr.ts │ │ │ ├── theme.ts │ │ │ └── utils/ │ │ │ └── index.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── Heatmap.spec.tsx │ │ └── server.spec.tsx │ ├── highlight/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── case-sensitive.demo.vue │ │ │ │ ├── component.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ └── style.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── case-sensitive.demo.vue │ │ │ ├── component.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ └── style.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Highlight.tsx │ │ │ ├── public-types.ts │ │ │ └── utils.ts │ │ └── tests/ │ │ └── utils.spec.ts │ ├── icon/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── custom-icon.demo.vue │ │ │ │ ├── depth.demo.vue │ │ │ │ ├── icon-wrapper.demo.vue │ │ │ │ └── index.demo-entry.md │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── custom-icon.demo.vue │ │ │ ├── depth.demo.vue │ │ │ ├── icon-wrapper.demo.vue │ │ │ └── index.demo-entry.md │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Icon.ts │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Icon.spec.ts │ │ ├── __snapshots__/ │ │ │ └── Icon.spec.ts.snap │ │ └── server.spec.tsx │ ├── icon-wrapper/ │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── IconWrapper.tsx │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── IconWrapper.spec.ts │ │ └── server.spec.tsx │ ├── image/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── component-preview-group.demo.vue │ │ │ │ ├── component-preview.demo.vue │ │ │ │ ├── custom-error.demo.vue │ │ │ │ ├── custom-toolbar.demo.vue │ │ │ │ ├── custom.demo.vue │ │ │ │ ├── error.demo.vue │ │ │ │ ├── group.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── lazy.demo.vue │ │ │ │ ├── manually-open-preview.demo.vue │ │ │ │ ├── preview-disabled.demo.vue │ │ │ │ ├── previewed-img-props.demo.vue │ │ │ │ └── tooltip.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── component-preview-group-debug.demo.vue │ │ │ ├── component-preview-group.demo.vue │ │ │ ├── component-preview.demo.vue │ │ │ ├── custom-error.demo.vue │ │ │ ├── custom-toolbar.demo.vue │ │ │ ├── custom.demo.vue │ │ │ ├── error.demo.vue │ │ │ ├── full-debug.demo.vue │ │ │ ├── group.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── lazy.demo.vue │ │ │ ├── manually-open-preview.demo.vue │ │ │ ├── preview-disabled.demo.vue │ │ │ ├── previewed-img-props.demo.vue │ │ │ └── tooltip.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Image.tsx │ │ │ ├── ImageGroup.tsx │ │ │ ├── ImagePreview.tsx │ │ │ ├── icons.tsx │ │ │ ├── interface.ts │ │ │ ├── public-types.ts │ │ │ ├── styles/ │ │ │ │ └── index.cssr.ts │ │ │ └── utils.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Image.spec.tsx │ │ └── server.spec.tsx │ ├── index.ts │ ├── infinite-scroll/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── chat.demo.vue │ │ │ │ └── index.demo-entry.md │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── chat.demo.vue │ │ │ └── index.demo-entry.md │ │ ├── index.ts │ │ └── src/ │ │ └── InfiniteScroll.tsx │ ├── input/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── autosize.demo.vue │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── clearable.demo.vue │ │ │ │ ├── count.demo.vue │ │ │ │ ├── disabled.demo.vue │ │ │ │ ├── event.demo.vue │ │ │ │ ├── focus.demo.vue │ │ │ │ ├── graphemes.demo.vue │ │ │ │ ├── icon.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── input-group.demo.vue │ │ │ │ ├── input-props.demo.vue │ │ │ │ ├── loading.demo.vue │ │ │ │ ├── pair.demo.vue │ │ │ │ ├── passively-activated.demo.vue │ │ │ │ ├── password.demo.vue │ │ │ │ ├── pattern.demo.vue │ │ │ │ ├── round.demo.vue │ │ │ │ ├── size.demo.vue │ │ │ │ ├── status.demo.vue │ │ │ │ └── trim.demo.vue │ │ │ └── zhCN/ │ │ │ ├── autosize.demo.vue │ │ │ ├── basic.demo.vue │ │ │ ├── clearable.demo.vue │ │ │ ├── count.demo.vue │ │ │ ├── disabled.demo.vue │ │ │ ├── event.demo.vue │ │ │ ├── focus.demo.vue │ │ │ ├── graphemes.demo.vue │ │ │ ├── icon-debug.demo.vue │ │ │ ├── icon.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── input-group.demo.vue │ │ │ ├── input-props.demo.vue │ │ │ ├── loading.demo.vue │ │ │ ├── modal-debug.demo.vue │ │ │ ├── pair.demo.vue │ │ │ ├── passively-activated.demo.vue │ │ │ ├── password.demo.vue │ │ │ ├── pattern.demo.vue │ │ │ ├── prefix-debug.demo.vue │ │ │ ├── round.demo.vue │ │ │ ├── rtl-debug.demo.vue │ │ │ ├── size.demo.vue │ │ │ ├── status.demo.vue │ │ │ └── textarea-resize-debug.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Input.tsx │ │ │ ├── InputGroup.tsx │ │ │ ├── InputGroupLabel.tsx │ │ │ ├── WordCount.tsx │ │ │ ├── interface.ts │ │ │ ├── public-types.ts │ │ │ ├── styles/ │ │ │ │ ├── input-group-label.cssr.ts │ │ │ │ ├── input-group.cssr.ts │ │ │ │ ├── input.cssr.ts │ │ │ │ └── rtl.cssr.ts │ │ │ └── utils.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── Input.spec.tsx │ │ ├── __snapshots__/ │ │ │ └── Input.spec.tsx.snap │ │ └── server.spec.tsx │ ├── input-number/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── button-placement.demo.vue │ │ │ │ ├── change-timing.demo.vue │ │ │ │ ├── custom-icon.demo.vue │ │ │ │ ├── disable-keyboard.demo.vue │ │ │ │ ├── disabled.demo.vue │ │ │ │ ├── event.demo.vue │ │ │ │ ├── icon.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── loading.demo.vue │ │ │ │ ├── min-max.demo.vue │ │ │ │ ├── parse.demo.vue │ │ │ │ ├── precision.demo.vue │ │ │ │ ├── show-button.demo.vue │ │ │ │ ├── size.demo.vue │ │ │ │ ├── status.demo.vue │ │ │ │ ├── step.demo.vue │ │ │ │ └── validator.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── button-placement.demo.vue │ │ │ ├── change-timing.demo.vue │ │ │ ├── custom-icon.demo.vue │ │ │ ├── debug.demo.vue │ │ │ ├── disable-keyboard.demo.vue │ │ │ ├── disabled.demo.vue │ │ │ ├── event.demo.vue │ │ │ ├── icon.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── loading.demo.vue │ │ │ ├── min-max.demo.vue │ │ │ ├── parse.demo.vue │ │ │ ├── precision-debug.demo.vue │ │ │ ├── precision.demo.vue │ │ │ ├── rtl-debug.demo.vue │ │ │ ├── show-button.demo.vue │ │ │ ├── size.demo.vue │ │ │ ├── status.demo.vue │ │ │ ├── step.demo.vue │ │ │ ├── theme-debug.demo.vue │ │ │ └── validator.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── InputNumber.tsx │ │ │ ├── interface.ts │ │ │ ├── public-types.ts │ │ │ ├── styles/ │ │ │ │ ├── input-number.cssr.ts │ │ │ │ └── rtl.cssr.ts │ │ │ └── utils.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── InputNumber.spec.tsx │ │ └── server.spec.tsx │ ├── input-otp/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── block.demo.vue │ │ │ │ ├── form.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── mask.demo.vue │ │ │ │ ├── pattern.demo.vue │ │ │ │ ├── size.demo.vue │ │ │ │ ├── status.demo.vue │ │ │ │ └── template.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── block.demo.vue │ │ │ ├── form.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── mask.demo.vue │ │ │ ├── pattern.demo.vue │ │ │ ├── rtl-debug.demo.vue │ │ │ ├── size.demo.vue │ │ │ ├── status.demo.vue │ │ │ └── template.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── InputOtp.tsx │ │ │ ├── public-types.ts │ │ │ └── styles/ │ │ │ ├── index.cssr.ts │ │ │ └── input-otp-rtl.cssr.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── InputOtp.spec.tsx │ │ └── server.spec.tsx │ ├── layout/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── absolute.demo.vue │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── border.demo.vue │ │ │ │ ├── collapse-right.demo.vue │ │ │ │ ├── collapse.demo.vue │ │ │ │ ├── embedded.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── inverted.demo.vue │ │ │ │ ├── scroll-to.demo.vue │ │ │ │ ├── scrollbar.demo.vue │ │ │ │ ├── set-padding.demo.vue │ │ │ │ └── show-sider-content.demo.vue │ │ │ └── zhCN/ │ │ │ ├── absolute.demo.vue │ │ │ ├── basic.demo.vue │ │ │ ├── border.demo.vue │ │ │ ├── collapse-right.demo.vue │ │ │ ├── collapse.demo.vue │ │ │ ├── embedded.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── inverted.demo.vue │ │ │ ├── keep-alive-debug.demo.vue │ │ │ ├── scroll-to.demo.vue │ │ │ ├── scrollbar.demo.vue │ │ │ ├── set-padding.demo.vue │ │ │ └── show-sider-content.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Layout.tsx │ │ │ ├── LayoutContent.tsx │ │ │ ├── LayoutFooter.tsx │ │ │ ├── LayoutHeader.tsx │ │ │ ├── LayoutSider.tsx │ │ │ ├── ToggleBar.tsx │ │ │ ├── ToggleButton.tsx │ │ │ ├── interface.ts │ │ │ └── styles/ │ │ │ ├── layout-footer.cssr.ts │ │ │ ├── layout-header.cssr.ts │ │ │ ├── layout-sider.cssr.ts │ │ │ └── layout.cssr.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Layout.spec.ts │ │ ├── __snapshots__/ │ │ │ └── Layout.spec.ts.snap │ │ └── server.spec.tsx │ ├── legacy-grid/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── gutter.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── offset.demo.vue │ │ │ │ └── push-pull.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── gutter.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── offset.demo.vue │ │ │ ├── push-pull.demo.vue │ │ │ ├── rtl-debug.demo.vue │ │ │ └── wrap-debug.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Col.tsx │ │ │ ├── Row.tsx │ │ │ ├── interface.ts │ │ │ └── styles/ │ │ │ ├── index.cssr.ts │ │ │ └── rtl.cssr.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── Grid.spec.tsx │ │ └── server.spec.tsx │ ├── legacy-transfer/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── filterable.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── large-data.demo.vue │ │ │ │ └── size.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── filterable.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── large-data.demo.vue │ │ │ └── size.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Transfer.tsx │ │ │ ├── TransferFilter.tsx │ │ │ ├── TransferHeader.tsx │ │ │ ├── TransferList.tsx │ │ │ ├── TransferListItem.tsx │ │ │ ├── interface.ts │ │ │ ├── styles/ │ │ │ │ └── index.cssr.ts │ │ │ └── use-transfer-data.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Transfer.spec.ts │ │ ├── __snapshots__/ │ │ │ └── Transfer.spec.ts.snap │ │ └── server.spec.tsx │ ├── list/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── border.demo.vue │ │ │ │ ├── hoverable.demo.vue │ │ │ │ └── index.demo-entry.md │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── border.demo.vue │ │ │ ├── hoverable.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ └── rtl-debug.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── List.tsx │ │ │ ├── ListItem.tsx │ │ │ └── styles/ │ │ │ ├── index.cssr.ts │ │ │ └── rtl.cssr.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── List.spec.ts │ │ └── server.spec.tsx │ ├── loading-bar/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── container.demo.vue │ │ │ │ └── index.demo-entry.md │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── container.demo.vue │ │ │ └── index.demo-entry.md │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── LoadingBar.tsx │ │ │ ├── LoadingBarProvider.tsx │ │ │ ├── context.ts │ │ │ ├── styles/ │ │ │ │ └── index.cssr.ts │ │ │ └── use-loading-bar.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ └── LoadingBar.spec.tsx │ ├── locales/ │ │ ├── __snapshots__/ │ │ │ └── index.spec.tsx.snap │ │ ├── common/ │ │ │ ├── arDZ.ts │ │ │ ├── azAZ.ts │ │ │ ├── csCZ.ts │ │ │ ├── daDK.ts │ │ │ ├── deDE.ts │ │ │ ├── enGB.ts │ │ │ ├── enUS.ts │ │ │ ├── eo.ts │ │ │ ├── esAR.ts │ │ │ ├── etEE.ts │ │ │ ├── faIR.ts │ │ │ ├── frFR.ts │ │ │ ├── idID.ts │ │ │ ├── itIT.ts │ │ │ ├── jaJP.ts │ │ │ ├── kmKH.ts │ │ │ ├── koKR.ts │ │ │ ├── nbNO.ts │ │ │ ├── nlNL.ts │ │ │ ├── plPL.ts │ │ │ ├── ptBR.ts │ │ │ ├── ruRU.ts │ │ │ ├── skSK.ts │ │ │ ├── svSE.ts │ │ │ ├── thTH.ts │ │ │ ├── trTR.ts │ │ │ ├── ugCN.ts │ │ │ ├── ukUA.ts │ │ │ ├── uzUZ.ts │ │ │ ├── viVN.ts │ │ │ ├── zhCN.ts │ │ │ └── zhTW.ts │ │ ├── date/ │ │ │ ├── arDZ.ts │ │ │ ├── azAZ.ts │ │ │ ├── csCZ.ts │ │ │ ├── daDK.ts │ │ │ ├── deDE.ts │ │ │ ├── enGB.ts │ │ │ ├── enUS.ts │ │ │ ├── eo.ts │ │ │ ├── esAR.ts │ │ │ ├── etEE.ts │ │ │ ├── faIR.ts │ │ │ ├── frFR.ts │ │ │ ├── idID.ts │ │ │ ├── itIT.ts │ │ │ ├── jaJP.ts │ │ │ ├── kmKH.ts │ │ │ ├── koKR.ts │ │ │ ├── nbNO.ts │ │ │ ├── nlNL.ts │ │ │ ├── plPL.ts │ │ │ ├── ptBR.ts │ │ │ ├── ruRU.ts │ │ │ ├── skSK.ts │ │ │ ├── svSE.ts │ │ │ ├── thTH.ts │ │ │ ├── trTR.ts │ │ │ ├── ugCN.ts │ │ │ ├── ukUA.ts │ │ │ ├── uzUZ.ts │ │ │ ├── viVN.ts │ │ │ ├── zhCN.ts │ │ │ └── zhTW.ts │ │ ├── index.spec.tsx │ │ ├── index.ts │ │ └── utils/ │ │ └── index.ts │ ├── log/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── auto-bottom.demo.vue │ │ │ │ ├── event.demo.vue │ │ │ │ ├── highlight.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── loading.demo.vue │ │ │ │ ├── scroll.demo.vue │ │ │ │ └── size.demo.vue │ │ │ └── zhCN/ │ │ │ ├── auto-bottom.demo.vue │ │ │ ├── debug.demo.vue │ │ │ ├── event.demo.vue │ │ │ ├── highlight.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── loading.demo.vue │ │ │ ├── scroll.demo.vue │ │ │ └── size.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Log.tsx │ │ │ ├── LogLine.tsx │ │ │ ├── LogLoader.tsx │ │ │ ├── context.ts │ │ │ ├── public-types.ts │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Log.spec.tsx │ │ └── server.spec.tsx │ ├── marquee/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── auto-fill.demo.vue │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── image.demo.vue │ │ │ │ └── index.demo-entry.md │ │ │ └── zhCN/ │ │ │ ├── auto-fill.demo.vue │ │ │ ├── basic.demo.vue │ │ │ ├── image.demo.vue │ │ │ └── index.demo-entry.md │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Marquee.tsx │ │ │ ├── props.ts │ │ │ ├── public-types.ts │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Marquee.spec.ts │ │ └── server.spec.tsx │ ├── mention/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── async.demo.vue │ │ │ │ ├── autosize.demo.vue │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── custom-prefix.demo.vue │ │ │ │ ├── form.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── manual-trigger.demo.vue │ │ │ │ ├── render-label.demo.vue │ │ │ │ ├── status.demo.vue │ │ │ │ └── textarea.demo.vue │ │ │ └── zhCN/ │ │ │ ├── async.demo.vue │ │ │ ├── autosize.demo.vue │ │ │ ├── basic.demo.vue │ │ │ ├── custom-prefix.demo.vue │ │ │ ├── form.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── manual-trigger.demo.vue │ │ │ ├── render-label.demo.vue │ │ │ ├── status.demo.vue │ │ │ └── textarea.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Mention.tsx │ │ │ ├── interface.ts │ │ │ ├── public-types.ts │ │ │ ├── styles/ │ │ │ │ └── index.cssr.ts │ │ │ └── utils.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Mention.spec.ts │ │ └── server.spec.tsx │ ├── menu/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── accordion.demo.vue │ │ │ │ ├── collapse.demo.vue │ │ │ │ ├── customize-field.demo.vue │ │ │ │ ├── default-expanded-keys.demo.vue │ │ │ │ ├── expand-selected-option.demo.vue │ │ │ │ ├── horizontal.demo.vue │ │ │ │ ├── indent.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── inverted.demo.vue │ │ │ │ ├── long-label.demo.vue │ │ │ │ ├── render-label.demo.vue │ │ │ │ ├── router-link.demo.vue │ │ │ │ ├── select.demo.vue │ │ │ │ └── show.demo.vue │ │ │ └── zhCN/ │ │ │ ├── accordion.demo.vue │ │ │ ├── collapse.demo.vue │ │ │ ├── customize-field.demo.vue │ │ │ ├── debug.demo.vue │ │ │ ├── default-expanded-keys.demo.vue │ │ │ ├── expand-selected-option.demo.vue │ │ │ ├── horizontal.demo.vue │ │ │ ├── indent.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── inverted.demo.vue │ │ │ ├── long-label.demo.vue │ │ │ ├── render-label.demo.vue │ │ │ ├── router-link.demo.vue │ │ │ ├── select.demo.vue │ │ │ ├── show-debug.demo.vue │ │ │ └── show.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Menu.tsx │ │ │ ├── MenuDivider.tsx │ │ │ ├── MenuOption.tsx │ │ │ ├── MenuOptionContent.tsx │ │ │ ├── MenuOptionGroup.tsx │ │ │ ├── Submenu.tsx │ │ │ ├── context.ts │ │ │ ├── interface.ts │ │ │ ├── styles/ │ │ │ │ └── index.cssr.ts │ │ │ ├── use-menu-child-props.ts │ │ │ ├── use-menu-child.ts │ │ │ ├── useCheckDeprecated.ts │ │ │ └── utils.tsx │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Menu.spec.tsx │ │ └── server.spec.tsx │ ├── message/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── about-theme.demo.vue │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── closable.demo.vue │ │ │ │ ├── customize-message.demo.vue │ │ │ │ ├── icon.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── manually-close.demo.vue │ │ │ │ ├── modify-content.demo.vue │ │ │ │ ├── multiple-line.demo.vue │ │ │ │ ├── no-icon.demo.vue │ │ │ │ ├── placement.demo.vue │ │ │ │ └── timing.demo.vue │ │ │ └── zhCN/ │ │ │ ├── about-theme.demo.vue │ │ │ ├── basic.demo.vue │ │ │ ├── closable.demo.vue │ │ │ ├── customize-message.demo.vue │ │ │ ├── icon.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── manually-close.demo.vue │ │ │ ├── modify-content.demo.vue │ │ │ ├── multiple-line.demo.vue │ │ │ ├── no-icon.demo.vue │ │ │ ├── placement.demo.vue │ │ │ ├── rtl-debug.demo.vue │ │ │ └── timing.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Message.tsx │ │ │ ├── MessageEnvironment.tsx │ │ │ ├── MessageProvider.tsx │ │ │ ├── context.ts │ │ │ ├── message-props.ts │ │ │ ├── public-types.ts │ │ │ ├── styles/ │ │ │ │ ├── index.cssr.ts │ │ │ │ └── rtl.cssr.ts │ │ │ ├── types.ts │ │ │ └── use-message.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── Message.spec.tsx │ │ └── server.spec.tsx │ ├── modal/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── content-scrollable.demo.vue │ │ │ │ ├── controlled.demo.vue │ │ │ │ ├── custom-position.demo.vue │ │ │ │ ├── draggable.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── mask-closable.demo.vue │ │ │ │ ├── mask-visible.demo.vue │ │ │ │ ├── preset-card.demo.vue │ │ │ │ ├── preset-confirm-slot.demo.vue │ │ │ │ ├── preset-confirm.demo.vue │ │ │ │ ├── reactive.demo.vue │ │ │ │ └── transform-origin.demo.vue │ │ │ └── zhCN/ │ │ │ ├── a11y-debug.demo.vue │ │ │ ├── basic.demo.vue │ │ │ ├── content-scrollable.demo.vue │ │ │ ├── controlled.demo.vue │ │ │ ├── custom-position.demo.vue │ │ │ ├── dark-1-debug.demo.vue │ │ │ ├── dark-10-debug.demo.vue │ │ │ ├── dark-2-debug.demo.vue │ │ │ ├── dark-3-debug.demo.vue │ │ │ ├── dark-4-debug.demo.vue │ │ │ ├── dark-5-debug.demo.vue │ │ │ ├── dark-6-debug.demo.vue │ │ │ ├── dark-7-debug.demo.vue │ │ │ ├── dark-8-debug.demo.vue │ │ │ ├── dark-9-debug.demo.vue │ │ │ ├── draggable.demo.vue │ │ │ ├── drawer-debug.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── mask-click-debug.demo.vue │ │ │ ├── mask-closable.demo.vue │ │ │ ├── mask-visible.demo.vue │ │ │ ├── nested-debug.demo.vue │ │ │ ├── preset-card.demo.vue │ │ │ ├── preset-confirm-slot.demo.vue │ │ │ ├── preset-confirm.demo.vue │ │ │ ├── raw-debug.demo.vue │ │ │ ├── reactive.demo.vue │ │ │ └── transform-origin.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── BodyWrapper.tsx │ │ │ ├── Modal.tsx │ │ │ ├── ModalEnvironment.tsx │ │ │ ├── ModalProvider.ts │ │ │ ├── composables.ts │ │ │ ├── context.ts │ │ │ ├── interface.ts │ │ │ ├── presetProps.ts │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Modal.spec.tsx │ │ └── server.spec.tsx │ ├── notification/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── change-content.demo.vue │ │ │ │ ├── closable.demo.vue │ │ │ │ ├── duration.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── max.demo.vue │ │ │ │ ├── placement.demo.vue │ │ │ │ ├── scrollable.demo.vue │ │ │ │ └── type.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── change-content.demo.vue │ │ │ ├── closable.demo.vue │ │ │ ├── duration.demo.vue │ │ │ ├── error-debug.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── max.demo.vue │ │ │ ├── placement.demo.vue │ │ │ ├── rtl-debug.demo.vue │ │ │ ├── scrollable.demo.vue │ │ │ └── type.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Notification.tsx │ │ │ ├── NotificationContainer.tsx │ │ │ ├── NotificationEnvironment.tsx │ │ │ ├── NotificationProvider.tsx │ │ │ ├── context.ts │ │ │ ├── styles/ │ │ │ │ ├── index.cssr.ts │ │ │ │ └── rtl.cssr.ts │ │ │ └── use-notification.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ └── Notification.spec.tsx │ ├── number-animation/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── finish.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── intl.demo.vue │ │ │ │ ├── precision.demo.vue │ │ │ │ └── separator.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── finish.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── intl.demo.vue │ │ │ ├── precision.demo.vue │ │ │ └── separator.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── NumberAnimation.tsx │ │ │ └── utils.ts │ │ └── tests/ │ │ ├── NumericAnimation.spec.ts │ │ └── server.spec.tsx │ ├── page-header/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ └── index.demo-entry.md │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ └── rtl-debug.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── PageHeader.tsx │ │ │ └── styles/ │ │ │ ├── index.cssr.ts │ │ │ └── rtl.cssr.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── PageHeader.spec.tsx │ │ └── server.spec.tsx │ ├── pagination/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── disabled.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── item-count.demo.vue │ │ │ │ ├── page-size-option.demo.vue │ │ │ │ ├── prefix.demo.vue │ │ │ │ ├── prev.demo.vue │ │ │ │ ├── quick-jumper.demo.vue │ │ │ │ ├── simple.demo.vue │ │ │ │ ├── size-picker.demo.vue │ │ │ │ ├── size.demo.vue │ │ │ │ └── slot.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── disabled.demo.vue │ │ │ ├── display-order.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── item-count.demo.vue │ │ │ ├── page-size-option.demo.vue │ │ │ ├── prefix.demo.vue │ │ │ ├── prev.demo.vue │ │ │ ├── quick-jumper.demo.vue │ │ │ ├── rtl-debug.demo.vue │ │ │ ├── simple.demo.vue │ │ │ ├── size-picker.demo.vue │ │ │ ├── size.demo.vue │ │ │ └── slot.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Pagination.tsx │ │ │ ├── interface.ts │ │ │ ├── public-types.ts │ │ │ ├── styles/ │ │ │ │ ├── index.cssr.ts │ │ │ │ └── rtl.cssr.ts │ │ │ └── utils.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── Pagination.spec.tsx │ │ ├── server.spec.tsx │ │ └── utils.spec.ts │ ├── popconfirm/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── actions.demo.vue │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── custom-action.demo.vue │ │ │ │ ├── custom-icon.demo.vue │ │ │ │ ├── event.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ └── no-icon.demo.vue │ │ │ └── zhCN/ │ │ │ ├── actions.demo.vue │ │ │ ├── basic.demo.vue │ │ │ ├── custom-action.demo.vue │ │ │ ├── custom-icon.demo.vue │ │ │ ├── event.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ └── no-icon.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Popconfirm.tsx │ │ │ ├── PopconfirmPanel.tsx │ │ │ ├── interface.ts │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Popconfirm.spec.ts │ │ └── server.spec.tsx │ ├── popover/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── delay.demo.vue │ │ │ │ ├── event.demo.vue │ │ │ │ ├── flip.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── manual-position.demo.vue │ │ │ │ ├── no-arrow.demo.vue │ │ │ │ ├── placement.demo.vue │ │ │ │ ├── raw-content.demo.vue │ │ │ │ ├── slots.demo.vue │ │ │ │ ├── style.demo.vue │ │ │ │ ├── trigger-width.demo.vue │ │ │ │ └── trigger.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── center-arrow-debug.demo.vue │ │ │ ├── delay.demo.vue │ │ │ ├── event.demo.vue │ │ │ ├── flip.demo.vue │ │ │ ├── hoist-debug.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── manual-position.demo.vue │ │ │ ├── nested-debug.demo.vue │ │ │ ├── nested2-debug.demo.vue │ │ │ ├── nested3-debug.demo.vue │ │ │ ├── no-arrow.demo.vue │ │ │ ├── placement.demo.vue │ │ │ ├── raw-content.demo.vue │ │ │ ├── rtl-debug.demo.vue │ │ │ ├── slots.demo.vue │ │ │ ├── style.demo.vue │ │ │ ├── trigger-width.demo.vue │ │ │ ├── trigger.demo.vue │ │ │ ├── width-debug.demo.vue │ │ │ └── zindex-debug.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Popover.tsx │ │ │ ├── PopoverBody.tsx │ │ │ ├── interface.ts │ │ │ └── styles/ │ │ │ ├── index.cssr.ts │ │ │ └── rtl.cssr.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── Popover.spec.tsx │ │ └── server.spec.tsx │ ├── popselect/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── cancelable.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── multiple.demo.vue │ │ │ │ ├── scrollable.demo.vue │ │ │ │ ├── size.demo.vue │ │ │ │ └── slot.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── cancelable.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── multiple.demo.vue │ │ │ ├── scrollable.demo.vue │ │ │ ├── size.demo.vue │ │ │ └── slot.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Popselect.tsx │ │ │ ├── PopselectPanel.tsx │ │ │ ├── interface.ts │ │ │ ├── public-types.ts │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Popselect.spec.ts │ │ └── server.spec.tsx │ ├── preset.ts │ ├── progress/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── circle-offset.demo.vue │ │ │ │ ├── circle.demo.vue │ │ │ │ ├── color.demo.vue │ │ │ │ ├── custom-indicator.demo.vue │ │ │ │ ├── dashboard.demo.vue │ │ │ │ ├── gradient.demo.vue │ │ │ │ ├── height.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── line.demo.vue │ │ │ │ ├── multiple-circle.demo.vue │ │ │ │ ├── no-indicator.demo.vue │ │ │ │ └── processing.demo.vue │ │ │ └── zhCN/ │ │ │ ├── circle-offset.demo.vue │ │ │ ├── circle.demo.vue │ │ │ ├── color.demo.vue │ │ │ ├── custom-indicator.demo.vue │ │ │ ├── dashboard.demo.vue │ │ │ ├── gradient.demo.vue │ │ │ ├── height.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── line.demo.vue │ │ │ ├── multiple-circle.demo.vue │ │ │ ├── no-indicator.demo.vue │ │ │ └── processing.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Circle.tsx │ │ │ ├── Line.tsx │ │ │ ├── MultipleCircle.tsx │ │ │ ├── Progress.tsx │ │ │ ├── public-types.ts │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Progress.spec.ts │ │ └── server.spec.tsx │ ├── qr-code/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── color.demo.vue │ │ │ │ ├── download.demo.vue │ │ │ │ ├── error-correction.demo.vue │ │ │ │ ├── icon.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── size.demo.vue │ │ │ │ └── type.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── color.demo.vue │ │ │ ├── download.demo.vue │ │ │ ├── error-correction.demo.vue │ │ │ ├── icon.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── size.demo.vue │ │ │ └── type.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── QrCode.tsx │ │ │ ├── qrcodegen.ts │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ └── styles/ │ │ ├── dark.ts │ │ ├── index.ts │ │ └── light.ts │ ├── radio/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── button-group.demo.vue │ │ │ │ ├── group.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ └── size.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── button-group.demo.vue │ │ │ ├── group.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── radio-focus-debug.demo.vue │ │ │ ├── rtl-debug.demo.vue │ │ │ ├── size.demo.vue │ │ │ ├── tooltip-debug.demo.vue │ │ │ └── uncontrolled-debug.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Radio.tsx │ │ │ ├── RadioButton.tsx │ │ │ ├── RadioGroup.tsx │ │ │ ├── interface.ts │ │ │ ├── public-types.ts │ │ │ ├── styles/ │ │ │ │ ├── radio-group.cssr.ts │ │ │ │ ├── radio.cssr.ts │ │ │ │ └── rtl.cssr.ts │ │ │ └── use-radio.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── Radio.spec.ts │ │ ├── __snapshots__/ │ │ │ └── Radio.spec.ts.snap │ │ └── server.spec.tsx │ ├── rate/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── allow-half.demo.vue │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── clearable.demo.vue │ │ │ │ ├── color.demo.vue │ │ │ │ ├── icon.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── readonly.demo.vue │ │ │ │ └── size.demo.vue │ │ │ └── zhCN/ │ │ │ ├── allow-half.demo.vue │ │ │ ├── basic.demo.vue │ │ │ ├── clearable.demo.vue │ │ │ ├── color.demo.vue │ │ │ ├── icon.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── readonly.demo.vue │ │ │ └── size.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Rate.tsx │ │ │ ├── StarIcon.tsx │ │ │ ├── interface.ts │ │ │ ├── public-types.ts │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Rate.spec.ts │ │ ├── __snapshots__/ │ │ │ └── Rate.spec.ts.snap │ │ └── server.spec.tsx │ ├── result/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── custom.demo.vue │ │ │ │ ├── error.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── info.demo.vue │ │ │ │ ├── s-403.demo.vue │ │ │ │ ├── s-404.demo.vue │ │ │ │ ├── s-418.demo.vue │ │ │ │ ├── s-500.demo.vue │ │ │ │ ├── size.demo.vue │ │ │ │ ├── success.demo.vue │ │ │ │ └── warning.demo.vue │ │ │ └── zhCN/ │ │ │ ├── custom.demo.vue │ │ │ ├── error.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── info.demo.vue │ │ │ ├── s-403.demo.vue │ │ │ ├── s-404.demo.vue │ │ │ ├── s-418.demo.vue │ │ │ ├── s-500.demo.vue │ │ │ ├── size.demo.vue │ │ │ ├── success.demo.vue │ │ │ └── warning.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── 403.tsx │ │ │ ├── 404.tsx │ │ │ ├── 418.tsx │ │ │ ├── 500.tsx │ │ │ ├── Result.tsx │ │ │ ├── public-types.ts │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Result.spec.ts │ │ ├── __snapshots__/ │ │ │ └── Result.spec.ts.snap │ │ └── server.spec.tsx │ ├── scrollbar/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── custom.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── no-sync.demo.vue │ │ │ │ ├── placement.demo.vue │ │ │ │ ├── trigger.demo.vue │ │ │ │ └── x.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── custom.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── no-sync.demo.vue │ │ │ ├── placement.demo.vue │ │ │ ├── rtl-debug.demo.vue │ │ │ ├── trigger.demo.vue │ │ │ └── x.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ └── Scrollbar.tsx │ │ └── tests/ │ │ ├── Scrollbar.spec.ts │ │ └── server.spec.tsx │ ├── select/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── action.demo.vue │ │ │ │ ├── add-tooltip.demo.vue │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── clearable.demo.vue │ │ │ │ ├── custom-field.demo.vue │ │ │ │ ├── custom-option.demo.vue │ │ │ │ ├── custom-suffix.demo.vue │ │ │ │ ├── events.demo.vue │ │ │ │ ├── fallback-option.demo.vue │ │ │ │ ├── filterable.demo.vue │ │ │ │ ├── focus.demo.vue │ │ │ │ ├── group.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── many-options.demo.vue │ │ │ │ ├── max-tag-count.demo.vue │ │ │ │ ├── menu-width.demo.vue │ │ │ │ ├── multiple.demo.vue │ │ │ │ ├── remote-multiple.demo.vue │ │ │ │ ├── remote.demo.vue │ │ │ │ ├── render-person.demo.vue │ │ │ │ ├── render-tag.demo.vue │ │ │ │ ├── scroll-event.demo.vue │ │ │ │ ├── size.demo.vue │ │ │ │ ├── status.demo.vue │ │ │ │ ├── tag-input.demo.vue │ │ │ │ └── tag.demo.vue │ │ │ └── zhCN/ │ │ │ ├── action.demo.vue │ │ │ ├── add-tooltip.demo.vue │ │ │ ├── basic.demo.vue │ │ │ ├── clearable.demo.vue │ │ │ ├── create-debug.demo.vue │ │ │ ├── custom-field.demo.vue │ │ │ ├── custom-option.demo.vue │ │ │ ├── custom-suffix.demo.vue │ │ │ ├── empty-debug.demo.vue │ │ │ ├── events.demo.vue │ │ │ ├── fallback-option.demo.vue │ │ │ ├── filterable-debug.demo.vue │ │ │ ├── filterable.demo.vue │ │ │ ├── focus.demo.vue │ │ │ ├── fullscreen-debug.demo.vue │ │ │ ├── group.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── many-options.demo.vue │ │ │ ├── max-tag-count.demo.vue │ │ │ ├── menu-debug.demo.vue │ │ │ ├── menu-width.demo.vue │ │ │ ├── multiple.demo.vue │ │ │ ├── options-change-debug.demo.vue │ │ │ ├── placeholder-debug.demo.vue │ │ │ ├── remote-multiple.demo.vue │ │ │ ├── remote.demo.vue │ │ │ ├── render-debug.demo.vue │ │ │ ├── render-person.demo.vue │ │ │ ├── render-tag.demo.vue │ │ │ ├── rtl-debug.demo.vue │ │ │ ├── scroll-event.demo.vue │ │ │ ├── size.demo.vue │ │ │ ├── spin-debug.demo.vue │ │ │ ├── status.demo.vue │ │ │ ├── tag-input.demo.vue │ │ │ └── tag.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Select.tsx │ │ │ ├── interface.ts │ │ │ ├── public-types.ts │ │ │ ├── styles/ │ │ │ │ └── index.cssr.ts │ │ │ └── utils.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── Select.spec.tsx │ │ ├── __snapshots__/ │ │ │ └── Select.spec.tsx.snap │ │ └── server.spec.tsx │ ├── shims-vue.d.ts │ ├── skeleton/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── box.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ └── size.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── box.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ └── size.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Skeleton.tsx │ │ │ ├── bug.md │ │ │ ├── public-types.ts │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Skeleton.spec.tsx │ │ ├── __snapshots__/ │ │ │ └── Skeleton.spec.tsx.snap │ │ └── server.spec.tsx │ ├── slider/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── custom-marks.demo.vue │ │ │ │ ├── custom-thumb.demo.vue │ │ │ │ ├── disable-tooltip.demo.vue │ │ │ │ ├── disabled.demo.vue │ │ │ │ ├── format.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── mark.demo.vue │ │ │ │ ├── multiple-debug.demo.vue │ │ │ │ ├── range.demo.vue │ │ │ │ ├── restrict-selectable-values.demo.vue │ │ │ │ ├── reverse.demo.vue │ │ │ │ ├── show-tooltip.demo.vue │ │ │ │ └── vertical.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── custom-marks.demo.vue │ │ │ ├── custom-thumb.demo.vue │ │ │ ├── disable-tooltip.demo.vue │ │ │ ├── disabled.demo.vue │ │ │ ├── format.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── keyboard-debug.demo.vue │ │ │ ├── mark.demo.vue │ │ │ ├── multiple-debug.demo.vue │ │ │ ├── range.demo.vue │ │ │ ├── restrict-selectable-values.demo.vue │ │ │ ├── reverse.demo.vue │ │ │ ├── show-tooltip.demo.vue │ │ │ └── vertical.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Slider.tsx │ │ │ ├── interface.ts │ │ │ ├── styles/ │ │ │ │ └── index.cssr.ts │ │ │ └── utils.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Slider.spec.ts │ │ └── server.spec.tsx │ ├── space/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── center.demo.vue │ │ │ │ ├── from-end.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── reverse.demo.vue │ │ │ │ ├── space-around.demo.vue │ │ │ │ ├── space-between.demo.vue │ │ │ │ └── vertical.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── center.demo.vue │ │ │ ├── from-end.demo.vue │ │ │ ├── grid-debug.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── reverse.demo.vue │ │ │ ├── rtl-debug.demo.vue │ │ │ ├── space-around.demo.vue │ │ │ ├── space-between.demo.vue │ │ │ └── vertical.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Space.tsx │ │ │ ├── public-types.ts │ │ │ ├── styles/ │ │ │ │ └── rtl.cssr.ts │ │ │ └── utils.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── Space.spec.tsx │ │ ├── __snapshots__/ │ │ │ └── Space.spec.tsx.snap │ │ └── server.spec.tsx │ ├── spin/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── customize-icon.demo.vue │ │ │ │ ├── delay.demo.vue │ │ │ │ ├── description.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ └── wrap.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── blocking-debug.demo.vue │ │ │ ├── customize-icon.demo.vue │ │ │ ├── delay.demo.vue │ │ │ ├── description.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ └── wrap.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Spin.tsx │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Spin.spec.ts │ │ ├── __snapshots__/ │ │ │ └── Spin.spec.ts.snap │ │ └── server.spec.tsx │ ├── split/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── controlled.demo.vue │ │ │ │ ├── event.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── nest.demo.vue │ │ │ │ ├── slot.demo.vue │ │ │ │ └── vertical.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── controlled.demo.vue │ │ │ ├── event.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── nest.demo.vue │ │ │ ├── pixel-value.demo.vue │ │ │ ├── slot.demo.vue │ │ │ └── vertical.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Split.tsx │ │ │ ├── styles/ │ │ │ │ └── index.cssr.ts │ │ │ └── types.ts │ │ └── styles/ │ │ ├── dark.ts │ │ ├── index.ts │ │ └── light.ts │ ├── statistic/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ └── index.demo-entry.md │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ └── rtl-debug.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Statistic.tsx │ │ │ └── styles/ │ │ │ ├── index.cssr.ts │ │ │ └── rtl.cssr.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── Statistic.spec.ts │ │ └── server.spec.tsx │ ├── steps/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── click.demo.vue │ │ │ │ ├── content-placement.demo.vue │ │ │ │ ├── content.demo.vue │ │ │ │ ├── custom-icon.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── size.demo.vue │ │ │ │ └── vertical.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── click.demo.vue │ │ │ ├── content-placement.demo.vue │ │ │ ├── content.demo.vue │ │ │ ├── custom-icon.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── rtl-debug.demo.vue │ │ │ ├── size.demo.vue │ │ │ ├── vertical-debug.demo.vue │ │ │ └── vertical.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Step.tsx │ │ │ ├── Steps.tsx │ │ │ └── styles/ │ │ │ ├── index.cssr.ts │ │ │ └── rtl.cssr.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── Steps.spec.ts │ │ └── server.spec.tsx │ ├── styles.ts │ ├── switch/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── color.demo.vue │ │ │ │ ├── content.demo.vue │ │ │ │ ├── customize-value.demo.vue │ │ │ │ ├── event.demo.vue │ │ │ │ ├── icon.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── loading.demo.vue │ │ │ │ ├── shape.demo.vue │ │ │ │ └── size.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── color.demo.vue │ │ │ ├── content.demo.vue │ │ │ ├── customize-value.demo.vue │ │ │ ├── event.demo.vue │ │ │ ├── icon.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── loading.demo.vue │ │ │ ├── shape.demo.vue │ │ │ └── size.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Switch.tsx │ │ │ ├── interface.ts │ │ │ ├── public-types.ts │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Switch.spec.tsx │ │ ├── __snapshots__/ │ │ │ └── Switch.spec.tsx.snap │ │ └── server.spec.tsx │ ├── table/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── bordered.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── single-column.demo.vue │ │ │ │ ├── single-line.demo.vue │ │ │ │ ├── size.demo.vue │ │ │ │ └── striped.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── bordered.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── rtl-debug.demo.vue │ │ │ ├── single-column.demo.vue │ │ │ ├── single-line.demo.vue │ │ │ ├── size.demo.vue │ │ │ └── striped.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Table.tsx │ │ │ ├── Tbody.tsx │ │ │ ├── Td.tsx │ │ │ ├── Th.tsx │ │ │ ├── Thead.tsx │ │ │ ├── Tr.tsx │ │ │ ├── public-types.ts │ │ │ └── styles/ │ │ │ ├── index.cssr.ts │ │ │ └── rtl.cssr.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── Table.spec.ts │ │ ├── __snapshots__/ │ │ │ └── Table.spec.ts.snap │ │ └── server.spec.tsx │ ├── tabs/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── addable.demo.vue │ │ │ │ ├── bar-width.demo.vue │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── before-leave.demo.vue │ │ │ │ ├── card.demo.vue │ │ │ │ ├── display-directive.demo.vue │ │ │ │ ├── flex-label.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── no-pane.demo.vue │ │ │ │ ├── placement.demo.vue │ │ │ │ ├── prefix.demo.vue │ │ │ │ ├── segment.demo.vue │ │ │ │ ├── size.demo.vue │ │ │ │ ├── trigger.demo.vue │ │ │ │ └── update-bar-manually.demo.vue │ │ │ └── zhCN/ │ │ │ ├── addable-debug.demo.vue │ │ │ ├── addable.demo.vue │ │ │ ├── animation-debug.demo.vue │ │ │ ├── animationx-debug.demo.vue │ │ │ ├── bar-width.demo.vue │ │ │ ├── basic.demo.vue │ │ │ ├── before-leave.demo.vue │ │ │ ├── card.demo.vue │ │ │ ├── display-directive.demo.vue │ │ │ ├── flex-label.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── line-debug.demo.vue │ │ │ ├── modal-debug.demo.vue │ │ │ ├── no-pane.demo.vue │ │ │ ├── none-debug.demo.vue │ │ │ ├── placement.demo.vue │ │ │ ├── prefix.demo.vue │ │ │ ├── segment.demo.vue │ │ │ ├── shadow-debug.demo.vue │ │ │ ├── size.demo.vue │ │ │ ├── style-inherit-debug.demo.vue │ │ │ ├── trigger.demo.vue │ │ │ ├── unkeyed-debug.demo.vue │ │ │ └── update-bar-manually.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Tab.tsx │ │ │ ├── TabPane.tsx │ │ │ ├── Tabs.tsx │ │ │ ├── interface.ts │ │ │ ├── public-types.ts │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Tabs.spec.tsx │ │ ├── __snapshots__/ │ │ │ └── Tabs.spec.tsx.snap │ │ └── server.spec.tsx │ ├── tag/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── avatar.demo.vue │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── bordered.demo.vue │ │ │ │ ├── checkable.demo.vue │ │ │ │ ├── closable.demo.vue │ │ │ │ ├── color.demo.vue │ │ │ │ ├── disabled.demo.vue │ │ │ │ ├── icon.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── shape.demo.vue │ │ │ │ └── size.demo.vue │ │ │ └── zhCN/ │ │ │ ├── avatar.demo.vue │ │ │ ├── basic.demo.vue │ │ │ ├── bordered.demo.vue │ │ │ ├── checkable.demo.vue │ │ │ ├── closable.demo.vue │ │ │ ├── color.demo.vue │ │ │ ├── disabled.demo.vue │ │ │ ├── icon.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── rtl-debug.demo.vue │ │ │ ├── shape.demo.vue │ │ │ └── size.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Tag.tsx │ │ │ ├── common-props.ts │ │ │ ├── public-types.ts │ │ │ └── styles/ │ │ │ ├── index.cssr.ts │ │ │ └── rtl.cssr.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── Tag.spec.ts │ │ ├── __snapshots__/ │ │ │ └── Tag.spec.ts.snap │ │ └── server.spec.tsx │ ├── theme-editor/ │ │ ├── index.ts │ │ └── src/ │ │ ├── MaximizeIcon.tsx │ │ ├── MinimizeIcon.tsx │ │ └── ThemeEditor.tsx │ ├── themes/ │ │ ├── dark.ts │ │ ├── index.ts │ │ ├── interface.ts │ │ ├── light.ts │ │ └── utils.ts │ ├── thing/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── indent.demo.vue │ │ │ │ └── index.demo-entry.md │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── indent.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ └── rtl-debug.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Thing.tsx │ │ │ └── styles/ │ │ │ ├── index.cssr.ts │ │ │ └── rtl.cssr.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── Thing.spec.ts │ │ └── server.spec.tsx │ ├── time/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── format.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── relative.demo.vue │ │ │ │ ├── timezone.demo.vue │ │ │ │ ├── type.demo.vue │ │ │ │ └── unix.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── format.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── relative.demo.vue │ │ │ ├── timezone-debug.demo.vue │ │ │ ├── timezone.demo.vue │ │ │ ├── type.demo.vue │ │ │ └── unix.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ └── Time.ts │ │ └── tests/ │ │ ├── Time.spec.ts │ │ └── server.spec.tsx │ ├── time-picker/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── actions.demo.vue │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── confirm.demo.vue │ │ │ │ ├── disabled-time.demo.vue │ │ │ │ ├── focus.demo.vue │ │ │ │ ├── format.demo.vue │ │ │ │ ├── formatted.demo.vue │ │ │ │ ├── hours12.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── size.demo.vue │ │ │ │ ├── status.demo.vue │ │ │ │ ├── step-time.demo.vue │ │ │ │ └── timezone.demo.vue │ │ │ └── zhCN/ │ │ │ ├── actions.demo.vue │ │ │ ├── basic.demo.vue │ │ │ ├── confirm.demo.vue │ │ │ ├── disabled-time.demo.vue │ │ │ ├── focus.demo.vue │ │ │ ├── format.demo.vue │ │ │ ├── formatted.demo.vue │ │ │ ├── hours12.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── size.demo.vue │ │ │ ├── status.demo.vue │ │ │ ├── step-time.demo.vue │ │ │ ├── timezone-debug.demo.vue │ │ │ └── timezone.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Panel.tsx │ │ │ ├── PanelCol.tsx │ │ │ ├── TimePicker.tsx │ │ │ ├── interface.ts │ │ │ ├── public-types.ts │ │ │ ├── styles/ │ │ │ │ └── index.cssr.ts │ │ │ └── utils.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── TimePicker.spec.ts │ │ ├── __snapshots__/ │ │ │ └── TimePicker.spec.ts.snap │ │ └── server.spec.tsx │ ├── timeline/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── customize-icon.demo.vue │ │ │ │ ├── horizontal.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── item-placement.demo.vue │ │ │ │ └── size.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── customize-icon.demo.vue │ │ │ ├── horizontal.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── item-placement.demo.vue │ │ │ ├── nested-debug.demo.vue │ │ │ └── size.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Timeline.tsx │ │ │ ├── TimelineItem.tsx │ │ │ └── styles/ │ │ │ └── index.cssr.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Timeline.spec.ts │ │ └── server.spec.tsx │ ├── tooltip/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── arrow.demo.vue │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── body-style.demo.vue │ │ │ │ ├── event.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── placement.demo.vue │ │ │ │ └── trigger.demo.vue │ │ │ └── zhCN/ │ │ │ ├── arrow.demo.vue │ │ │ ├── basic.demo.vue │ │ │ ├── body-style.demo.vue │ │ │ ├── event.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── placement.demo.vue │ │ │ ├── rtl-debug.demo.vue │ │ │ └── trigger.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ └── Tooltip.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Tooltip.spec.ts │ │ └── server.spec.tsx │ ├── transfer/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── filterable.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── large-data.demo.vue │ │ │ │ ├── render-label.demo.vue │ │ │ │ ├── render-source-list.demo.vue │ │ │ │ └── size.demo.vue │ │ │ └── zhCN/ │ │ │ ├── asynchronous-options-debug.demo.vue │ │ │ ├── basic.demo.vue │ │ │ ├── filterable.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── large-data.demo.vue │ │ │ ├── render-label.demo.vue │ │ │ ├── render-source-list.demo.vue │ │ │ ├── size.demo.vue │ │ │ └── value-debug.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Transfer.tsx │ │ │ ├── TransferFilter.tsx │ │ │ ├── TransferHeader.tsx │ │ │ ├── TransferList.tsx │ │ │ ├── TransferListItem.tsx │ │ │ ├── interface.ts │ │ │ ├── public-types.ts │ │ │ ├── styles/ │ │ │ │ └── index.cssr.ts │ │ │ └── use-transfer-data.ts │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Transfer.spec.ts │ │ ├── __snapshots__/ │ │ │ └── Transfer.spec.ts.snap │ │ └── server.spec.tsx │ ├── tree/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── async.demo.vue │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── batch-render.demo.vue │ │ │ │ ├── cascade.demo.vue │ │ │ │ ├── checkbox-placement.demo.vue │ │ │ │ ├── custom-field.demo.vue │ │ │ │ ├── disabled.demo.vue │ │ │ │ ├── drag-drop.demo.vue │ │ │ │ ├── file-tree.demo.vue │ │ │ │ ├── filter.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── multiple.demo.vue │ │ │ │ ├── node-props.demo.vue │ │ │ │ ├── override-click-behavior.demo.vue │ │ │ │ ├── prefix-and-suffix.demo.vue │ │ │ │ ├── show-line.demo.vue │ │ │ │ ├── switcher-icon.demo.vue │ │ │ │ └── virtual.demo.vue │ │ │ └── zhCN/ │ │ │ ├── async.demo.vue │ │ │ ├── basic.demo.vue │ │ │ ├── batch-render.demo.vue │ │ │ ├── cascade.demo.vue │ │ │ ├── change-debug.demo.vue │ │ │ ├── check-strategy-debug.demo.vue │ │ │ ├── checkbox-placement.demo.vue │ │ │ ├── custom-field.demo.vue │ │ │ ├── disabled.demo.vue │ │ │ ├── drag-drop.demo.vue │ │ │ ├── ellipsis.demo.vue │ │ │ ├── expand-debug.demo.vue │ │ │ ├── file-tree.demo.vue │ │ │ ├── filter.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── multiple.demo.vue │ │ │ ├── node-props.demo.vue │ │ │ ├── override-click-behavior.demo.vue │ │ │ ├── prefix-and-suffix.demo.vue │ │ │ ├── rtl-debug.demo.vue │ │ │ ├── scroll-debug.demo.vue │ │ │ ├── scrollbar-debug.demo.vue │ │ │ ├── show-line.demo.vue │ │ │ ├── switcher-icon.demo.vue │ │ │ └── virtual.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── MotionWrapper.tsx │ │ │ ├── Tree.tsx │ │ │ ├── TreeNode.tsx │ │ │ ├── TreeNodeCheckbox.tsx │ │ │ ├── TreeNodeContent.tsx │ │ │ ├── TreeNodeSwitcher.tsx │ │ │ ├── dnd.tsx │ │ │ ├── interface.ts │ │ │ ├── keyboard.tsx │ │ │ ├── public-types.ts │ │ │ ├── styles/ │ │ │ │ ├── index.cssr.ts │ │ │ │ └── rtl.cssr.ts │ │ │ └── utils.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── Tree.spec.ts │ │ └── server.spec.tsx │ ├── tree-select/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── action.demo.vue │ │ │ │ ├── async.demo.vue │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── check-strategy.demo.vue │ │ │ │ ├── checkbox.demo.vue │ │ │ │ ├── custom-field.demo.vue │ │ │ │ ├── debug.demo.vue │ │ │ │ ├── file-picker.demo.vue │ │ │ │ ├── filterable.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── multiple.demo.vue │ │ │ │ ├── show-line.demo.vue │ │ │ │ └── status.demo.vue │ │ │ └── zhCN/ │ │ │ ├── action.demo.vue │ │ │ ├── async.demo.vue │ │ │ ├── basic.demo.vue │ │ │ ├── check-strategy-debug.demo.vue │ │ │ ├── check-strategy.demo.vue │ │ │ ├── checkbox.demo.vue │ │ │ ├── custom-field.demo.vue │ │ │ ├── debug.demo.vue │ │ │ ├── field-search-debug.demo.vue │ │ │ ├── file-picker.demo.vue │ │ │ ├── filterable.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── multiple.demo.vue │ │ │ ├── render-debug.demo.vue │ │ │ ├── rtl-debug.demo.vue │ │ │ ├── show-line.demo.vue │ │ │ └── status.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── TreeSelect.tsx │ │ │ ├── interface.ts │ │ │ ├── public-types.ts │ │ │ ├── styles/ │ │ │ │ └── index.cssr.ts │ │ │ └── utils.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── TreeSelect.spec.ts │ │ └── server.spec.tsx │ ├── tsconfig.cjs.json │ ├── tsconfig.demo.json │ ├── tsconfig.esm.json │ ├── typography/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── header.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── router-link.demo.vue │ │ │ │ ├── tags.demo.vue │ │ │ │ └── text.demo.vue │ │ │ └── zhCN/ │ │ │ ├── header.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── router-link.demo.vue │ │ │ ├── tags.demo.vue │ │ │ └── text.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── a.tsx │ │ │ ├── blockquote.tsx │ │ │ ├── create-header.ts │ │ │ ├── headers.ts │ │ │ ├── hr.tsx │ │ │ ├── li.tsx │ │ │ ├── ol.tsx │ │ │ ├── p.tsx │ │ │ ├── styles/ │ │ │ │ ├── a.cssr.ts │ │ │ │ ├── blockquote.cssr.ts │ │ │ │ ├── header.cssr.ts │ │ │ │ ├── hr.cssr.ts │ │ │ │ ├── list.cssr.ts │ │ │ │ ├── p.cssr.ts │ │ │ │ └── text.cssr.ts │ │ │ ├── text.tsx │ │ │ └── ul.tsx │ │ ├── styles/ │ │ │ ├── _common.ts │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ └── light.ts │ │ └── tests/ │ │ ├── Typography.spec.ts │ │ ├── __snapshots__/ │ │ │ └── Typography.spec.ts.snap │ │ └── server.spec.tsx │ ├── upload/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── abstract.demo.vue │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── before-upload.demo.vue │ │ │ │ ├── controlled.demo.vue │ │ │ │ ├── custom-download.demo.vue │ │ │ │ ├── custom-request.demo.vue │ │ │ │ ├── default-files.demo.vue │ │ │ │ ├── download.demo.vue │ │ │ │ ├── drag.demo.vue │ │ │ │ ├── image-card-style.demo.vue │ │ │ │ ├── image-style.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── on-finish.demo.vue │ │ │ │ └── submit-manually.demo.vue │ │ │ └── zhCN/ │ │ │ ├── abstract.demo.vue │ │ │ ├── basic.demo.vue │ │ │ ├── before-upload.demo.vue │ │ │ ├── controlled.demo.vue │ │ │ ├── custom-download.demo.vue │ │ │ ├── custom-request.demo.vue │ │ │ ├── debug.demo.vue │ │ │ ├── default-files.demo.vue │ │ │ ├── download.demo.vue │ │ │ ├── drag.demo.vue │ │ │ ├── image-card-style.demo.vue │ │ │ ├── image-style.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── on-finish.demo.vue │ │ │ ├── rtl-debug.demo.vue │ │ │ └── submit-manually.demo.vue │ │ ├── index.ts │ │ ├── src/ │ │ │ ├── Upload.tsx │ │ │ ├── UploadDragger.tsx │ │ │ ├── UploadFile.tsx │ │ │ ├── UploadFileList.tsx │ │ │ ├── UploadProgress.tsx │ │ │ ├── UploadTrigger.tsx │ │ │ ├── icons.tsx │ │ │ ├── interface.ts │ │ │ ├── public-types.ts │ │ │ ├── styles/ │ │ │ │ ├── index.cssr.ts │ │ │ │ └── rtl.cssr.ts │ │ │ └── utils.ts │ │ ├── styles/ │ │ │ ├── dark.ts │ │ │ ├── index.ts │ │ │ ├── light.ts │ │ │ └── rtl.ts │ │ └── tests/ │ │ ├── Upload.spec.tsx │ │ └── server.spec.tsx │ ├── version.ts │ ├── virtual-list/ │ │ ├── demos/ │ │ │ ├── enUS/ │ │ │ │ ├── basic.demo.vue │ │ │ │ ├── dynamic-size.demo.vue │ │ │ │ ├── index.demo-entry.md │ │ │ │ ├── keep-alive.demo.vue │ │ │ │ └── scroll.demo.vue │ │ │ └── zhCN/ │ │ │ ├── basic.demo.vue │ │ │ ├── dynamic-size.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ ├── keep-alive.demo.vue │ │ │ └── scroll.demo.vue │ │ ├── index.ts │ │ └── src/ │ │ └── VirtualList.tsx │ ├── vitest-setup.ts │ └── watermark/ │ ├── demos/ │ │ ├── enUS/ │ │ │ ├── basic.demo.vue │ │ │ ├── custom.demo.vue │ │ │ ├── fullscreen.demo.vue │ │ │ ├── image.demo.vue │ │ │ ├── index.demo-entry.md │ │ │ └── multiline.demo.vue │ │ └── zhCN/ │ │ ├── basic.demo.vue │ │ ├── custom.demo.vue │ │ ├── fullscreen.demo.vue │ │ ├── image.demo.vue │ │ ├── index.demo-entry.md │ │ └── multiline.demo.vue │ ├── index.ts │ ├── src/ │ │ ├── Watermark.tsx │ │ └── styles/ │ │ └── index.cssr.ts │ ├── styles/ │ │ ├── dark.ts │ │ ├── index.ts │ │ └── light.ts │ └── tests/ │ ├── Watermark.spec.ts │ └── server.spec.tsx ├── test.html ├── themes/ │ └── tusimple/ │ ├── .npmignore │ ├── README.md │ ├── package.json │ ├── src/ │ │ ├── TsConfigProvider.tsx │ │ ├── data-table.tsx │ │ ├── icons.tsx │ │ ├── index.ts │ │ ├── theme-overrides-dark.ts │ │ ├── theme-overrides-light.ts │ │ ├── unconfigurable-style-dark.ts │ │ ├── unconfigurable-style-light.ts │ │ ├── use-ts-dialog.ts │ │ ├── use-ts-message.ts │ │ └── vars.ts │ ├── tsconfig.cjs.json │ └── tsconfig.esm.json ├── tsconfig.cjs.json ├── tsconfig.esbuild.json ├── tsconfig.esm.json ├── tsconfig.json ├── tsconfig.scripts.json ├── tsconfig.test.json ├── umd-test/ │ ├── index.spec.js │ └── setupVue.js ├── vite.config.mts ├── volar.d.ts └── vue3.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms # github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] # patreon: # Replace with a single Patreon username # open_collective: # Replace with a single Open Collective username # ko_fi: # Replace with a single Ko-fi username # tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel # community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry # liberapay: # Replace with a single Liberapay username # issuehunt: # Replace with a single IssueHunt username # otechie: # Replace with a single Otechie username # custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.yml ================================================ name: 🐞 Bug report description: Report an issue with naive-ui labels: [untriaged] body: - type: markdown attributes: value: | Thanks for taking the time to fill out this bug report! - type: textarea id: bug-description attributes: label: Describe the bug description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us in the description. Thanks! placeholder: Bug description validations: required: true - type: textarea id: steps attributes: label: Steps to reproduce description: Clear and concise steps to reproduce this bug. placeholder: | 1. apply magic 2. wait 3.1415 seconds 3. 🧙🏽‍♂️ validations: required: true - type: input id: reproduction attributes: label: Link to minimal reproduction description: Please provide a [minimal reproduction](https://stackoverflow.com/help/minimal-reproducible-example). Provide a streamlined [Playground](https://play-naive.pro-components.cn)/CodePen/CodeSandbox or GitHub repository link. Please don't fill in a link randomly. placeholder: Reproduction validations: required: true - type: textarea id: system-info attributes: label: System Info description: Output of `npx envinfo --system --npmPackages 'naive-ui,vue' --binaries --browsers` render: Shell placeholder: System, Binaries, Browsers validations: required: true - type: dropdown id: package-manager attributes: label: Used Package Manager description: Select the used package manager options: - npm - yarn - pnpm validations: required: true - type: checkboxes id: checkboxes attributes: label: Validations description: Before submitting the issue, please make sure you do the following options: - label: Read the [Contributing Guidelines](https://github.com/tusen-ai/naive-ui/blob/main/CONTRIBUTING.md). required: true - label: Read the [docs](https://www.naiveui.com/en-US/). required: true - label: Check that there isn't [already an issue](https://github.com/tusen-ai/naive-ui/issues) that reports the same bug to avoid creating a duplicate. required: true - label: Check that this is a concrete bug. For Q&A open a [GitHub Discussion](https://github.com/tusen-ai/naive-ui/discussions). required: true - label: The provided reproduction is a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) of the bug. required: true ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.zh-CN.yml ================================================ name: 🐞 错误报告 description: 报告 naive-ui 的问题 labels: [untriaged] body: - type: markdown attributes: value: | 感谢您抽出时间填写此错误报告! - type: textarea id: bug-description attributes: label: 描述错误 description: 对错误的清晰而简明的描述。如果您打算为此问题提交 PR,请在描述中告诉我们。谢谢! placeholder: 错误描述 validations: required: true - type: textarea id: steps attributes: label: 复现步骤 description: 复现此错误的清晰而简明的步骤。 placeholder: | 1. 应用魔法 2. 等待 3.1415 秒 3. 🧙🏽‍♂️ validations: required: true - type: input id: reproduction attributes: label: 最小复现链接 description: 请提供[最小复现](https://stackoverflow.com/help/minimal-reproducible-example)链接。提供一个简化的 [Playground](https://play.pro-components.cn)/CodePen/CodeSandbox 或 GitHub 仓库链接。请不要随机填写链接。 placeholder: 复现链接 validations: required: true - type: textarea id: system-info attributes: label: 系统信息 description: "`npx envinfo --system --npmPackages 'naive-ui,vue' --binaries --browsers` 命令的输出" render: Shell placeholder: 系统信息,二进制文件,浏览器 validations: required: true - type: dropdown id: package-manager attributes: label: 使用的包管理器 description: 选择所使用的包管理器 options: - npm - yarn - pnpm validations: required: true - type: checkboxes id: checkboxes attributes: label: 验证 description: 在提交问题之前,请确保完成以下步骤 options: - label: 阅读 [贡献指南](https://github.com/tusen-ai/naive-ui/blob/main/CONTRIBUTING.zh-CN.md)。 required: true - label: 阅读 [文档](https://www.naiveui.com/zh-CN/)。 required: true - label: 检查是否已经存在[相同问题的问题](https://github.com/tusen-ai/naive-ui/issues),以避免创建重复的问题。 required: true - label: 确保这是一个具体的 bug。有关问题和答案,请打开 [GitHub 讨论](https://github.com/tusen-ai/naive-ui/discussions)。 required: true - label: 提供的复现是[最小可复现的示例](https://stackoverflow.com/help/minimal-reproducible-example)。 required: true ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: 💬 Discord Chat url: https://discord.gg/Pqv7Mev5Dd about: Ask questions and discuss with other Naive UI users in real time. - name: ❓ Questions & Discussions url: https://github.com/tusen-ai/naive-ui/discussions about: Use GitHub discussions for message-board style questions and discussions. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.yml ================================================ name: 🚀 New feature request description: Propose a new feature to be added to Naive UI labels: [feature request] body: - type: markdown attributes: value: | Thanks for your interest in the project and taking the time to fill out this feature request! - type: textarea id: feature-description attributes: label: Clear and concise description of the problem description: 'As a developer using Naive UI I want [goal / wish] so that [benefit]. If you intend to submit a PR for this issue, tell us in the description. Thanks!' validations: required: true - type: textarea id: suggested-solution attributes: label: Suggested solution description: 'In module [xy] we could provide following implementation...' validations: required: true - type: textarea id: alternative attributes: label: Alternative description: Clear and concise description of any alternative solutions or features you've considered. - type: textarea id: additional-context attributes: label: Additional context description: Any other context or screenshots about the feature request here. - type: checkboxes id: checkboxes attributes: label: Validations description: Before submitting the issue, please make sure you do the following options: - label: Read the [Contributing Guidelines](https://github.com/tusen-ai/naive-ui/blob/main/CONTRIBUTING.md). required: true - label: Read the [docs](https://www.naiveui.com/en-US). required: true - label: Check that there isn't already an issue that request the same feature to avoid creating a duplicate. required: true ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.zh-CN.yml ================================================ name: 🚀 新功能请求 description: 提议将新功能添加到 Naive UI labels: [feature request] body: - type: markdown attributes: value: | 感谢您对该项目的关注,并抽出时间填写此功能请求! - type: textarea id: feature-description attributes: label: 问题的清晰而简明的描述 description: '作为使用 Naive UI 的开发者,我希望 [目标/愿望],以便 [好处]。如果您打算为此问题提交 PR,请在描述中告诉我们。谢谢!' validations: required: true - type: textarea id: suggested-solution attributes: label: 建议的解决方案 description: '在模块 [xy] 中,我们可以提供以下实现...' validations: required: true - type: textarea id: alternative attributes: label: 备选方案 description: 对您考虑过的任何备选解决方案或功能的清晰而简明的描述。 - type: textarea id: additional-context attributes: label: 附加上下文 description: 在这里提供有关功能请求的任何其他上下文或截图。 - type: checkboxes id: checkboxes attributes: label: 验证 description: 在提交问题之前,请确保完成以下步骤 options: - label: 阅读 [贡献指南](https://github.com/tusen-ai/naive-ui/blob/main/CONTRIBUTING.md)。 required: true - label: 阅读 [文档](https://www.naiveui.com/en-US)。 required: true - label: 检查是否已经存在请求相同功能的问题,以避免创建重复的问题。 required: true ================================================ FILE: .github/dependabot.yml ================================================ # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: npm # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "daily" ================================================ FILE: .github/pull_request_template.md ================================================ ================================================ FILE: .github/workflows/node.js.yml ================================================ # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions name: Node.js CI on: push: branches: [main, feat, docs] pull_request: branches: [main, feat, docs] jobs: lint: environment: test runs-on: ubuntu-latest strategy: matrix: node-version: [22] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - uses: actions/checkout@v3 - name: Install pnpm run: corepack enable - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - run: pnpm install - name: Lint run: pnpm run lint test: environment: test runs-on: ubuntu-latest strategy: matrix: node-version: [22] steps: - uses: actions/checkout@v3 - name: Install pnpm run: corepack enable - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - name: Setup timezone uses: szenius/set-timezone@v2.0 with: timezoneLinux: Asia/Shanghai timezoneMacos: Asia/Shanghai timezoneWindows: Asia/Shanghai - run: pnpm install - name: Test run: pnpm run test:cov - name: Code coverage uses: codecov/codecov-action@v3 with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage/lcov.info ================================================ FILE: .github/workflows/publish-to-pkg.pr.new.yml ================================================ name: Publish to pkg.pr.new on: push: branches: [main] pull_request: types: [labeled] # Cancel all previous workflows on main. concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} jobs: publish: runs-on: ubuntu-latest if: github.event_name == 'push' || (github.event_name == 'pull_request' && contains(github.event.label.name, 'preview')) steps: - name: Checkout code uses: actions/checkout@v4 - run: corepack enable - uses: actions/setup-node@v4 with: node-version: 22 cache: "pnpm" cache-dependency-path: "package.json" - name: Install dependencies run: pnpm install - name: Build run: pnpm build:package - name: Publish preview run: pnpx pkg-pr-new publish --compact ================================================ FILE: .gitignore ================================================ node_modules dist docDist build-doc/deploy-doc.sh build-doc/dist test-size test-bundle test/unit/coverage package-lock.json playground/temp yarn.lock .DS_Store .vscode .idea .pulsar *.swp *.tgz coverage /fonts /site /package lib es *.tsbuildinfo pnpm-lock.yaml web-types.json ~* .pnpm-debug.log .history ================================================ FILE: .husky/pre-commit ================================================ pnpm exec lint-staged ================================================ FILE: .npmrc ================================================ shell-emulator=true ================================================ FILE: .nvmrc ================================================ 22 ================================================ FILE: .pnpmfile.cjs ================================================ // We will lock vue version if there's regression const lockVueVersion = false const vueVersion = '3.2.36' const isSameVersionVuePackage = (pkgName) => [ 'vue', '@vue/shared', '@vue/compiler-sfc', '@vue/server-renderer', '@vue/reactivity', '@vue/reactivity-transform', '@vue/compiler-dom', '@vue/compiler-ssr', '@vue/compiler-core' ].some((name) => name === pkgName) function readPackage(pkg) { if (isSameVersionVuePackage(pkg.name)) { pkg.version = vueVersion } ;['dependencies', 'peerDependencies'].forEach((depsType) => { const deps = { ...pkg[depsType] } for (dep in pkg[depsType]) { if (isSameVersionVuePackage(dep)) { deps[dep] = vueVersion } } pkg[depsType] = deps }) return pkg } module.exports = lockVueVersion ? { hooks: { readPackage } } : {} ================================================ FILE: .prettierignore ================================================ *.cssr.js *.cssr.ts ================================================ FILE: .prettierrc ================================================ { "semi": false, "singleQuote": true, "printWidth": 80, "trailingComma": "none", "proseWrap": "never" } ================================================ FILE: CHANGELOG.en-US.md ================================================ # CHANGELOG ## NEXT_VERSION ### Fixes - Fix `n-color-picker` passed `style` and `click` (onClick) not applied to trigger, closes [#7528](https://github.com/tusen-ai/naive-ui/issues/7528). ## 2.44.1 `2026-03-08` ### Feats - `n-config-provider` adds per-component `size` options in `component-options` (`AutoComplete`, `Cascader`, `ColorPicker`, `DatePicker`, `InputNumber`, `InputOtp`, `Mention`, `Select`, `TimePicker`, `Transfer`, `TreeSelect`). ### Fixes - `n-config-provider` fixes `size` prop doesn't work for some components. ## 2.44.0 `2026-03-08` ### Breaking Changes - `n-color-picker` refactor DOM structure & trigger class names. - `n-card`'s `n-card__content` class name is renamed to `n-card-content`. - `n-select` will clear options created by `tag` prop by default when clear button is clicked. ### Feats - `n-color-picker` adds `trigger` slot, closes [#7192](https://github.com/tusen-ai/naive-ui/issues/7192). - `n-select` adds `clear-created-options-on-clear` prop to control whether created options are cleared when clearing while `tag` and clearable are enabled. - `n-select`, `n-auto-complete`, `n-mention`, `n-popselect`, `n-cascader`, `n-pagination` add `scrollbar-props` prop. - `n-config-provider` adds component-level `renderEmpty` options (`Cascader`, `DataTable`, `Select`, `Transfer`, `Tree`, `TreeSelect`). - `n-config-provider` adds per-component `size` options in `component-options` (`Button`, `Card`, `Checkbox`, `DataTable`, `Descriptions`, `Dropdown`, `DynamicTags`, `Form`, `Input`, `Pagination`, `Popselect`, `Radio`, `Rate`, `Result`, `Skeleton`, `Space`, `Switch`, `Table`, `Tabs`, `Tag`), closes [#356](https://github.com/tusen-ai/naive-ui/issues/356). - `n-upload`'s `submit` method adds `retry` option. - `n-breadcrumb-item` adds `show-separator` prop for controlling separator visibility when used with `transition-group`, closes [#3614](https://github.com/tusen-ai/naive-ui/issues/3614). - `n-card` adds `content-scrollable` prop, closes [#4848](https://github.com/tusen-ai/naive-ui/issues/4848), [#6759](https://github.com/tusen-ai/naive-ui/pull/6759). - `n-date-picker` adds `fast-year-select` prop. - `n-date-picker` adds `fast-month-select` prop. - `n-button` adds `spin-props` prop. - `n-cascader` adds `spin-props` prop. - `n-log` adds `spin-props` prop. - `n-message` adds `spin-props` prop. - `n-switch` adds `spin-props` prop. - `n-tree` adds `spin-props` prop. - `n-spin` adds `radius` and `scale` props. - `n-tree-select` adds `show-line` prop. ### Fixes - Fix `katex` type compatibility by upgrading to `0.16.28` and removing `@types/katex` dependency, closes [#7423](https://github.com/tusen-ai/naive-ui/issues/7423). - Fix `n-select`'s created option is not cleared when clearing dynamic options while `tag` and `clearable` are enabled, closes [#7405](https://github.com/tusen-ai/naive-ui/issues/7405). - Fix `n-modal` event listeners not removed, closes [#7341](https://github.com/tusen-ai/naive-ui/issues/7341) by [@lu-han](https://github.com/lu-han). - Fix `n-marquee` component Non-function value encountered for default slot warning. - Fix `n-data-table`'s empty slot is not shown in a `display: flex` container. - Fix `n-data-table` missing header scrollbar in `n-data-table` when in empty state with `max-height` or `flex-height`, closes [#7479](https://github.com/tusen-ai/naive-ui/pull/7479). - Fix `n-input`, `n-select`, `n-date-picker`, `n-auto-complete`, `n-cascader`, `n-color-picker`, `n-input-number`, `n-input-otp`, `n-mention`, `n-time-picker`, `n-transfer`, `n-tree-select` not reading global `size` config from `n-config-provider`. - Fix `n-data-table` and `n-tag` `size` prop default value preventing global config from taking effect. ## 2.43.2 `2025-11-16` ### Fixes - Fix seemly dependency version range allows incompatible versions. - Fix `n-progress` style is incorrect after using the dashboard mode exceeding 100%, closes [#6627](https://github.com/tusen-ai/naive-ui/issues/6627). - Fix `n-modal`'s outside content can't be interacted with when `show-mask` is set to `false`. ### Feats - `n-date-picker` prop `defaultTime` can also accept a function that will return a formatted string. - `n-steps` adds `content-placement` prop, closes [#7044](https://github.com/tusen-ai/naive-ui/issues/7044). ## 2.43.1 `2025-09-14` ### Fixes - Fix esm format files are importing `lodash` rather than `lodash-es`. ## 2.43.0 `2025-09-13` ### Breaking Changes - Modal's inner close button won't be focusable using keyboard and it'll no longer be focused first by default. ### i18n - Add daDK locale. ### Fixes - Fix `n-color-picker` black and white effect of text is not obvious, closes [#7074](https://github.com/tusen-ai/naive-ui/issues/7074). - Fix `n-image` zoom in & out locale text in `da-DK` `sv-SE`. - Fix `n-popover`'s `themeOverrides` property does not have the `Scrollbar` style configuration. - Fix `n-input`'s `themeOverrides` property does not have the `Scrollbar` style configuration. - Fix `n-anchor` can't activate link in the bottom of the page by click, closes [#7033](https://github.com/tusen-ai/naive-ui/issues/7033), closes [#6918](https://github.com/tusen-ai/naive-ui/issues/6918), [#6844](https://github.com/tusen-ai/naive-ui/issues/6844), [#6782](https://github.com/tusen-ai/naive-ui/issues/6782). - Fix `n-upload` component `'Non-function value encountered for default slot'` warning. - Fix `n-input`'s `tabindex` property setting of `input-props` does not take effect. - Fix `n-tab` scroll shadow pseudo-class style conflicts in multi-layer tab nesting scenarios, closes [#6854](https://github.com/tusen-ai/naive-ui/issues/6854). - Fix `n-menu`'s disabled style not working when parent node is set to `disabled` and child node has `type: "group"`, closes [#6792](https://github.com/tusen-ai/naive-ui/issues/6792). - Fix `n-input-group-label` not injecting `formItemInjectionKey`, causing the `size` property to fail, and close [#7066](https://github.com/tusen-ai/naive-ui/issues/7066). - Fix the issue of style confusion in `n-carousel` when there is only one image, close [#6476](https://github.com/tusen-ai/naive-ui/issues/6476). - Fix `n-marquee` component 'Non-function value encountered for default slot' warning. - Fix memory leak caused by `n-upload` icon. ### Features - `n-data-table`'s column adds `customNextSortOrder` attribute, closes [#6850](https://github.com/tusen-ai/naive-ui/issues/6850). - Add `n-avatar-group`'s corresponding generic component `NGAvatarGroup`, closes [#6909](https://github.com/tusen-ai/naive-ui/issues/6909). - `n-avatar-group` adds `size` prop. - `n-popover` supports RTL. - `n-tooltip` supports RTL. - `n-upload` supports RTL. - `n-tree-select` supports RTL. - `useDialog`'s option supports `z-index`, closes [#4349](https://github.com/tusen-ai/naive-ui/issues/4349). - Add `n-heatmap` component. - - `n-image` adds showPreview methods, closes [#6695](https://github.com/tusen-ai/naive-ui/issues/6695). - Add `n-image-preview` component. - `n-image-group` can be used without `n-image`. - `n-input-otp` adds `focusOnChar` util method, closes [#7073](https://github.com/tusen-ai/naive-ui/issues/7073). - `n-form-item` adds `content-class`、`content-style` props. - `n-message` adds `border` theme variable, closes [#7105](https://github.com/tusen-ai/naive-ui/issues/7105). - `n-modal` adds `show-mask` prop. - `useModal` add `render` function, closes [#5857](https://github.com/tusen-ai/naive-ui/issues/5857). - `n-card` add `close-focusable` prop. - `n-dialog` add `close-focusable` prop. - `n-form-item` adds `invalidateLabelWidth` method,`n-form` adds `invalidateLabelWidths` method,closes [#5939](https://github.com/tusen-ai/naive-ui/issues/5939). ## 2.42.0 `2025-06-17` ### Breaking Changes - Fix `n-date-picker`'s `time-picker-format` prop doesn't work, the prop is misspelled as `timer-picker-format` before, closes [#6820](https://github.com/tusen-ai/naive-ui/issues/6820). ### Fixes - Fix `n-input-otp` event bindings not removed when elements are disabled (disabled/readonly), fix width style specificity conflict. - Fix `n-date-picker`'s `shortcuts`' time value will be overrided if `default-time` is set in with `type="datetimerange"`, closes [#6902](https://github.com/tusen-ai/naive-ui/pull/6902), [#6901](https://github.com/tusen-ai/naive-ui/pull/6901). - Fix `n-radio`, `n-radio-button` can't use `n-popover` inside, closes [#6837](https://github.com/tusen-ai/naive-ui/pull/6837), [#6832](https://github.com/tusen-ai/naive-ui/pull/6832). - Fix scrolling performance issue caused by too many layers, closes [#6887](https://github.com/tusen-ai/naive-ui/issues/6887). ### Features - `n-anchor-link` adds `title` slot, closes [#6845](https://github.com/tusen-ai/naive-ui/issues/6845). ## 2.41.1 `2025-06-08` ### Fixes - Fix `n-tabs`'s type exception of the tab slots in TabPane. - Fix `n-data-table` missing css variable of sorting cells, closes [#6521](https://github.com/tusen-ai/naive-ui/issues/6521). ### Features - 🌟 Adds `n-input-otp` component, closes [#1385](https://github.com/tusen-ai/naive-ui/issues/1385), [#5681](https://github.com/tusen-ai/naive-ui/issues/5681), [#6222](https://github.com/tusen-ai/naive-ui/issues/6222). - `n-upload` adds `custom-download`, closes [#5946](https://github.com/tusen-ai/naive-ui/issues/5946), [#6800](https://github.com/tusen-ai/naive-ui/issues/6800). - `n-upload` adds `uploadDownload` util method. - `n-tree-select` adds `indent` prop. ## 2.41.0 `2025-01-05` ### Breaking Changes - (**Vue 3.3+ required**) Add slot type for all components. ### i18n - Add kmKH locale. - Add ugCN locale. ### Features - `n-modal` adds `draggable` prop, closes [#6525](https://github.com/tusen-ai/naive-ui/issues/6525), [#5792](https://github.com/tusen-ai/naive-ui/issues/5792), [#5711](https://github.com/tusen-ai/naive-ui/issues/5711), [#5501](https://github.com/tusen-ai/naive-ui/issues/5501) and [#2152](https://github.com/tusen-ai/naive-ui/issues/2152). - `useDialog` supports `draggable` option. - `useModal` supports `draggable` option. ### Fixes - Fix `n-data-table` may have multiple expand trigger with tree data. - Fix `n-date-picker`'s `confirm`, `now`, `clear` slots doesn't work with `'month'`, `'monthrange'`, `'quarter'`, `'quarterrange'`, `'year'` and `'yearrange'` type. - Fix `n-input`'s `render-count` prop doesn't work when type is not `'textarea'`. ## 2.40.4 `2024-12-20` ### Fixes - Fix `inset` CSS property caused compatibility issues in some browsers, closes [#6604](https://github.com/tusen-ai/naive-ui/issues/6604),close [#6602](https://github.com/tusen-ai/naive-ui/issues/6602). - Fix memory leak problem when used with new version of vue. Note: After the fix you may still find memory leak in Chrome >= 129, since they introduced a bug, see https://github.com/vuejs/core/issues/12306, https://issues.chromium.org/issues/376777343. ## 2.40.3 `2024-12-02` - Fix marquee is not exported with correct name. ## 2.40.2 `2024-11-26` ### Fixes - Fix `n-time-picker`'s `use-12-hours` type error warning, closes [#4308](https://github.com/tusen-ai/naive-ui/issues/4308). - Fix `input-number` the problem that the negative sign is replaced when the negative sign is entered. - Fix `n-data-table`'s header will show scrollbar in some old browsers, closes [#6557](https://github.com/tusen-ai/naive-ui/issues/6557). ### Features - 🌟 Adds `n-marquee` component. - `n-image` adds `error` slot, closes [#5649](https://github.com/tusen-ai/naive-ui/issues/5649). - `n-date-picker` adds `date-format` prop. - `n-date-picker` adds `calendar-day-format` prop. - `n-date-picker` adds `calendar-header-year-format` prop. - `n-date-picker` adds `calendar-header-month-format` prop. - `n-date-picker` adds `calendar-header-month-before-year` prop. - `n-date-picker` adds `calendar-header-month-year-separator` prop. - `n-progress`'s `color` prop supports gradient config. - `n-select` adds `font-weight` theme variable. - `n-input` adds `font-weight` theme variable. - `n-data-table` adds `get-csv-header` and `get-csv-cell` props, closes [#6542](https://github.com/tusen-ai/naive-ui/issues/6542). ## 2.40.1 `2024-09-26` ### Fixes - Fix `n-data-table` in virtual-x mode, when all column objects do not have the `fixed` attribute configured, it cannot be displayed normally. - Fix `css-render` dependency version is not high enough to affect the `style-mount-target` attribute of `n-config-provider`. - Fix `n-data-table`'s `rowProps` generated `class` will override the original row DOM's class. ## 2.40.0 `2024-09-26` ### Breaking Changes - Fix `n-config-provider` doesn't inherit the class prefix from it's ascendant `n-config-provider`, closes [#5970](https://github.com/tusen-ai/naive-ui/issues/5970). - Fix `n-date-picker`'s `weekFormat` uses different standards for year and week formatting. Now it uses local week and local year for year and week formatting. - Dependency `date-fns` is upgraded to V3. - `n-cascader`'s `filter` prop is now case insensitive. ### Fixes - Fix `n-infinite-scroll` reaching bottom check is not correct, closes [#6133](https://github.com/tusen-ai/naive-ui/issues/6133). - Fix `n-slider`'s rail may have styling issue in `vertical` mode when global CSS box-sizing is overrided, closes [#6114](https://github.com/tusen-ai/naive-ui/issues/6114). - Fix `n-tabs` has style issue when using `prefix` slot, `suffix` slot or `addable` prop with vertical placement, closes [#6059](https://github.com/tusen-ai/naive-ui/issues/6059), [#6060](https://github.com/tusen-ai/naive-ui/pull/6060). - Fix `n-upload` can only upload a maximum of 100 files in some old browsers when uploading directories, closes [#6027](https://github.com/tusen-ai/naive-ui/issues/6027). - Fix `n-date-picker`'s `input-readonly` prop not working in the panel's inner input for `'datetime'` or `'datetimerange'`. - Fix `n-menu` can't apply HTML attributes correctly on the component when `responsive` is set. - Fix `n-float-button` throws error when used with `popover` component, closes [#5933](https://github.com/tusen-ai/naive-ui/issues/5933). - Fix `n-badge` can't mask focus element's border, closes [#5929](https://github.com/tusen-ai/naive-ui/issues/5929). - Fix `n-button` font-weight CSS variable's name is not correct, closes [#5922](https://github.com/tusen-ai/naive-ui/issues/5922). - Fix `n-icon`'s `component` prop doesn't accept functional component. - Fix `n-mention`'s panel is misplaced if `placement` is set to `'top'` or padding is set in component's style, closes [#6241](https://github.com/tusen-ai/naive-ui/issues/6241). - Fix `n-carousel`'s transition doesn't work as expected, closes [#5993](https://github.com/tusen-ai/naive-ui/issues/5993). ### Features - 🌟 `n-data-table` adds `virtual-scroll-x`, `virtual-scroll-header`, `height-for-row`, `header-height` and `min-row-height` props for supporting horizontal virtual scrolling when there are massive columns. - 🌟 Adds `n-highlight` component. - `n-scrollbar` adds `x-placement` and `y-placement` props, closes [#6089](https://github.com/tusen-ai/naive-ui/issues/6089). - `n-date-picker` adds `clear`, `now` and `confirm` slots, closes [#6013](https://github.com/tusen-ai/naive-ui/issues/6013). - `n-upload` adds `on-retry` prop, closes [#6031](https://github.com/tusen-ai/naive-ui/issues/6031). - `n-slider`'s `marks` prop supports render function, closes [#5967](https://github.com/tusen-ai/naive-ui/issues/5967). - `n-transfer`'s `source-title` and `target-title` props support render function, closes [#6004](https://github.com/tusen-ai/naive-ui/issues/6004). - `n-data-table` add `rowData` params for `render-expand-icon` prop, closes [#6108](https://github.com/tusen-ai/naive-ui/issues/6108). - `n-empty`'s `size` prop supports `'tiny'` size. - `n-config-provider` adds `style-mount-target` prop to control where to mount components' style. - `n-data-table` adds `allowExport` prop for column object. - `n-date-picker` adds `year-range` prop. - `n-tree-select` adds `header` slot, closes [#5915](https://github.com/tusen-ai/naive-ui/issues/5915). - `n-select` adds `menu-size` prop. ## 2.39.0 `2024-07-15` ### Breaking Changes - Fix `n-input-number` Exception when the value is a string in precision mode, closes [#6091](https://github.com/tusen-ai/naive-ui/issues/6091). ### Fixes - Fix `n-form-item` validation state is not correctly updated [#6068](https://github.com/tusen-ai/naive-ui/issues/6068). - Fix `n-select`'s header make inner input unavailable, closes [#6030](https://github.com/tusen-ai/naive-ui/issues/6030). - Fix `n-tree` may have incorrect node selection status when `show-irrelevant-nodes` is disabled, close [#6115](https://github.com/tusen-ai/naive-ui/issues/6115). ### Features - `n-data-table` adds `filter-icon-popover-props` prop, closes [#6111](https://github.com/tusen-ai/naive-ui/issues/6111). - `n-input-number` adds `round` prop, closes [#6097](https://github.com/tusen-ai/naive-ui/issues/6097). - `n-color-picker` add `on-clear` props. - `n-upload`'s `on-preview` prop adds `detail.event` parameter. You can use `preventDefault` to prevent default anchor link open behavior. Closes [#6036](https://github.com/tusen-ai/naive-ui/issues/6036). - `n-data-table` adds `thColorSorting`, `thColorSortingModal`, `thColorSortingPopover`, `tdColorSorting`, `tdColorSortingModal` and `tdColorSortingPopover` theme variables, closes [#6118](https://github.com/tusen-ai/naive-ui/issues/6118), [#6120](https://github.com/tusen-ai/naive-ui/issues/6120). ### i18n - Add azAZ locale. - Add uzUZ locale. ## 2.38.2 `2024-05-03` ### Fixes - Fix `n-menu` Submenu's wai-aria role is not correct, closes [#5729](https://github.com/tusen-ai/naive-ui/issues/5729). - Fix `n-tabs` style bug with type is `segment`,closes [#5728](https://github.com/tusen-ai/naive-ui/issues/5728). - Fix the get\*String() methods for UTC/locale mismatch, closes [#5702](https://github.com/tusen-ai/naive-ui/issues/5702). - Fix `n-dialog` / `n-modal` calling `destroy` method may throw error. - Fix `useModal` setting `card` preset without corresponding props in `n-card` slots, closes [#5746](https://github.com/tusen-ai/naive-ui/issues/5746). - Fix `Submenu` component's wai-aria role setting error of `n-menu`,closes [#5729](https://github.com/tusen-ai/naive-ui/issues/5729). - Fix the `common` type error in the `theme-overrides` prop when modifying components' themes. - Fix `n-split` may emit value less than `0`. ### Features - `n-watermark` support multi-lines in content. - Adds `n-infinite-scroll` component. - `n-watermark` adds `text-align` prop. - `n-qr-code` adds `type` prop, Customize rendering output by setting `type`, providing two options: `canvas` and `svg`. - `n-card` adds `action`, `content`, `cover`, `footer` and `header-extra` props. - `n-card`'s `title` prop supports render function. - `n-upload` expose the `index` arg in `on-remove` function, closes [#5747](https://github.com/tusen-ai/naive-ui/issues/5747). - `n-upload` exports `UploadOnDownload`, `UploadOnRemove`, `UploadOnFinish` and `UploadOnChange` types. - `n-dialog` adds `action-class`, `action-style`, `content-class`, `content-style`, `title-class` and `title-style` props. - `n-split` adds `pane1-class`, `pane1-style`, `pane2-class` and `pane2-style` props. - `n-mention` adds `filter` method, closes [#5721](https://github.com/tusen-ai/naive-ui/pull/5721). - `n-slider` adds wai-aria support. - `n-date-picker` adds `time-picker-format` prop. - `n-form-item` adds `feedback-class` and `feedback-style` props. - `n-split` supports using pixel unit string as `value`. - `n-scrollbar` adds `content-style` and `content-class` props, closes [#4497](https://github.com/tusen-ai/naive-ui/issues/4497). - `n-image` adds `render-toolbar` prop. - `n-cascader` adds `get-column-style` prop. - `n-cascader` adds `get-render-prefix` prop. - `n-cascader` adds `get-render-suffix` prop. - `n-image` optimizes download icon style. - `n-scrollbar` adds `height`, `width`, `radius`, `railInsetHorizontal`, `railInsetVertical` and `railColor` theme variables. ### i18n - Add csCZ locale. - Add missing itIT locale translations. ## 2.38.1 `2024-02-26` ### Fixes - Fix `n-split`'s `min` attribute does not take effect. - Fix `n-result` built-in icons not re-rendered after hydration. - Fix `n-tabs` whose `type` is `'segment'` has capsule with wrong width and position after resize, closes [#5705](https://github.com/tusen-ai/naive-ui/issues/5705). - Fix `n-tabs`'s capsule wrong width and position after resize within `n-modal`, closes [#5569](https://github.com/tusen-ai/naive-ui/issues/5569). - Fix `n-split` doesn't work with `inline-theme-disabled` prop. - Fix `n-float-button` doesn't work with `inline-theme-disabled` prop. ### Features - `n-date-picker` adds `default-calendar-start-time` props when `type` is `'date'`/`'datetime'` or `'week'`, closes [#4493](https://github.com/tusen-ai/naive-ui/issues/4493). - `n-tree-select` adds `get-children` prop. ## 2.38.0 `2024-02-22` ### Breaking Changes - Fix `n-scrollbar`'s `scrollTo(x: number, y: number)` error where the order of method parameters does not match the document. ### Fixes - Fix `n-tree`'s `override-default-node-click-behavior` prop may conflict with default switcher click or checkbox click behavior. - Fix `n-scrollbar`'s typo on `aria-hidden` attribute. - Fix `n-form-item`'s feedback may hide and show again, closes [#5583](https://github.com/tusen-ai/naive-ui/issues/5583). - Fix `n-popselect`'s header make inner input unavailable, closes [#5494](https://github.com/tusen-ai/naive-ui/pull/5494). - Fix `n-qr-code`'s style of size. - Fix `n-badge` affects child elements' text color. ### Features - 🌟 Adds `n-modal-provider` component and `useModal` method. - 🌟 Adds `n-float-button` and `n-float-button-group` component. - 🌟 Provides ES module bundle at `/dist/index.mjs` and `/dist/index.prod.mjs`. - `n-auto-complete` adds `append` prop. - `n-select` add native `title` attribute when `filterable` and blur input. - `n-split` adds `size` prop and `on-update:size` prop. - `n-split` adds `watch-props` prop, closes [#5526](https://github.com/tusen-ai/naive-ui/issues/5526). - `n-drawer` adds `borderRadius` theme variable. - adds `n-float-button` component. - Provides ES module bundle. ### i18n - Add `etEE` locale. ## 2.37.3 `2024-01-09` ### Fixes - Fix `n-split` has no color if it's not used in a card. ## 2.37.2 `2024-01-09` ### Fixes - `n-data-table`'s `downloadCsv` method will export selection & expand column. ## 2.37.1 `2024-01-08` ### Fixes - Click clear button on components with popup may trigger reopen behaviors. - Fix `n-form`'s `validate` returned `Promise` may not `resolve`. ### Features - `n-collapse` adds `trigger-areas` prop. - `n-date-picker`'s `is-date-disabled` callback prop supports get detail info of date/year/month/quarter button as parameter, closes [#4649](https://github.com/tusen-ai/naive-ui/issues/4649). - `n-auto-complete` adds `empty` slot. - `n-auto-complete` adds `show-empty` prop. ## 2.37.0 `2024-01-07` ### Breaking Changes - `module` prop in `package.json` is changed from `es/index.js` to `es/index.mjs`. ### Fixes - Fix `n-space` vnode reuse problem caused by filtering out comment nodes of slot, closes [#5136](https://github.com/tusen-ai/naive-ui/issues/5136). - Fix `n-data-table`'s prop `pagination`'s `default-page-size` and `default-page` not work in uncontrolled mode, closes [#5201](https://github.com/tusen-ai/naive-ui/issues/5201). - Fix `n-time-picker` formatting (format="HH: mm: ss. SSS") preventing modification of milliseconds in the edit box, closes [#5224](https://github.com/tusen-ai/naive-ui/issues/5224). - Fix `n-notification` notification clips out of screen when screen width is less than 400px wide. - Fix `n-carousel` transition effect incorrect when using the `slide` effect in loop mode with only two elements, closes [#4323](https://github.com/tusen-ai/naive-ui/issues/4323). - Fix `n-carousel` trigger incorrect `current-index` value on arrow button click with single image, closes [#5130](https://github.com/tusen-ai/naive-ui/issues/5130). - Fix `n-upload-trigger` in directory drag mode with a lot of files, some of the files are not read. - Fix `n-dynamic-tags`'s abnormal behavior when using keyboard to trigger add button, closes [#5077](https://github.com/tusen-ai/naive-ui/issues/5077). - Fix `n-tree` leaf node line color. - Fix `n-collapse-item` cursor pointer to correct element, closes [#5482](https://github.com/tusen-ai/naive-ui/issues/5482). - Fix `n-data-table` throws error if summary config has empty column. - Fix `n-drawer`'s `on-mask-click` may be called multiple times. - Fix `n-tree`'s `data` When the data source 'data' switches several times according to a certain scene, some logic of animation processing can cause errors in rendering the displayed data, closes [#5217](https://github.com/tusen-ai/naive-ui/issues/5217). - Fix `n-radio` value's native input element's checked value is not updated, closes [#5184](https://github.com/tusen-ai/naive-ui/issues/5184). - Fix `n-data-table` height incorrect when set `min-height` in empty state,closes [#5108](https://github.com/tusen-ai/naive-ui/issues/5108). - Fix `n-tabs`'s bar not hidden when `value` is set manually to the value other than the children `n-tab`s, closes [#5100](https://github.com/tusen-ai/naive-ui/issues/5100). - Fix `n-spin` abnormal animation, closes [#3556](https://github.com/tusen-ai/naive-ui/issues/3556). - Fix `n-avatar`'s lazy loading and `fallback-src` prop not working when load error in lazy, closes [#5007](https://github.com/tusen-ai/naive-ui/issues/5007). - Fix `n-split` has no color if it's not used in a card. - Fix `n-card` `footer-class` prop not working. - Fix `n-input` click clear icon to trigger twice when using the `clearable`, closes [#5510](https://github.com/tusen-ai/naive-ui/issues/5510). - Fix `n-tabs` may miss over-scroll shadow if `placement` is `'left'` or `'right'`. - Fix `n-date-picker` with range type can input start time that is later than end time, closes [#5544](https://github.com/tusen-ai/naive-ui/issues/5544). ### Features - 🌟 Adds `n-flex` component. - 🌟 `n-date-picker`'s `type` prop supports `'week'`. - 🌟 `n-data-table` adds `downloadCsv` method, closes [#4260](https://github.com/tusen-ai/naive-ui/issues/4260). - 🌟 `n-date-picker` adds `month-format`, `year-format` and `quarter-format` props, closes [#4891](https://github.com/tusen-ai/naive-ui/issues/4891). - 🌟 `n-tree` adds `override-default-node-click-behavior` prop. - 🌟 `n-tree-select` adds `override-default-node-click-behavior` prop. - `n-space` adds `reverse` prop. - `n-input` adds `clear` method, closes [#5423](https://github.com/tusen-ai/naive-ui/issues/5423). - `n-time-picker` adds `'clear'` action, closes [#5334](https://github.com/tusen-ai/naive-ui/issues/5334). - `n-select` supports RTL. - `n-data-table` supports RTL. - `n-dialog` supports RTL. - `n-date-picker` adds `on-prev-month` `on-next-month` `on-prev-year` `on-next-year` prop, closes [#5350](https://github.com/tusen-ai/naive-ui/issues/5350). - `n-input-number` adds `input-props` prop, closes [#5450](https://github.com/tusen-ai/naive-ui/issues/5450). - Update `ruRU` locale. - `n-drawer` adds `content-class` prop. - `n-drawer-content` adds `body-class` `body-content-class` `footer-class` and `header-class` props. - `n-tree` adds multiple `scrollTo` configurations. - `n-form` adds `level` property from `FormItemRule` to show abnormal values but not block submit. - `n-cascader` adds `ellipsis-tag-popover-props` prop. - `n-select` adds `ellipsis-tag-popover-props` prop. - `n-tree-select` adds `ellipsis-tag-popover-props` prop. - `n-avatar-group` adds `expand-on-hover` prop. - `n-tabs` adds `tab-class`, `add-tab-style` and `add-tab-class` props. - `n-tree` adds `override-default-node-click-behavior` prop. - `n-tree-select` adds `override-default-node-click-behavior` prop. - Adds `n-flex` component. - `n-pagination` adds `show-quick-jump-dropdown` prop, closes [#5251](https://github.com/tusen-ai/naive-ui/issues/5251). ## 2.36.0 `2023-12-18` ### Fixes - Fix `n-tree` unexposed line color variable `--n-line-color`, closes [#5339](https://github.com/tusen-ai/naive-ui/issues/5339). - Fix `n-tree` The style of the selected node is not displayed in the case of 'disabled'. - Fix `n-tree` on `virtual-scroll` empty data placeholder lost problem. - Fix `n-watermark` won't clear it's content when `content` prop is set to empty. - Fix `n-tree` use `render-switcher-icon` prop to customize switcher icon will cause node selection, closes [#5380](https://github.com/tusen-ai/naive-ui/issues/5380). - Fix `n-input` will display the password reveal button by default when the `type` is set to `password`. Starting with Microsoft Edge browser Version 87. closes [#5384](https://github.com/tusen-ai/naive-ui/issues/5384). - Fix `n-radio-button` css var `buttonColor` not working. - Fix `n-input` not display vertical scrollbar when `type` is `textarea` and the inline theme is disabled, closes [#5418](https://github.com/tusen-ai/naive-ui/issues/5418). - Fix if `inline-theme-disabled` is set, custom color whose params include decimal won't work in `n-tag`, `n-avatar`, `n-badge`, `n-button`, `n-rate`. - Fix `n-tabs`'s border height in `vertical` mode. - Fix `n-tree`'s node's hover color has higher priority than selected color in `block-line` mode. - Fix `n-tree` click expand switch causes checkbox being checked. - Fix `n-progress` multiple `type="circle"` cases style override issue, closes [#7172](https://github.com/tusen-ai/naive-ui/issues/7172). ### Features - `n-tree` adds `treeGetClickTarget` method to get click target of node click event, closes [#5375](https://github.com/tusen-ai/naive-ui/issues/5375). - `n-space` adds `item-class` prop. - `n-layout` adds `content-class` prop. - `n-layout-sider` adds `collapsed-trigger-class` and `trigger-class` props. - `n-spin` adds `content-class` and `content-style` props. - `n-popover` adds `arrow-class`, `arrow-wrapper-class`, `arrow-wrapper-style`, `content-class`, `footer-class` and `header-class` props. - `n-notification-provider` adds `container-class` prop. - `n-message-provider` adds `container-class` prop. - `n-loading-bar-provider` adds `container-class` prop. - `n-thing` adds `content-class` and `description-class` props. - `n-card` adds `content-class`, `footer-class`, `header-class` and `header-extra-class` props. - `n-descriptions` adds `content-class` and `label-class` props. - `n-upload` adds `file-list-class` and `trigger-class` props. - `n-dynamic-tags` adds `input-class` and `tag-class` props. - `n-dynamic-input` adds `item-class` prop. - `n-slider` adds `on-dragstart` `on-dragend` prop, closes [#5365](https://github.com/tusen-ai/naive-ui/issues/5365). - `n-dialog` adds `close` slot. - `n-equation` export the `EquationProps` type. - `n-popselect` adds `header` slot. - `n-tree-select` adds `watch-props` prop. - Adds `n-split` component, closes [#3557](https://github.com/tusen-ai/naive-ui/issues/3557). - Adds `n-virtual-list` component. - Adds `n-qr-code` component, closes [#2535](https://github.com/tusen-ai/naive-ui/issues/2535). - `n-menu` add `responsive` prop, it will collapse overflow menu items in horizontal mode. - `n-menu` add `deriveResponsiveState` method. ## 2.35.0 `2023-10-02` ### Breaking Changes - `n-input`'s `suffix` to the back of `loading`, close [#4685](https://github.com/tusen-ai/naive-ui/issues/4685). - Fix `n-log`'s `silent` attribute spelling problem, closes [#4875](https://github.com/tusen-ai/naive-ui/issues/4875). ### Fixes - Fix `n-radio` export `radioProps` dosen't not includes `theme-overrides`. - Fix `n-description-item`'s `span` doesn't work when `n-descriptions`'s `label-placement` is `'top'` if there's only single line, closes [#4874](https://github.com/tusen-ai/naive-ui/issues/4874). - Fix `n-upload`'s `data` prop type can't include `Blob` element. - Fix `n-select` allows option to be created with existed label, closes [#4703](https://github.com/tusen-ai/naive-ui/issues/4703). - Fix `n-upload`'s `render-icon` prop's type. - Fix `n-auto-complete`'s `onSelect` type, closes [#4617](https://github.com/tusen-ai/naive-ui/issues/4617). - Fix `n-grid-item`'s suffix prop won't work with responsive config, closes [#4635](https://github.com/tusen-ai/naive-ui/issues/4635). - Fix `n-tabs`'s `paneWrapperStyle` prop missing height after animation. - Fix `n-tree` should check all items instead of uncheck all if indeterminate checkbox is clicked, closes [#4941](https://github.com/tusen-ai/naive-ui/issues/4941). - Fix the Popover was not displayed when the `n-internal-selection` was disabled and the mouse was moved over the '+n' tag, closes [#4789](https://github.com/tusen-ai/naive-ui/issues/4789). - Fix `n-input` doesn't display the vertical scroll bar when `type` is `textarea`, closes [#4570](https://github.com/tusen-ai/naive-ui/issues/4570). - Fix `n-alert`'s content style problem, when there is no title and use closable, closes [#4588](https://github.com/tusen-ai/naive-ui/issues/4588). - Fix `n-select`'s `empty` slot action then it is an interactive component, closes [#4700](https://github.com/tusen-ai/naive-ui/issues/4700). - Fix `n-data-table` header and body's scrolling are not sync when using the keyboard, closes [#3941](https://github.com/tusen-ai/naive-ui/issues/3941). - Fix `n-data-table` drag column causing text selection in Safari, closes [#4957](https://github.com/tusen-ai/naive-ui/issues/4957). - Fix `n-data-table` ellipsis content in table cell would wrap with expand button when using tree data, closes [#3755](https://github.com/tusen-ai/naive-ui/issues/3755). - Fix `useLoadingBar` can't finish loading when called `finish` method, closes [#4965](https://github.com/tusen-ai/naive-ui/issues/4965). - Fix `n-select` can still trigger focus and blur event in the disabled state, closes [#4454](https://github.com/tusen-ai/naive-ui/issues/4454). - Fix `n-steps` may have line wrap issue if step is more than 9. - Fix `n-grid` v-show reports errors when switching multiple times, closes [#4422](https://github.com/tusen-ai/naive-ui/issues/4422). - Fix `n-tree`'s `TreeOption`'s `checkboxDisabled` prop doesn't work when `check-on-click` is `true`. - Fix rapid clicks on `n-date-input`'s buttons triggering a text select for the rest of the website. - Fix `n-auto-complete`'s autocomplete menu's unexpected open when clicking the clear icon with the input not focused, closes [#4658](https://github.com/tusen-ai/naive-ui/issues/4658). - Fix `n-input`'s `on-keyup` prop type, closes [#5101](https://github.com/tusen-ai/naive-ui/issues/5101). - Fix `n-data-table`'s default sorter to place null values at the very top or bottom, closes [#5281](https://github.com/tusen-ai/naive-ui/issues/5281). - Fix `n-popconfirm`'s action button should not be triggered multiple times,closes [#4687](https://github.com/tusen-ai/naive-ui/issues/4687). ### Features - `n-drawer` adds `max-height`, `min-height`, `max-width` and `max-width` props. - `n-progress` supports indicator slot when the `indicator-placement` is set to `'inside'` in the `'line'` type, closes [#4888](https://github.com/tusen-ai/naive-ui/issues/4888). - `n-image-preview` adds `downaload` button, closes [#4302](https://github.com/tusen-ai/naive-ui/issues/4302). - `n-transfer` adds `select-all-text` and `clear-text` prop, closes [#4910](https://github.com/tusen-ai/naive-ui/issues/4910). - `n-tree` adds `scrollbar-props` prop, closes [#4021](https://github.com/tusen-ai/naive-ui/issues/4666). - `n-select` adds `focusInput` `blurInput` methods. - `n-tree-select` adds `focusInput` `blurInput` methods. - `n-image-group` adds `on-preview-prev` `on-preview-next` prop. - `n-tree` adds `show-line` prop, closes [#3796](https://github.com/tusen-ai/naive-ui/issues/3796), [#4554](https://github.com/tusen-ai/naive-ui/pull/4554). - `n-tree` adds node information for `render-switcher-icon` props, closes [#4815](https://github.com/tusen-ai/naive-ui/issues/4815). - `n-input-number` export the `select` method. - `n-data-table` adds `n-data-table-tr--expanded` class to expanded rows, and `n-data-table-tr n-data-table-tr--expand` class to the additional row, closes [#4420](https://github.com/tusen-ai/naive-ui/issues/4420). - `n-spin` adds `delay` prop. - Adds `n-performant-ellipsis` component. - `DataTableBaseColumn` adds `ellipsisComponent` prop. ### i18n - Update `zhTW` locale. - Add `svSE` locale. - Update `jaJP` locale. ## 2.34.4 `2023-05-21` ### Fixes - Fix `n-notification`'s `description` does not wrap when there is English, closes [#4609](https://github.com/tusen-ai/naive-ui/issues/4609). - Fix `n-dynamic-input` can't access `value[index]` by `index` passed in `on-remove` prop. - Fix `n-dynamic-input` doesn't return correct `index` in `on-create` callback. - Fix `trTR` i18n, closes [#4231](https://github.com/tusen-ai/naive-ui/issues/4231). - Fix `n-input`'s show password icon is offset when use both `password` and `disabled`, closes [#4364](https://github.com/tusen-ai/naive-ui/issues/4364). - Fix `n-image` set `fallback-src` prop and lazy loading dosen't work, closes[#4480](https://github.com/tusen-ai/naive-ui/issues/4480). - Fix `n-upload` warning cause by extraneous non-props attributes were passed to vue component `TransitionGroup` but could not be automatically inherited, closes [#4447](https://github.com/tusen-ai/naive-ui/issues/4447). - Fix `n-menu` `show` `default` attribute spelling problem, closes [#4750](https://github.com/tusen-ai/naive-ui/issues/4750). - Fix `n-icon-wrapper`'s theme error, closes [#4768](https://github.com/tusen-ai/naive-ui/issues/4768). ### Feats - `n-dynamic-input` adds `action` slot, closes [#3981](https://github.com/tusen-ai/naive-ui/issues/3981). - `n-dynamic-input` add `disabled` prop, closes [#4055](https://github.com/tusen-ai/naive-ui/issues/4055). - `n-data-table` adds `titleAlign` prop, closes [#3954](https://github.com/tusen-ai/naive-ui/issues/3954). - `n-rate` exposes `index` in the default slot, closes [#4413](https://github.com/tusen-ai/naive-ui/issues/4413). - `n-scrollbar` adds `size` prop, closes [#3896](https://github.com/tusen-ai/naive-ui/issues/3896). - `n-data-table`'s `render-expand-icon` add `expanded` param, closes [#4439](https://github.com/tusen-ai/naive-ui/issues/4439). - `n-tabs` adds `pane-wrapper-class` `pane-wrapper-style` prop. - `n-collapse` adds `titlePadding` theme variable, closes [#4728](https://github.com/tusen-ai/naive-ui/issues/4728). - `n-tabs` adds `placement` prop. ### i18n - Update `zhTW` locale. - Add `faIR` locale. ## 2.34.3 `2022-12-24` ### Fixes - Fix `n-progress`'s `indicator-text-color` prop doesn't work when `indicator-placement` is `'inside'`. - Fix `n-image` error while operating the previewed image, closes [#4157](https://github.com/tusen-ai/naive-ui/issues/4157). - Fix `n-tree` cannot access `mergedFilterRef` before initialization error, closes [#4134](https://github.com/tusen-ai/naive-ui/issues/4134). - Fix `n-menu` can't override submenu dropdown's `trigger` by `dropdown-props`, closes [#4147](https://github.com/tusen-ai/naive-ui/issues/4147). - Fix `n-ellipsis` cannot be closed when using `keep-alive`, closes [#4079](https://github.com/tusen-ai/naive-ui/issues/4079). - Fix `n-upload` doesn't show thumbnail for file with image type file name, closes [#4198](https://github.com/tusen-ai/naive-ui/issues/4198). - Fix `n-input` style bug of tiny size with autosize prop, closes [#4167](https://github.com/tusen-ai/naive-ui/issues/4167). - Fix `n-image` & `n-avatar` in `lazy` mode, after setting the `intersection-observer-options` `rootMargin` attribute, the preload does not take effect. ### Feats - `n-tree` adds `get-children` prop, closes [#4128](https://github.com/tusen-ai/naive-ui/issues/4128). - `n-badge` adds `offset` prop, closes [#4149](https://github.com/tusen-ai/naive-ui/issues/4149). - `n-card` adds `tag` prop. - demos can now use `` return script } export async function convertMd2ComponentDocumentation( text: string, url: string, env: string = 'development' ): Promise { const forceShowAnchor = !!~text.search('') const colSpan = ~text.search('') ? 1 : 2 const hasApi = !!~text.search('## API') const tokens = marked.lexer(text) // resolve external components const componentsIndex = tokens.findIndex( token => token.type === 'code' && (token as Tokens.Code).lang === 'component' ) let components: ComponentInfo[] = [] if (~componentsIndex) { const token = tokens[componentsIndex] as Tokens.Code components = token.text .split('\n') .map((component) => { const [ids, importStmt] = component.split(':') if (!ids.trim()) throw new Error('No component id') if (!importStmt.trim()) throw new Error('No component source url') return { ids: ids.split(',').map(id => id.trim()), importStmt: importStmt.trim() } }) .filter(({ ids, importStmt }) => ids && importStmt) tokens.splice(componentsIndex, 1) } // add edit on github button on title const titleIndex = tokens.findIndex( token => token.type === 'heading' && token.depth === 1 ) if (titleIndex > -1) { const titleText = JSON.stringify( (tokens[titleIndex] as Tokens.Heading).text ) const btnTemplate = `<\/edit-on-github-header>` tokens.splice(titleIndex, 1, { type: 'html', pre: false, text: btnTemplate } as Tokens.HTML) } // resolve demos, debug demos are removed from production build const demosIndex = tokens.findIndex( token => token.type === 'code' && (token as Tokens.Code).lang === 'demo' ) let demoInfos: DemoInfo[] = [] if (~demosIndex) { demoInfos = await resolveDemoInfos( (tokens[demosIndex] as Tokens.Code).text, url, env ) tokens.splice(demosIndex, 1, { type: 'html', pre: false, text: genDemosTemplate(demoInfos, colSpan) } as any) } const docMainTemplate = marked.parser(tokens, { gfm: true, renderer: mdRenderer }) // generate page const docTemplate = ` ` const docScript = await genScript(demoInfos, components, url, forceShowAnchor) return `${docTemplate}\n\n${docScript}` } ================================================ FILE: build/loaders/convert-vue-to-demo.ts ================================================ import type { Token } from 'marked' import fs from 'node:fs' import path from 'node:path' import process from 'node:process' import { fileURLToPath } from 'node:url' import { marked } from 'marked' import { handleMergeCode } from '../utils/handle-merge-code' import { createRenderer } from './md-renderer' interface Parts { template?: string script?: string style?: string title: string content: string language: 'ts' | 'js' api: 'composition' | 'options' } interface MergedParts extends Parts { tsCode: string jsCode: string } interface MergePartsOptions { parts: Parts isVue: boolean } interface ConvertVue2DemoOptions { content: string resourcePath: string relativeUrl: string isVue?: boolean } const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) const mdRenderer = createRenderer() const __HTTP__ = process.env.NODE_ENV !== 'production' ? 'http' : 'https' const demoBlock = fs .readFileSync(path.resolve(__dirname, 'ComponentDemoTemplate.vue')) .toString() function mergeParts({ parts, isVue }: MergePartsOptions): MergedParts { const mergedParts: Partial = { ...parts, title: parts.title, content: parts.content, tsCode: '', jsCode: '' } handleMergeCode({ parts, mergedParts: mergedParts as MergedParts, isVue }) mergedParts.tsCode = encodeURIComponent(mergedParts.tsCode!) mergedParts.jsCode = encodeURIComponent(mergedParts.jsCode!) return mergedParts as MergedParts } const cssRuleRegex = /([^{}]*)(\{[^}]*\})/g // simulate scss style // to remove dep of sass // xxx { // mystyle // } function genStyle(sourceStyle: string): string | null { let match let matched = false const rules: string[] = [] let matchResult = cssRuleRegex.exec(sourceStyle) while (matchResult !== null) { match = matchResult matchResult = cssRuleRegex.exec(sourceStyle) matched = true const selector = match[1] const body = match[2] rules.push( selector .split(',') .map(part => `.demo-card__view ${part}, .naive-ui-doc ${part}`) .join(',') + body ) } if (!matched) return null return `` } function genVueComponent( parts: MergedParts, fileName: string, relativeUrl: string ): string { const demoFileNameReg = //g const relativeUrlReg = //g const titleReg = //g const contentReg = // const tsCodeReg = // const jsCodeReg = // const scriptReg = // const styleReg = // const demoReg = // const languageTypeReg = // let src = demoBlock src = src.replace(demoFileNameReg, fileName) src = src.replace(relativeUrlReg, relativeUrl) if (parts.content) { src = src.replace(contentReg, parts.content) } if (parts.title) { src = src.replace(titleReg, parts.title) } if (parts.tsCode) { src = src.replace(tsCodeReg, parts.tsCode) } if (parts.jsCode) { src = src.replace(jsCodeReg, parts.jsCode) } if (parts.script) { const attributes = `${parts.api === 'composition' ? ' setup' : ''}${ parts.language === 'ts' ? ' lang="ts"' : '' }` const startScriptTag = `\n` src = src.replace(scriptReg, `${startScriptTag + parts.script}\n`) } if (parts.language) { src = src.replace(languageTypeReg, parts.language) } if (parts.style) { const style = genStyle(parts.style) if (style !== null) { src = src.replace(styleReg, style) } } if (parts.template) { src = src.replace(demoReg, parts.template) } if (/__HTTP__/.test(src)) { src = src.replace(/__HTTP__/g, __HTTP__) } return src.trim() } function getFileName(resourcePath: string): [string, string] { const dirs = resourcePath.split('/') const fileNameWithExtension = dirs[dirs.length - 1] return [fileNameWithExtension.split('.')[0], fileNameWithExtension] } function getPartsOfDemo(text: string): Parts { // slot template const firstIndex = text.indexOf('') template = template.slice(0, lastIndex) const script = text.match(/([\s\S]*?)<\/script>/)?.[1]?.trim() const style = text.match(/` mergedParts.tsCode += style mergedParts.jsCode += style } } else { // only js when md or vue file if (parts.template) { mergedParts.jsCode += isVue ? `` : `` } if (parts.script) { if (parts.template) { mergedParts.jsCode += '\n\n' } mergedParts.jsCode += `\n${parts.script}\n` } if (parts.style) { if (parts.template || parts.script) { mergedParts.jsCode += '\n\n' } const style = isVue ? `` : `` mergedParts.jsCode += style } } } ================================================ FILE: build/utils/terse-cssr.ts ================================================ import { generate } from '@babel/generator' import { parse } from '@babel/parser' import _traverse from '@babel/traverse' export function terseCssr(code: string): string { // https://github.com/babel/babel/discussions/13093 const traverse = (_traverse as any)?.default ?? _traverse const patternSpace = / +/g const patternEnter = /\n+/g const ast: any = parse(code, { sourceType: 'module' }) traverse(ast, { TemplateElement(path: any) { ;(['raw', 'cooked'] as const).forEach((type) => { path.node.value[type] = path.node.value[type] .replace(patternSpace, ' ') .replace(patternEnter, '\n') }) } }) return generate(ast).code } ================================================ FILE: build/utils/tsToJs.ts ================================================ import { transformSync } from 'esbuild' export function tsToJs(content: string | null): string { if (!content) { return '' } // esbuild will remove blank line const beforeTransformContent = content.replace( /\n(\s)*\n/g, '\n__blankline\n' ) const { code } = transformSync(beforeTransformContent, { loader: 'ts', minify: false, minifyWhitespace: false, charset: 'utf8' }) return code.trim().replace(/__blankline;/g, '') } ================================================ FILE: build/vite-plugin-css-render.ts ================================================ import type { Plugin } from 'vite' import { terseCssr } from './utils/terse-cssr' export function cssRenderPlugin(): Plugin { return { name: 'css-render-vite', transform(src: string, id: string) { if (id.endsWith('.cssr.ts') || id.endsWith('.cssr.js')) { return terseCssr(src) } } } } ================================================ FILE: build/vite-plugin-demo.ts ================================================ import type { Plugin } from 'vite' import vue from '@vitejs/plugin-vue' import { getTransformedVueSrc } from './utils/get-demo-by-path' import { cssRenderPlugin } from './vite-plugin-css-render' import { demoIndexTransFormPlugin } from './vite-plugin-index-tranform' const fileRegex = /\.(md|vue)$/ const vuePlugin = vue({ include: [/\.vue$/, /\.md$/] }) export function createDemoPlugin(): Plugin[] { const naiveDemoVitePlugin: Plugin = { name: 'demo-vite', async transform(_, id) { if (fileRegex.test(id)) { return await getTransformedVueSrc(id) } }, async handleHotUpdate(ctx) { const { file } = ctx if (fileRegex.test(file)) { const code = await getTransformedVueSrc(file) if (code === undefined) return [] const { handleHotUpdate } = vuePlugin if (typeof handleHotUpdate === 'function') { return handleHotUpdate({ ...ctx, read: () => code }) } else if (handleHotUpdate?.handler) { return handleHotUpdate.handler({ ...ctx, read: () => code }) } return [] } } } const cssrPlugin = cssRenderPlugin() return [demoIndexTransFormPlugin, naiveDemoVitePlugin, vuePlugin, cssrPlugin] } ================================================ FILE: build/vite-plugin-index-tranform.ts ================================================ import type { Plugin } from 'vite' import { env } from 'node:process' function transformIndexHtml(code: string): string { switch (env.NODE_ENV) { case 'production': return code.replace(/__INDEX__/, 'demo/index.prod.js') default: return code.replace(/__INDEX__/, 'demo/index.dev.js') } } export const demoIndexTransFormPlugin: Plugin = { name: 'demo-transform', transformIndexHtml: { order: 'pre', handler: (code: string) => { return transformIndexHtml(code) } } } ================================================ FILE: demo/Caveat.md ================================================ # Caveat Code is messy here because I've no time to refactor it. It works matter. ================================================ FILE: demo/Site.vue ================================================ ================================================ FILE: demo/SiteHeader.vue ================================================ ================================================ FILE: demo/SiteRoot.vue ================================================ ================================================ FILE: demo/index.dev.js ================================================ import { createApp } from 'vue' import naive, { NThemeEditor } from '../src/index' import createDemoRouter from './routes/router' import { routes } from './routes/routes' import { installDemoComponents } from './setup' import SiteRoot from './SiteRoot.vue' const app = createApp(SiteRoot) const router = createDemoRouter(app, routes) app.use(router) app.use(naive) app.component('NThemeEditor', NThemeEditor) installDemoComponents(app) router.isReady().then(() => { app.mount('#app') }) ================================================ FILE: demo/index.prod.js ================================================ import naive, { NThemeEditor } from 'naive-ui' import { createApp } from 'vue' import createDemoRouter from './routes/router' import { routes } from './routes/routes' import { installDemoComponents } from './setup' import SiteRoot from './SiteRoot.vue' const app = createApp(SiteRoot) const router = createDemoRouter(app, routes) app.use(router) app.use(naive) app.component('NThemeEditor', NThemeEditor) installDemoComponents(app) router.isReady().then(() => { app.mount('#app') }) ================================================ FILE: demo/pages/Layout.vue ================================================ ================================================ FILE: demo/pages/docs/changelog/enUS/index.vue ================================================ ================================================ FILE: demo/pages/docs/changelog/zhCN/index.vue ================================================ ================================================ FILE: demo/pages/docs/common-issues/enUS/index.md ================================================ # Common Issues ## 1. The difference between @update:xxx and on-update:xxx ### Case 1 If you are not using `v-model:xxx` with `on-update:xxx` on the same component, there is no difference between `@update:xxx` and `on-update:xxx` when used in templates. In Naive UI, all API documents use the `on-update:xxx` format, because `@` is just a shorthand provided by Vue. If you prefer camelCase, you can use `onUpdate:xxx`. If you are using JSX, you can use `onUpdateXxx` (all `onUpdate:xxx` have an equivalent implementation of `onUpdateXxx`). ### Case 2 If you are using `v-model:xxx`, you should use `@update:xxx` on the same component. ✅ example ``. ❌ example ``. That is because `v-model:value="xxx"` will be transformed to `:onUpdate:value="xxx"`. If you are using `@update:value="yyy"` together, it will be `:onUpdate:value="[xxx, yyy]"`, and then Naive UI would take care of if. However if you are using `on-update:value="yyy"`, Vue would generate code like `:onUpdate:value="xxx" :on-update-value="yyy"` and the second one would override the first one in Vue runtime. The `v-model:value` would be broken. If you have any problems, feel free to create a PR or issue on GitHub. ## 2. How to use in Single File Component(SFC)? please see [Usage in SFC](usage-sfc) ================================================ FILE: demo/pages/docs/common-issues/zhCN/index.md ================================================ # 常见问题 ## 1. @update:xxx 和 on-update:xxx 的区别 ### 情况 1 如果你没有在同一个组件上同时使用 `v-model:xxx` 和 `on-update:xxx`,`@update:xxx` 和 `on-update:xxx` 在模版中使用时没有任何区别。 在 Naive UI 中,全部的 API 文档使用 `on-update:xxx` 格式,因为 `@` 只是 Vue 提供的一种简写。 如果你偏爱 camelCase,可以使用 `onUpdate:xxx`。 如果你在使用 JSX,可以使用 `onUpdateXxx`(所有的 `onUpdate:xxx` 都有一个 `onUpdateXxx` 的对等实现)。 ### 情况 2 如果你在一个组件上使用了 `v-model:xxx`,你应该使用 `@update:xxx`。 ✅ 例子 ``。 ❌ 例子 ``。 这是因为 `v-model:value="xxx"` 会被转化为 `:onUpdate:value="xxx"`。如果你同时使用了 `@update:value="yyy"`,他们会被转化为 `:onUpdate:value="[xxx, yyy]"`,然后 Naive UI 会来处理这种情况。 然而如果你使用了 `on-update:value="yyy"`,Vue 会生成类似于 `:onUpdate:value="xxx" :on-update:value="yyy"` 的代码,然后第二个属性会在运行时覆盖掉第一个,`v-model:value` 会崩掉。 如果你发现任何问题,欢迎在 GitHub 上提交 issue 和 PR。 ## 2. 如何在单文件组件(SFC - Single File Component)中使用? 详见 [在 SFC 中使用](usage-sfc) ================================================ FILE: demo/pages/docs/community/enUS/index.md ================================================ # Third-Party Libraries Naive UI is a high-quality Vue component library of unified design specifications. We prefer to provide only UI components with a unified specification and visual presentation. So we recommend the following great resources from the community that complement Naive UI. If you want to contribute excellent resources, you can create a pull request of this page in naive-ui's github repo. | Resources | Descriptions | | --- | --- | | [Naive UI Admin](https://github.com/jekip/naive-ui-admin) | A free open source out-of-box UI solution for enterprise applications. | | [Admin Work](https://github.com/qingqingxuan/admin-work) | A free open source, powerful, easy to use, beautiful back-office management system solution. | | [Soybean Admin](https://github.com/honghuangdc/soybean-admin) | A beautiful vue admin template, based on Vue3 + Vite + Naive UI + TypeScript. | | [Rengar Admin](https://github.com/RengarJS/rengar-admin) | A mid-backend management system template based on Vue 3.5 + Vite 7 + TypeScript + Naive UI + Pinia + UnoCSS, which is concise without excessive encapsulation and has simple and easy-to-understand code. | | [GoView](https://github.com/dromara/go-view) | A free open source and powerful yet accessible data visualization tool. | | [Vue Naive Admin](https://github.com/zclzone/vue-naive-admin) | A lightweight backend management template base on Vue3 + Vite4 + Pinia + Unocss + Naive UI. | | [Vue Bag Admin](https://vite.itnavs.com/admin/) | 🎉vue-bag-admin,Using Vite4, Vue3, TypeScript, JavaScript construction, support a variety of writing and call, a complete framework system, responsive background management system | | [Celeris Web](https://github.com/kirklin/celeris-web) | Celeris Web, based on Vue 3 and Vite, is a free and open-source front-end framework with Naive UI components and TypeScript support, featuring a Monorepo structure. It also integrates with OpenAI, providing natural language processing capabilities for modern web development. | | [Nova admin](https://github.com/chansee97/nova-admin) | a clean and concise back-end management template based on Vue3, Vite5, Typescript, and Naive UI. It implements complete functionality in a simple way, while also considering code standards, readability, and avoiding excessive encapsulation to facilitate secondary development. | | [chatgpt-web](https://github.com/Chanzhaoyu/chatgpt-web) | ChatGPT demo web page built with Express and Vue3 | | [pro-naive-ui](https://github.com/Zheng-Changfu/pro-naive-ui) | Based on Naive UI secondary encapsulation, more functions are extended on the original components | | [vue3-dynamic-form](https://github.com/yayaluoya/vue3-dynamic-form) | A dynamic form designer based on Vue3, Naive UI, to implement a lot of basic controls, can be very convenient to customize the control. | | [astral-3d-editor](https://github.com/mlt131220/Astral3DEditor) | 3D scene building tool based on Vue3 + Naive UI + ThreeJS + Vite5 + Typescript, supporting CAD & BIM analysis, including weather system, particle system, plug-in center... | | [it-tools](https://github.com/CorentinTh/it-tools) | Developed based on Vue3 and the Naive UI component library, it brings together a variety of practical online tools | | [Lithe Admin](https://github.com/tenianon/lithe-admin) | A lightweight and elegant backend management template, built using Vue3 + Naive UI + Vite + TypeScript + Pinia, featuring a Tailwind CSS Color color scheme and a frosted texture design. | ================================================ FILE: demo/pages/docs/community/zhCN/index.md ================================================ # 社区精选资源 Naive UI 是统一设计规范的高质量 Vue 组件库,我们倾向于只提供符合统一规范且带有视觉展现的 UI 组件。所以我们推荐以下社区已有的优秀资源,与 Naive UI 形成互补。 想贡献优秀资源,可直接在项目中 PR 此页面。 | 资源 | 描述 | | --- | --- | | [Naive UI Admin](https://github.com/jekip/naive-ui-admin) | 免费开源的中后台模板 | | [Admin Work](https://github.com/qingqingxuan/admin-work) | 一款免费开源、功能强大、方便易用、漂亮的中后台管理系统模板 | | [Soybean Admin](https://github.com/honghuangdc/soybean-admin) | 一个基于 Vue3 + Vite + Naive UI + TypeScript 的漂亮清爽的中后台模版 | | [Rengar Admin](https://github.com/RengarJS/rengar-admin) | 基于 Vue 3.5 + Vite 7 + TypeScript + Naive UI + Pinia + UnoCSS 的中后台管理系统模板,简洁不过度封装,代码简单易懂 | | [GoView](https://gitee.com/dromara/go-view) | 开源、精美、便捷的「数据可视化」低代码开发平台 | | [Vue Naive Admin](https://github.com/zclzone/vue-naive-admin) | 基于 Vue3 + Vite4 + Pinia + Unocss + Naive UI 的轻量级后台管理模板 | | [Vue Bag Admin](https://vite.itnavs.com/admin/) | 🎉vue-bag-admin,采用Vite4、Vue3、TypeScript、JavaScript构建,支持多种写法和调用,完整的框架体系,响应式中后台管理系统 | | [Celeris Web](https://github.com/kirklin/celeris-web) | 一个基于 Vue 3 和 Vite 的免费开源前端框架,具有 Naive UI 组件和 TypeScript 支持,采用 Monorepo 结构。它还集成了 OpenAI,为现代 Web 开发提供自然语言处理能力。 | | [Nova admin](https://github.com/chansee97/nova-admin) | 一个基于Vue3、Vite5、Typescript、Naive UI, 简洁干净后台管理模板,用简单的方式实现完整功能,并尽可能的考虑代码规范,易读易理解无过度封装,方便二次开发。 | | [chatgpt-web](https://github.com/Chanzhaoyu/chatgpt-web) | 使用 Express 和 Vue3 搭建的 ChatGPT 演示网页 | | [pro-naive-ui](https://github.com/Zheng-Changfu/pro-naive-ui) | 基于 Naive UI 二次封装, 基于原有组件扩展了更多功能 | | [vue3-dynamic-form](https://github.com/yayaluoya/vue3-dynamic-form) | 一个基于 Vue3、Naive UI 的动态表单设计器,实现了很多基础控件,能非常方便的自定义控件。 | | [astral-3d-editor](https://github.com/mlt131220/Astral3DEditor) | 基于 Vue3 + Naive UI + ThreeJS + Vite5 + Typescript 的三维场景构建工具,支持 CAD & BIM 解析,内含天气系统、粒子系统、插件中心... | | [it-tools](https://github.com/CorentinTh/it-tools) | 基于 Vue3 和 Naive UI 组件库开发,汇集了多种实用的在线工具 | | [Lithe Admin](https://github.com/tenianon/lithe-admin) | 一个轻盈而优雅的后台管理模板,基于 Vue3 + Naive UI + Vite + TypeScript + Pinia 构建,采用 Tailwind CSS Color 配色方案和磨砂纹理质感的设计 | | [DVHA Pro](https://github.com/duxweb/dvha) | 一款基于 Vue 3 的无头(Headless)中后台前端开发框架,搭配了 Naive UI 封装了开箱即用的异步能力,同时增加了异步渲染基座模式,无需编译即可集成到任何后端使用。 | ================================================ FILE: demo/pages/docs/controlled-uncontrolled/enUS/index.md ================================================ # Controlled manner & uncontrolled manner A component's manner can be controlled or uncontrolled. Uncontrolled manner means you only listen to its change but not control its value. Controlled manner means you control the component's value. ## Uncontrolled manner In this situation, you don't set ``'s `value` but only listen to its change. Component's value will be controlled by itself. ```html ``` ## Controlled manner In the situation, you listen to component's value & change at the same time. If you don't update `value`, component's value won't be changed. Its value is controlled by you. ```html ``` ### `v-model` A component with `v-model` works in controlled manner, since `v-model` is the same as `:model-value` and `@update:model-value`. ## Uncontrolled manner in naive-ui Different library has different behavior on how to distinguish controlled or uncontrolled manner. In naive-ui, if `value` is `undefined` or not passed, it will be uncontrolled. That is to say, if you set a component's value to `undefined` won't clear it but transform it from controlled manner to uncontrolled manner. If you need to clear it, at most time you can use `null`. ### Not only `value` Any props pair like `xxx` & `@update:xxx` can work both in controlled manner or uncontrolled manner. ================================================ FILE: demo/pages/docs/controlled-uncontrolled/zhCN/index.md ================================================ # 受控模式与非受控模式 一个组件的行为可以分为受控模式和非受控模式两种。非受控模式指的是只监听组件的变化,而不去控制组件的 value,受控模式指的是控制组件的值。 ## 非受控模式 在这种情况下,你不能控制 `` 的 `value`,而只能监听它的变化,组件值的变化由组件自身控制。 ```html ``` ## 受控模式 在这种情况下,你既监听了组件的变化,然后也控制了组件的值。如果你不更新 `value`,那么组件的值不会改变,组件值的变化由你控制。 ```html ``` ### `v-model` `v-model` 控制的组件在受控模式下,因为 `v-model` 等同于 `:model-value` 和 `@update:model-value` 的组合。 ## naive-ui 中的受控模式 不同的组件库区分受控与非受控模式的区别是不同的。在 naive-ui 中,只要 `value` 是 `undefined` 或者根本没有传,那么组件的值会是非受控的。也就是说你将一个组件的值设为 `undefined` 并不能清空它,只会改变它的控制模式。一般情况下清空可以使用 `null`。 ### 不止 `value` 任何 `xxx` 与 `@update:xxx` 的属性对都可以有受控和非受控模式。 ================================================ FILE: demo/pages/docs/customize-theme/enUS/index.md ================================================ # Customizing theme Naive-ui provides `n-config-provider` to customize the theme. By default all of the components are light themed, without any configuration. To learn more about `n-config-provider`, see [Config Provider](../components/config-provider). ## Use dark theme Set `n-config-provider`'s `theme` prop to `darkTheme` imported from naive-ui to set dark theme inside `n-config-provider`. If `theme` is `undefined` it won't affect the theme of components inside. ```html ``` You may need to include `` if you want to apply the dark theme globally, not just to the components. To learn more, see [n-global-style](../docs/customize-theme#Sync-style-of-the-body-element). ## Get theme vars Whether it's the default light theme (`lightTheme`), the modified dark theme (`darkTheme`), or the custom theme we adjusted, you can get theme variables within the scope of the theme using [useThemeVars](./theme#use-theme-vars). ## Customizing theme vars (design tokens) No CSS (Scss, Less) needed. The configured global theme variables will overwrite the theme variables that take effect on descendant components. Set `n-config-provider`'s `theme-overrides` to customize theme vars. Naive-ui exports type `GlobalThemeOverrides` to help you define `theme-overrides`. For available vars please follow the type hint of `GlobalThemeOverrides`. If you want to view more theme variables, you can view them in the edit button at the bottom right corner of the Naive UI homepage. You can modify the corresponding theme variable, you can get the themeOverrides object after export. ```html ``` ## Customizing theme vars in TypeScript If you are using ts to write code, this one is more suitable for you. ```html ``` ## Customizing component theme vars The use of component theme variables is the same as the use of global theme variables, and the component theme variables will override the global theme variables. ```html ``` ## Customizing theme vars under different themes If you want to use different theme variables on light and dark theme at the same time, you can take a look at this. ```html ``` ## Use the peers vars In many cases, another component will be reused inside a component, so the theme variable of peers appears. The theme variables related to peers have not been exposed yet. Use `GlobalThemeOverrides` to view the peers variables of the corresponding component. The specific available peers will be updated later. ```html ``` ## Sync style of the body element For the following reasons, you may need to set some styles on `document.body`. 1. Naive-ui will mount some global style that is unresponsive (to theme, not media query). For example `font-family`. The style works fine by default, however they won't change when theme is changed. 2. `n-config-provider` can't sync global style (for example, body's background color) outside it. You can use `n-global-style` to sync common global style to the body element. In the following example, `n-global-style` will sync the theme provided by `n-config-provider` to `document.body`. ```html ``` ## Theme editor Naive-ui provides a theme editor to help you edit theme and export the corresponding configuration. It can be placed inside `n-config-provider`. The theme editor is not included in global installation (`app.use(naive)`). You need to import it explicitly to use it. ```html ``` ================================================ FILE: demo/pages/docs/customize-theme/zhCN/index.md ================================================ # 调整主题 Naive UI 通过使用 `n-config-provider` 调整主题。 默认情况下所有组件均为亮色主题,无需任何配置。 了解更多关于 `n-config-provider` 的信息,参见 [全局化配置](../components/config-provider)。 ## 使用暗色主题 将 `n-config-provider` 的 `theme` 设为从 naive-ui 导入的 `darkTheme` 来设定暗色主题。 若 `theme` 为 `undefined` 则不会影响内部组件的主题。 ```html ``` 如果你需要给 body 元素也应用 dark theme,而不仅仅是组件,你可能需要使用 ``。更多细节请参考 [n-global-style](../docs/customize-theme#Sync-style-of-the-body-element)。 ## 获取主题变量 无论是默认的亮色主题(`lightTheme`),还是修改后的暗色主题(`darkTheme`),亦或我们通过调整得到的自定义主题,在该主题生效范围内的组件中都可以通过 [useThemeVars](./theme#use-theme-vars) 来获取主题变量。 ## 调整主题变量 你不需要写任何 CSS(Scss、Less...)。 配置的全局主题变量会对后代组件生效的主题变量覆盖。 通过设定 `n-config-provider` 的 `theme-overrides` 来调整主题变量。naive-ui 导出了 `GlobalThemeOverrides` 类型帮助你定义主题。 具体可使用变量请参考 `GlobalThemeOverrides` 类型提示。 如果想要查看更多的主题变量,可在 Naive UI 主页的右下角的 edit 按钮查看。 可以修改对应的主题变量,导出后可以拿到 themeOverrides 对象。 ```html ``` ## TS 下使用主题变量 如果你正在使用 ts 写代码,这块比较适合你。 ```html ``` ## 调整组件主题变量 组件主题变量使用方法同全局主题变量使用方法,并且组件主题变量会覆盖全局主题变量。 ```html ``` ## 不同主题下调整主题变量 如果你想要在亮色和暗色上同时使用不同的主题变量,可以来看看这个。 ```html ``` ## 使用 peers 主题变量 很多时候组件内部都会复用另一个组件,因此出现了 peers 的主题变量。 peers 相关的主题变量还没有暴露,使用 `GlobalThemeOverrides` 可以查看对应组件的 peers 变量。 具体哪些可使用的 peers 后续会更新。 ```html ``` ## 同步 body 元素的样式 出于以下原因,你可能需要将某些样式设定在 `document.body` 上。 1. naive-ui 会设定一些非响应式的全局样式(例如字体),它们在默认状况下工作良好,但是不能响应主题的变化。 2. `n-config-provider` 无法将全局样式同步到它以外的地方(例如 body 背景色)。 通过使用 `n-global-style` 可以将常见的全局样式同步到 body 上。在下面的例子中,`n-global-style` 会将 `n-config-provider` 提供的主题同步到 `document.body` 上。 ```html ``` ## 主题编辑器 naive-ui 提供主题编辑器帮助你方便的编辑主题并导出对应配置。它可以被嵌套于 `n-config-provider` 中。 主题编辑器不包含在全局安装中(`app.use(naive)`)。你需要显式引入来使用它。 ```html ``` ================================================ FILE: demo/pages/docs/experimental-features/enUS/index.md ================================================ # Experimental Features The following features are unstable. Use them if you really need and perpare to follow the API changes. ## Use TuSimple Theme ```html ``` ================================================ FILE: demo/pages/docs/experimental-features/zhCN/index.md ================================================ # 试验性特性 下列的所有功能都是不稳定的。只在真的需要的时候再使用他们,API 有可能在未来被改变。 ## 使用图森主题 ```html ``` ================================================ FILE: demo/pages/docs/fonts/enUS/index.md ================================================ # Configuring Fonts Naive UI works with [vfonts](https://github.com/07akioni/vfonts). You can use fonts from `vfonts` easily which includes general fonts and monospace fonts. Just import fonts from in your app's entry file. Then it will work. ```js // entry js file of your app // ... // General Font import 'vfonts/Lato.css' // Monospace Font import 'vfonts/FiraCode.css' // then it works // ... ``` Note: Different fonts from vfonts have different font weights. If you want to use `Lato` or `OpenSans` you need to configure the global font weight of naive-ui. ```html ``` ## Change Global Fonts by Customizing Theme If you don't use `vfont` and want change global fonts by customzing theme, you need to use `n-global-style` to achieve that. Components' font won't be responsive to `theme-overrides` without `n-global-style`. Note: Make `vfont` work without `n-global-style` is a compromised design (I think this is a correct behavior). In the next major version, global reset style won't contain font related style. They'll be all put inside `n-global-style` component. ================================================ FILE: demo/pages/docs/fonts/zhCN/index.md ================================================ # 配置字体 Naive UI 可以和 [vfonts](https://github.com/07akioni/vfonts) 配合,你可以简单的引入 `vfonts` 中的字体,包含常规字体和等宽字体。 只需要在你 App 的入口文件导入字体,即可调整 Naive UI 的字体。 ```js // 你 App 的入口 js 文件 // ... // 通用字体 import 'vfonts/Lato.css' // 等宽字体 import 'vfonts/FiraCode.css' const app = createApp() app.use(naive) // ... ``` 注意:不同 vfonts 字体提供的字重不同,在使用 `Lato`、`OpenSans` 的时候你需要全局调整 naive-ui 的字重配置。 ```html ``` ## 通过定制主题修改全局字体 如果你不打算使用 `vfonts` 并且想要通过主题调整修改其为别的字体,你需要使用 `n-global-style` 来做到这一点。在不使用 `n-global-style` 的情况下组件不会响应 `theme-overrides` 中的字体变更。 题外话:不使用 `n-global-style` 就能让 `vfonts` 直接生效是一个设计上的妥协,在下个大的版本默认的全局 reset 样式将不再带有字体相关的样式,而是全部置于 `n-global-style` 组件中。 ================================================ FILE: demo/pages/docs/i18n/enUS/index.md ================================================ # Internationalization Naive-ui provides `n-config-provider` to customize the internationalization. By default, all components are in English. To learn more about `n-config-provider`, see [Config Provider](../components/config-provider). ## Configure Set `n-config-provider`'s `locale` prop to `enUS` imported from naive-ui to set Chinese theme inside `n-config-provider`. Set `n-config-provider`'s `date-locale` prop to `dateEnUS` imported from naive-ui to set Chinese theme's date inside `n-config-provider`. ```html ``` ## Supported languages PRs are welcomed for locales that are not supported yet! The following list is sorted by 'Config' column. | Language | Config | Date config | Version | | -------------------------- | ------ | ----------- | ------- | | Arabic (العربية) | arDZ | dateArDZ | 2.34.0 | | Azerbaijani (Azərbaycanca) | azAZ | dateAzAZ | 2.39.0 | | Czech (Czechia) | csCZ | dateCsCz | 2.38.2 | | Danish (Denmark) | daDK | dateDaDK | 2.43.0 | | German (Germany) | deDE | dateDeDE | | | English (British) | enGB | dateEnGB | 2.25.1 | | English | enUS | dateEnUS | | | Esperanto | eo | dateEo | 2.25.2 | | Spanish (Argentina) | esAR | dateEsAR | 2.24.2 | | Estonian | etEE | dateEtEE | 2.38.0 | | Persian | faIR | dateFaIR | 2.34.4 | | French | frFR | dateFrFR | | | Bahasa Indonesia | idID | dateIdID | | | Italiano | itIT | dateItIT | 2.24.2 | | Japanese | jaJP | dateJaJP | | | Khmer (Cambodia) | kmKH | dateKmKH | 2.41.0 | | Korean (South Korea) | koKR | dateKoKR | 2.28.1 | | Norwegian Bokmål (Norway) | nbNO | dateNbNO | | | Dutch (Netherlands) | nlNL | dateNlNL | 2.29.0 | | Polish (Poland) | plPL | datePlPL | 2.25.2 | | Portuguese (Brazil) | ptBR | datePtBR | 2.28.1 | | Russian | ruRU | dateRuRU | | | Slovak | skSK | dateSkSK | 2.25.3 | | Swedish | svSE | dateSvSE | 2.35.0 | | Thai (Thailand) | thTH | dateThTH | 2.27.0 | | Turkish | trTR | dateTrTR | 2.34.0 | | Uyghur (China) | ugCN | dateUgCN | | | Ukrainian | ukUA | dateUkUA | | | Uzbek (Uzbekistan) | uzUZ | dateUzUZ | 2.39.0 | | Vietnamese (Vietnam) | viVN | dateViVN | 2.30.7 | | Chinese (Simplified) | zhCN | dateZhCN | | | Chinese (Traditional) | zhTW | dateZhTW | | ## Customize the existing locale You can use `createLocale` to customize the existing locale. ```html ``` ================================================ FILE: demo/pages/docs/i18n/zhCN/index.md ================================================ # 国际化 Naive-ui 通过使用 `n-config-provider` 调整语言,默认情况下所有组件均为英语。 了解更多关于 `n-config-provider` 的信息,参见 [全局化配置](../components/config-provider)。 ## 配置 将 `n-config-provider` 的 `locale` 设为从 naive-ui 导入的 `zhCN` 来设定全局中文。 将 `n-config-provider` 的 `date-locale` 设为从 naive-ui 导入的 `dateZhCN` 来设定全局日期中文。 ```html ``` ## 支持语言 欢迎提交 PR 来支持尚未支持的语言。 以下列表依据“配置”列排序。 | 语言 | 配置 | 日期配置 | 版本 | | ------------------ | ---- | -------- | ------ | | 阿拉伯语 | arDZ | dateArDZ | 2.34.0 | | 阿塞拜疆语 | azAZ | dateAzAZ | 2.39.0 | | 捷克语(捷克) | csCZ | dateCsCz | 2.38.2 | | 丹麦 | daDK | dateDaDK | 2.43.0 | | 德语 | deDE | dateDeDE | | | 英国英语 | enGB | dateEnGB | 2.25.1 | | 英语 | enUS | dateEnUS | | | 世界语 | eo | dateEo | 2.25.2 | | 西班牙语(阿根廷) | esAR | dateEsAR | 2.24.2 | | 爱沙尼亚语 | etEE | dateEtEE | 2.38.0 | | 波斯语 | faIR | dateFaIR | 2.34.4 | | 法语 | frFR | dateFrFR | | | 印度尼西亚语 | idID | dateIdID | | | 意大利语 | itIT | dateItIT | 2.24.2 | | 日语 | jaJP | dateJaJP | | | 高棉语(柬埔寨) | kmKH | dateKmKH | 2.41.0 | | 韩语 | koKR | dateKoKR | 2.28.1 | | 书面挪威语 | nbNO | dateNbNO | | | 荷兰语(荷兰) | nlNL | dateNlNL | 2.29.0 | | 波兰语(波兰) | plPL | datePlPL | 2.25.2 | | 葡萄牙语 (巴西) | ptBR | datePtBR | 2.28.1 | | 俄罗斯语 | ruRU | dateRuRU | | | 斯洛伐克语 | skSK | dateSkSK | 2.25.3 | | 瑞典語 | svSE | dateSvSE | 2.35.0 | | 泰语(泰国) | thTH | dateThTH | 2.27.0 | | 土耳其语 | trTR | dateTrTR | 2.34.0 | | 维吾尔语 | ugCN | dateUgCN | 2.41.0 | | 乌克兰语 | ukUA | dateUkUA | | | 乌兹别克语 | uzUZ | dateUzUZ | 2.39.0 | | 越南语(越南) | viVN | dateViVN | 2.30.7 | | 简体中文 | zhCN | dateZhCN | | | 繁体中文 | zhTW | dateZhTW | | ## 在现有国际化基础上调整 你可以使用 `createLocale` 在现有国际化基础上调整。 ```html ``` ================================================ FILE: demo/pages/docs/import-on-demand/enUS/index.md ================================================ # Import on Demand (Tree Shaking) Naive UI supports tree shaking for components, locales and themes. By default the component theme is light, locale is enUS, and no extra imports are needed. For more info about theming, see [Customizing Theme](customize-theme). ## Import Directly ```html ``` ## Auto Import You can use the `unplugin-auto-import` plugin to automatically import APIs. If you develop using SFC, you can use the `unplugin-vue-components` plugin to automatically import components on demand.The plugin will automatically parse the components used in the template and import the components. ```ts import vue from '@vitejs/plugin-vue' import AutoImport from 'unplugin-auto-import/vite' import { NaiveUiResolver } from 'unplugin-vue-components/resolvers' import Components from 'unplugin-vue-components/vite' // vite.config.ts import { defineConfig } from 'vite' // https://vitejs.dev/config/ export default defineConfig({ plugins: [ vue(), AutoImport({ imports: [ 'vue', { 'naive-ui': [ 'useDialog', 'useMessage', 'useNotification', 'useLoadingBar' ] } ] }), Components({ resolvers: [NaiveUiResolver()] }) ] }) ``` ## Install on Demand Globally ```js import { // create naive ui create, // component NButton } from 'naive-ui' import { createApp } from 'vue' const naive = create({ components: [NButton] }) const app = createApp() app.use(naive) ``` After the installation, you can use the components you installed in SFC like this. ```html ``` ================================================ FILE: demo/pages/docs/import-on-demand/zhCN/index.md ================================================ # 按需引入(Tree Shaking) Naive UI 支持 tree shaking,组件、语言、主题均可 tree-shaking。 默认情况组件主题为亮色,语言为英文,无需额外导入。 了解更多关于主题设定的信息,参见[调整主题](customize-theme)。 ## 手动引入 ```html ``` ## 自动引入 可以使用 `unplugin-auto-import` 插件来自动导入 API。 如果使用模板方式进行开发,可以使用 `unplugin-vue-components` 插件来按需自动加载组件,插件会自动解析模板中的使用到的组件,并导入组件。 ```ts import vue from '@vitejs/plugin-vue' import AutoImport from 'unplugin-auto-import/vite' import { NaiveUiResolver } from 'unplugin-vue-components/resolvers' import Components from 'unplugin-vue-components/vite' // vite.config.ts import { defineConfig } from 'vite' // https://vitejs.dev/config/ export default defineConfig({ plugins: [ vue(), AutoImport({ imports: [ 'vue', { 'naive-ui': [ 'useDialog', 'useMessage', 'useNotification', 'useLoadingBar' ] } ] }), Components({ resolvers: [NaiveUiResolver()] }) ] }) ``` ## 按需全局安装组件(手动) ```js import { // create naive ui create, // component NButton } from 'naive-ui' import { createApp } from 'vue' const naive = create({ components: [NButton] }) const app = createApp() app.use(naive) ``` 安装后,你可以这样在 SFC 中使用你安装的组件。 ```html ``` ================================================ FILE: demo/pages/docs/installation/enUS/index.md ================================================ # Installation > Please note that naive-ui only supports Vue3. If you are using Vue2, you may look at other libraries. ## npm Use npm to install. ```bash npm i -D naive-ui ``` ## UMD Please refer to [Using UMD](umd). ## Fonts ```bash npm i -D vfonts ``` ## Icons naive-ui recommends using [xicons](https://www.xicons.org) as icon library. ## Design Resources ```component ArrowDownload16Regular: import ArrowDownload16Regular from '@vicons/fluent/ArrowDownload16Regular' ``` ================================================ FILE: demo/pages/docs/installation/zhCN/index.md ================================================ # 安装 > 注意,naive-ui 仅支持 Vue3。如果你在使用 Vue2,可以去看看别的库。 ## npm 使用 npm 安装。 ```bash npm i -D naive-ui ``` ## UMD 参考 [使用 UMD](umd)。 ## 字体 ```bash npm i -D vfonts ``` ## 图标 naive-ui 建议使用 [xicons](https://www.xicons.org) 作为图标库。 ## 设计资源 ```component ArrowDownload16Regular: import ArrowDownload16Regular from '@vicons/fluent/ArrowDownload16Regular' ``` ================================================ FILE: demo/pages/docs/introduction/enUS/index.md ================================================ # Naive UI Naive UI is a Vue 3 component library. To know how to install it, see [Installation](installation). It is fairly complete, themeable, written in typescript, not too slow. ## Fairly Complete There are more than 90 components. Hope they can help you write less code. What's more, they are all treeshakable. ## Theme Customizable We provide an advanced type safe theme system that is built with typescript. All you need is to provide a theme overrides object in JS. Then all the stuffs will be done by us. What's more, no less/sass/css variables, no webpack loaders are required. And you can try the theme editor on the bottom right of the page. ## Uses TypeScript All the stuff in Naive UI is written in TypeScript. It can work with your typescript project seamlessly. What's more, you don't need to import any CSS to use the components. ## Fast I try to make it not rather slow. At least select, tree, transfer, table and cascader work with virtual list. What's more, ..., no more. Just enjoy it. ## Community - [Discord](https://discord.gg/Pqv7Mev5Dd) - 钉钉一群 33482509 (已满) - 钉钉二群 35886835 (已满) - 钉钉三群 32377370 (已满) - 钉钉四群 8165002788 (已满) - 钉钉五群 31205022250 (已满) - 钉钉六群 62720001971 (已满) - 钉钉七群 172000005810 ## Contributing Please see [CONTRIBUTING.md](https://github.com/tusen-ai/naive-ui/blob/main/CONTRIBUTING.md). ## License Naive UI is licensed under the [MIT license](https://opensource.org/licenses/MIT). ================================================ FILE: demo/pages/docs/introduction/zhCN/index.md ================================================ # Naive UI Naive UI 是一个 Vue3 的组件库。 要了解如何安装,参见[安装](installation)。 它比较完整,主题可调,用 TypeScript 写的,快。 ## 比较完整 有超过 90 个组件,希望能帮你少写点代码。 顺便一提,它们全都可以 treeshaking。 ## 主题可调 我们提供了一个使用 TypeScript 构建的先进的类型安全主题系统。你只需要提供一个样式覆盖的对象,剩下的都交给我们。 顺便一提,不用 less、sass、css 变量,也不用 webpack 的 loaders。以及你可以试试右下角的主题编辑器。 ## 使用 TypeScript Naive UI 全量使用 TypeScript 编写,和你的 TypeScript 项目无缝衔接。 顺便一提,你不需要导入任何 CSS 就能让组件正常工作。 ## 快 我尽力让它不要太慢。至少 select、tree、transfer、table、cascader 都可以用虚拟列表。 顺便一提,...,没有顺便了。祝你使用愉快。 ## 社区 - [Discord](https://discord.gg/Pqv7Mev5Dd) - 钉钉一群 33482509 (已满) - 钉钉二群 35886835 (已满) - 钉钉三群 32377370 (已满) - 钉钉四群 8165002788 (已满) - 钉钉五群 31205022250 (已满) - 钉钉六群 62720001971 (已满) - 钉钉七群 172000005810 ## 贡献 请参考 [CONTRIBUTING.md](https://github.com/tusen-ai/naive-ui/blob/main/CONTRIBUTING.md)。 ## 许可 Naive UI 使用 [MIT license](https://opensource.org/licenses/MIT) 许可证书。 ================================================ FILE: demo/pages/docs/jsx/enUS/index.md ================================================ # JSX & TSX ## Enable JSX & TSX For how to enable JSX & TSX, please look at your toolchain's docs. ## Use Component We recommend importing components directly when using JSX. ```js import { NButton } from 'naive-ui' import { defineComponent } from 'vue' export default defineComponent({ render() { return {{ default: () => 'Star Kirby' }} } }) ``` ## Props look like @update:\* In naive-ui, all props look like `on-update:*` has a corresponding `onUpdate*` prop (since in JSX `on-update:*` and `onUpdate:*` are not valid prop names). If you find it doesn't exist, I must have forgotten to make it. Please create an issue or PR. For example, `` in template can be written in ``. ================================================ FILE: demo/pages/docs/jsx/zhCN/index.md ================================================ # JSX & TSX ## 启用 JSX & TSX 关于启用 JSX 和 TSX,请参考你使用的工具链的相关文档。 ## 使用组件 在 JSX 中,推荐以直接引入的形式使用组件。 ```js import { NButton } from 'naive-ui' import { defineComponent } from 'vue' export default defineComponent({ render() { return {{ default: () => 'Star Kirby' }} } }) ``` ## 形如 @update:\* 的 prop naive-ui 中,所有形如 `on-update:*` 的 prop 都有一个对应的 `onUpdate*` 属性可供使用(由于 JSX 自身的规定,`on-update:*` 和 `onUpdate:*` 不是合法的 prop 名称)。 如果你发现没有,那一定是我忘了写了,请提交一个 Issue 或者 PR。 例如,在模板中 `` 在 JSX 中可以写为 ``。 ================================================ FILE: demo/pages/docs/nuxtjs/enUS/index.md ================================================ # Nuxt.js ## Caveat This document pertains to SSR (Server-Side Rendering). Please familiarize yourself with the [SSR Caveats](ssr#Caveat) before proceeding. ## Nuxt.js Demo You can refer to [example](https://github.com/07akioni/naive-ui-nuxt-demo). ## Using Nuxt Module This is the same approach which previous demo uses. Install the [module](https://github.com/07akioni/nuxtjs-naive-ui) to your Nuxt application with one command: ```bash # npm npx nuxi module add nuxtjs-naive-ui # pnpm pnpm dlx nuxi module add nuxtjs-naive-ui ``` ## Using Auto Import in Nuxt You can also use the `unplugin-auto-import` plugin to automatically import APIs and the `unplugin-vue-components` plugin to automatically import components on demand. In this case, the `nuxt.config.ts` file will have a few additional configuration lines compared to the example above. ```ts import AutoImport from 'unplugin-auto-import/vite' import { NaiveUiResolver } from 'unplugin-vue-components/resolvers' import Components from 'unplugin-vue-components/vite' // https://nuxt.com/docs/api/configuration/nuxt-config export default defineNuxtConfig({ modules: ['nuxtjs-naive-ui'], vite: { plugins: [ AutoImport({ imports: [ { 'naive-ui': [ 'useDialog', 'useMessage', 'useNotification', 'useLoadingBar' ] } ] }), Components({ resolvers: [NaiveUiResolver()] }) ] } }) ``` ================================================ FILE: demo/pages/docs/nuxtjs/zhCN/index.md ================================================ # Nuxt.js ## 注意 本文档涉及到 SSR,请先了解[SSR 的注意事项](ssr#注意)。 ## Nuxt.js 示例 参考[例子](https://github.com/07akioni/naive-ui-nuxt-demo)。 ## 使用 Nuxt Module 这和上一个示例使用的是同样的方式。 在你的 Nuxt 应用中使用下列命令安装此[模块](https://github.com/07akioni/nuxtjs-naive-ui): ```bash # npm npx nuxi module add nuxtjs-naive-ui # pnpm pnpm dlx nuxi module add nuxtjs-naive-ui ``` ## 在 Nuxt 中使用自动引入 同样可以使用 `unplugin-auto-import` 插件来自动导入 API,使用 `unplugin-vue-components` 插件来按需自动加载组件。在这种情况下,`nuxt.config.ts` 会比上面的例子多几行配置。 ```ts import AutoImport from 'unplugin-auto-import/vite' import { NaiveUiResolver } from 'unplugin-vue-components/resolvers' import Components from 'unplugin-vue-components/vite' // https://nuxt.com/docs/api/configuration/nuxt-config export default defineNuxtConfig({ modules: ['nuxtjs-naive-ui'], vite: { plugins: [ AutoImport({ imports: [ { 'naive-ui': [ 'useDialog', 'useMessage', 'useNotification', 'useLoadingBar' ] } ] }), Components({ resolvers: [NaiveUiResolver()] }) ] } }) ``` ================================================ FILE: demo/pages/docs/ssr/enUS/index.md ================================================ # Server-Sider Rendering Since naive-ui is using CSS in JS, in SSR mode it needs some extra configuration. ## Caveat When using SSR under any framework, it is essential to ensure that the project meets the following conditions: 1. During the build process, any direct or indirect references to the `@css-render/*` and `css-render` packages must have a version of `>=0.15.14`. 2. During the build process, each direct or indirect reference to any `@css-render/*` and `css-render` package should ultimately point to a single target (a package should not have multiple versions or multiple copies of the same version). You can search for `css-render` in the lock file to check for duplicate packages. Failure to meet these conditions may result in SSR build failures. If you encounter issues due to this, you can resolve the problem by directing all related packages to the same version using the `resolution` field in the `package.json` file. ## Nuxt.js See [Nuxt.js](nuxtjs). ## Vitepress See [Vitepress](vitepress). ## Vite SSG/SSE See [Vite SSG/SSE](vite-ssge). ## Vite Example If you are using Vite, please see [example](https://github.com/07akioni/naive-ui-vite-ssr). ## Webpack Example If you are using Webpack, please see [example](https://github.com/tusen-ai/naive-ui/tree/main/playground/ssr). ## Inline Style Optimization By default, naive-ui bind inline theme style on components, it may increase SSR rendered HTML size. You may use `inline-theme-disabled` prop on `n-config-provider` to disable it. For pros & cons see `n-config-provider`'s doc. ## Known Issues The following components has some bugs in SSR scene, please avoid using them if possible. We will fix them gradually. - `n-scrollbar`, `n-data-table` (It works after vue >= 3.2.36) - `n-anchor` - `n-avatar-group` - `n-watermark` - `n-affix` - `n-transfer` ================================================ FILE: demo/pages/docs/ssr/zhCN/index.md ================================================ # 服务端渲染 Server-Sider Rendering 由于 naive-ui 在使用 CSS in JS,在 SSR 的情况下需要一些额外的配置。 ## 注意 无论在任何框架下使用 SSR,需要确保项目满足以下条件: 1. 构建时,任何被直接和间接引用的 `@css-render/*` 和 `css-render` 包版本都 `>=0.15.14` 2. 构建时,任何被直接和间接引用的每个 `@css-render/*` 和 `css-render` 包最终只都指向一个目标(一个包不会有多个版本,也不会有同一个版本的多个副本) 你可以在 lock file 中搜索 `css-render` 去检查是否有重复的包。 如果上述条件没有满足,可能会导致 SSR 构建失败。 如果因为这个原因遇到问题,你可以通过 `package.json` 中的 `resolution` 字段来让所有相关包指向同一个版本来解决问题。 ## Nuxt.js 参考 [Nuxt.js](nuxtjs)。 ## Vitepress 参考 [Vitepress](vitepress)。 ## Vite SSG/SSE 参考 [Vite SSG/SSE](vite-ssge)。 ## Vite 示例 如果你是用的是 Vite,请参考[例子](https://github.com/07akioni/naive-ui-vite-ssr)。 ## Webpack 示例 如果你使用的是 Webpack,请参考[例子](https://github.com/tusen-ai/naive-ui/tree/main/playground/ssr)。 ## 内联样式优化 默认情况下,naive-ui 会在组件上绑定 inline 主题样式,这可能会影响 SSR 的尺寸。你可以使用 `n-config-provider` 的 `inline-theme-disabled` 属性来优化,详细的优劣请参考 `n-config-provider` 的文档。 ## 已知问题 下列组件在 SSR 场景中存在一些 Bug,使用时请尽量规避,我们会逐步修复。 - `n-scrollbar`, `n-data-table`(vue 版本 >= 3.2.36 后没有问题) - `n-anchor` - `n-avatar-group` - `n-watermark` - `n-affix` - `n-transfer` ================================================ FILE: demo/pages/docs/style-conflict/enUS/index.md ================================================ # Potential Style Conflict ## Style Element Position Sometimes you want to control where the style element should be inserted. For example. If you have a tailwind reset style, you don't want it to be inserted after naive-ui's style. Since it may override button's style, etc. You can create a `` element inside `head` element, then naive-ui's style will be inserted right before it. Also, naive-ui uses [vueuc](https://github.com/07akioni/vueuc). If you need, its style can be controlled by `` (generally it's not needed). ```html ``` ## About Tailwind's Preflight Style Override You may find adding a meta tag to your static html files doesn't work (naive's style would still be overriden), since your toolchain may always insert tailwind's style at the end of the head tag. In this situation, you need to insert the meta tag dynamically right before the app is mounted. ```ts const meta = document.createElement('meta') meta.name = 'naive-ui-style' document.head.appendChild(meta) vueApp.mount('#app') ``` ## Disable Preflight Style (CSS Reset) To make naive-ui work for most users out of box, mounting any component of naive would create global preflight style. However it may not be expected. If you want to disable it, using `n-config-provider` like this. ```html ``` ================================================ FILE: demo/pages/docs/style-conflict/zhCN/index.md ================================================ # 潜在的样式冲突 ## 样式元素的位置 有时你可能希望控制样式元素插入的位置。 例如,如果你使用了 tailwind 的 reset 样式,你不希望它被插入 naive-ui 样式的后面,因为这可能会覆盖按钮等组件的样式。 你可以在 `head` 元素中加入一个 `` 元素,naive-ui 会把所有的样式刚好插入这个元素的前面。 同时,naive-ui 依赖 [vueuc](https://github.com/07akioni/vueuc)。如果你需要(通常应该不会),它的样式位置可以通过 `` 控制。 ```html ``` ## 关于 tailwind 的 preflight 样式 你可能会发现在静态 HTML 文件中加入 meta 标签没用(naive 的样式仍然可能被覆盖),因为你的工具链可能永远会把 tailwind 的样式插入 head 的尾部。这种情况下,你需要在 app 挂载之前动态的插入 meta 标签。 ```ts const meta = document.createElement('meta') meta.name = 'naive-ui-style' document.head.appendChild(meta) vueApp.mount('#app') ``` ## 禁用 preflight 样式 为了让多数用户直接无配置的使用 naive-ui,挂载任何一个组件都会创建全局的 CSS 样式。但是这样可能不是期望的行为,你可以使用 `n-config-provider` 来禁用这个行为。 ```html ``` ================================================ FILE: demo/pages/docs/supported-platforms/enUS/index.md ================================================ # Supported Platforms ## Browsers IE is not supported. Modern browsers such as `Edge`, `Firefox`, `Chrome`, `Safari` are tested on the latest 2 versions. No strict tests are being implemented on other versions due to the constraint of development resources, but naive-ui is expected to work on those browsers whose version is not too old (eg. in 2 years). If you find any problems please feel free to create an issue. ## Vue Only Vue 3 (>3.0.5) is supported. ## TypeScript Requires version > 4.1. ================================================ FILE: demo/pages/docs/supported-platforms/zhCN/index.md ================================================ # 支持的平台 ## 浏览器 不支持 IE 浏览器。 `Edge`、`Firefox`、`Chrome`、`Safari` 等现代浏览器的最新的 2 个版本确保会被支持。 对于这些浏览器的其他版本中,由于开发资源的限制并没有做过严格的测试。但是我们预期 naive-ui 应该在这些浏览器不算太老的版本上能正常的运行(比如 2 年之内的版本)。如果你发现了任何问题欢迎来提 issue。 ## Vue 只支持 Vue 3(>3.0.5)。 ## TypeScript 需要版本 > 4.1。 ================================================ FILE: demo/pages/docs/theme/enUS/element.demo.vue ================================================ # Use Naive Element Naive UI has `n-element` component. See [Element](../components/element). ================================================ FILE: demo/pages/docs/theme/enUS/index.demo-entry.md ================================================ # Create Themed Component You may not want to use only the provided components and want to write themed components. Naive UI provides some tools for developers to create themed components easier. ## Demos ```demo provide-theme.vue element.vue use-theme-vars.vue ``` ================================================ FILE: demo/pages/docs/theme/enUS/provide-theme.demo.vue ================================================ # Provide Theme Use `n-config-provider` to set the theme of all its descendant components. ================================================ FILE: demo/pages/docs/theme/enUS/use-theme-vars.demo.vue ================================================ # useThemeVars Naive UI provides `useThemeVars`. It contains common theme variables. ================================================ FILE: demo/pages/docs/theme/zhCN/element.demo.vue ================================================ # 使用元素组件 Naive UI 提供 `n-element` 组件,参考 [Element](../components/element)。 ================================================ FILE: demo/pages/docs/theme/zhCN/index.demo-entry.md ================================================ # 创建适配主题的组件 你可能觉得只用内置的组件不够爽,想自己也写适配主题的组件。 Naive UI 提供一些工具帮助开发者简单的创建支持主题的组件。 ## 演示 ```demo provide-theme.vue element.vue use-theme-vars.vue ``` ================================================ FILE: demo/pages/docs/theme/zhCN/provide-theme.demo.vue ================================================ # 提供主题 使用 `n-config-provider` 来设定它全部的后代组件主题。 ================================================ FILE: demo/pages/docs/theme/zhCN/use-theme-vars.demo.vue ================================================ # useThemeVars Naive UI 提供 `useThemeVars`,它包含了常见的主题变量。 ================================================ FILE: demo/pages/docs/umd/enUS/index.md ================================================ # Using UMD There is a self-closing bug in UMD version of naive. Please close tags explicitly like <n-input></n-input>. After version `2.30.3`, you can use UMD version of naive. If you want to use a minified version of naive, use `https://unpkg.com/naive-ui@version/dist/index.prod.js` as `src`. `version` is the version of naive-ui you want to use. If you don't specify a version, latest version would be used. It's preferable to link to a specific version of the package, as always relying on the latest version may lead to breaking changes. Here's a basic [demo](https://jsbin.com/saxubitaki/1/edit?html,output): ```html
{{ message }}
``` ================================================ FILE: demo/pages/docs/umd/zhCN/index.md ================================================ # 使用 UMD 目前 UMD 版本的 naive 标签自闭合存在问题。请显式进行标签闭合。如:<n-input></n-input> 在 `2.30.3` 版本后,你可以使用 UMD 版本的 naive。 如果你要使用 minify 版本的包,将 `https://unpkg.com/naive-ui@version/dist/index.prod.js` 作为 `src`,`version` 是你期望使用的版本,如果不指定 `version` 则会使用最新的版本。 你最好锁定包的版本,不然可能会有不兼容变更。 下面是个基本 [demo](https://jsbin.com/saxubitaki/1/edit?html,output): ```html
{{ message }}
``` ================================================ FILE: demo/pages/docs/usage-sfc/enUS/index.md ================================================ # Usage in SFC If you want to use the Single File Component(SFC) style, you can directly import components from Naive UI or install it globally to a Vue app. ## Import Directly (Recommended) You can import a component directly and use it. In this form, only components imported will be bundled. If you want to know how to import themes and locales, please see [Import on Demand](import-on-demand). ```html ``` If you can use vue setup script, you can use it like this. ```html ``` ## Install Globally (Not Recommended) ### Install All Components No tree-shaking. Bundle will have redundant codes. If you want to install globally but don't want all components, please see [Import on Demand](import-on-demand). ```js import naive from 'naive-ui' import { createApp } from 'vue' const app = createApp(App) app.use(naive) ``` After the installation, you can use all the components in your SFC like this. ```html ``` ### Volar support (2.24.2) If you are using Volar, you can specify global component types by configuring `compilerOptions.types` in `tsconfig.json`. ```json // tsconfig.json { "compilerOptions": { // ... "types": ["naive-ui/volar"] } } ``` ================================================ FILE: demo/pages/docs/usage-sfc/zhCN/index.md ================================================ # 在 SFC 中使用 如果你想使用单文件组件风格(SFC - Single File Component),可以选择直接引入或全局安装在 Vue App 中。 ## 直接引入(推荐) 你可以直接导入组件并使用它。这种情况下,只有导入的组件才会被打包。 如果你想知道如何按需引入主题和语言包,请参考[按需引入](import-on-demand)。 ```html ``` 如果你可以使用 setup script,你可以用下面的方式使用组件。 ```html ``` ## 全局安装(不推荐) ### 安装全部组件 失去 tree-shaking 的能力,打包有冗余代码。 如果你想全局安装但是不想安装全部组件,请参考[按需引入](import-on-demand)。 ```js import naive from 'naive-ui' import { createApp } from 'vue' const app = createApp(App) app.use(naive) ``` 安装后,你可以这样在 SFC 中使用全部组件。 ```html ``` ### Volar 支持(2.24.2) 如果你在使用 Volar,那么可以在 `tsconfig.json` 中配置 `compilerOptions.types` 来指定全局组件类型。 ```json // tsconfig.json { "compilerOptions": { // ... "types": ["naive-ui/volar"] } } ``` ================================================ FILE: demo/pages/docs/vite-ssge/enUS/index.md ================================================ # Vite SSG/SSE ## Caveat This document pertains to SSR (Server-Side Rendering). Please familiarize yourself with the [SSR Caveats](ssr#Caveat) before proceeding. ## Setup Guide If you are using `vite-sse` or `vite-ssg`. Follow the following steps to setup `naive-ui`. ### 1. Install `naive-ui`, `@css-render/vue3-ssr` ```bash # pnpm pnpm i naive-ui @css-render/vue3-ssr # npm npm i naive-ui @css-render/vue3-ssr ``` ### 2. Modify `vite.config.ts` Add following content. If there exists content already, merge them. ```ts import { setup } from '@css-render/vue3-ssr' defineConfig({ ssr: { noExternal: ['naive-ui', 'vueuc', 'date-fns'] }, ssgOptions: { async onBeforePageRender(_, __, appCtx) { const { collect } = setup(appCtx.app) ;(appCtx as any).__collectStyle = collect return undefined }, async onPageRendered(_, renderedHTML, appCtx) { return renderedHTML.replace( /<\/head>/, `${(appCtx as any).__collectStyle()}` ) } } }) ``` Then you can using naive-ui in `vite-ssg` or `vite-sse` project. ================================================ FILE: demo/pages/docs/vite-ssge/zhCN/index.md ================================================ # Vite SSG/SSE ## 注意 本文档涉及到 SSR,请先了解[SSR 的注意事项](ssr#注意)。 ## 配置指南 如果你正在使用 `vite-sse` 或者 `vite-ssg`,通过下面的步骤设定 `naive-ui`。 ### 1. 安装 `naive-ui`、`@css-render/vue3-ssr` ```bash # pnpm pnpm i naive-ui @css-render/vue3-ssr # npm npm i naive-ui @css-render/vue3-ssr ``` ### 2. 修改 `vite.config.ts` 增加下列内容,如果已经存在一些内容,则合并他们。 ```ts import { setup } from '@css-render/vue3-ssr' defineConfig({ ssr: { noExternal: ['naive-ui', 'vueuc', 'date-fns'] }, ssgOptions: { async onBeforePageRender(_, __, appCtx) { const { collect } = setup(appCtx.app) ;(appCtx as any).__collectStyle = collect return undefined }, async onPageRendered(_, renderedHTML, appCtx) { return renderedHTML.replace( /<\/head>/, `${(appCtx as any).__collectStyle()}` ) } } }) ``` 现在你可以在 `vite-ssg` 或 `vite-sse` 项目中使用 `naive-ui` 了。 ================================================ FILE: demo/pages/docs/vitepress/enUS/index.md ================================================ # Vitepress ## Caveat This document pertains to SSR (Server-Side Rendering). Please familiarize yourself with the [SSR Caveats](ssr#Caveat) before proceeding. ## Example This is a [demo](https://github.com/07akioni/naive-ui-vitepress-demo) for using `naive-ui` in `vitepress` with SSR enabled. You can directly use the demo. ## Key process from scratch If you want to build your own demo from scratch, follow the next steps: ### 0. Install `@css-render/vue3-ssr` Make sure its version `>=0.15.14`. ```bash # npm npm install --save-dev @css-render/vue3-ssr # pnpm pnpm install --save-dev @css-render/vue3-ssr ``` ### 1. Add this content to `.vitepress/theme/index.js` ```js // .vitepress/theme/index.js import { setup } from '@css-render/vue3-ssr' import { NConfigProvider } from 'naive-ui' import { useRoute } from 'vitepress' import DefaultTheme from 'vitepress/theme' import { defineComponent, h, inject } from 'vue' const { Layout } = DefaultTheme const CssRenderStyle = defineComponent({ setup() { const collect = inject('css-render-collect') return { style: collect() } }, render() { return h('css-render-style', { innerHTML: this.style }) } }) const VitepressPath = defineComponent({ setup() { const route = useRoute() return () => { return h('vitepress-path', null, [route.path]) } } }) const NaiveUIProvider = defineComponent({ render() { return h( NConfigProvider, { abstract: true, inlineThemeDisabled: true }, { default: () => [ h(Layout, null, { default: this.$slots.default?.() }), import.meta.env.SSR ? [h(CssRenderStyle), h(VitepressPath)] : null ] } ) } }) export default { extends: DefaultTheme, Layout: NaiveUIProvider, enhanceApp: ({ app }) => { if (import.meta.env.SSR) { const { collect } = setup(app) app.provide('css-render-collect', collect) } } } ``` ### 2. Add this content to `.vitepress/config.mts` ```ts import { defineConfig } from 'vitepress' const fileAndStyles: Record = {} export default defineConfig({ // ... vite: { ssr: { noExternal: ['naive-ui', 'date-fns', 'vueuc'] } }, postRender(context) { const styleRegex = /((.|\s)+)<\/css-render-style>/ const vitepressPathRegex = /(.+)<\/vitepress-path>/ const style = styleRegex.exec(context.content)?.[1] const vitepressPath = vitepressPathRegex.exec(context.content)?.[1] if (vitepressPath && style) { fileAndStyles[vitepressPath] = style } context.content = context.content.replace(styleRegex, '') context.content = context.content.replace(vitepressPathRegex, '') }, transformHtml(code, id) { const html = id.split('/').pop() if (!html) return const style = fileAndStyles[`/${html}`] if (style) { return code.replace(/<\/head>/, `${style}`) } } }) ``` ### 3. Start using naive-ui in your markdown file ```md ... Hello World ... ``` ================================================ FILE: demo/pages/docs/vitepress/zhCN/index.md ================================================ # Vitepress ## 注意 本文档涉及到 SSR,请先了解[SSR 的注意事项](ssr#注意)。 ## 例子 这是一个使用 `naive-ui`、`vitepress` 的[样例](https://github.com/07akioni/naive-ui-vitepress-demo),支持 SSR。 你可以直接使用这个样例。 ## 从零开始的关键步骤 如果你希望从头开始改造一个 vitepress 项目,遵循下列步骤 ### 0. 安装 `@css-render/vue3-ssr` 确保其版本 `>=0.15.14`。 ```bash # npm npm install --save-dev @css-render/vue3-ssr # pnpm pnpm install --save-dev @css-render/vue3-ssr ``` ### 1. 把下面的内容增加到 `.vitepress/theme/index.js` ```js // .vitepress/theme/index.js import { setup } from '@css-render/vue3-ssr' import { NConfigProvider } from 'naive-ui' import { useRoute } from 'vitepress' import DefaultTheme from 'vitepress/theme' import { defineComponent, h, inject } from 'vue' const { Layout } = DefaultTheme const CssRenderStyle = defineComponent({ setup() { const collect = inject('css-render-collect') return { style: collect() } }, render() { return h('css-render-style', { innerHTML: this.style }) } }) const VitepressPath = defineComponent({ setup() { const route = useRoute() return () => { return h('vitepress-path', null, [route.path]) } } }) const NaiveUIProvider = defineComponent({ render() { return h( NConfigProvider, { abstract: true, inlineThemeDisabled: true }, { default: () => [ h(Layout, null, { default: this.$slots.default?.() }), import.meta.env.SSR ? [h(CssRenderStyle), h(VitepressPath)] : null ] } ) } }) export default { extends: DefaultTheme, Layout: NaiveUIProvider, enhanceApp: ({ app }) => { if (import.meta.env.SSR) { const { collect } = setup(app) app.provide('css-render-collect', collect) } } } ``` ### 2. 把下面的内容增加到 `.vitepress/config.mts` ```ts import { defineConfig } from 'vitepress' const fileAndStyles: Record = {} export default defineConfig({ // ... vite: { ssr: { noExternal: ['naive-ui', 'date-fns', 'vueuc'] } }, postRender(context) { const styleRegex = /((.|\s)+)<\/css-render-style>/ const vitepressPathRegex = /(.+)<\/vitepress-path>/ const style = styleRegex.exec(context.content)?.[1] const vitepressPath = vitepressPathRegex.exec(context.content)?.[1] if (vitepressPath && style) { fileAndStyles[vitepressPath] = style } context.content = context.content.replace(styleRegex, '') context.content = context.content.replace(vitepressPathRegex, '') }, transformHtml(code, id) { const html = id.split('/').pop() if (!html) return const style = fileAndStyles[`/${html}`] if (style) { return code.replace(/<\/head>/, `${style}`) } } }) ``` ### 3. 在 markdown 文件中开始使用 naive-ui ```md ... Hello World ... ``` ================================================ FILE: demo/pages/docs/vue3/enUS/index.vue ================================================ ================================================ FILE: demo/pages/docs/vue3/zhCN/index.vue ================================================ ================================================ FILE: demo/pages/home/Footer.vue ================================================ ================================================ FILE: demo/pages/home/Left.vue ================================================ ================================================ FILE: demo/pages/home/Right.vue ================================================ ================================================ FILE: demo/pages/home/index.vue ================================================ ================================================ FILE: demo/routes/router.js ================================================ import { nextTick } from 'vue' import { createRouter, createWebHistory } from 'vue-router' import { useLocaleName } from '../store' export const loadingBarApiRef = {} export default function createDemoRouter(app, routes) { const router = createRouter({ history: createWebHistory(), routes }) router.beforeEach((to, from, next) => { if (!from || to.path !== from.path) { if (loadingBarApiRef.value) { loadingBarApiRef.value.start() } } next() }) router.afterEach((to, from) => { if (!from || to.path !== from.path) { if (loadingBarApiRef.value) { loadingBarApiRef.value.finish() } if (to.hash && to.hash !== from.hash) { nextTick(() => { const el = document.querySelector(to.hash) if (el) el.scrollIntoView() }) } nextTick(() => { const h1s = document.getElementsByTagName('h1') if (to.name !== 'home' && h1s.length !== 0) { document.title = `${h1s[0].textContent} - Naive UI` } else { // defined in index.html window.deriveTitleFromLocale(useLocaleName().value) } }) } }) return router } ================================================ FILE: demo/routes/routes.js ================================================ export const enDocRoutes = [ // basic docs { path: 'introduction', component: () => import('../pages/docs/introduction/enUS/index.md') }, { path: 'installation', component: () => import('../pages/docs/installation/enUS/index.md') }, { path: 'usage-sfc', component: () => import('../pages/docs/usage-sfc/enUS/index.md') }, { path: 'supported-platforms', component: () => import('../pages/docs/supported-platforms/enUS/index.md') }, // { // path: 'from-v1', // component: () => import('../pages/docs/vue3/enUS/index.vue') // }, // { // path: 'experimental-features', // component: () => import('../pages/docs/experimental-features/enUS/index.md') // }, { path: 'customize-theme', component: () => import('../pages/docs/customize-theme/enUS/index.md') }, { path: 'community', component: () => import('../pages/docs/community/enUS/index.md') }, { path: 'i18n', component: () => import('../pages/docs/i18n/enUS/index.md') }, { path: 'changelog', component: () => import('../pages/docs/changelog/enUS/index.vue') }, { path: 'theme', component: () => import('../pages/docs/theme/enUS/index.demo-entry.md') }, { path: 'jsx', component: () => import('../pages/docs/jsx/enUS/index.md') }, { path: 'ssr', component: () => import('../pages/docs/ssr/enUS/index.md') }, { path: 'nuxtjs', component: () => import('../pages/docs/nuxtjs/enUS/index.md') }, { path: 'vitepress', component: () => import('../pages/docs/vitepress/enUS/index.md') }, { path: 'vite-ssge', component: () => import('../pages/docs/vite-ssge/enUS/index.md') }, { path: 'common-issues', component: () => import('../pages/docs/common-issues/enUS/index.md') }, { path: 'fonts', component: () => import('../pages/docs/fonts/enUS/index.md') }, { path: 'import-on-demand', component: () => import('../pages/docs/import-on-demand/enUS/index.md') }, { path: 'style-conflict', component: () => import('../pages/docs/style-conflict/enUS/index.md') }, { path: 'controlled-uncontrolled', component: () => import('../pages/docs/controlled-uncontrolled/enUS/index.md') }, { path: 'umd', component: () => import('../pages/docs/umd/enUS/index.md') } ] export const zhDocRoutes = [ // basic docs { path: 'introduction', component: () => import('../pages/docs/introduction/zhCN/index.md') }, { path: 'installation', component: () => import('../pages/docs/installation/zhCN/index.md') }, { path: 'usage-sfc', component: () => import('../pages/docs/usage-sfc/zhCN/index.md') }, { path: 'supported-platforms', component: () => import('../pages/docs/supported-platforms/zhCN/index.md') }, { path: 'from-v1', component: () => import('../pages/docs/vue3/zhCN/index.vue') }, { path: 'customize-theme', component: () => import('../pages/docs/customize-theme/zhCN/index.md') }, { path: 'community', component: () => import('../pages/docs/community/zhCN/index.md') }, { path: 'i18n', component: () => import('../pages/docs/i18n/zhCN/index.md') }, // { // path: 'experimental-features', // component: () => import('../pages/docs/experimental-features/zhCN/index.md') // }, { path: 'changelog', component: () => import('../pages/docs/changelog/zhCN/index.vue') }, { path: 'theme', component: () => import('../pages/docs/theme/zhCN/index.demo-entry.md') }, { path: 'jsx', component: () => import('../pages/docs/jsx/zhCN/index.md') }, { path: 'ssr', component: () => import('../pages/docs/ssr/zhCN/index.md') }, { path: 'nuxtjs', component: () => import('../pages/docs/nuxtjs/zhCN/index.md') }, { path: 'vitepress', component: () => import('../pages/docs/vitepress/zhCN/index.md') }, { path: 'vite-ssge', component: () => import('../pages/docs/vite-ssge/zhCN/index.md') }, { path: 'common-issues', component: () => import('../pages/docs/common-issues/zhCN/index.md') }, { path: 'fonts', component: () => import('../pages/docs/fonts/zhCN/index.md') }, { path: 'import-on-demand', component: () => import('../pages/docs/import-on-demand/zhCN/index.md') }, { path: 'style-conflict', component: () => import('../pages/docs/style-conflict/zhCN/index.md') }, { path: 'controlled-uncontrolled', component: () => import('../pages/docs/controlled-uncontrolled/zhCN/index.md') }, { path: 'umd', component: () => import('../pages/docs/umd/zhCN/index.md') } ] export const enComponentRoutes = [ // components { path: 'layout', component: () => import('../../src/layout/demos/enUS/index.demo-entry.md') }, { path: 'gradient-text', component: () => import('../../src/gradient-text/demos/enUS/index.demo-entry.md') }, { path: 'icon', component: () => import('../../src/icon/demos/enUS/index.demo-entry.md') }, { path: 'checkbox', component: () => import('../../src/checkbox/demos/enUS/index.demo-entry.md') }, { path: 'button', component: () => import('../../src/button/demos/enUS/index.demo-entry.md') }, { path: 'switch', component: () => import('../../src/switch/demos/enUS/index.demo-entry.md') }, { path: 'data-table', component: () => import('../../src/data-table/demos/enUS/index.demo-entry.md') }, { path: 'input', component: () => import('../../src/input/demos/enUS/index.demo-entry.md') }, { path: 'input-otp', component: () => import('../../src/input-otp/demos/enUS/index.demo-entry.md') }, { path: 'select', component: () => import('../../src/select/demos/enUS/index.demo-entry.md') }, { path: 'cascader', component: () => import('../../src/cascader/demos/enUS/index.demo-entry.md') }, { path: 'dynamic-input', component: () => import('../../src/dynamic-input/demos/enUS/index.demo-entry.md') }, { path: 'modal', component: () => import('../../src/modal/demos/enUS/index.demo-entry.md') }, { path: 'message', component: () => import('../../src/message/demos/enUS/index.demo-entry.md') }, { path: 'tooltip', component: () => import('../../src/tooltip/demos/enUS/index.demo-entry.md') }, { path: 'popover', component: () => import('../../src/popover/demos/enUS/index.demo-entry.md') }, { path: 'notification', component: () => import('../../src/notification/demos/enUS/index.demo-entry.md') }, { path: 'pagination', component: () => import('../../src/pagination/demos/enUS/index.demo-entry.md') }, { path: 'alert', component: () => import('../../src/alert/demos/enUS/index.demo-entry.md') }, { path: 'date-picker', component: () => import('../../src/date-picker/demos/enUS/index.demo-entry.md') }, { path: 'input-number', component: () => import('../../src/input-number/demos/enUS/index.demo-entry.md') }, { path: 'radio', component: () => import('../../src/radio/demos/enUS/index.demo-entry.md') }, { path: 'form', component: () => import('../../src/form/demos/enUS/index.demo-entry.md') }, { path: 'tabs', component: () => import('../../src/tabs/demos/enUS/index.demo-entry.md') }, { path: 'time-picker', component: () => import('../../src/time-picker/demos/enUS/index.demo-entry.md') }, { path: 'dialog', component: () => import('../../src/dialog/demos/enUS/index.demo-entry.md') }, { path: 'badge', component: () => import('../../src/badge/demos/enUS/index.demo-entry.md') }, { path: 'steps', component: () => import('../../src/steps/demos/enUS/index.demo-entry.md') }, { path: 'collapse', component: () => import('../../src/collapse/demos/enUS/index.demo-entry.md') }, { path: 'progress', component: () => import('../../src/progress/demos/enUS/index.demo-entry.md') }, { path: 'tag', component: () => import('../../src/tag/demos/enUS/index.demo-entry.md') }, { path: 'menu', component: () => import('../../src/menu/demos/enUS/index.demo-entry.md') }, { path: 'timeline', component: () => import('../../src/timeline/demos/enUS/index.demo-entry.md') }, { path: 'back-top', component: () => import('../../src/back-top/demos/enUS/index.demo-entry.md') }, { path: 'divider', component: () => import('../../src/divider/demos/enUS/index.demo-entry.md') }, { path: 'popconfirm', component: () => import('../../src/popconfirm/demos/enUS/index.demo-entry.md') }, { path: 'anchor', component: () => import('../../src/anchor/demos/enUS/index.demo-entry.md') }, { path: 'dropdown', component: () => import('../../src/dropdown/demos/enUS/index.demo-entry.md') }, { path: 'popselect', component: () => import('../../src/popselect/demos/enUS/index.demo-entry.md') }, { path: 'config-provider', component: () => import('../../src/config-provider/demos/enUS/index.demo-entry.md') }, { path: 'transfer', component: () => import('../../src/transfer/demos/enUS/index.demo-entry.md') }, { path: 'legacy-transfer', component: () => import('../../src/legacy-transfer/demos/enUS/index.demo-entry.md') }, { path: 'spin', component: () => import('../../src/spin/demos/enUS/index.demo-entry.md') }, { path: 'drawer', component: () => import('../../src/drawer/demos/enUS/index.demo-entry.md') }, { path: 'loading-bar', component: () => import('../../src/loading-bar/demos/enUS/index.demo-entry.md') }, { path: 'time', component: () => import('../../src/time/demos/enUS/index.demo-entry.md') }, { path: 'slider', component: () => import('../../src/slider/demos/enUS/index.demo-entry.md') }, { path: 'tree', component: () => import('../../src/tree/demos/enUS/index.demo-entry.md') }, { path: 'affix', component: () => import('../../src/affix/demos/enUS/index.demo-entry.md') }, { path: 'statistic', component: () => import('../../src/statistic/demos/enUS/index.demo-entry.md') }, { path: 'legacy-grid', component: () => import('../../src/legacy-grid/demos/enUS/index.demo-entry.md') }, { path: 'grid', component: () => import('../../src/grid/demos/enUS/index.demo-entry.md') }, { path: 'breadcrumb', component: () => import('../../src/breadcrumb/demos/enUS/index.demo-entry.md') }, { path: 'descriptions', component: () => import('../../src/descriptions/demos/enUS/index.demo-entry.md') }, { path: 'list', component: () => import('../../src/list/demos/enUS/index.demo-entry.md') }, { path: 'card', component: () => import('../../src/card/demos/enUS/index.demo-entry.md') }, { path: 'avatar', component: () => import('../../src/avatar/demos/enUS/index.demo-entry.md') }, { path: 'result', component: () => import('../../src/result/demos/enUS/index.demo-entry.md') }, { path: 'thing', component: () => import('../../src/thing/demos/enUS/index.demo-entry.md') }, { path: 'auto-complete', component: () => import('../../src/auto-complete/demos/enUS/index.demo-entry.md') }, { path: 'empty', component: () => import('../../src/empty/demos/enUS/index.demo-entry.md') }, { path: 'element', component: () => import('../../src/element/demos/enUS/index.demo-entry.md') }, { path: 'log', component: () => import('../../src/log/demos/enUS/index.demo-entry.md') }, { path: 'code', component: () => import('../../src/code/demos/enUS/index.demo-entry.md') }, { path: 'typography', component: () => import('../../src/typography/demos/enUS/index.demo-entry.md') }, { path: 'upload', component: () => import('../../src/upload/demos/enUS/index.demo-entry.md') }, { path: 'table', component: () => import('../../src/table/demos/enUS/index.demo-entry.md') }, { path: 'space', component: () => import('../../src/space/demos/enUS/index.demo-entry.md') }, { path: 'flex', component: () => import('../../src/flex/demos/enUS/index.demo-entry.md') }, { path: 'rate', component: () => import('../../src/rate/demos/enUS/index.demo-entry.md') }, { path: 'dynamic-tags', component: () => import('../../src/dynamic-tags/demos/enUS/index.demo-entry.md') }, { path: 'ellipsis', component: () => import('../../src/ellipsis/demos/enUS/index.demo-entry.md') }, { path: 'mention', component: () => import('../../src/mention/demos/enUS/index.demo-entry.md') }, { path: 'page-header', component: () => import('../../src/page-header/demos/enUS/index.demo-entry.md') }, { path: 'global-style', component: () => import('../../src/global-style/demos/enUS/index.demo-entry.md') }, { path: 'image', component: () => import('../../src/image/demos/enUS/index.demo-entry.md') }, { path: 'skeleton', component: () => import('../../src/skeleton/demos/enUS/index.demo-entry.md') }, { path: 'calendar', component: () => import('../../src/calendar/demos/enUS/index.demo-entry.md') }, { path: 'color-picker', component: () => import('../../src/color-picker/demos/enUS/index.demo-entry.md') }, { path: 'tree-select', component: () => import('../../src/tree-select/demos/enUS/index.demo-entry.md') }, { path: 'carousel', component: () => import('../../src/carousel/demos/enUS/index.demo-entry.md') }, { path: 'collapse-transition', component: () => import('../../src/collapse-transition/demos/enUS/index.demo-entry.md') }, { path: 'scrollbar', component: () => import('../../src/scrollbar/demos/enUS/index.demo-entry.md') }, { path: 'countdown', component: () => import('../../src/countdown/demos/enUS/index.demo-entry.md') }, { path: 'number-animation', component: () => import('../../src/number-animation/demos/enUS/index.demo-entry.md') }, { path: 'watermark', component: () => import('../../src/watermark/demos/enUS/index.demo-entry.md') }, { path: 'discrete', component: () => import('../../src/discrete/demos/enUS/index.demo-entry.md') }, { path: 'equation', component: () => import('../../src/equation/demos/enUS/index.demo-entry.md') }, { path: 'qr-code', component: () => import('../../src/qr-code/demos/enUS/index.demo-entry.md') }, { path: 'virtual-list', component: () => import('../../src/virtual-list/demos/enUS/index.demo-entry.md') }, { path: 'split', component: () => import('../../src/split/demos/enUS/index.demo-entry.md') }, { path: 'infinite-scroll', component: () => import('../../src/infinite-scroll/demos/enUS/index.demo-entry.md') }, { path: 'float-button', component: () => import('../../src/float-button/demos/enUS/index.demo-entry.md') }, { path: 'highlight', component: () => import('../../src/highlight/demos/enUS/index.demo-entry.md') }, { path: 'marquee', component: () => import('../../src/marquee/demos/enUS/index.demo-entry.md') }, { path: 'heatmap', component: () => import('../../src/heatmap/demos/enUS/index.demo-entry.md') } ] export const zhComponentRoutes = [ // components { path: 'layout', component: () => import('../../src/layout/demos/zhCN/index.demo-entry.md') }, { path: 'gradient-text', component: () => import('../../src/gradient-text/demos/zhCN/index.demo-entry.md') }, { path: 'icon', component: () => import('../../src/icon/demos/zhCN/index.demo-entry.md') }, { path: 'checkbox', component: () => import('../../src/checkbox/demos/zhCN/index.demo-entry.md') }, { path: 'button', component: () => import('../../src/button/demos/zhCN/index.demo-entry.md') }, { path: 'switch', component: () => import('../../src/switch/demos/zhCN/index.demo-entry.md') }, { path: 'data-table', component: () => import('../../src/data-table/demos/zhCN/index.demo-entry.md') }, { path: 'input', component: () => import('../../src/input/demos/zhCN/index.demo-entry.md') }, { path: 'input-otp', component: () => import('../../src/input-otp/demos/zhCN/index.demo-entry.md') }, { path: 'select', component: () => import('../../src/select/demos/zhCN/index.demo-entry.md') }, { path: 'cascader', component: () => import('../../src/cascader/demos/zhCN/index.demo-entry.md') }, { path: 'dynamic-input', component: () => import('../../src/dynamic-input/demos/zhCN/index.demo-entry.md') }, { path: 'modal', component: () => import('../../src/modal/demos/zhCN/index.demo-entry.md') }, { path: 'message', component: () => import('../../src/message/demos/zhCN/index.demo-entry.md') }, { path: 'tooltip', component: () => import('../../src/tooltip/demos/zhCN/index.demo-entry.md') }, { path: 'popover', component: () => import('../../src/popover/demos/zhCN/index.demo-entry.md') }, { path: 'notification', component: () => import('../../src/notification/demos/zhCN/index.demo-entry.md') }, { path: 'pagination', component: () => import('../../src/pagination/demos/zhCN/index.demo-entry.md') }, { path: 'alert', component: () => import('../../src/alert/demos/zhCN/index.demo-entry.md') }, { path: 'date-picker', component: () => import('../../src/date-picker/demos/zhCN/index.demo-entry.md') }, { path: 'input-number', component: () => import('../../src/input-number/demos/zhCN/index.demo-entry.md') }, { path: 'radio', component: () => import('../../src/radio/demos/zhCN/index.demo-entry.md') }, { path: 'form', component: () => import('../../src/form/demos/zhCN/index.demo-entry.md') }, { path: 'tabs', component: () => import('../../src/tabs/demos/zhCN/index.demo-entry.md') }, { path: 'time-picker', component: () => import('../../src/time-picker/demos/zhCN/index.demo-entry.md') }, { path: 'dialog', component: () => import('../../src/dialog/demos/zhCN/index.demo-entry.md') }, { path: 'badge', component: () => import('../../src/badge/demos/zhCN/index.demo-entry.md') }, { path: 'steps', component: () => import('../../src/steps/demos/zhCN/index.demo-entry.md') }, { path: 'collapse', component: () => import('../../src/collapse/demos/zhCN/index.demo-entry.md') }, { path: 'progress', component: () => import('../../src/progress/demos/zhCN/index.demo-entry.md') }, { path: 'tag', component: () => import('../../src/tag/demos/zhCN/index.demo-entry.md') }, { path: 'menu', component: () => import('../../src/menu/demos/zhCN/index.demo-entry.md') }, { path: 'timeline', component: () => import('../../src/timeline/demos/zhCN/index.demo-entry.md') }, { path: 'back-top', component: () => import('../../src/back-top/demos/zhCN/index.demo-entry.md') }, { path: 'divider', component: () => import('../../src/divider/demos/zhCN/index.demo-entry.md') }, { path: 'popconfirm', component: () => import('../../src/popconfirm/demos/zhCN/index.demo-entry.md') }, { path: 'anchor', component: () => import('../../src/anchor/demos/zhCN/index.demo-entry.md') }, { path: 'dropdown', component: () => import('../../src/dropdown/demos/zhCN/index.demo-entry.md') }, { path: 'popselect', component: () => import('../../src/popselect/demos/zhCN/index.demo-entry.md') }, { path: 'config-provider', component: () => import('../../src/config-provider/demos/zhCN/index.demo-entry.md') }, { path: 'transfer', component: () => import('../../src/transfer/demos/zhCN/index.demo-entry.md') }, { path: 'legacy-transfer', component: () => import('../../src/legacy-transfer/demos/zhCN/index.demo-entry.md') }, { path: 'spin', component: () => import('../../src/spin/demos/zhCN/index.demo-entry.md') }, { path: 'drawer', component: () => import('../../src/drawer/demos/zhCN/index.demo-entry.md') }, { path: 'loading-bar', component: () => import('../../src/loading-bar/demos/zhCN/index.demo-entry.md') }, { path: 'time', component: () => import('../../src/time/demos/zhCN/index.demo-entry.md') }, { path: 'slider', component: () => import('../../src/slider/demos/zhCN/index.demo-entry.md') }, { path: 'tree', component: () => import('../../src/tree/demos/zhCN/index.demo-entry.md') }, { path: 'affix', component: () => import('../../src/affix/demos/zhCN/index.demo-entry.md') }, { path: 'statistic', component: () => import('../../src/statistic/demos/zhCN/index.demo-entry.md') }, { path: 'legacy-grid', component: () => import('../../src/legacy-grid/demos/zhCN/index.demo-entry.md') }, { path: 'grid', component: () => import('../../src/grid/demos/zhCN/index.demo-entry.md') }, { path: 'breadcrumb', component: () => import('../../src/breadcrumb/demos/zhCN/index.demo-entry.md') }, { path: 'descriptions', component: () => import('../../src/descriptions/demos/zhCN/index.demo-entry.md') }, { path: 'list', component: () => import('../../src/list/demos/zhCN/index.demo-entry.md') }, { path: 'card', component: () => import('../../src/card/demos/zhCN/index.demo-entry.md') }, { path: 'avatar', component: () => import('../../src/avatar/demos/zhCN/index.demo-entry.md') }, { path: 'result', component: () => import('../../src/result/demos/zhCN/index.demo-entry.md') }, { path: 'thing', component: () => import('../../src/thing/demos/zhCN/index.demo-entry.md') }, { path: 'auto-complete', component: () => import('../../src/auto-complete/demos/zhCN/index.demo-entry.md') }, { path: 'empty', component: () => import('../../src/empty/demos/zhCN/index.demo-entry.md') }, { path: 'element', component: () => import('../../src/element/demos/zhCN/index.demo-entry.md') }, { path: 'log', component: () => import('../../src/log/demos/zhCN/index.demo-entry.md') }, { path: 'code', component: () => import('../../src/code/demos/zhCN/index.demo-entry.md') }, { path: 'typography', component: () => import('../../src/typography/demos/zhCN/index.demo-entry.md') }, { path: 'upload', component: () => import('../../src/upload/demos/zhCN/index.demo-entry.md') }, { path: 'table', component: () => import('../../src/table/demos/zhCN/index.demo-entry.md') }, { path: 'space', component: () => import('../../src/space/demos/zhCN/index.demo-entry.md') }, { path: 'flex', component: () => import('../../src/flex/demos/zhCN/index.demo-entry.md') }, { path: 'rate', component: () => import('../../src/rate/demos/zhCN/index.demo-entry.md') }, { path: 'dynamic-tags', component: () => import('../../src/dynamic-tags/demos/zhCN/index.demo-entry.md') }, { path: 'ellipsis', component: () => import('../../src/ellipsis/demos/zhCN/index.demo-entry.md') }, { path: 'mention', component: () => import('../../src/mention/demos/zhCN/index.demo-entry.md') }, { path: 'page-header', component: () => import('../../src/page-header/demos/zhCN/index.demo-entry.md') }, { path: 'global-style', component: () => import('../../src/global-style/demos/zhCN/index.demo-entry.md') }, { path: 'image', component: () => import('../../src/image/demos/zhCN/index.demo-entry.md') }, { path: 'skeleton', component: () => import('../../src/skeleton/demos/zhCN/index.demo-entry.md') }, { path: 'calendar', component: () => import('../../src/calendar/demos/zhCN/index.demo-entry.md') }, { path: 'color-picker', component: () => import('../../src/color-picker/demos/zhCN/index.demo-entry.md') }, { path: 'tree-select', component: () => import('../../src/tree-select/demos/zhCN/index.demo-entry.md') }, { path: 'carousel', component: () => import('../../src/carousel/demos/zhCN/index.demo-entry.md') }, { path: 'collapse-transition', component: () => import('../../src/collapse-transition/demos/zhCN/index.demo-entry.md') }, { path: 'scrollbar', component: () => import('../../src/scrollbar/demos/zhCN/index.demo-entry.md') }, { path: 'countdown', component: () => import('../../src/countdown/demos/zhCN/index.demo-entry.md') }, { path: 'number-animation', component: () => import('../../src/number-animation/demos/zhCN/index.demo-entry.md') }, { path: 'watermark', component: () => import('../../src/watermark/demos/zhCN/index.demo-entry.md') }, { path: 'discrete', component: () => import('../../src/discrete/demos/zhCN/index.demo-entry.md') }, { path: 'equation', component: () => import('../../src/equation/demos/zhCN/index.demo-entry.md') }, { path: 'qr-code', component: () => import('../../src/qr-code/demos/zhCN/index.demo-entry.md') }, { path: 'virtual-list', component: () => import('../../src/virtual-list/demos/zhCN/index.demo-entry.md') }, { path: 'split', component: () => import('../../src/split/demos/zhCN/index.demo-entry.md') }, { path: 'infinite-scroll', component: () => import('../../src/infinite-scroll/demos/zhCN/index.demo-entry.md') }, { path: 'float-button', component: () => import('../../src/float-button/demos/zhCN/index.demo-entry.md') }, { path: 'highlight', component: () => import('../../src/highlight/demos/zhCN/index.demo-entry.md') }, { path: 'marquee', component: () => import('../../src/marquee/demos/zhCN/index.demo-entry.md') }, { path: 'heatmap', component: () => import('../../src/heatmap/demos/zhCN/index.demo-entry.md') } ] export const routes = [ { name: 'home', path: '/:lang/:theme', component: () => import('../pages/home/index.vue') }, { name: 'enDocs', path: '/en-US/:theme/docs', component: () => import('../pages/Layout.vue'), children: enDocRoutes }, { name: 'zhDocs', path: '/zh-CN/:theme/docs', component: () => import('../pages/Layout.vue'), children: zhDocRoutes }, { name: 'enComponents', path: '/en-US/:theme/components', component: () => import('../pages/Layout.vue'), children: enComponentRoutes }, { name: 'zhComponents', path: '/zh-CN/:theme/components', component: () => import('../pages/Layout.vue'), children: zhComponentRoutes }, { name: 'not-found', path: '/:pathMatch(.*)*', redirect: { name: 'home', params: { lang: navigator.language === 'zh-CN' ? 'zh-CN' : 'en-US', theme: 'os-theme' } } } ] ================================================ FILE: demo/setup.js ================================================ import ComponentDemo from './utils/ComponentDemo.vue' import ComponentDemos from './utils/ComponentDemos' import EditOnGithubHeader from './utils/EditOnGithubHeader.vue' import './styles/demo.css' import 'vfonts/Inter.css' import 'vfonts/FiraCode.css' import 'katex/dist/katex.css' export function installDemoComponents(app) { app.component('ComponentDemo', ComponentDemo) app.component('ComponentDemos', ComponentDemos) app.component('EditOnGithubHeader', EditOnGithubHeader) } ================================================ FILE: demo/store/hljs.js ================================================ import hljs from 'highlight.js/lib/core' import cpp from 'highlight.js/lib/languages/cpp' import javascript from 'highlight.js/lib/languages/javascript' import python from 'highlight.js/lib/languages/python' import xml from 'highlight.js/lib/languages/xml' hljs.registerLanguage('javascript', javascript) hljs.registerLanguage('python', python) hljs.registerLanguage('cpp', cpp) hljs.registerLanguage('html', xml) hljs.registerLanguage('naive-log', () => ({ contains: [ { className: 'number', begin: /\d+/ } ] })) export default hljs ================================================ FILE: demo/store/index.js ================================================ import { darkTheme, dateEnUS, dateZhCN, enUS, NConfigProvider, useOsTheme, zhCN } from 'naive-ui' import { useMemo } from 'vooks' import { computed, ref } from 'vue' import { TsConfigProvider } from '../../themes/tusimple/src' import { i18n, useIsMobile } from '../utils/composables' import hljs from './hljs' import { createComponentMenuOptions, createDocumentationMenuOptions } from './menu-options' let route = null let router = null // locale let localeNameRef = null // useMemo let dateLocaleRef = null // theme const osThemeRef = useOsTheme() let themeNameRef = null let rawThemeNameRef = null // could be `os-theme` export function initRouter(_router, _route) { route = _route router = _router localeNameRef = useMemo({ get() { return route.path.startsWith('/zh-CN') ? 'zh-CN' : 'en-US' }, set(locale) { router.push(changeLangInPath(route.fullPath, locale)) } }) dateLocaleRef = useMemo(() => { return route.path.startsWith('/zh-CN') ? dateZhCN : dateEnUS }) rawThemeNameRef = useMemo(() => route.params.theme) themeNameRef = useMemo({ get() { switch (route.params.theme) { case 'os-theme': return osThemeRef.value case 'dark': return 'dark' default: return 'light' } }, set(theme) { router.push(changeThemeInPath(route.fullPath, theme)) } }) } // display mode const _displayModeRef = ref(window.localStorage.getItem('mode') ?? 'debug') const displayModeRef = computed({ get() { return _displayModeRef.value }, set(value) { _displayModeRef.value = value window.localStorage.setItem('mode', value) } }) const localeRef = computed(() => { return localeNameRef.value === 'zh-CN' ? zhCN : enUS }) const themeRef = computed(() => { const { value } = themeNameRef return value === 'dark' ? darkTheme : null }) // config provider // eslint-disable-next-line node/prefer-global/process const configProviderNameRef = ref(process.env.TUSIMPLE ? 'tusimple' : 'default') const configProviderRef = computed(() => { return configProviderNameRef.value === 'tusimple' ? TsConfigProvider : NConfigProvider }) // options const docOptionsRef = computed(() => createDocumentationMenuOptions({ theme: rawThemeNameRef.value, lang: localeNameRef.value, mode: displayModeRef.value }) ) const componentOptionsRef = computed(() => createComponentMenuOptions({ theme: rawThemeNameRef.value, lang: localeNameRef.value, mode: displayModeRef.value }) ) const flattenedDocOptionsRef = computed(() => { const flattenedItems = [] const traverse = (items) => { if (!items) return items.forEach((item) => { if (item.children) traverse(item.children) else flattenedItems.push(item) }) } traverse(docOptionsRef.value) traverse(componentOptionsRef.value) return flattenedItems }) export function siteSetup() { i18n.provide(computed(() => localeNameRef.value)) const isMobileRef = useIsMobile() return { themeEditorStyle: computed(() => { return isMobileRef.value ? 'right: 18px; bottom: 24px;' : undefined }), configProvider: configProviderRef, hljs, themeName: themeNameRef, theme: themeRef, locale: localeRef, dateLocale: dateLocaleRef } } function changeLangInPath(path, lang) { const langReg = /^\/(zh-CN|en-US)\// return path.replace(langReg, `/${lang}/`) } function changeThemeInPath(path, theme) { const themeReg = /(^\/[^/]+\/)([^/]+)/ return path.replace(themeReg, `$1${theme}`) } export function push(partialPath) { const { fullPath } = route router.push( fullPath.replace(/(^\/[^/]+\/[^/]+)((\/.*)|$)/, `$1${partialPath}`) ) } export function useDisplayMode() { return displayModeRef } export function useLocaleName() { return localeNameRef } export function useThemeName() { return themeNameRef } export function useDocOptions() { return docOptionsRef } export function useComponentOptions() { return componentOptionsRef } export function useFlattenedDocOptions() { return flattenedDocOptionsRef } export function useConfigProviderName() { return configProviderNameRef } ================================================ FILE: demo/store/menu-options.js ================================================ // rubbish code here import { NSpace, NTag } from 'naive-ui' import { h } from 'vue' import { RouterLink } from 'vue-router' export function renderMenuLabel(option) { if (!('path' in option) || option.label === '--Debug') { return option.label } return h( RouterLink, { to: option.path }, { default: () => option.label } ) } function renderNewTag(isZh) { return h( NTag, { type: 'success', size: 'small', round: true, bordered: false, style: { pointerEvents: 'none' } }, { default: isZh ? () => '新' : () => 'New' } ) } function renderItemExtra(rawItem, isZh) { if (!rawItem.enSuffix || !isZh) { return rawItem.isNew ? renderNewTag : undefined } const renderEn = () => h( NSpace, { inline: true, size: 6, wrapItem: false, align: 'center' }, { default: () => [rawItem.en, renderNewTag(isZh)] } ) return rawItem.isNew ? renderEn : rawItem.en } function getItemExtraString(rawItem, isZh) { if (!rawItem.enSuffix || !isZh) { return '' } else { return rawItem.en } } function appendCounts(item) { if (!item.children) { item.count = 1 return item } if (item.children) { item.children.forEach(appendCounts) item.count = item.children.reduce((sum, item) => sum + item.count, 0) if (item.type === 'group') { item.en += ` (${item.count})` item.zh += ` (${item.count})` } return item } } function createItems(lang, theme, prefix, items) { const isZh = lang === 'zh-CN' const langKey = isZh ? 'zh' : 'en' return items.map((rawItem) => { const item = { ...rawItem, key: rawItem.en, label: rawItem[langKey] || rawItem.en, extra: renderItemExtra(rawItem, isZh), extraString: getItemExtraString(rawItem, isZh), path: rawItem.path ? `/${lang}/${theme}${prefix}${rawItem.path}` : undefined } if (rawItem.children) { item.children = createItems(lang, theme, prefix, rawItem.children) } return item }) } export function createDocumentationMenuOptions({ lang, theme }) { return createItems(lang, theme, '/docs', [ { en: 'Introduction', zh: '介绍', type: 'group', children: [ { en: 'Naive UI', zh: 'Naive UI', path: '/introduction' } ] }, { en: 'Getting Started', zh: '快速上手', type: 'group', children: [ { en: 'Installation', zh: '安装', path: '/installation' }, { en: 'Usage in SFC', zh: '在 SFC 中使用', path: '/usage-sfc' }, { en: 'Using UMD', zh: '使用 UMD', path: '/umd' }, { en: 'Configuring Fonts', zh: '配置字体', path: '/fonts' }, { en: 'Import on Demand', zh: '按需引入', path: '/import-on-demand' }, { en: 'Supported Platforms', zh: '支持的平台', path: '/supported-platforms' }, { en: 'Common Issues', zh: '常见问题', path: '/common-issues' }, { en: 'Controlled & Uncontrolled', zh: '受控与非受控', path: '/controlled-uncontrolled' } ] }, { en: 'Guides', zh: '指南', type: 'group', children: [ { en: 'JSX & TSX', zh: 'JSX & TSX', path: '/jsx' }, { en: 'Server-Sider Rendering', zh: '服务端渲染 SSR', path: '/ssr' }, { en: 'Nuxt.js', zh: 'Nuxt.js', path: '/nuxtjs' }, { en: 'Vitepress', zh: 'Vitepress', path: '/vitepress' }, { en: 'Vite SSG/SSE', zh: 'Vite SSG/SSE', path: '/vite-ssge' }, { en: 'Customizing Theme', zh: '调整主题', path: '/customize-theme' }, { en: 'Internationalization', zh: '国际化', path: '/i18n' }, { en: 'Create Themed Component', zh: '创建适配主题的组件', path: '/theme' }, { en: 'Potential Style Conflict', zh: '潜在的样式冲突', path: '/style-conflict' }, { en: 'Third-Party Libraries', zh: '社区精选资源', path: '/community' } ] }, { en: 'Version', zh: '版本', type: 'group', children: [ { en: 'Change Log', zh: '变更日志', path: '/changelog' } ] } ]) } export function createComponentMenuOptions({ lang, theme }) { return createItems(lang, theme, '/components', [ appendCounts({ zh: '通用组件', en: 'Common Components', type: 'group', children: [ { en: 'Avatar', zh: '头像', enSuffix: true, path: '/avatar' }, { en: 'Button', zh: '按钮', enSuffix: true, path: '/button' }, { en: 'Card', zh: '卡片', enSuffix: true, path: '/card' }, { en: 'Carousel', zh: '轮播图', enSuffix: true, path: '/carousel' }, { en: 'Collapse', zh: '折叠面板', enSuffix: true, path: '/collapse' }, { en: 'Divider', zh: '分割线', enSuffix: true, path: '/divider' }, { en: 'Dropdown', zh: '下拉菜单', enSuffix: true, path: '/dropdown' }, { en: 'Ellipsis', zh: '文本省略', enSuffix: true, path: '/ellipsis' }, { en: 'Gradient Text', zh: '渐变文字', enSuffix: true, path: '/gradient-text' }, { en: 'Icon', zh: '图标', enSuffix: true, path: '/icon' }, { en: 'PageHeader', zh: '页头', enSuffix: true, path: '/page-header' }, { en: 'Tag', zh: '标签', enSuffix: true, path: '/tag' }, { en: 'Typography', zh: '排印', enSuffix: true, path: '/typography' }, { en: 'Watermark', zh: '水印', enSuffix: true, path: '/watermark' }, { en: 'Float Button', zh: '浮动按钮', enSuffix: true, path: '/float-button', isNew: true } ] }), appendCounts({ zh: '数据录入组件', en: 'Data Input Components', type: 'group', children: [ { en: 'Auto Complete', zh: '自动填充', enSuffix: true, path: '/auto-complete' }, { en: 'Cascader', zh: '级联选择', enSuffix: true, path: '/cascader' }, { en: 'Color Picker', zh: '颜色选择器', enSuffix: true, path: '/color-picker' }, { en: 'Checkbox', zh: '复选框', enSuffix: true, path: '/checkbox' }, { en: 'Date Picker', zh: '日期选择器', enSuffix: true, path: '/date-picker' }, { en: 'Dynamic Input', zh: '动态录入', enSuffix: true, path: '/dynamic-input' }, { en: 'Dynamic Tags', zh: '动态标签', enSuffix: true, path: '/dynamic-tags' }, { en: 'Form', zh: '表单', enSuffix: true, path: '/form' }, { en: 'Input', zh: '文本输入', enSuffix: true, path: '/input' }, { en: 'Input Number', zh: '数字输入', enSuffix: true, path: '/input-number' }, { en: 'Input OTP', zh: '验证码', enSuffix: true, path: '/input-otp', isNew: true }, { en: 'Mention', zh: '提及', enSuffix: true, path: '/mention' }, { en: 'Radio', zh: '单选', enSuffix: true, path: '/radio' }, { en: 'Rate', zh: '评分', enSuffix: true, path: '/rate' }, { en: 'Select', zh: '选择器', enSuffix: true, path: '/select' }, { en: 'Slider', zh: '滑动选择', enSuffix: true, path: '/slider' }, { en: 'Switch', zh: '开关', enSuffix: true, path: '/switch' }, { en: 'Time Picker', zh: '时间选择器', enSuffix: true, path: '/time-picker' }, { en: 'Transfer', zh: '穿梭框', enSuffix: true, path: '/transfer' }, { en: 'Tree Select', zh: '树型选择', enSuffix: true, path: '/tree-select' }, { en: 'Upload', zh: '上传', enSuffix: true, path: '/upload' } ] }), appendCounts({ zh: '数据展示组件', en: 'Data Display Components', type: 'group', children: [ { en: 'Calendar', zh: '日历', enSuffix: true, path: '/calendar' }, { en: 'Countdown', zh: '倒计时', enSuffix: true, path: '/countdown' }, { en: 'Code', zh: '代码', enSuffix: true, path: '/code' }, { en: 'Data Table', zh: '数据表格', enSuffix: true, path: '/data-table' }, { en: 'Descriptions', zh: '描述', enSuffix: true, path: '/descriptions' }, { en: 'Empty', zh: '无内容', enSuffix: true, path: '/empty' }, { en: 'Equation', zh: '公式', enSuffix: true, path: '/equation' }, { en: 'Image', zh: '图像', enSuffix: true, path: '/image' }, { en: 'List', zh: '列表', enSuffix: true, path: '/list' }, { en: 'Log', zh: '日志', enSuffix: true, path: '/log' }, { en: 'Number Animation', zh: '数值动画', enSuffix: true, path: '/number-animation' }, { en: 'QR Code', zh: '二维码', enSuffix: true, path: '/qr-code', isNew: true }, { en: 'Statistic', zh: '统计数据', enSuffix: true, path: '/statistic' }, { en: 'Table', zh: '表格', enSuffix: true, path: '/table' }, { en: 'Thing', zh: '东西', enSuffix: true, path: '/thing' }, { en: 'Time', zh: '时间', enSuffix: true, path: '/time' }, { en: 'Timeline', zh: '时间线', enSuffix: true, path: '/timeline' }, { en: 'Tree', zh: '树', enSuffix: true, path: '/tree' }, { en: 'Infinite Scroll', zh: '无限滚动', enSuffix: true, path: '/infinite-scroll', isNew: true }, { en: 'Highlight', zh: '高亮文本', enSuffix: true, path: '/highlight', isNew: true }, { en: 'Heatmap', zh: '热力图', enSuffix: true, path: '/heatmap', isNew: true } ] }), appendCounts({ zh: '导航组件', en: 'Navigation Components', type: 'group', children: [ { en: 'Affix', zh: '固钉', enSuffix: true, path: '/affix' }, { en: 'Anchor', zh: '侧边导航', enSuffix: true, path: '/anchor' }, { en: 'Back Top', zh: '回到顶部', enSuffix: true, path: '/back-top' }, { en: 'Breadcrumb', zh: '面包屑', enSuffix: true, path: '/breadcrumb' }, { en: 'Loading Bar', zh: '加载条', enSuffix: true, path: '/loading-bar' }, { en: 'Menu', zh: '菜单', enSuffix: true, path: '/menu' }, { en: 'Pagination', zh: '分页', enSuffix: true, path: '/pagination' }, { en: 'Steps', zh: '步骤', enSuffix: true, path: '/steps' }, { en: 'Tabs', zh: '标签页', enSuffix: true, path: '/tabs' } ] }), appendCounts({ zh: '反馈组件', en: 'Feedback Components', type: 'group', children: [ { en: 'Alert', zh: '警告信息', enSuffix: true, path: '/alert' }, { en: 'Badge', zh: '标记', enSuffix: true, path: '/badge' }, { en: 'Dialog', zh: '对话框', enSuffix: true, path: '/dialog' }, { en: 'Drawer', zh: '抽屉', enSuffix: true, path: '/drawer' }, { en: 'Marquee', zh: '跑马灯', enSuffix: true, path: '/marquee', isNew: true }, { en: 'Message', zh: '信息', enSuffix: true, path: '/message' }, { en: 'Modal', zh: '模态框', enSuffix: true, path: '/modal' }, { en: 'Notification', zh: '通知', enSuffix: true, path: '/notification' }, { en: 'Popconfirm', zh: '弹出确认', enSuffix: true, path: '/popconfirm' }, { en: 'Popover', zh: '弹出信息', enSuffix: true, path: '/popover' }, { en: 'Popselect', zh: '弹出选择', enSuffix: true, path: '/popselect' }, { en: 'Progress', zh: '进度', enSuffix: true, path: '/progress' }, { en: 'Result', zh: '结果页', enSuffix: true, path: '/result' }, { en: 'Skeleton', zh: '骨架屏', enSuffix: true, path: '/skeleton' }, { en: 'Spin', zh: '加载', enSuffix: true, path: '/spin' }, { en: 'Tooltip', zh: '弹出提示', enSuffix: true, path: '/tooltip' } ] }), appendCounts({ zh: '布局组件', en: 'Layout Components', type: 'group', children: [ { en: 'Flex', zh: '弹性布局', enSuffix: true, path: '/flex', isNew: true }, { en: 'Layout', zh: '布局', enSuffix: true, path: '/layout' }, { en: 'Legacy Grid', zh: '旧版栅格', enSuffix: true, path: '/legacy-grid' }, { en: 'Grid', zh: '栅格', enSuffix: true, path: '/grid' }, { en: 'Space', zh: '间距', enSuffix: true, path: '/space' }, { en: 'Split', zh: '面板分割', enSuffix: true, path: '/split', isNew: true } ] }), appendCounts({ zh: '工具组件', en: 'Utility Components', type: 'group', children: [ { en: 'Collapse Transition', zh: '折叠渐变', enSuffix: true, path: '/collapse-transition' }, { en: 'Discrete API', zh: '独立 API', enSuffix: true, path: '/discrete' }, { en: 'Scrollbar', zh: '滚动条', enSuffix: true, path: '/scrollbar' }, { en: 'Virtual List', zh: '虚拟列表', enSuffix: true, path: '/virtual-list', isNew: true } ] }), appendCounts({ zh: '配置组件', en: 'Config Components', type: 'group', children: [ { en: 'Config Provider', zh: '全局化配置', enSuffix: true, path: '/config-provider' }, { en: 'Element', zh: '元素', enSuffix: true, path: '/element' }, { en: 'Global Style', zh: '全局样式', enSuffix: true, path: '/global-style' } ] }), appendCounts({ zh: '废弃的组件', en: 'Deprecated Components', type: 'group', children: [ { en: 'Legacy Transfer', zh: '旧版穿梭框', enSuffix: true, path: '/legacy-transfer' } ] }) ]) } ================================================ FILE: demo/styles/Metropolis.css ================================================ @font-face { font-family: 'Metropolis'; font-weight: 600; src: url('./Metropolis-Bold.woff2'); } ================================================ FILE: demo/styles/demo.css ================================================ @import 'Metropolis.css'; body { overflow: hidden; --header-height: 64px; } .demo-card .demo-card__view:not(:first-child) { margin-top: 16px; } .demo-card .demo-card__view pre { font-family: v-mono, monospace; } .demo-card .demo-card__view code { font-family: v-mono, monospace; } .demo-card code.n-text { white-space: nowrap; } .doc .n-p, .n-blockquote { max-width: 680px; } .md-table-wrapper { margin-bottom: 16px; } .md-table-wrapper:last-child, .md-card:last-child { margin-bottom: 0; } .md-table td:first-child { font-size: 0.93em !important; font-family: v-mono, monospace; } .md-table .n-text.n-text--code { font-size: 0.93em !important; } .md-card { margin-bottom: 16px; } .ts-dark-theme .demo-card { background-color: #141414; } .md-table td { word-break: normal; } @media only screen and (max-width: 1280px) { .md-table td { word-break: break-word; } } @media only screen and (max-width: 639px) { body { overflow: visible; } .md-table td { word-break: normal; } .md-table-wrapper { overflow: auto; margin: 0 -16px; display: flex; flex-wrap: nowrap; } .md-table-wrapper::before, .md-table-wrapper::after { content: ''; width: 16px; flex-shrink: 0; } } ================================================ FILE: demo/utils/ComponentDemo.vue ================================================ ================================================ FILE: demo/utils/ComponentDemos.tsx ================================================ import { computed, defineComponent, Fragment, h } from 'vue' import { useIsMobile, useIsSmallDesktop, useIsTablet } from './composables' export default defineComponent({ name: 'ComponentDemos', props: { span: { type: Number, default: 2 } }, setup(props) { const isMobileRef = useIsMobile() const isTabletRef = useIsTablet() const isSmallDesktop = useIsSmallDesktop() const mergedColsRef = computed(() => { return isMobileRef.value || isTabletRef.value || isSmallDesktop.value ? 1 : props.span }) return { mergedCols: mergedColsRef } }, render() { const children = this.$slots.default?.() ?? [] const { mergedCols } = this return (
{mergedCols === 1 ? ( children ) : ( <>
{children.filter((_, index) => index % 2 === 0)}
{children.filter((_, index) => index % 2 === 1)}
)}
) } }) ================================================ FILE: demo/utils/CopyCodeButton.vue ================================================ ================================================ FILE: demo/utils/EditInCodeSandboxButton.vue ================================================ ================================================ FILE: demo/utils/EditInPlaygroundButton.vue ================================================ ================================================ FILE: demo/utils/EditOnGithubButton.vue ================================================ ================================================ FILE: demo/utils/EditOnGithubHeader.vue ================================================ ================================================ FILE: demo/utils/codesandbox.js ================================================ import { getParameters } from 'codesandbox/lib/api/define' const indexHtml = ` Naive UI Demo
` const appVue = ` ` const mainJs = `import { createApp } from "vue"; import naive from "naive-ui"; import App from "./App.vue"; const app = createApp(App); app.use(naive); app.mount("#app"); ` function getDeps(code) { return (code.match(/from '([^']+)'\n/g) || []) .map(v => v.slice(6, v.length - 2)) .reduce((prevV, dep) => { prevV[dep] = 'latest' return prevV }, {}) } export function getCodeSandboxParams(code) { return getParameters({ files: { 'package.json': { content: { dependencies: { ...getDeps(code), vue: 'latest', 'vue-router': 'latest', 'naive-ui': 'latest' }, devDependencies: { '@vue/cli-plugin-babel': '~4.5.0', typescript: '~4.6.3' } } }, 'index.html': { content: indexHtml }, 'src/Demo.vue': { content: code }, 'src/App.vue': { content: appVue }, 'src/main.js': { content: mainJs } } }) } ================================================ FILE: demo/utils/composables.js ================================================ import { useBreakpoint, useMemo } from 'vooks' import { inject, provide, reactive, toRef, watchEffect } from 'vue' export function useIsMobile() { const breakpointRef = useBreakpoint() return useMemo(() => { return breakpointRef.value === 'xs' }) } export function useIsTablet() { const breakpointRef = useBreakpoint() return useMemo(() => { return breakpointRef.value === 's' }) } export function useIsSmallDesktop() { const breakpointRef = useBreakpoint() return useMemo(() => { return breakpointRef.value === 'm' }) } export function i18n(data) { const localeReactive = inject('i18n', null) return { locale: toRef(localeReactive, 'locale'), t(key) { const { locale } = localeReactive return data[locale][key] } } } i18n.provide = function (localeRef) { const localeReactive = reactive({}) watchEffect(() => { localeReactive.locale = localeRef.value }) provide('i18n', localeReactive) } ================================================ FILE: demo/utils/composables.ts ================================================ import { useBreakpoint, useMemo } from 'vooks' import { inject, provide, reactive, toRef, watchEffect } from 'vue' export function useIsMobile() { const breakpointRef = useBreakpoint() return useMemo(() => { return breakpointRef.value === 'xs' }) } export function useIsTablet() { const breakpointRef = useBreakpoint() return useMemo(() => { return breakpointRef.value === 's' }) } export function useIsSmallDesktop() { const breakpointRef = useBreakpoint() return useMemo(() => { return breakpointRef.value === 'm' }) } export function i18n(data: Record>) { const localeReactive = inject<{ locale: string } | null>('i18n', null) if (!localeReactive) { throw new Error('i18n context not provided') } return { locale: toRef(localeReactive, 'locale'), t(key) { const { locale } = localeReactive return data[locale][key] } } } i18n.provide = function (localeRef) { const localeReactive = reactive<{ locale: string }>({ locale: '' }) watchEffect(() => { localeReactive.locale = localeRef.value }) provide('i18n', localeReactive) } ================================================ FILE: demo/utils/github-url.js ================================================ export const repoUrl = 'https://github.com/tusen-ai/naive-ui' export const blobUrl = `${repoUrl}/blob/main/` ================================================ FILE: demo/utils/playground.js ================================================ export const playgroundUrl = 'https://play-naive.pro-components.cn' export const appCode = ` ` ================================================ FILE: demo/utils/route.js ================================================ export function findMenuValue(options, path) { for (const option of options) { if (option.children) { const value = findMenuValue(option.children, path) if (value) return value } if (option.path === path) { return option.key } } return undefined } ================================================ FILE: design-notes/design-token-status.md ================================================ ## Color --color --color-hover --color-pressed --color-active --color-active-hover --color-active-pressed --color-focus --color-focus-hover --color-focus-active --color-focus-pressed --color-disabled --color-disabled-active disabled-active > disabled > active-hover > active > pressed > hover > default ================================================ FILE: design-notes/how-to-name-a-style-var.md ================================================ # How to Name a Style Var For example, you have a button, which has `default` and `error` type. How will you name the background color of the button. ``` buttonColor errorButtonColor ``` or ``` buttonColor buttonColorError ``` The second is the better (The second is easy to extend and read. Also it follow the css pattern for example `padding-right` and `padding-left`). When it comes to hover style? ``` buttonColor buttonColorHover buttonColorError buttonColorErrorHover <- ``` or ``` buttonColor buttonColorHover buttonColorError buttonColorHoverError <- ``` The second is the better since if you want write a map entity, you always loop on types. For example: ```js // better ;['Default', 'Error'].map((type) => [ 'buttonColor' + type, 'buttonColorHover' + type ]) // worse, hard to maintain ;['Default', 'Error'].map((type) => [ 'button' + type + 'Color', 'buttonHover' + type + 'Color' ]) ``` ================================================ FILE: design-notes/maintaining.md ================================================ # 这个文档是为仓库维护者提供的 ## 关于 PR 合并 1. docs、main 的 PR 都要 squash 合并。 2. 分支间的合并使用最普通的 merge 合并(一定不要 squash,不要 rebase) ## 发版流程 1. 将 docs 合并到 main(发起一个 PR,CI 过了就合并) 2. 拉下最新的 main 3. 从 main checkout 到 release 分支 4. 修改 package.json 版本号 5. npm run gen-version 6. 修改两个语言的 changelog 版本号(还有日期) 7. 把改动提交到 release 分支,git commit -m "x.x.x" 8. 将 release 分支合并到 main(发起一个 PR,CI 过了就合并) 9. 回到 main 分支拉回最新的代码 10. git clean -fdx && pnpm i && npm run release:package 11. 将 main 合并到 docs(发起一个 PR,CI 过了就合并) ## 提交信息 ### Commit Message 使用 Angular Style,`feat(xxx): yyy` #### 注意事项 - `feat(xxx)` 必须是组件,不能加 `n`,`feat(input)` ✅,`feat(n-input)` ❌ - feat 或者是 fix 的话如果有对应的 issue 的话需要自己增加 comment 如 fix #514 或者 close #514 ## Changelog - 新增属性 ``` - `n-xxx` add `xxx` prop. - `n-xxx` 新增 `xxx` 属性 注意 `xxx` 属性必须是 kebab-case,不能是 camelCase。 ``` - 修复 Bug ``` - Fix `n-xxx` ... - 修复 `n-xxx` ... ``` ## PR checklist - [ ] 我在中文文档中使用了中文的标点符号(`(`、`,`、`、`) - [ ] 我在英文文档中使用了英文的标点符号 - [ ] 我没有在中文 API Table 的描述中使用句号 - [ ] 我在英文文档的 API Table description 中增加了句号 - [ ] 我在中文和英文以及中文和数字之间添加了空格 - [ ] 我在文档中的 prop 使用了 kebab-case - [ ] 文档的 API 表格按照字母顺序 - [ ] feat 或者 fix 要注意有没有加 CHANGELOG - [ ] feat 尽量增加测试 - [ ] 文档要精简,可读性好,没用的空格删除 - [ ] 代码要精简,没用的代码删除 - [ ] 测试代码不要牵扯太多无关代码,测试的方式要准确、灵活 ================================================ FILE: design-notes/think.md ================================================ ## git problem... 我的锅...远程分支没设定好,推得哪个自己都不知道。 ## test 时候 vue v-model 产生的效果必须在 \$nextTick() 之后才会产生,虽然它声称自己是同步的... 我的锅,忘了为啥了。确实是同步的,是我没有考虑到 v-model 这东西应该用什么方法测试。 ## Vue loader 编译深度选择器出了问题 a >>> b 被编译成了 a > > > b ## input 的 change 不随外界 props value 的改变改变 其他的我还没想好 ## 2019.7.17 There may be a bug of chrome 在一个 single filterable select 中,会出现一个 bug...,这个 bug 会让溢出屏幕的 item 不再显示。感觉可能是 Chrome 为了性能优化没有渲染一些在屏幕外面的东西,暂时通过 translateZ(0) 解决了。 ## 2019.7.17 Popover popover 的 hover 有问题,哎...一大堆东西闪来闪去就可能 mouseleave 没触发就一直留在那了 ## 2019.7.31 Margin 没有撑开元素 overflow: hidden 可以解决,这是 BFC 的问题 ## 2019.8.1 width: fit-content, min-content, max-content, intrinsic, extrinsic flex, flex-grow ``` render (h) { console.log(this.$props) return h('div', { staticClass: 'n-steps' }, mapSteps({ ...this.$props }, this.$slots.default)) } ``` 为什么 \$props 不一样?解构赋值做了什么 document.querySelector('\*[n-id=888d3] .simulate-transparent-text') 不合法? ## 2019.8.8 函数节流别忘了加上@scroll ## 2019.8.27 activator 外面不应该包元素检查内存泄露可能性考虑级联异步 api placement \$refs 变化更改 ## 2019.8.28 picker 在 input 聚焦的时候,esc 按钮不符合预期 ## 2019.8.29 block transition for date picker in range not via :key ## 2019.9.2 select menu 更加自治一点 比如 isSelected,可能最后要加到 vmodel 为止 tooltip css max-content 兼容性 popover z-index 问题 ## 2019.9.3 popover 停止追踪的时机 ## 2019.9.24 popover 可能 activator 和 content 不同步,在 beforeRouteEnter locale change 的时候!!! ## 2019.9.28 confirm 有 bug = =...和 button 颜色相关,之后检查吧 ## 2019.10.21 Radio Button 默认主题下是否 hollow out,这是个问题 ## 2019.11.14 base picker focus 问题 ## 2019.12.3 Dropdown Submenu 定位问题还有那个... Modal + border 的问题,怎么解决 ## 2019.12.20 Anchor Bug 复现 at typography ## 2019.12.23 Damn eslint ``` What fixed it for me was using double quotes and an escaping slash like so: "lint": "eslint \"**/*.{js,jsx}\" --quiet", Previously, it would not work with single quotes: "lint": "eslint '**/*.{js,jsx}' --quiet", ``` ## 2019.12.30 为了按需引入打包大小依旧可控,需要用某种方式让互相的依赖变为外部依赖。或者就维持原本的文件结构,这样的话需要的是 babel。看起来还是有点麻烦,之后再解决吧。 ## 2019.2.26 虽然没啥用了,还是留着吧 ``` /** * In Chrome, Firefox, Safari, this only happens when devtool opened * However only Chrome and Firefox will refocus at input element */ const devtoolIsOpened = e.target === document.activeElement ``` ## 2020.3.7 主体差不多做完了,好累 ## 2020.3.8 zindex 有点问题 ## 2020.3.11 zindex 的问题解决了,虽然很简单,在 static 元素上设定 zindex 没用,😅 ## 2020.3.15 在搞响应式页面的时候,我意识到存粹依赖 css 是一种不可靠的方式。虽然从布局层面 grid layout 已经给出了一种近乎完美的解决方案,但是涉及到一切内联样式的... 都需要从 js 的层面来解决。目前想来最 trival 的方式就是让每个 config consumer 都能拿到这个值。 ## 2020.3.20 一想到到了 1.0 就要写 changelog,就感觉 🥚 在隐隐作痛 ## 2020.3.25 一个有趣的事情是,blur 到 document.body 之后,document.activeElement 是 body,但是 blurEvent.relatedTarget 会被设为 null ## 2020.5.18 rerender 报警了 ``` h('NLayout', { ref: 'body', style: { ...this.contentStyle }, props: { 'use-native-scrollbar': false, 'scroll-content-style': { width: '100%', boxSizing: 'border-box', padding: this.paddingBody ? '21px 48px' : null } } }, null) ``` 一个是 computed 导致重复渲染,还有一个是 getScrollParent 缓存的问题 ## 2020.5.19 我在想要不要帮用户检查值,比如说一些不存在的值...现在这个问题就出现在 checkbox 里面,这是个哲学问题 ## 2020.5.19 得看懂 vue 究竟是怎么更新组件的... ## 2020.5.28 目前看来,对于动态样式,有两种情况,一种是像直接通过 prop 生效,另一种是通过组合才生效,比如 error 的 formitem 与 input,判断是否要生成样式是个比较麻烦的事,目前看来比较凑巧的是这个样式的体积不是很大,其实放在哪边都差不多= = ## 2020.5.29 主题变量有两种组织方式: ``` light { error: { backgroundColor: { hover } } } 或者 light { backgroundColor: { error: { default } } } ``` 我目前觉得第一种好点,因为扩展一般是按照种类扩展,每次插入一个同样结构的块。而第二种是每次扩展的时候在每个块里同样的位置插入一行,这样扩展的时候写起来很别扭。 ## 2020.7.1 shouldMount 可以加到 withCssr ## 2020.7.8 style overrides RFC ## 2020.8.2 颜色命名的方式 ``` 1. { xColor, xActiveColor, xDisabledColor, secondaryTextColor, tertiaryTextColor }, { // 这种拒绝,因为只有一个状态也需要 default xColor: { default, active, disabled } } // 现在感觉第三种比较好 { xColor, xColorActive, xColorDisabled, textColor2, // 暂时可以先不改 textColor3, // textColorDisabled, textColor1, textColor2, textColor3, textColorQ... } // 还有一点是去掉 backgroundColor 中的 background,因为 color & textColor 区分度已经够了 ``` ```js // no import on demand import naive from 'naive-ui' naive.Button.overrideStyles({ light: { borderRadius: 8 }, dark: { borderRadius: 8 } }) naive.InputNumber.overrideStyles({ light: { borderRadius: 8 }, dark: { borderRadius: 8 } }) naive.themes.light.overrideStyles({ primaryColor: '1234' }) Vue.use(naive) ``` ```js // import on demand // essential import create from 'naive-ui/create' import lightStyle from 'naive-ui/styles/light' import darkStyle from 'naive-ui/styles/dark' // components import Button from 'naive-ui/button' // styles import buttonLightStyle from 'naive-ui/button/styles/light' import buttonDarkStyle from 'naive-ui/button/styles/dark' create({ components: [Button], styles: [ // base styles, required for each theme lightStyle, darkStyle, // component styles buttonLightStyle, buttonDarkStyle ] }) ``` ## 2020.10.22 ```js // extend Element.prototype.scrollTo // override 1 (xCoord, yCoord) keep it // overried 2 ScrollOptions // ScrollOptions { // x: number, // y: number, // behavior: 'auto' | 'smooth' // index?: number // key?: number // el?: Element // position?: 'bottom' | 'top', // slient?: boolean // } ``` ## TODO 排序不分先后 1. Focus Detector on Time Selector 2. Menu Root Indent = 0 可能造成问题 3. 用 RAF 优化 scrollbar 性能(不一定需要)为了有时候滚动同步的一致性,决定不做了 4. Anchor 切换有 bug,忽然闪现第一个 5. Safari select lightbar container overflow 边角(或许是 webkit 的问题)默认选中第一个可以装作这个问题不存在 6. Chrome lightbar offset @table fitler select menu 始终存在这个问题,不知道原因是什么 7. cascader 数据结构重构,维持原选项! 这个不做了,对 Children 的处理过于边角,Patch 那块也会从声明式变成命令式 8. 排查 render 函数是否每个地方都支持数组 我现在明白了,只要套上数组,vue 就能完美的渲染 9. form async validation 10. table filter 重构支持异步 11. tabs resize bug 12. CSS 整理 暂且算做完了 13. 落地页 14. 按需引入 babel plugin 这个不做了,收益不大,工作量不小 15. form table 需要 size 16. input number 需要一个小型的 目前看来也不是很必要,input number 其实可以很小 17. layout scroll api 18. cascader select menu disabled 选项 19. icon 的默认 stroke 20. tree 组件 21. 文件上传组件 22. custom-input 废弃或者重构 23. Date 键盘操作 Time 键盘操作 先把能做的做了 24. Time 格式化 25. Date 格式化 26. base cancel mark rename suffix 27. base picker => base selection 28. base lightbar => base tracking rect 29. loader 区分 debug 和 非 debug 30. 逐步放宽对宽度必需传 number 的现实,尤其是对于 table 31. BaseLoading 代替 Log 里的 Spin 32. Modal 内部组件的卸载方式 33. Anchor 的另一种模式,追踪内容按照的是中间范围而不是自身大小 34. bug md-loader alert 内的 code 不显示 不是 bug,md 就这么渲染 35. backtop mounted blink 36. Tab keep alive 37. Cascader submenu 的 lightbar 用 base tracking rect 代替 38. Dropdown 样式微调 39. Card 用 padding 代替 margin 来避免 margin 折叠的问题 不做了,要是想避免折叠让用户自己写 bfc 吧 40. 检查 Icon 在 button 中切换有没有问题,直觉来说应该没问题 41. Radio Group 和 Steps 的镂空效果在 mounted 的时候 transition 没有关闭 42. Typography 文档待更新 43. Tab 文档待更新 44. Icon 文档待更新 45. 把 Upload 的回调 API 从 promise 改成 callback 格式的,文档也要对应修改 我决定全面提供 Promise API 而不是 callback 风格的 46. Date Picker 的 format 文档内容s 47. Button Group 样式 48. text button icon 49. tab scroll 计算有潜在 bug 50. data-table 的 header height 为啥要固定?因为固定列的问题,sticky 解决了就没事 51. scrollbar 在平时不显示用不到的 rail 52. placeholder transition 对 firefox 做兼容 53. 调整 date picker 的尺寸s 54. Select option 右侧 padding 调整 55. Popover arrow shadow 调整 56. Input number 按钮样式调整 57. 一部分组件在 Modal 内部的样式 还差 data table 的选项们 58. 更新 Progress 文档 59. 为所有的数据录入组件的尺寸 60. 审核文档 就这样吧,凑活 61. 调整 transfer 的样式 62. Table cascader 差一点国际化 63. Anchor 初次滚动的逻辑是不是要手动调整一下,现在位置实际上不一致 64. 将所有的 Typography 组件改为非 Functional 的,因为获取不到 context 本身的主题 65. 搞明白 table layout 这个 css 属性是在干什么 66. Icon 颜色 原来我早就解决了,都忘了... 67. Modal 滚动之后的 transform origin 68. Slider 对于 modal 适配 69. table fixed scroll checkbox, not sync 70. Transfer no data 71. Metropolis 从自带字体去掉 72. Input Group 73. Dropdown API 修改为 label + value,目前为止 UI 并没有出现过 key,不能只为了它搞特殊,value 可以,key 甚至会另有作用,现在占用实际是个危险的事情 74. card slot 75. table fixed computed left right 76. gradient text 的切换能解决?目前我想不出解决办法,希望未来 CSS 能增加 gradient 的渐变 77. Auto Complete Group 78. select add tag 79. dynamic input 还差英文文档 80. input 严格受控 81. 搞明白 vue 的 scoped 和 普通的关系 82. Form Item 不返回 true 的时候... 错误信息不会消失,这似乎不太合理? async-validator 的特性 83. Form Item 应该和 Input Group 也契合良好 能用 84. Notification 主题切换需要工作良好 85. Notification 这个东西很特殊...感觉也没那么特殊 86. 文件上传的回调方式改成传对象 87. 抵御外界 CSS 变化,比如 line-height 之类的,需要一个良好的测试机制 88. CSS Font 选择 对英文应该没有那么麻烦,走系统字体就好了,然而对中文又没什么好的解决方案,so 先这个样子,之后再琢磨琢磨 89. 把所有用 $slots 判断的地方都改成 $scopedSlots,[原因](https://vuejs.org/v2/api/#vm-scopedSlots) done 90. 调整默认状态下 button, input 的 icon 的颜色 91. 优化 button 的样式,现在太冗余了,关键是怎么同步按钮的主题变量呢... 92. placeable 按需注册 93. steps 列表优化 94. tabs 列表优化 95. tree 列表优化 96. cascader 列表优化 97. 更多的 tabs 选择 98. 下拉菜单应该给 icon 留空间 99. Form item feedback 好像有点问题,消失的时候,自定义时候 100. Confim 重命名 Dialog 算了不换了,牵扯的地方太多不好改了,confirm 也就凑活吧 101. Dark Debug 3,Safari 你可太秀了...... 102. close-outline icon 103. input + icon 主题切换 transition 有问题!!! 104. auto-complete 自定义元素 demo 有问题 有 pendingData 的时候 preventDefault 105. markdown 渲染缺少空格,修正了 n-select single filterable 时 placeholder 的样式问题 caused by vue compiler `preserveWhitespace: false`, change it to `whitespace: 'condense'` 106. drawer 加上滚动条,以及 overflow auto 的样式 107. 所有弹出的东西改成 body-style 108. size 切换的时候... transfer 动画问题 109. 利用了公用组件的组件样式没办法特殊调教,比如 select 组件自身是没有命名空间的,这个想要单独定制就比较麻烦。先不出来怎么解决 110. treemate 重构 cascader,异步 api 需要更改 111. 样式不能被 cloned,因为 peers 机制 ``` Done // modal transform scale // Notification content close // form required css // Md Loader 对于 strong 的处理 // pref hollowout, cache next bg color // issue fix, add delay prop // add trigger to tooltip // outside click delegate select // DatePicker range Bug // Scrollbar Firefox... // scrollbar 在 resize 之后滚动会有问题 // ResizeObserver Polyfill // Select 需要进一步重构,现在这种状况 collector 在 corner case 中不会调 updated 钩子,要把 key 换成 value ``` ```cache 需要注意的是,以 Base 开头的 CSS 文件并不会确保随着版本的更新保持稳定(我会尽力的保持)。因为它是内部实现的一部分。如果你维持按需引入时升级后样式出现了错误,可以来这里检查一下。虽然把这些公共样式各自打包进每个用到他们的组件是可行的,但是相比于升级的繁琐,我更不喜欢重复的代码。(这不意味着这种解决方案是“更好的”,它只是种选择而已) What should be noted is the CSS files start with 'Base' are not guaranteed to be stable (I'll try not to change them). Because they are parts of internal implementation of the library. If you find import mistakes after upgrade the package, you may have a look at here. It is possible to pack these common styles inside every component using it. However, compared to add routines when upgrading package, I perfer not to import duplicate codes. (It doesn't mean this is a better solution. It is only a choice.) ``` How I want to use styling API ? ```js // index.js import { createApp } from 'vue' import { create, enUS, buttonLight, inputLight } from 'naive-ui' const app = createApp() const naive = create({ // install components globally or import it in other component are both ok // components: [ // Button, // Input // ], styles: [buttonLight, inputLight], locales: [enUS] }) app.use(naive) ``` ## 2020.11.14 是否要给 icon 加上默认的 color transition? 否。前提条件是 svg 在合适的地方用到了 currentColor。 for example: ``` ``` 我期望的是 n-icon 一直跟着外部走,如果外面有 transtion 了那内部其实不需要 transition,不然的话会重叠。如果一定需要 transition 的话应该去保证样式带 color transition。 ================================================ FILE: design-notes/todo.md ================================================ # TODO ## Urgent - message fullscreen - use resolve slot!!! - manual trigger style ## DataTable ## Form - (easy) message support render function ## Popover ## Radio - (easy) Button custom align & width ## Select ## Tabs - (moderate) Left, right tabs ## Tree - (moderate) Use set to check if node is checked or selected internally ## TreeSelect - (moderate) Async ## DatePicker - Do not allow start > end https://github.com/tusen-ai/naive-ui/pull/4853 Later- ================================================ FILE: eslint.config.mjs ================================================ import antfu from '@antfu/eslint-config' export default antfu( { languageOptions: { ecmaVersion: 'latest', }, regexp: false, ignores: [ 'node_modules', 'lib', 'test/unit/coverage', 'src/_deprecated/icons', 'dist', 'es' ] }, { files: ['**/*.demo.vue'], rules: { 'no-console': 'off', 'vue/one-component-per-file': 'off' } }, { files: ['**/*.tsx'], rules: { 'unused-imports/no-unused-imports': 'off' } }, { files: ['**/*'], rules: { 'style/multiline-ternary': 'off', 'style/max-statements-per-line': 'off', 'style/comma-dangle': 'off', 'style/quote-props': 'off', 'jsdoc/require-returns-description': 'off', 'jsdoc/check-param-names': 'off' } } ) ================================================ FILE: esm-test/index.spec.js ================================================ import { NDataTable } from '../dist/index.prod.mjs' import { createApp } from 'vue' // eslint-disable-next-line n/no-exports-assign exports = undefined describe('esm', () => { it('works', () => { const div = document.createElement('div') document.body.appendChild(div) createApp(NDataTable).mount(div) expect(div.innerHTML).toContain('n-data-table') }) }) ================================================ FILE: generic/AvatarGroup.vue ================================================ ================================================ FILE: generic/index.ts ================================================ export { default as NGAvatarGroup } from './AvatarGroup.vue' ================================================ FILE: index.html ================================================ Naive UI
================================================ FILE: package.json ================================================ { "name": "naive-ui", "version": "2.44.1", "packageManager": "pnpm@10.29.2", "description": "A Vue 3 Component Library. Fairly Complete, Theme Customizable, Uses TypeScript, Fast", "author": "07akioni", "license": "MIT", "homepage": "https://www.naiveui.com", "repository": { "type": "git", "url": "https://github.com/tusen-ai/naive-ui" }, "keywords": [ "naive-ui", "component library", "ui framework", "ui", "vue" ], "sideEffects": false, "main": "lib/index.js", "module": "es/index.mjs", "unpkg": "dist/index.js", "jsdelivr": "dist/index.js", "types": "es/index.d.ts", "files": [ "README.md", "dist", "es", "generic", "lib", "volar.d.ts", "web-types.json" ], "engines": { "node": ">=20" }, "scripts": { "start": "pnpm run dev", "dev": "pnpm run clean && pnpm run gen-version && pnpm run gen-volar-dts && NODE_ENV=development vite", "build:package": "pnpm run gen-version && pnpm run clean && pnpm run gen-volar-dts && tsc -b --force tsconfig.esm.json && tsx scripts/pre-build/pre-cjs-build.ts && tsc -b --force tsconfig.cjs.json && rollup -c && pnpm run test:umd && pnpm run test:esm && pnpm run post-build && rimraf {es,lib}/*.tsbuildinfo", "build:themes": "tsc -b --force themes/tusimple/tsconfig.esm.json && tsc -b --force themes/tusimple/tsconfig.cjs.json", "build:site": "bash ./scripts/pre-build-site/pre-build-site.sh && NODE_ENV=production NODE_OPTIONS=--max-old-space-size=4096 vite build && bash ./scripts/post-build-site/post-build-site.sh", "clean": "rimraf site lib es dist node_modules/naive-ui themes/tusimple/es themes/tusimple/lib", "release:package": "pnpm run test && pnpm run build:package && pnpm publish --no-git-checks", "release:changelog": "tsx scripts/release-changelog.ts", "lint": "pnpm run lint:code && pnpm run lint:type", "lint:type": "pnpm run lint:src-type && pnpm run lint:demo-type", "lint:code": "eslint \"{src,build,scripts,demo}/**/*.{ts,tsx,js,vue,md}\"", "lint:fix": "eslint --fix \"{src,build,scripts,demo}/**/*.{ts,tsx,js,vue,md}\"", "lint:src-type": "tsc -b --force tsconfig.esm.json", "lint:demo-type": "NODE_OPTIONS=--max-old-space-size=4096 vue-tsc -p src/tsconfig.demo.json", "format": "pnpm run format:code && pnpm run format:md && pnpm run lint:fix", "format:code": "prettier --write \"{src,demo,scripts,build}/**/*.{vue,js,ts,tsx}\"", "format:md": "prettier --write --parser markdown --prose-wrap never \"(src|demo)/**/*.md\"", "test": "vitest run", "test:update": "vitest --run --update", "test:cov": "vitest run --coverage", "test:watch": "vitest --watch", "test:umd": "vitest run umd-test/index.spec.js", "test:esm": "vitest run esm-test/index.spec.js", "gen-version": "tsx scripts/gen-version.ts", "gen-volar-dts": "tsx scripts/gen-component-declaration.ts", "post-build": "tsx scripts/post-build/index.ts", "build:site:ts": "bash ./scripts/pre-build-site/pre-build-site.sh && TUSIMPLE=true NODE_ENV=production NODE_OPTIONS=--max-old-space-size=4096 vite build && bash ./scripts/post-build-site/post-build-site.sh", "prepare": "husky", "transpile-docs": "tsx scripts/md-to-vue.ts", "release:site": "TUSIMPLE=true pnpm run build:site && node build-doc/generate-deploy-sh.js && sudo bash build-doc/deploy-doc.sh" }, "web-types": "./web-types.json", "peerDependencies": { "vue": "^3.0.0" }, "dependencies": { "@css-render/plugin-bem": "^0.15.14", "@css-render/vue3-ssr": "^0.15.14", "@types/lodash": "^4.17.20", "@types/lodash-es": "^4.17.12", "async-validator": "^4.2.5", "css-render": "^0.15.14", "csstype": "^3.1.3", "date-fns": "^4.1.0", "date-fns-tz": "^3.2.0", "evtd": "^0.2.4", "highlight.js": "^11.8.0", "lodash": "^4.17.21", "lodash-es": "^4.17.21", "seemly": "^0.3.10", "treemate": "^0.3.11", "vdirs": "^0.1.8", "vooks": "^0.2.12", "vueuc": "^0.4.65" }, "devDependencies": { "@antfu/eslint-config": "^5.3.0", "@babel/core": "^7.28.0", "@babel/generator": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/preset-env": "^7.28.0", "@babel/traverse": "^7.28.0", "@lylajs/web": "^2.1.0", "@rollup/plugin-babel": "^6.0.4", "@rollup/plugin-commonjs": "^28.0.6", "@rollup/plugin-node-resolve": "^16.0.1", "@rollup/plugin-replace": "^6.0.2", "@rollup/plugin-terser": "^0.4.4", "@types/babel__core": "^7.20.5", "@types/babel__generator": "^7.27.0", "@types/babel__traverse": "^7.28.0", "@types/estree": "^1.0.1", "@types/fs-extra": "^11.0.4", "@types/node": "^24.0.12", "@types/superagent": "^8.1.9", "@vicons/fluent": "^0.13.0", "@vicons/ionicons4": "^0.13.0", "@vicons/ionicons5": "^0.13.0", "@vitejs/plugin-vue": "^6.0.0", "@vitest/coverage-v8": "^3.2.4", "@vue/compiler-sfc": "^3.5.21", "@vue/server-renderer": "^3.5.21", "@vue/test-utils": "^2.4.6", "autoprefixer": "^10.4.21", "codesandbox": "^2.2.3", "cssnano": "^7.1.0", "deepmerge": "^4.3.1", "esbuild": "0.25.10", "eslint": "^9.6.0", "express": "^5.1.0", "fast-glob": "^3.3.1", "fflate": "^0.8.2", "fs-extra": "^11.3.0", "grapheme-splitter": "^1.0.4", "husky": "^9.1.7", "inquirer": "^12.7.0", "jsdom": "^27.0.0", "katex": "^0.16.28", "lint-staged": "^16.1.6", "marked": "^12.0.2", "prettier": "^3.6.2", "rimraf": "^6.0.1", "rollup": "^4.45.1", "rollup-plugin-esbuild": "^6.2.1", "superagent": "^10.2.2", "tsx": "^4.20.3", "typescript": "^5.9.2", "vfonts": "^0.0.3", "vite": "^7.0.4", "vitest": "^3.2.4", "vue": "^3.5.21", "vue-router": "^4.5.1", "vue-tsc": "^3.0.7" }, "pnpm": { "overrides": { "cssstyle": "5.2.1" }, "peerDependencyRules": { "ignoreMissing": [ "@babel/core", "postcss", "rollup", "webpack" ] } }, "lint-staged": { "*.js": [ "prettier --write", "eslint --fix" ], "*.ts": [ "prettier --write", "eslint --fix" ], "*.tsx": [ "prettier --write", "eslint --fix" ], "*.vue": [ "prettier --parser=vue --write", "eslint --fix" ], "*.css": [ "prettier --write" ], "*.md": [ "prettier --write --parser markdown --prose-wrap never", "eslint --fix" ] } } ================================================ FILE: playground/collect-vars.js ================================================ const varRegex = /var\(--([^)]+)\)/g function getVars (input) { console.log( Array.from( new Set(Array.from(input.matchAll(varRegex)).map((v) => '// --' + v[1])) ) .sort() .join('\n') ) } getVars(document.body.textContent) ================================================ FILE: playground/ssr/app.js ================================================ import { h, defineComponent, ref } from 'vue' import { NButton } from 'naive-ui' const App = defineComponent({ setup () { return { count: ref(0) } }, render () { return [ h( NButton, { onClick: () => this.count++ }, { default: () => this.count } ) ] } }) export default App ================================================ FILE: playground/ssr/build.sh ================================================ webpack ./client.js --mode=development --output-filename=client.js webpack --config ./webpack.config.server.js ================================================ FILE: playground/ssr/client.js ================================================ import { createApp } from 'vue' import App from './app' createApp(App).mount('#app') ================================================ FILE: playground/ssr/pre-build.sh ================================================ # make sure cwd is the sh's dir cd ./../../ ./scripts/pre-build-site/pre-build-site.sh ================================================ FILE: playground/ssr/readme.md ================================================ You need to install `webpack` & `webpack-cli` globally. ``` ./pre-build.sh ./build.sh node dist/server.js # browse localhost:8086 ``` ================================================ FILE: playground/ssr/server.js ================================================ const fs = require('fs') const path = require('path') const express = require('express') const { createSSRApp } = require('vue') const { renderToString } = require('@vue/server-renderer') const { setup } = require('@css-render/vue3-ssr') const { default: App } = require('./app') const server = express() // serve js server.use((req, res, next) => { if (req.path.endsWith('.js')) { // the file will be in dist folder after build // so no `dist` is required in path.resolve res.send(fs.readFileSync(path.resolve(__dirname, 'client.js'), 'utf8')) } else { next() } }) // serve html server.use((req, res, next) => { if (req.path.endsWith('.js')) { next() return } const ssrApp = createSSRApp(App) res.write('SSR Test') const { collect } = setup(ssrApp) renderToString(ssrApp).then((html) => { res.write(collect()) res.write(`
${html}
`) res.write('') res.end() }) }) server.listen(8086) ================================================ FILE: playground/ssr/webpack.config.server.js ================================================ module.exports = { mode: 'development', externalsPresets: { node: true }, externals: [/^[@/a-z\-0-9]+$/], target: 'node', entry: './server.js', output: { filename: 'server.js', library: { type: 'commonjs2' } } } ================================================ FILE: playground/testAsyncValidator.js ================================================ const Schema = require('async-validator') const descriptor = { value: { type: 'string', required: true, validator: () => true } } const validator = new Schema(descriptor) validator.validate({ value: '123' }, (errors, fields) => { console.log('wtf') }) ================================================ FILE: playground/testColor.js ================================================ const tinyColor = require('tinycolor2') const red = tinyColor('#ce347c').getBrightness() const green = tinyColor('#00c060').getBrightness() const orange = tinyColor('#EF8745').getBrightness() const blue = tinyColor('#2090f0').getBrightness() console.log(red, green, orange, blue) ================================================ FILE: playground/uploadServer.js ================================================ const express = require('express') const multer = require('multer') const cors = require('cors') const path = require('path') const fs = require('fs') const app = express() const dest = path.resolve(__dirname, 'temp') const upload = multer({ dest }) app.options('/upload-test', cors()) app.post( '/upload-test', cors(), function (req, res, next) { req.on('close', () => { console.log('文件上传取消') }) req.on('error', () => { console.log('文件上传出错') }) next() }, upload.any(), function (req, res, next) { if (!fs.existsSync(dest)) fs.mkdirSync(dest) console.log(req.headers) console.log(req.body) res.send('very good') } ) app.listen(3000) ================================================ FILE: postcss.config.js ================================================ // this file is used by vite, keep it here module.exports = { plugins: [ require('autoprefixer'), require('cssnano')({ preset: [ 'default', { normalizeWhitespace: false, colormin: false } ] }) ] } ================================================ FILE: rollup.config.mjs ================================================ import path from 'node:path' import { fileURLToPath } from 'node:url' import babel from '@rollup/plugin-babel' import commonjs from '@rollup/plugin-commonjs' import nodeResolve from '@rollup/plugin-node-resolve' import replace from '@rollup/plugin-replace' import terser from '@rollup/plugin-terser' import merge from 'deepmerge' import { defineConfig } from 'rollup' import esbuild from 'rollup-plugin-esbuild' const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) const extensions = ['.mjs', '.js', '.json', '.ts'] const baseConfig = defineConfig({ input: path.resolve('./src/index.ts'), external: ['vue'], plugins: [ nodeResolve({ extensions }), esbuild({ tsconfig: path.resolve(__dirname, 'tsconfig.esbuild.json'), target: 'esnext', sourceMap: true }), babel({ extensions, babelHelpers: 'bundled' }), commonjs() ] }) const umdConfig = defineConfig({ output: { name: 'naive', format: 'umd', exports: 'named', globals: { vue: 'Vue' } } }) const esmConfig = defineConfig({ output: { format: 'esm' } }) const devConfig = defineConfig({ plugins: [ replace({ values: { __DEV__: JSON.stringify(true), 'process.env.NODE_ENV': JSON.stringify('development') }, preventAssignment: true }) ] }) const umdDevOutputConfig = defineConfig({ output: { file: path.resolve('dist/index.js') } }) const esmDevOutputConfig = defineConfig({ output: { file: path.resolve('dist/index.mjs') } }) const prodConfig = defineConfig({ plugins: [ replace({ values: { __DEV__: JSON.stringify(false), 'process.env.NODE_ENV': JSON.stringify('production') }, preventAssignment: true }), terser() ] }) const umdProdOutputConfig = defineConfig({ output: { file: path.resolve('dist/index.prod.js') } }) const esmProdOutputConfig = defineConfig({ output: { file: path.resolve('dist/index.prod.mjs') } }) export default [ // umd dev merge.all([baseConfig, umdConfig, devConfig, umdDevOutputConfig]), // umd prod merge.all([baseConfig, umdConfig, prodConfig, umdProdOutputConfig]), // esm dev merge.all([baseConfig, esmConfig, devConfig, esmDevOutputConfig]), // esm prod merge.all([baseConfig, esmConfig, prodConfig, esmProdOutputConfig]) ] ================================================ FILE: scripts/gen-component-declaration.ts ================================================ import { existsSync } from 'node:fs' import { readFile, writeFile } from 'node:fs/promises' import path from 'node:path' import process from 'node:process' import * as globalComponents from '../src/components' const TYPE_ROOT = process.cwd() // XButton is for tsx type checking, shouldn't be exported const excludeComponents: string[] = ['NxButton'] function exist(path: string): boolean { return existsSync(path) } function parseComponentsDeclaration(code: string): Record { if (!code) { return {} } return Object.fromEntries( Array.from(code.matchAll(/(? [i[1], i[2]] ) ) } async function generateComponentsType(): Promise { const components: Record = {} Object.keys(globalComponents).forEach((key: string) => { const entry = `(typeof import('naive-ui'))['${key}']` if (key.startsWith('N') && !excludeComponents.includes(key)) { components[key] = entry } }) const originalContent = exist(path.resolve(TYPE_ROOT, 'volar.d.ts')) ? await readFile(path.resolve(TYPE_ROOT, 'volar.d.ts'), 'utf-8') : '' const originImports = parseComponentsDeclaration(originalContent) const lines = Object.entries({ ...originImports, ...components }) .filter(([name]: [string, string]) => { return components[name] }) .map(([name, v]: [string, string]) => { if (!/^\w+$/.test(name)) { name = `'${name}'` } return `${name}: ${v}` }) const code = `// Auto generated component declarations declare module 'vue' { export interface GlobalComponents { ${lines.join('\n ')} } } export {} ` if (code !== originalContent) { await writeFile(path.resolve(TYPE_ROOT, 'volar.d.ts'), code, 'utf-8') } } generateComponentsType() ================================================ FILE: scripts/gen-css-vars-dts.ts ================================================ import { promises as fs } from 'node:fs' import path from 'node:path' import process from 'node:process' import { walk } from './utils' import { collectVars, genDts } from './utils/collect-vars' const srcPath = path.resolve(process.cwd(), 'src') ;(async () => { for await (const p of walk(srcPath)) { if (p.endsWith('.cssr.ts')) { const dts = genDts(collectVars(await fs.readFile(p, 'utf-8'))) console.log(p, dts) } } })() ================================================ FILE: scripts/gen-version.ts ================================================ import { writeFileSync } from 'node:fs' import { resolve } from 'node:path' import { cwd } from 'node:process' import packageJson from '../package.json' with { type: 'json' } writeFileSync( resolve(cwd(), 'src', 'version.ts'), `export default '${packageJson.version}'\n` ) ================================================ FILE: scripts/md-to-vue.ts ================================================ import { argv } from 'node:process' import { convertFilesByComponentName } from './utils/loader' async function translateMdToVue(): Promise { const componentName = argv[2] await convertFilesByComponentName(componentName) } translateMdToVue() ================================================ FILE: scripts/post-build/complete-path.ts ================================================ import path from 'node:path' import process from 'node:process' import * as babel from '@babel/core' import glob from 'fast-glob' import fs from 'fs-extra' interface FormatConfig { root: string parse: (code: string, filePath: string, currentDir: string) => Promise } const formatConfigs: Record = { es: { root: path.join(process.cwd(), 'es'), async parse(code: string, filePath: string, currentDir: string) { const suffix = '.mjs' const result = await babel.transformAsync(code, { root: this.root, babelrc: false, filename: filePath, sourceType: 'module', plugins: [ { visitor: { ImportDeclaration: ({ node }) => { const source = node.source.value const parsedSource = parseSource(source, currentDir, suffix) if (parsedSource) { node.source.value = parsedSource } }, ExportNamedDeclaration: ({ node }) => { if (node.source) { const source = node.source.value const parsedSource = parseSource(source, currentDir, suffix) if (parsedSource) { node.source.value = parsedSource } } }, ExportAllDeclaration: ({ node }) => { const source = node.source.value const parsedSource = parseSource(source, currentDir, suffix) if (parsedSource) { node.source.value = parsedSource } } } } ] }) const newFilePath = replaceExtname(filePath, suffix) await fs.writeFile(newFilePath, result?.code || code) await fs.unlink(filePath) } }, lib: { root: path.join(process.cwd(), 'lib'), async parse(code: string, filePath: string, currentDir: string) { const suffix = '.js' const result = await babel.transformAsync(code, { root: this.root, babelrc: false, filename: filePath, plugins: [ { visitor: { CallExpression: ({ node }) => { if ( node.callee.type === 'Identifier' && node.callee.name === 'require' ) { const firstArg = node.arguments[0] if (firstArg.type === 'StringLiteral') { const source = firstArg.value const parsedSource = parseSource(source, currentDir, suffix) if (parsedSource) { firstArg.value = parsedSource } } } } } } ] }) await fs.writeFile(filePath, result?.code || code) } } } export async function completePath(formats: ('es' | 'lib')[]): Promise { await Promise.all( formats.map(async (format) => { const config = formatConfigs[format] const files = await glob('**/*.js', { cwd: config.root, absolute: true, onlyFiles: true }) await Promise.all( files.map(async (filePath) => { const code = await fs.readFile(filePath, 'utf-8') await config.parse(code, filePath, path.dirname(filePath)) }) ) }) ) } function parseSource( source: string, currentDir: string, suffix: string ): string | null { if (source.startsWith('.')) { const fullPath = joinPath(currentDir, source) return fs.existsSync(fullPath) ? path.extname(fullPath) ? source : joinPath(source, `index${suffix}`) : source + suffix } else { return source } } function replaceExtname(filePath: string, ext: string): string { const oldExt = path.extname(filePath) if (!oldExt) return filePath + ext return joinPath(path.dirname(filePath), path.basename(filePath, oldExt) + ext) } const normalizePath = (p: string): string => p.replace(/\\/g, '/') function joinPath(firstPath: string, ...restPath: string[]): string { const joinedPath = normalizePath(path.join(firstPath, ...restPath)) return firstPath.startsWith('./') ? `./${joinedPath}` : joinedPath } ================================================ FILE: scripts/post-build/gen-web-types.ts ================================================ import fs from 'node:fs' import path from 'node:path' import process, { argv } from 'node:process' import { kebabCase } from 'lodash' import * as components from '../../src/components' import version from '../../src/version' const baseDir = path.resolve(process.cwd()) interface WebTypesScaffold { $schema: string framework: string name: string version: string 'js-types-syntax': string contributions: { html: { 'vue-components': VueComponent[] } } } interface VueComponent { name: string description?: string 'doc-url'?: string source: { symbol: string } slots: Slot[] attributes: any[] props: Prop[] js: { events: Event[] } } interface Slot { name: string 'doc-url'?: string description?: string type?: string 'description-sections'?: { since: string } } interface Prop { name: string 'doc-url'?: string type?: string description?: string default?: string 'description-sections'?: { since: string } } interface Event { name: string 'doc-url'?: string description?: string type?: string 'description-sections'?: { since: string } } export function genWebTypes(): void { const vueComponents: VueComponent[] = [] const scaffold: WebTypesScaffold = { $schema: 'https://raw.githubusercontent.com/JetBrains/web-types/master/schema/web-types.json', framework: 'vue', name: 'naive-ui', version, 'js-types-syntax': 'typescript', contributions: { html: { 'vue-components': vueComponents } } } const ignoredPropNames = ['theme', 'themeOverrides', 'builtinThemeOverrides'] Object.entries(components).forEach( ([exportName, component]: [string, any]) => { if (exportName[0] !== 'N') return if (exportName.startsWith('Nx')) return const { props: docProps, slots: docSlots, description, docUrl } = loadDocs(component.name) const { props: componentProps } = component const name = exportName const slots: Slot[] = [] const attributes: any[] = [] const props: Prop[] = [] const events: Event[] = [] componentProps && Object.entries(componentProps).forEach( ([propName, prop]: [string, any]) => { if (propName.startsWith('internal')) return if (ignoredPropNames.includes(propName)) return if (propName.startsWith('on') && /[A-Z]/.test(propName[2])) { // is event const event: Event = { name: kebabCase(propName.slice(2)), 'doc-url': docUrl } assignDocs(event, docProps[kebabCase(propName)]) delete docProps[kebabCase(propName)] events.push(event) } else { const resultProp: Prop = { name: kebabCase(propName), 'doc-url': docUrl } const type = prop ? getType(prop) : null if (type !== null) { resultProp.type = type } assignDocs(resultProp, docProps[resultProp.name]) delete docProps[resultProp.name] props.push(resultProp) } } ) // Add the rest of the props and events from docs for (const name in docProps) { const prop: Prop = { name, 'doc-url': docUrl } assignDocs(prop, docProps[name]) if (prop.name.startsWith('on-')) { ;(prop as any).name = prop.name.substring(3) events.push(prop as any) } else { props.push(prop) } } for (const name in docSlots) { const slot: Slot = { name, 'doc-url': docUrl } assignDocs(slot, docSlots[name]) slots.push(slot) } vueComponents.push({ name, description, 'doc-url': docUrl, source: { symbol: exportName }, slots, attributes, props, js: { events } }) } ) fs.writeFileSync( path.resolve(process.cwd(), 'web-types.json'), JSON.stringify(scaffold, null, 2), { encoding: 'utf-8' } ) function getType(prop: any): string | null { if (typeof prop !== 'object' && typeof prop !== 'function') { console.error(`invalid prop: ${prop}`) return null } if ('type' in prop) { return _getType(prop.type) } if ('validator' in prop) { return null } return _getType(prop) } function _getType(propType: any): string | null { if (Array.isArray(propType)) { const types = propType.map(mapType) if (types.includes(null)) return null return types.join(' | ') } return mapType(propType) } function mapType(type: any): string | null { const typeMap = new Map([ [String, 'string'], [Number, 'number'], [Boolean, 'boolean'], [Array, 'Array'], [Object, 'object'], [Function, 'Function'], [Date, 'Date'] ]) if (typeMap.has(type)) { return typeMap.get(type)! } console.error(`unknown type ${type}`) return null } function loadDocs(componentName: string): { props: Record slots: Record description?: string docUrl?: string } { let componentPath = kebabCase(componentName) switch (componentPath) { case 'row': case 'col': componentPath = 'legacy-grid' break case 'step': componentPath = 'steps' break case 'h-1': case 'h-2': case 'h-3': case 'h-4': case 'h-5': case 'h-6': case 'text': case 'p': case 'ul': case 'ol': case 'li': case 'hr': case 'a': case 'blockquote': componentPath = 'typography' break case 'th': case 'tr': case 'td': case 'thead': case 'tbody': componentPath = 'table' break case 'tab-pane': case 'tab': componentPath = 'tabs' break } let docsPath: string | undefined do { docsPath = path.resolve( baseDir, `src/${componentPath}/demos/enUS/index.demo-entry.md` ) if (fs.existsSync(docsPath)) break componentPath = componentPath.substring( 0, Math.max(0, componentPath.lastIndexOf('-')) ) } while (componentPath) if (!componentPath || !docsPath) { console.log(`Docs not found for ${componentName}`) return { props: {}, slots: {} } } const docsFile = fs.readFileSync(docsPath).toString() const props = extractSectionTable('Props') const slots = extractSectionTable('Slots') const description = extractComponentDescription() return { props, slots, description, docUrl: `https://www.naiveui.com/en-US/os-theme/components/${componentPath}` } function extractComponentDescription(): string | undefined { const description = docsFile.match( new RegExp(`#.*${componentName}\n(.*)## Demos`, 's') ) if (description) { return description[1].trim() } } function extractSectionTable(sectionName: string): Record { const result: Record = {} try { const sectionHeaderRegex = new RegExp( `##.*${componentName}[, ].*${sectionName}\n` ) const location = docsFile.match(sectionHeaderRegex) if (!location || location.index === undefined || location.index < 0) return result let end = docsFile.indexOf('##', location.index + 3) if (end < 0) end = docsFile.length const rowRegex = /\|((?:\\\||[^|])+)/g const sectionContents = docsFile.substring( location.index + location[0].length, end ) const table = sectionContents .split('\n') .map(it => it.trim()) .filter(it => !!it && !it.includes('| ---') && it.startsWith('|')) .map((it) => { const row = it.match(rowRegex) if (row === null) throw new Error(`Failed to match: ${it}`) return row.map(it => it.substring(1).trim()) }) if (table.length > 0) { if (table[0][0] !== 'Name') { throw new Error(`Bad table ${sectionName} in ${componentName}`) } for (let i = 1; i < table.length; i++) { const row = table[i] const name = row[0] const info: Record = {} for (let j = 1; j < row.length; j++) { info[table[0][j].toLowerCase()] = row[j] } result[name] = info } } } catch (e) { console.error( `Failed to build table info for section ${sectionName} of ${componentName} under ${docsPath}`, e ) } return result } } function assignDocs(target: any, docs: any): void { if (!docs) return if (docs.parameters) { // For slot const type = strip(strip(docs.parameters, '`'), '(', ')') const objDefStart = type ? type.indexOf('{') : -1 if (objDefStart >= 0) { target.type = type.substring(objDefStart).replaceAll('\\|', '|') } } if (docs.type) { // For props and events target.type = strip(docs.type, '`').replaceAll('\\|', '|') } if (docs.description) { target.description = docs.description } if (docs.default) { target.default = strip(docs.default, '`') } if (docs.version) { target['description-sections'] = { since: docs.version } } } function strip(str: string, prefix: string, suffix?: string): string { if (!str) return str if (!suffix) suffix = prefix if (str.startsWith(prefix) && str.endsWith(suffix)) { return str.substring(1, str.length - 1).trim() } return str } } if (import.meta.url === `file://${argv[1]}`) { genWebTypes() } ================================================ FILE: scripts/post-build/index.ts ================================================ import { outDirs, replaceDefine, srcDir } from '../utils' import { completePath } from './complete-path' import { genWebTypes } from './gen-web-types' import { terseCssr } from './terse-cssr' ; (async () => { await terseCssr() await replaceDefine(outDirs, { __DEV__: 'process.env.NODE_ENV !== \'production\'' }) await replaceDefine([srcDir], { // the sequence is crucial '\'lodash\'': '\'lodash-es\'' }) // complete require and import source path await completePath(['es']) // generate web-types.json for webstorm & vetur // web-types.json is only a very loose description for auto-complete // vscode is a much better choice genWebTypes() })() ================================================ FILE: scripts/post-build/terse-cssr.ts ================================================ import { promises as fs } from 'node:fs' import { argv } from 'node:process' import { terseCssr as terseCssrUtil } from '../../build/utils/terse-cssr' import { outDirs, walk } from '../utils' export async function terseCssr(): Promise { for (const dir of outDirs) { for await (const p of walk(dir)) { if (p.includes('.cssr.js')) { const code = await fs.readFile(p, 'utf-8') await fs.writeFile(p, terseCssrUtil(code)) } } } } if (import.meta.url === `file://${argv[1]}`) { terseCssr() } ================================================ FILE: scripts/post-build-site/post-build-site.sh ================================================ cp site/index.html site/404.html rm -rf node_modules/naive-ui ================================================ FILE: scripts/pre-build/pre-cjs-build.ts ================================================ import { replaceDefine, srcDir } from '../utils' ; (async () => { await replaceDefine([srcDir], { '\'lodash-es\'': '\'lodash\'' }) })() ================================================ FILE: scripts/pre-build-site/pre-build-site.sh ================================================ if ! [[ -x $(command -v pnpm) ]]; then curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@6 fi pnpm run build:package pnpm pack UI_PACKAGE_NAME=$(ls | grep naive-ui) tar -xzvf $UI_PACKAGE_NAME mv package node_modules/naive-ui rm $UI_PACKAGE_NAME ================================================ FILE: scripts/release-changelog.ts ================================================ import fs from 'node:fs' import path from 'node:path' import { env } from 'node:process' import url from 'node:url' import inquirer from 'inquirer' import request from 'superagent' const __dirname = url.fileURLToPath(new URL('.', import.meta.url)) const { DINGTALK_TOKEN, DINGTALK_TOKEN_2, DINGTALK_TOKEN_3, DINGTALK_TOKEN_4, DINGTALK_TOKEN_5, DINGTALK_TOKEN_6, DINGTALK_TOKEN_7 } = env const { DISCORD_TOKEN } = env async function releaseChangelogToDingTalk(): Promise { const allLog = fs .readFileSync(path.resolve(__dirname, '../CHANGELOG.zh-CN.md'), 'utf-8') .split(/^## /gm)[1] const changelog = allLog .replace(/(^.*?\n)/g, '') .replace(/^##/gm, '') .replace(/\[([^\]]+)\]\([^)]+\)/g, '[$1]') const number = allLog.split(/^(.*)$/m)[1] const title = `变更日志 ${number.trim()}` const text = `${changelog.trim()}\n\n完整信息见 https://github.com/tusen-ai/naive-ui/blob/main/CHANGELOG.zh-CN.md\n` await inquirer .prompt([ { type: 'confirm', name: 'release-changelog', message: `发布以下变更日志到钉钉群:\n\n${title}\n\n${text}` } ]) .then(async (ans) => { if (ans['release-changelog']) { for (const token of [ DINGTALK_TOKEN, DINGTALK_TOKEN_2, DINGTALK_TOKEN_3, DINGTALK_TOKEN_4, DINGTALK_TOKEN_5, DINGTALK_TOKEN_6, DINGTALK_TOKEN_7 ]) { if (token) { await request .post('https://oapi.dingtalk.com/robot/send') .query({ access_token: token }) .type('application/json') .send({ msgtype: 'markdown', markdown: { title, text: `${title}\n\n${text}` } }) .then((res) => { console.log(res.text) }) } } } }) } async function releaseChangelogToDiscord(): Promise { const changelog = fs .readFileSync(path.resolve(__dirname, '../CHANGELOG.en-US.md'), 'utf-8') .split(/^## /gm)[1] .replace(/^##/gm, '') .replace(/\[([^\]]+)\]\([^)]+\)/g, '[$1]') const message = `Changelog ${changelog.trim()}\n\nSee https://github.com/tusen-ai/naive-ui/blob/main/CHANGELOG.en-US.md for details.\n` await inquirer .prompt([ { type: 'confirm', name: 'release-changelog', message: `发布以下变更日志到 Discord:\n\n${message}` } ]) .then((ans) => { if (ans['release-changelog']) { ;(async () => { if (DISCORD_TOKEN) { for (let i = 0; i < message.length; i += 1800) { const part = message.slice(i, i + 1800) await request .post(`https://discord.com/api/webhooks/${DISCORD_TOKEN}`) .type('application/json') .send({ content: part }) .then(() => { console.log('done') }) .catch((e) => { console.error(e) console.log('Error happens.') }) } } })() } }) } ;(async () => { await releaseChangelogToDingTalk() await releaseChangelogToDiscord() })() ================================================ FILE: scripts/utils/collect-vars.ts ================================================ const pattern = /var\(([^)]+)\)/g const patternDetail = /var\(([^)]+)\)/ const commentPattern = /^( *)(\*|(\S\S)|(\S\*))/g export function collectVars(code: string): string[] { const vars = new Set() const lines = code.split('\n') lines.forEach((line) => { if (line.match(commentPattern)) { return } const result = line.match(pattern) if (result) { result.forEach((varExpr) => { const match = varExpr.match(patternDetail) if (match) vars.add(match[1]) }) } }) return Array.from(vars).sort() } export function genDts(vars: string[]): string { console.log(vars) return `interface CssVars { ${vars.map(v => ` '${v}': string`).join('\n')} }` } ================================================ FILE: scripts/utils/index.ts ================================================ import { promises as fs } from 'node:fs' import { join, resolve } from 'node:path' import process from 'node:process' export async function* walk(dir: string): AsyncGenerator { for await (const d of await fs.opendir(dir)) { const entry = join(dir, d.name) if (d.isDirectory()) { yield* walk(entry) } else if (d.isFile()) { yield entry } } } export const outDirs = ['es', 'lib'].map(d => resolve(process.cwd(), d)) export const srcDir = resolve(process.cwd(), 'src') export { replaceDefine } from './replace-define' ================================================ FILE: scripts/utils/loader.ts ================================================ import type { TokensList } from 'marked' import path from 'node:path' import process from 'node:process' import fs from 'fs-extra' import { marked } from 'marked' const fileRegex = /\.demo\.md$/ interface DemoParts { template: string | null script: string | null style: string | null title: string | null content: string | null } interface FileInfo { path: string dir: string file: string name: string } function getPartsOfMdDemo(tokens: TokensList): DemoParts { let template = null let script = null let style = null let title = null let content = null for (const token of tokens) { if (token.type === 'heading' && token.depth === 1) { title = token.text } else if ( token.type === 'code' && (token.lang === 'template' || token.lang === 'html') ) { template = token.text } else if ( token.type === 'code' && (token.lang === 'script' || token.lang === 'js' || token.lang === 'ts') ) { script = token.text } else if ( token.type === 'code' && (token.lang === 'style' || token.lang === 'css') ) { style = token.text } else if (token.type === 'paragraph') { content = token.text } } return { template, script, style, title, content } } function createBlockTemplate( tag: string, content: string, attrs?: Record ): string { const attrsStr = attrs ? Object.keys(attrs).reduce((attrsStr, key) => { return `${attrsStr} ${key}="${attrs[key]}"` }, '') : '' return `<${tag}${attrsStr}>\n${content}\n` } async function loadFile(filepath: string): Promise { if (fs.existsSync(filepath)) { return await fs.readFile(filepath, 'utf-8') } return undefined } async function loadAllMdFile(filePathArr: string[]): Promise { const filesArr: FileInfo[] = [] for (let i = 0; i < filePathArr.length; i++) { const filePath = filePathArr[i] if (fs.existsSync(filePath)) { const files = await fs.readdir(filePath) const filesObjArr = files .filter(file => fileRegex.test(file)) .map((file) => { const index = file.lastIndexOf('.') return { path: `${filePath}/${file}`, dir: filePath, file, name: file.slice(0, index) } }) filesArr.push(...filesObjArr) } } return filesArr } async function updateIndexEntryDemo(file: FileInfo): Promise { let indexFileContent = await loadFile( path.resolve(file.dir, './index.demo-entry.md') ) if (indexFileContent) { const index = file.name.indexOf('.') const name = file.name.slice(0, index) indexFileContent = indexFileContent.replace(name, `${name}.vue`) fs.writeFileSync( path.resolve(file.dir, './index.demo-entry.md'), indexFileContent ) } } const LINE_SPACE = '\n\n' async function transformMdToVueAndUpdateEntryFile( files: FileInfo[] ): Promise { for (const file of files) { const fileString = await loadFile(file.path) if (fileString) { const tokens = marked.lexer(fileString) const parts = getPartsOfMdDemo(tokens) const vueDemoBlocks: string[] = [] if (parts.title || parts.content) { vueDemoBlocks.push( createBlockTemplate( 'markdown', `# ${parts.title}${parts.content ? `${LINE_SPACE}${parts.content}` : ''}` ) ) } if (parts.template) { vueDemoBlocks.push(createBlockTemplate('template', parts.template)) } if (parts.script) { vueDemoBlocks.push( createBlockTemplate('script', parts.script, { lang: 'ts' }) ) } if (parts.style) { vueDemoBlocks.push(createBlockTemplate('style', parts.style)) } await fs.remove(file.path) await fs.ensureDir(file.dir) fs.writeFileSync( path.resolve(file.dir, `./${file.name}.vue`), `${vueDemoBlocks.join(LINE_SPACE)}\n` ) // should be able to be modified together await updateIndexEntryDemo(file) } } } const COMPONENT_ROOT = path.resolve(process.cwd(), 'src') export async function convertFilesByComponentName( componentName: string ): Promise { const folders = ['zhCN', 'enUS'].map(item => path.resolve(COMPONENT_ROOT, `${componentName}/demos/${item}`) ) if (folders.length) { const files = await loadAllMdFile(folders) transformMdToVueAndUpdateEntryFile(files) } } ================================================ FILE: scripts/utils/replace-define.ts ================================================ import { promises as fs } from 'node:fs' import { walk } from '.' export async function replaceDefine( dirs: string[], defines: Record ): Promise { const defineKeys = Object.keys(defines) const patterns: Record = {} defineKeys.forEach((key) => { patterns[key] = new RegExp(key, 'g') }) for (const dir of dirs) { for await (const p of walk(dir)) { if (p.endsWith('.vue')) continue let code = await fs.readFile(p, 'utf-8') for (const key of defineKeys) { const pattern = patterns[key] if (pattern.test(code)) { code = code.replace(pattern, defines[key]) } } await fs.writeFile(p, code) } } } ================================================ FILE: src/_internal/README.md ================================================ Themeable components: - selection - select-menu - clear ================================================ FILE: src/_internal/clear/index.ts ================================================ export { default } from './src/Clear' ================================================ FILE: src/_internal/clear/src/Clear.tsx ================================================ import type { PropType } from 'vue' import { defineComponent, h, toRef } from 'vue' import { useStyle } from '../../../_mixins' import { resolveSlot } from '../../../_utils' import { NBaseIcon } from '../../icon' import NIconSwitchTransition from '../../icon-switch-transition' import { ClearIcon } from '../../icons' import style from './styles/index.cssr' export default defineComponent({ name: 'BaseClear', props: { clsPrefix: { type: String, required: true }, show: Boolean, onClear: Function as PropType<(e: MouseEvent) => void> }, setup(props) { useStyle('-base-clear', style, toRef(props, 'clsPrefix')) return { handleMouseDown(e: MouseEvent) { e.preventDefault() } } }, render() { const { clsPrefix } = this return (
{{ default: () => { return this.show ? (
{resolveSlot(this.$slots.icon, () => [ {{ default: () => }} ])}
) : (
{this.$slots.placeholder?.()}
) } }}
) } }) ================================================ FILE: src/_internal/clear/src/styles/index.cssr.ts ================================================ import { iconSwitchTransition } from '../../../../_styles/transitions/icon-switch.cssr' import { c, cB, cE } from '../../../../_utils/cssr' // vars: // --n-bezier // --n-clear-color // --n-clear-size // --n-clear-color-hover // --n-clear-color-pressed export default cB('base-clear', ` flex-shrink: 0; height: 1em; width: 1em; position: relative; `, [ c('>', [ cE('clear', ` font-size: var(--n-clear-size); height: 1em; width: 1em; cursor: pointer; color: var(--n-clear-color); transition: color .3s var(--n-bezier); display: flex; `, [ c('&:hover', ` color: var(--n-clear-color-hover)!important; `), c('&:active', ` color: var(--n-clear-color-pressed)!important; `) ]), cE('placeholder', ` display: flex; `), cE('clear, placeholder', ` position: absolute; left: 50%; top: 50%; transform: translateX(-50%) translateY(-50%); `, [ iconSwitchTransition({ originalTransform: 'translateX(-50%) translateY(-50%)', left: '50%', top: '50%' }) ]) ]) ]) ================================================ FILE: src/_internal/close/index.ts ================================================ export { default as NBaseClose } from './src/Close' ================================================ FILE: src/_internal/close/src/Close.tsx ================================================ import type { PropType } from 'vue' import { defineComponent, h, toRef } from 'vue' import { useStyle } from '../../../_mixins' import { NBaseIcon } from '../../icon' import { CloseIcon } from '../../icons' import style from './styles/index.cssr' export default defineComponent({ name: 'BaseClose', props: { isButtonTag: { type: Boolean, default: true }, clsPrefix: { type: String, required: true }, disabled: { type: Boolean, default: undefined }, focusable: { type: Boolean, default: true }, round: Boolean, onClick: Function as PropType<(e: MouseEvent) => void>, absolute: Boolean }, setup(props) { useStyle('-base-close', style, toRef(props, 'clsPrefix')) return () => { const { clsPrefix, disabled, absolute, round, isButtonTag } = props const Tag = isButtonTag ? 'button' : 'div' return ( { if (!props.focusable) { e.preventDefault() } }} onClick={props.onClick} > {{ default: () => }} ) } } }) ================================================ FILE: src/_internal/close/src/styles/index.cssr.ts ================================================ import { c, cB, cM, cNotM } from '../../../../_utils/cssr' // vars: // --n-close-border-radius // --n-close-color-hover // --n-close-color-pressed // --n-close-icon-color // --n-close-icon-color-hover // --n-close-icon-color-pressed // --n-close-icon-color-disabled export default cB('base-close', ` display: flex; align-items: center; justify-content: center; cursor: pointer; background-color: transparent; color: var(--n-close-icon-color); border-radius: var(--n-close-border-radius); height: var(--n-close-size); width: var(--n-close-size); font-size: var(--n-close-icon-size); outline: none; border: none; position: relative; padding: 0; `, [ cM('absolute', ` height: var(--n-close-icon-size); width: var(--n-close-icon-size); `), c('&::before', ` content: ""; position: absolute; width: var(--n-close-size); height: var(--n-close-size); left: 50%; top: 50%; transform: translateY(-50%) translateX(-50%); transition: inherit; border-radius: inherit; `), cNotM('disabled', [ c('&:hover', ` color: var(--n-close-icon-color-hover); `), c('&:hover::before', ` background-color: var(--n-close-color-hover); `), c('&:focus::before', ` background-color: var(--n-close-color-hover); `), c('&:active', ` color: var(--n-close-icon-color-pressed); `), c('&:active::before', ` background-color: var(--n-close-color-pressed); `) ]), cM('disabled', ` cursor: not-allowed; color: var(--n-close-icon-color-disabled); background-color: transparent; `), cM('round', [ c('&::before', ` border-radius: 50%; `) ]) ]) ================================================ FILE: src/_internal/fade-in-expand-transition/index.ts ================================================ export { default } from './src/FadeInExpandTransition' ================================================ FILE: src/_internal/fade-in-expand-transition/src/FadeInExpandTransition.ts ================================================ import type { PropType, TransitionProps } from 'vue' import { defineComponent, h, Transition, TransitionGroup } from 'vue' export default defineComponent({ name: 'FadeInExpandTransition', props: { appear: Boolean, group: Boolean, mode: String as PropType<'in-out' | 'out-in' | 'default'>, onLeave: Function, onAfterLeave: Function, onAfterEnter: Function, width: Boolean, // reverse mode is only used in tree // it make it from expanded to collapsed after mounted reverse: Boolean }, setup(props, { slots }) { function handleBeforeLeave(el: HTMLElement): void { if (props.width) { el.style.maxWidth = `${el.offsetWidth}px` } else { el.style.maxHeight = `${el.offsetHeight}px` } void el.offsetWidth } function handleLeave(el: HTMLElement): void { if (props.width) { el.style.maxWidth = '0' } else { el.style.maxHeight = '0' } void el.offsetWidth const { onLeave } = props if (onLeave) onLeave() } function handleAfterLeave(el: HTMLElement): void { if (props.width) { el.style.maxWidth = '' } else { el.style.maxHeight = '' } const { onAfterLeave } = props if (onAfterLeave) onAfterLeave() } function handleEnter(el: HTMLElement): void { el.style.transition = 'none' if (props.width) { const memorizedWidth = el.offsetWidth el.style.maxWidth = '0' void el.offsetWidth el.style.transition = '' el.style.maxWidth = `${memorizedWidth}px` } else { if (props.reverse) { el.style.maxHeight = `${el.offsetHeight}px` void el.offsetHeight el.style.transition = '' el.style.maxHeight = '0' } else { const memorizedHeight = el.offsetHeight el.style.maxHeight = '0' void el.offsetWidth el.style.transition = '' el.style.maxHeight = `${memorizedHeight}px` } } void el.offsetWidth } function handleAfterEnter(el: HTMLElement): void { if (props.width) { el.style.maxWidth = '' } else { if (!props.reverse) { el.style.maxHeight = '' } } props.onAfterEnter?.() } return () => { const { group, width, appear, mode } = props const type = group ? TransitionGroup : Transition const resolvedProps = { name: width ? 'fade-in-width-expand-transition' : 'fade-in-height-expand-transition', appear, onEnter: handleEnter, onAfterEnter: handleAfterEnter, onBeforeLeave: handleBeforeLeave, onLeave: handleLeave, onAfterLeave: handleAfterLeave } if (!group) { ;(resolvedProps as unknown as TransitionProps).mode = mode } return h(type as any, resolvedProps, slots) } } }) ================================================ FILE: src/_internal/focus-detector/index.tsx ================================================ import FocusDetector from './src/FocusDetector' export default FocusDetector ================================================ FILE: src/_internal/focus-detector/src/FocusDetector.tsx ================================================ import type { PropType } from 'vue' import { defineComponent, h } from 'vue' export default defineComponent({ props: { onFocus: Function as PropType<(e: FocusEvent) => void>, onBlur: Function as PropType<(e: FocusEvent) => void> }, setup(props) { return () => (
) } }) ================================================ FILE: src/_internal/icon/index.ts ================================================ export { default as NBaseIcon } from './src/Icon' ================================================ FILE: src/_internal/icon/src/Icon.tsx ================================================ import type { PropType } from 'vue' import { defineComponent, h, toRef } from 'vue' import { useStyle } from '../../../_mixins' import style from './styles/index.cssr' export default defineComponent({ name: 'BaseIcon', props: { role: String, ariaLabel: String, ariaDisabled: { type: Boolean, default: undefined }, ariaHidden: { type: Boolean, default: undefined }, clsPrefix: { type: String, required: true }, onClick: Function as PropType<(e: MouseEvent) => void>, onMousedown: Function as PropType<(e: MouseEvent) => void>, onMouseup: Function as PropType<(e: MouseEvent) => void> }, setup(props) { useStyle('-base-icon', style, toRef(props, 'clsPrefix')) }, render() { return ( {this.$slots} ) } }) ================================================ FILE: src/_internal/icon/src/styles/index.cssr.ts ================================================ import { c, cB } from '../../../../_utils/cssr' export default cB('base-icon', ` height: 1em; width: 1em; line-height: 1em; text-align: center; display: inline-block; position: relative; fill: currentColor; `, [ c('svg', ` height: 1em; width: 1em; `) ]) ================================================ FILE: src/_internal/icon-switch-transition/index.ts ================================================ export { default } from './src/IconSwitchTransition' ================================================ FILE: src/_internal/icon-switch-transition/src/IconSwitchTransition.tsx ================================================ import { useIsMounted } from 'vooks' import { defineComponent, h, Transition } from 'vue' export default defineComponent({ name: 'BaseIconSwitchTransition', setup(_, { slots }) { const isMountedRef = useIsMounted() return () => ( {slots} ) } }) ================================================ FILE: src/_internal/icons/Add.tsx ================================================ import { defineComponent, h } from 'vue' export default defineComponent({ name: 'Add', render() { return ( ) } }) ================================================ FILE: src/_internal/icons/ArrowBack.tsx ================================================ import { defineComponent, h } from 'vue' export default defineComponent({ name: 'ArrowBack', render() { return ( ) } }) ================================================ FILE: src/_internal/icons/ArrowDown.tsx ================================================ import { defineComponent, h } from 'vue' export default defineComponent({ name: 'ArrowDown', render() { return ( ) } }) ================================================ FILE: src/_internal/icons/ArrowUp.tsx ================================================ import { defineComponent, h } from 'vue' export default defineComponent({ name: 'ArrowUp', render() { return ( ) } }) ================================================ FILE: src/_internal/icons/Attach.tsx ================================================ import { h } from 'vue' import { replaceable } from './replaceable' export default replaceable('attach', () => ( )) ================================================ FILE: src/_internal/icons/Backward.tsx ================================================ import { defineComponent, h } from 'vue' export default defineComponent({ name: 'Backward', render() { return ( ) } }) ================================================ FILE: src/_internal/icons/Cancel.tsx ================================================ import { h } from 'vue' import { replaceable } from './replaceable' export default replaceable('cancel', () => ( )) ================================================ FILE: src/_internal/icons/Checkmark.tsx ================================================ import { defineComponent, h } from 'vue' export default defineComponent({ name: 'Checkmark', render() { return ( ) } }) ================================================ FILE: src/_internal/icons/ChevronDown.tsx ================================================ import { defineComponent, h } from 'vue' export default defineComponent({ name: 'ChevronDown', render() { return ( ) } }) ================================================ FILE: src/_internal/icons/ChevronDownFilled.tsx ================================================ import { defineComponent, h } from 'vue' export default defineComponent({ name: 'ChevronDownFilled', render() { return ( ) } }) ================================================ FILE: src/_internal/icons/ChevronLeft.tsx ================================================ import { defineComponent, h } from 'vue' export default defineComponent({ name: 'ChevronLeft', render() { return ( ) } }) ================================================ FILE: src/_internal/icons/ChevronRight.tsx ================================================ import { defineComponent, h } from 'vue' export default defineComponent({ name: 'ChevronRight', render() { return ( ) } }) ================================================ FILE: src/_internal/icons/Clear.tsx ================================================ import { h } from 'vue' import { replaceable } from './replaceable' export default replaceable('clear', () => ( )) ================================================ FILE: src/_internal/icons/Close.tsx ================================================ import { h } from 'vue' import { replaceable } from './replaceable' export default replaceable('close', () => ( )) ================================================ FILE: src/_internal/icons/Date.tsx ================================================ import { h } from 'vue' import { replaceable } from './replaceable' export default replaceable('date', () => ( )) ================================================ FILE: src/_internal/icons/Download.tsx ================================================ import { h } from 'vue' import { replaceable } from './replaceable' export default replaceable('download', () => ( )) ================================================ FILE: src/_internal/icons/Empty.tsx ================================================ import { defineComponent, h } from 'vue' export default defineComponent({ name: 'Empty', render() { return ( ) } }) ================================================ FILE: src/_internal/icons/Error.tsx ================================================ import { h } from 'vue' import { replaceable } from './replaceable' export default replaceable('error', () => ( )) ================================================ FILE: src/_internal/icons/Eye.tsx ================================================ import { defineComponent, h } from 'vue' export default defineComponent({ name: 'Eye', render() { return ( ) } }) ================================================ FILE: src/_internal/icons/EyeOff.tsx ================================================ import { defineComponent, h } from 'vue' export default defineComponent({ name: 'EyeOff', render() { return ( ) } }) ================================================ FILE: src/_internal/icons/FastBackward.tsx ================================================ import { defineComponent, h } from 'vue' export default defineComponent({ name: 'FastBackward', render() { return ( ) } }) ================================================ FILE: src/_internal/icons/FastForward.tsx ================================================ import { defineComponent, h } from 'vue' export default defineComponent({ name: 'FastForward', render() { return ( ) } }) ================================================ FILE: src/_internal/icons/File.tsx ================================================ import { defineComponent, h } from 'vue' export default defineComponent({ name: 'File', render() { return ( ) } }) ================================================ FILE: src/_internal/icons/Filter.tsx ================================================ import { defineComponent, h } from 'vue' export default defineComponent({ name: 'Filter', render() { return ( ) } }) ================================================ FILE: src/_internal/icons/Forward.tsx ================================================ import { defineComponent, h } from 'vue' export default defineComponent({ name: 'Forward', render() { return ( ) } }) ================================================ FILE: src/_internal/icons/Info.tsx ================================================ import { h } from 'vue' import { replaceable } from './replaceable' export default replaceable('info', () => ( )) ================================================ FILE: src/_internal/icons/More.tsx ================================================ import { defineComponent, h } from 'vue' export default defineComponent({ name: 'More', render() { return ( ) } }) ================================================ FILE: src/_internal/icons/Photo.tsx ================================================ import { defineComponent, h } from 'vue' export default defineComponent({ name: 'Photo', render() { return ( ) } }) ================================================ FILE: src/_internal/icons/Remove.tsx ================================================ import { defineComponent, h } from 'vue' export default defineComponent({ name: 'Remove', render() { return ( ) } }) ================================================ FILE: src/_internal/icons/ResizeSmall.tsx ================================================ import { defineComponent, h } from 'vue' export default defineComponent({ name: 'ResizeSmall', render() { return ( ) } }) ================================================ FILE: src/_internal/icons/Retry.tsx ================================================ import { h } from 'vue' import { replaceable } from './replaceable' export default replaceable('retry', () => ( )) ================================================ FILE: src/_internal/icons/RotateClockwise.tsx ================================================ import { h } from 'vue' import { replaceable } from './replaceable' export default replaceable('rotateClockwise', () => ( )) ================================================ FILE: src/_internal/icons/RotateCounterclockwise.tsx ================================================ import { h } from 'vue' import { replaceable } from './replaceable' export default replaceable('rotateClockwise', () => ( )) ================================================ FILE: src/_internal/icons/Search.tsx ================================================ import { defineComponent, h } from 'vue' export default defineComponent({ name: 'Search', render() { return ( ) } }) ================================================ FILE: src/_internal/icons/Success.tsx ================================================ import { h } from 'vue' import { replaceable } from './replaceable' export default replaceable('success', () => ( )) ================================================ FILE: src/_internal/icons/Switcher.tsx ================================================ import { defineComponent, h } from 'vue' export default defineComponent({ name: 'Switcher', render() { return ( ) } }) ================================================ FILE: src/_internal/icons/Time.tsx ================================================ import { h } from 'vue' import { replaceable } from './replaceable' export default replaceable('time', () => ( )) ================================================ FILE: src/_internal/icons/To.tsx ================================================ import { h } from 'vue' import { replaceable } from './replaceable' export default replaceable('to', () => ( )) ================================================ FILE: src/_internal/icons/Trash.tsx ================================================ import { h } from 'vue' import { replaceable } from './replaceable' export default replaceable('trash', () => ( )) ================================================ FILE: src/_internal/icons/Warning.tsx ================================================ import { h } from 'vue' import { replaceable } from './replaceable' export default replaceable('warning', () => ( )) ================================================ FILE: src/_internal/icons/ZoomIn.tsx ================================================ import { h } from 'vue' import { replaceable } from './replaceable' export default replaceable('zoomIn', () => ( )) ================================================ FILE: src/_internal/icons/ZoomOut.tsx ================================================ import { h } from 'vue' import { replaceable } from './replaceable' export default replaceable('zoomOut', () => ( )) ================================================ FILE: src/_internal/icons/index.ts ================================================ export { default as AddIcon } from './Add' export { default as ArrowBackIcon } from './ArrowBack' export { default as ArrowDownIcon } from './ArrowDown' export { default as ArrowUpIcon } from './ArrowUp' export { default as AttachIcon } from './Attach' export { default as BackwardIcon } from './Backward' export { default as CancelIcon } from './Cancel' export { default as CheckmarkIcon } from './Checkmark' export { default as ChevronDownIcon } from './ChevronDown' export { default as ChevronDownFilledIcon } from './ChevronDownFilled' export { default as ChevronLeftIcon } from './ChevronLeft' export { default as ChevronRightIcon } from './ChevronRight' export { default as ClearIcon } from './Clear' export { default as CloseIcon } from './Close' export { default as DateIcon } from './Date' export { default as DownloadIcon } from './Download' export { default as EmptyIcon } from './Empty' export { default as ErrorIcon } from './Error' export { default as EyeIcon } from './Eye' export { default as EyeOffIcon } from './EyeOff' export { default as FastBackwardIcon } from './FastBackward' export { default as FastForwardIcon } from './FastForward' export { default as FileIcon } from './File' export { default as FilterIcon } from './Filter' export { default as ForwardIcon } from './Forward' export { default as InfoIcon } from './Info' export { default as MoreIcon } from './More' export { default as PhotoIcon } from './Photo' export { default as RemoveIcon } from './Remove' export { default as ResizeSmallIcon } from './ResizeSmall' export { default as RetryIcon } from './Retry' export { default as RotateClockwiseIcon } from './RotateClockwise' export { default as RotateCounterclockwiseIcon } from './RotateCounterclockwise' export { default as SearchIcon } from './Search' export { default as SuccessIcon } from './Success' export { default as SwitcherIcon } from './Switcher' export { default as TimeIcon } from './Time' export { default as ToIcon } from './To' export { default as TrashIcon } from './Trash' export { default as WarningIcon } from './Warning' export { default as ZoomInIcon } from './ZoomIn' export { default as ZoomOutIcon } from './ZoomOut' ================================================ FILE: src/_internal/icons/replaceable.tsx ================================================ import type { VNode } from 'vue' import type { GlobalIconConfig } from '../../config-provider/src/internal-interface' import { upperFirst } from 'lodash-es' import { defineComponent, h, inject } from 'vue' import { configProviderInjectionKey } from '../../config-provider/src/context' export function replaceable(name: keyof GlobalIconConfig, icon: () => VNode) { const IconComponent = defineComponent({ render() { return icon() } }) return defineComponent({ name: upperFirst(name), setup() { const mergedIconsRef = inject( configProviderInjectionKey, null )?.mergedIconsRef return () => { const iconOverride = mergedIconsRef?.value?.[name] return iconOverride ? iconOverride() : } } }) } ================================================ FILE: src/_internal/index.ts ================================================ export { default as NBaseClear } from './clear' export { NBaseClose } from './close' export { default as NFadeInExpandTransition } from './fade-in-expand-transition' export { default as NBaseFocusDetector } from './focus-detector' export { NBaseIcon } from './icon' export { default as NIconSwitchTransition } from './icon-switch-transition' export { default as NBaseLoading } from './loading' export type { SharedSpinProps } from './loading' export { default as NBaseMenuMask } from './menu-mask' export { NScrollbar, NxScrollbar } from './scrollbar' export type { ScrollbarInst, ScrollbarProps } from './scrollbar' export { default as NInternalSelectMenu } from './select-menu' export type { InternalSelectMenuRef } from './select-menu' export { default as NInternalSelection } from './selection' export type { InternalSelectionInst } from './selection' export { default as NBaseSlotMachine } from './slot-machine' export { default as NBaseSuffix } from './suffix' export { default as NBaseWave } from './wave' export type { BaseWaveRef } from './wave' ================================================ FILE: src/_internal/loading/index.ts ================================================ export { default } from './src/Loading' export type { SharedSpinProps } from './src/Loading' ================================================ FILE: src/_internal/loading/src/Loading.tsx ================================================ import type { ExtractPublicPropTypes } from '../../../_utils' import { defineComponent, h, toRef } from 'vue' import { useStyle } from '../../../_mixins' import NIconSwitchTransition from '../../icon-switch-transition' import style from './styles/index.cssr' const duration = '1.6s' export const exposedLoadingProps = { strokeWidth: { type: Number, default: 28 }, stroke: { type: String, default: undefined }, scale: { type: Number, default: 1 }, radius: { type: Number, default: 100 } } export type SharedSpinProps = ExtractPublicPropTypes export default defineComponent({ name: 'BaseLoading', props: { clsPrefix: { type: String, required: true }, show: { type: Boolean, default: true }, ...exposedLoadingProps }, setup(props) { useStyle('-base-loading', style, toRef(props, 'clsPrefix')) }, render() { const { clsPrefix, radius, strokeWidth, stroke, scale } = this const scaledRadius = radius / scale return ( ) } }) ================================================ FILE: src/_internal/loading/src/styles/index.cssr.ts ================================================ import { iconSwitchTransition } from '../../../../_styles/transitions/icon-switch.cssr' import { c, cB, cE } from '../../../../_utils/cssr' export default c([ c('@keyframes rotator', ` 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); } 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); }`), cB('base-loading', ` position: relative; line-height: 0; width: 1em; height: 1em; `, [ cE('transition-wrapper', ` position: absolute; width: 100%; height: 100%; `, [ iconSwitchTransition() ]), cE('placeholder', ` position: absolute; left: 50%; top: 50%; transform: translateX(-50%) translateY(-50%); `, [ iconSwitchTransition({ left: '50%', top: '50%', originalTransform: 'translateX(-50%) translateY(-50%)' }) ]), cE('container', ` animation: rotator 3s linear infinite both; `, [ cE('icon', ` height: 1em; width: 1em; `) ]) ]) ]) ================================================ FILE: src/_internal/menu-mask/index.ts ================================================ export type { MenuMaskRef } from './src/interface' export { default } from './src/MenuMask' ================================================ FILE: src/_internal/menu-mask/src/MenuMask.tsx ================================================ import type { MenuMaskRef } from './interface' import { defineComponent, h, onBeforeUnmount, ref, toRef, Transition } from 'vue' import { useStyle } from '../../../_mixins' import style from './styles/index.cssr' export default defineComponent({ name: 'BaseMenuMask', props: { clsPrefix: { type: String, required: true } }, setup(props) { useStyle('-base-menu-mask', style, toRef(props, 'clsPrefix')) const messageRef = ref(null) let timerId: number | null = null const uncontrolledShowRef = ref(false) onBeforeUnmount(() => { if (timerId !== null) { window.clearTimeout(timerId) } }) const exposedRef: MenuMaskRef = { showOnce(message: string, duration = 1500) { if (timerId) window.clearTimeout(timerId) uncontrolledShowRef.value = true messageRef.value = message timerId = window.setTimeout(() => { uncontrolledShowRef.value = false messageRef.value = null }, duration) } } return { message: messageRef, show: uncontrolledShowRef, ...exposedRef } }, render() { return ( {{ default: () => this.show ? (
{this.message}
) : null }}
) } }) ================================================ FILE: src/_internal/menu-mask/src/interface.ts ================================================ export interface MenuMaskRef { showOnce: (message: string, duration?: number) => void } ================================================ FILE: src/_internal/menu-mask/src/styles/index.cssr.ts ================================================ import { fadeInTransition } from '../../../../_styles/transitions/fade-in.cssr' import { cB } from '../../../../_utils/cssr' export default cB('base-menu-mask', ` position: absolute; left: 0; right: 0; top: 0; bottom: 0; display: flex; align-items: center; justify-content: center; text-align: center; padding: 14px; overflow: hidden; `, [ fadeInTransition() ]) ================================================ FILE: src/_internal/scrollbar/index.ts ================================================ export { default as NScrollbar, XScrollbar as NxScrollbar } from './src/Scrollbar' export type { ScrollbarInst, ScrollbarProps } from './src/Scrollbar' ================================================ FILE: src/_internal/scrollbar/src/Scrollbar.tsx ================================================ import type { CSSProperties, HTMLAttributes, PropType, VNode } from 'vue' import type { ThemeProps } from '../../../_mixins' import type { ExtractInternalPropTypes, ExtractPublicPropTypes } from '../../../_utils' import type { ScrollbarTheme } from '../styles' import { off, on } from 'evtd' import { depx, getPadding, getPreciseEventTarget, pxfy } from 'seemly' import { useIsIos } from 'vooks' import { computed, defineComponent, Fragment, h, mergeProps, onBeforeUnmount, onMounted, ref, Transition, watchEffect } from 'vue' import { VResizeObserver } from 'vueuc' import { useConfig, useRtl, useTheme, useThemeClass } from '../../../_mixins' import { rtlInset, useReactivated, Wrapper } from '../../../_utils' import { scrollbarLight } from '../styles' import style from './styles/index.cssr' interface MergedScrollOptions { left?: number top?: number el?: HTMLElement position?: 'top' | 'bottom' behavior?: ScrollBehavior debounce?: boolean index?: number elSize?: number } export interface ScrollTo { (x: number, y: number): void (options: { left?: number top?: number behavior?: ScrollBehavior debounce?: boolean }): void (options: { el: HTMLElement behavior?: ScrollBehavior debounce?: boolean }): void (options: { index: number elSize: number behavior?: ScrollBehavior debounce?: boolean }): void (options: { position: 'top' | 'bottom' behavior?: ScrollBehavior debounce?: boolean }): void } export interface ScrollBy { (x: number, y: number): void (options: { left?: number, top?: number, behavior?: ScrollBehavior }): void } export interface ScrollbarInstMethods { syncUnifiedContainer: () => void scrollTo: ScrollTo scrollBy: ScrollBy sync: () => void handleMouseEnterWrapper: () => void handleMouseLeaveWrapper: () => void } export interface ScrollbarInst extends ScrollbarInstMethods { $el: HTMLElement containerRef: HTMLElement | null contentRef: HTMLElement | null containerScrollTop: number } const scrollbarProps = { ...(useTheme.props as ThemeProps), duration: { type: Number, default: 0 }, scrollable: { type: Boolean, default: true }, xScrollable: Boolean, trigger: { type: String as PropType<'none' | 'hover'>, default: 'hover' }, useUnifiedContainer: Boolean, triggerDisplayManually: Boolean, // If container is set, resize observer won't not attached container: Function as PropType<() => HTMLElement | null | undefined>, content: Function as PropType<() => HTMLElement | null | undefined>, containerClass: String, containerStyle: [String, Object] as PropType, contentClass: [String, Array] as PropType>, contentStyle: [String, Object] as PropType, horizontalRailStyle: [String, Object] as PropType, verticalRailStyle: [String, Object] as PropType, onScroll: Function as PropType<(e: Event) => void>, onWheel: Function as PropType<(e: WheelEvent) => void>, onResize: Function as PropType<(e: ResizeObserverEntry) => void>, internalOnUpdateScrollLeft: Function as PropType< (scrollLeft: number) => void >, internalHoistYRail: Boolean, internalExposeWidthCssVar: Boolean, yPlacement: { type: String as PropType<'left' | 'right'>, default: 'right' }, xPlacement: { type: String as PropType<'top' | 'bottom'>, default: 'bottom' } } as const export type ScrollbarProps = ExtractPublicPropTypes export type ScrollbarInternalProps = ExtractInternalPropTypes< typeof scrollbarProps > const Scrollbar = defineComponent({ name: 'Scrollbar', props: scrollbarProps, inheritAttrs: false, setup(props) { const { mergedClsPrefixRef, inlineThemeDisabled, mergedRtlRef } = useConfig(props) const rtlEnabledRef = useRtl('Scrollbar', mergedRtlRef, mergedClsPrefixRef) // dom ref const wrapperRef = ref(null) const containerRef = ref(null) const contentRef = ref(null) const yRailRef = ref(null) const xRailRef = ref(null) // data ref const contentHeightRef = ref(null) const contentWidthRef = ref(null) const containerHeightRef = ref(null) const containerWidthRef = ref(null) const yRailSizeRef = ref(null) const xRailSizeRef = ref(null) const containerScrollTopRef = ref(0) const containerScrollLeftRef = ref(0) const isShowXBarRef = ref(false) const isShowYBarRef = ref(false) let yBarPressed = false let xBarPressed = false let xBarVanishTimerId: number | undefined let yBarVanishTimerId: number | undefined let memoYTop: number = 0 let memoXLeft: number = 0 let memoMouseX: number = 0 let memoMouseY: number = 0 const isIos = useIsIos() const themeRef = useTheme( 'Scrollbar', '-scrollbar', style, scrollbarLight, props, mergedClsPrefixRef ) const yBarSizeRef = computed(() => { const { value: containerHeight } = containerHeightRef const { value: contentHeight } = contentHeightRef const { value: yRailSize } = yRailSizeRef if ( containerHeight === null || contentHeight === null || yRailSize === null ) { return 0 } else { return Math.min( containerHeight, (yRailSize * containerHeight) / contentHeight + depx(themeRef.value.self.width) * 1.5 ) } }) const yBarSizePxRef = computed(() => { return `${yBarSizeRef.value}px` }) const xBarSizeRef = computed(() => { const { value: containerWidth } = containerWidthRef const { value: contentWidth } = contentWidthRef const { value: xRailSize } = xRailSizeRef if ( containerWidth === null || contentWidth === null || xRailSize === null ) { return 0 } else { return ( (xRailSize * containerWidth) / contentWidth + depx(themeRef.value.self.height) * 1.5 ) } }) const xBarSizePxRef = computed(() => { return `${xBarSizeRef.value}px` }) const yBarTopRef = computed(() => { const { value: containerHeight } = containerHeightRef const { value: containerScrollTop } = containerScrollTopRef const { value: contentHeight } = contentHeightRef const { value: yRailSize } = yRailSizeRef if ( containerHeight === null || contentHeight === null || yRailSize === null ) { return 0 } else { const heightDiff = contentHeight - containerHeight if (!heightDiff) return 0 return ( (containerScrollTop / heightDiff) * (yRailSize - yBarSizeRef.value) ) } }) const yBarTopPxRef = computed(() => { return `${yBarTopRef.value}px` }) const xBarLeftRef = computed(() => { const { value: containerWidth } = containerWidthRef const { value: containerScrollLeft } = containerScrollLeftRef const { value: contentWidth } = contentWidthRef const { value: xRailSize } = xRailSizeRef if ( containerWidth === null || contentWidth === null || xRailSize === null ) { return 0 } else { const widthDiff = contentWidth - containerWidth if (!widthDiff) return 0 return ( (containerScrollLeft / widthDiff) * (xRailSize - xBarSizeRef.value) ) } }) const xBarLeftPxRef = computed(() => { return `${xBarLeftRef.value}px` }) const needYBarRef = computed(() => { const { value: containerHeight } = containerHeightRef const { value: contentHeight } = contentHeightRef return ( containerHeight !== null && contentHeight !== null && contentHeight > containerHeight ) }) const needXBarRef = computed(() => { const { value: containerWidth } = containerWidthRef const { value: contentWidth } = contentWidthRef return ( containerWidth !== null && contentWidth !== null && contentWidth > containerWidth ) }) const mergedShowXBarRef = computed(() => { const { trigger } = props return trigger === 'none' || isShowXBarRef.value }) const mergedShowYBarRef = computed(() => { const { trigger } = props return trigger === 'none' || isShowYBarRef.value }) const mergedContainerRef = computed(() => { const { container } = props if (container) return container() return containerRef.value }) const mergedContentRef = computed(() => { const { content } = props if (content) return content() return contentRef.value }) const scrollTo: ScrollTo = ( options: MergedScrollOptions | number, y?: number ): void => { if (!props.scrollable) return if (typeof options === 'number') { scrollToPosition(options, y ?? 0, 0, false, 'auto') return } const { left, top, index, elSize, position, behavior, el, debounce = true } = options if (left !== undefined || top !== undefined) { scrollToPosition(left ?? 0, top ?? 0, 0, false, behavior) } if (el !== undefined) { scrollToPosition(0, el.offsetTop, el.offsetHeight, debounce, behavior) } else if (index !== undefined && elSize !== undefined) { scrollToPosition(0, index * elSize, elSize, debounce, behavior) } else if (position === 'bottom') { scrollToPosition(0, Number.MAX_SAFE_INTEGER, 0, false, behavior) } else if (position === 'top') { scrollToPosition(0, 0, 0, false, behavior) } } const activateState = useReactivated(() => { // Only restore for builtin container & content if (!props.container) { // remount scrollTo({ top: containerScrollTopRef.value, left: containerScrollLeftRef.value }) } }) // methods const handleContentResize = (): void => { if (activateState.isDeactivated) return sync() } const handleContainerResize = (e: ResizeObserverEntry): void => { if (activateState.isDeactivated) return const { onResize } = props if (onResize) onResize(e) sync() } const scrollBy: ScrollBy = ( options: ScrollOptions | number, y?: number ): void => { if (!props.scrollable) return const { value: container } = mergedContainerRef if (!container) return if (typeof options === 'object') { container.scrollBy(options) } else { container.scrollBy(options, y || 0) } } function scrollToPosition( left: number, top: number, elSize: number, debounce: boolean, behavior?: ScrollBehavior ): void { const { value: container } = mergedContainerRef if (!container) return if (debounce) { const { scrollTop, offsetHeight } = container if (top > scrollTop) { if (top + elSize <= scrollTop + offsetHeight) { // do nothing } else { container.scrollTo({ left, top: top + elSize - offsetHeight, behavior }) } return } } container.scrollTo({ left, top, behavior }) } function handleMouseEnterWrapper(): void { showXBar() showYBar() sync() } function handleMouseLeaveWrapper(): void { hideBar() } function hideBar(): void { hideYBar() hideXBar() } function hideYBar(): void { if (yBarVanishTimerId !== undefined) { window.clearTimeout(yBarVanishTimerId) } yBarVanishTimerId = window.setTimeout(() => { isShowYBarRef.value = false }, props.duration) } function hideXBar(): void { if (xBarVanishTimerId !== undefined) { window.clearTimeout(xBarVanishTimerId) } xBarVanishTimerId = window.setTimeout(() => { isShowXBarRef.value = false }, props.duration) } function showXBar(): void { if (xBarVanishTimerId !== undefined) { window.clearTimeout(xBarVanishTimerId) } isShowXBarRef.value = true } function showYBar(): void { if (yBarVanishTimerId !== undefined) { window.clearTimeout(yBarVanishTimerId) } isShowYBarRef.value = true } function handleScroll(e: Event): void { const { onScroll } = props if (onScroll) onScroll(e) syncScrollState() } function syncScrollState(): void { // only collect scroll state, do not trigger any dom event const { value: container } = mergedContainerRef if (container) { containerScrollTopRef.value = container.scrollTop containerScrollLeftRef.value = container.scrollLeft * (rtlEnabledRef?.value ? -1 : 1) } } function syncPositionState(): void { // only collect position state, do not trigger any dom event // Don't use getClientBoundingRect because element may be scale transformed const { value: content } = mergedContentRef if (content) { contentHeightRef.value = content.offsetHeight contentWidthRef.value = content.offsetWidth } const { value: container } = mergedContainerRef if (container) { containerHeightRef.value = container.offsetHeight containerWidthRef.value = container.offsetWidth } const { value: xRailEl } = xRailRef const { value: yRailEl } = yRailRef if (xRailEl) { xRailSizeRef.value = xRailEl.offsetWidth } if (yRailEl) { yRailSizeRef.value = yRailEl.offsetHeight } } /** * Sometimes there's only one element that we can scroll, * For example for textarea, there won't be a content element. */ function syncUnifiedContainer(): void { const { value: container } = mergedContainerRef if (container) { containerScrollTopRef.value = container.scrollTop containerScrollLeftRef.value = container.scrollLeft * (rtlEnabledRef?.value ? -1 : 1) containerHeightRef.value = container.offsetHeight containerWidthRef.value = container.offsetWidth contentHeightRef.value = container.scrollHeight contentWidthRef.value = container.scrollWidth } const { value: xRailEl } = xRailRef const { value: yRailEl } = yRailRef if (xRailEl) { xRailSizeRef.value = xRailEl.offsetWidth } if (yRailEl) { yRailSizeRef.value = yRailEl.offsetHeight } } function sync(): void { if (!props.scrollable) return if (props.useUnifiedContainer) { syncUnifiedContainer() } else { syncPositionState() syncScrollState() } } function isMouseUpAway(e: MouseEvent): boolean { return !wrapperRef.value?.contains( getPreciseEventTarget(e) as Node | null ) } function handleXScrollMouseDown(e: MouseEvent): void { e.preventDefault() e.stopPropagation() xBarPressed = true on('mousemove', window, handleXScrollMouseMove, true) on('mouseup', window, handleXScrollMouseUp, true) memoXLeft = containerScrollLeftRef.value memoMouseX = rtlEnabledRef?.value ? window.innerWidth - e.clientX : e.clientX } function handleXScrollMouseMove(e: MouseEvent): void { if (!xBarPressed) return if (xBarVanishTimerId !== undefined) { window.clearTimeout(xBarVanishTimerId) } if (yBarVanishTimerId !== undefined) { window.clearTimeout(yBarVanishTimerId) } const { value: containerWidth } = containerWidthRef const { value: contentWidth } = contentWidthRef const { value: xBarSize } = xBarSizeRef if (containerWidth === null || contentWidth === null) return const dX = rtlEnabledRef?.value ? window.innerWidth - e.clientX - memoMouseX : e.clientX - memoMouseX const dScrollLeft = (dX * (contentWidth - containerWidth)) / (containerWidth - xBarSize) const toScrollLeftUpperBound = contentWidth - containerWidth let toScrollLeft = memoXLeft + dScrollLeft toScrollLeft = Math.min(toScrollLeftUpperBound, toScrollLeft) toScrollLeft = Math.max(toScrollLeft, 0) const { value: container } = mergedContainerRef if (container) { container.scrollLeft = toScrollLeft * (rtlEnabledRef?.value ? -1 : 1) const { internalOnUpdateScrollLeft } = props if (internalOnUpdateScrollLeft) internalOnUpdateScrollLeft(toScrollLeft) } } function handleXScrollMouseUp(e: MouseEvent): void { e.preventDefault() e.stopPropagation() off('mousemove', window, handleXScrollMouseMove, true) off('mouseup', window, handleXScrollMouseUp, true) xBarPressed = false sync() if (isMouseUpAway(e)) { hideBar() } } function handleYScrollMouseDown(e: MouseEvent): void { e.preventDefault() e.stopPropagation() yBarPressed = true on('mousemove', window, handleYScrollMouseMove, true) on('mouseup', window, handleYScrollMouseUp, true) memoYTop = containerScrollTopRef.value memoMouseY = e.clientY } function handleYScrollMouseMove(e: MouseEvent): void { if (!yBarPressed) return if (xBarVanishTimerId !== undefined) { window.clearTimeout(xBarVanishTimerId) } if (yBarVanishTimerId !== undefined) { window.clearTimeout(yBarVanishTimerId) } const { value: containerHeight } = containerHeightRef const { value: contentHeight } = contentHeightRef const { value: yBarSize } = yBarSizeRef if (containerHeight === null || contentHeight === null) return const dY = e.clientY - memoMouseY const dScrollTop = (dY * (contentHeight - containerHeight)) / (containerHeight - yBarSize) const toScrollTopUpperBound = contentHeight - containerHeight let toScrollTop = memoYTop + dScrollTop toScrollTop = Math.min(toScrollTopUpperBound, toScrollTop) toScrollTop = Math.max(toScrollTop, 0) const { value: container } = mergedContainerRef if (container) { container.scrollTop = toScrollTop } } function handleYScrollMouseUp(e: MouseEvent): void { e.preventDefault() e.stopPropagation() off('mousemove', window, handleYScrollMouseMove, true) off('mouseup', window, handleYScrollMouseUp, true) yBarPressed = false sync() if (isMouseUpAway(e)) { hideBar() } } watchEffect(() => { const { value: needXBar } = needXBarRef const { value: needYBar } = needYBarRef const { value: mergedClsPrefix } = mergedClsPrefixRef const { value: xRailEl } = xRailRef const { value: yRailEl } = yRailRef if (xRailEl) { if (!needXBar) { xRailEl.classList.add(`${mergedClsPrefix}-scrollbar-rail--disabled`) } else { xRailEl.classList.remove( `${mergedClsPrefix}-scrollbar-rail--disabled` ) } } if (yRailEl) { if (!needYBar) { yRailEl.classList.add(`${mergedClsPrefix}-scrollbar-rail--disabled`) } else { yRailEl.classList.remove( `${mergedClsPrefix}-scrollbar-rail--disabled` ) } } }) onMounted(() => { // if container exist, it always can't be resolved when scrollbar is mounted // for example: // - component // - scrollbar // - inner // if you pass inner to scrollbar, you may use a ref inside component // however, when scrollbar is mounted, ref is not ready at component // you need to init by yourself if (props.container) return sync() }) onBeforeUnmount(() => { if (xBarVanishTimerId !== undefined) { window.clearTimeout(xBarVanishTimerId) } if (yBarVanishTimerId !== undefined) { window.clearTimeout(yBarVanishTimerId) } off('mousemove', window, handleYScrollMouseMove, true) off('mouseup', window, handleYScrollMouseUp, true) }) const cssVarsRef = computed(() => { const { common: { cubicBezierEaseInOut }, self: { color, colorHover, height, width, borderRadius, railInsetHorizontalTop, railInsetHorizontalBottom, railInsetVerticalRight, railInsetVerticalLeft, railColor } } = themeRef.value const { top: railTopHorizontalTop, right: railRightHorizontalTop, bottom: railBottomHorizontalTop, left: railLeftHorizontalTop } = getPadding(railInsetHorizontalTop) const { top: railTopHorizontalBottom, right: railRightHorizontalBottom, bottom: railBottomHorizontalBottom, left: railLeftHorizontalBottom } = getPadding(railInsetHorizontalBottom) const { top: railTopVerticalRight, right: railRightVerticalRight, bottom: railBottomVerticalRight, left: railLeftVerticalRight } = getPadding( rtlEnabledRef?.value ? rtlInset(railInsetVerticalRight) : railInsetVerticalRight ) const { top: railTopVerticalLeft, right: railRightVerticalLeft, bottom: railBottomVerticalLeft, left: railLeftVerticalLeft } = getPadding( rtlEnabledRef?.value ? rtlInset(railInsetVerticalLeft) : railInsetVerticalLeft ) return { '--n-scrollbar-bezier': cubicBezierEaseInOut, '--n-scrollbar-color': color, '--n-scrollbar-color-hover': colorHover, '--n-scrollbar-border-radius': borderRadius, '--n-scrollbar-width': width, '--n-scrollbar-height': height, '--n-scrollbar-rail-top-horizontal-top': railTopHorizontalTop, '--n-scrollbar-rail-right-horizontal-top': railRightHorizontalTop, '--n-scrollbar-rail-bottom-horizontal-top': railBottomHorizontalTop, '--n-scrollbar-rail-left-horizontal-top': railLeftHorizontalTop, '--n-scrollbar-rail-top-horizontal-bottom': railTopHorizontalBottom, '--n-scrollbar-rail-right-horizontal-bottom': railRightHorizontalBottom, '--n-scrollbar-rail-bottom-horizontal-bottom': railBottomHorizontalBottom, '--n-scrollbar-rail-left-horizontal-bottom': railLeftHorizontalBottom, '--n-scrollbar-rail-top-vertical-right': railTopVerticalRight, '--n-scrollbar-rail-right-vertical-right': railRightVerticalRight, '--n-scrollbar-rail-bottom-vertical-right': railBottomVerticalRight, '--n-scrollbar-rail-left-vertical-right': railLeftVerticalRight, '--n-scrollbar-rail-top-vertical-left': railTopVerticalLeft, '--n-scrollbar-rail-right-vertical-left': railRightVerticalLeft, '--n-scrollbar-rail-bottom-vertical-left': railBottomVerticalLeft, '--n-scrollbar-rail-left-vertical-left': railLeftVerticalLeft, '--n-scrollbar-rail-color': railColor } }) const themeClassHandle = inlineThemeDisabled ? useThemeClass('scrollbar', undefined, cssVarsRef, props) : undefined const exposedMethods: ScrollbarInstMethods = { scrollTo, scrollBy, sync, syncUnifiedContainer, handleMouseEnterWrapper, handleMouseLeaveWrapper } return { ...exposedMethods, mergedClsPrefix: mergedClsPrefixRef, rtlEnabled: rtlEnabledRef, containerScrollTop: containerScrollTopRef, wrapperRef, containerRef, contentRef, yRailRef, xRailRef, needYBar: needYBarRef, needXBar: needXBarRef, yBarSizePx: yBarSizePxRef, xBarSizePx: xBarSizePxRef, yBarTopPx: yBarTopPxRef, xBarLeftPx: xBarLeftPxRef, isShowXBar: mergedShowXBarRef, isShowYBar: mergedShowYBarRef, isIos, handleScroll, handleContentResize, handleContainerResize, handleYScrollMouseDown, handleXScrollMouseDown, containerWidth: containerWidthRef, cssVars: inlineThemeDisabled ? undefined : cssVarsRef, themeClass: themeClassHandle?.themeClass, onRender: themeClassHandle?.onRender } }, render() { const { $slots, mergedClsPrefix, triggerDisplayManually, rtlEnabled, internalHoistYRail, yPlacement, xPlacement, xScrollable } = this if (!this.scrollable) return $slots.default?.() const triggerIsNone = this.trigger === 'none' const createYRail = ( className: string | undefined, style: CSSProperties | undefined ): VNode => { return (
{h( (triggerIsNone ? Wrapper : Transition) as any, triggerIsNone ? null : { name: 'fade-in-transition' }, { default: () => this.needYBar && this.isShowYBar && !this.isIos ? (
) : null } )}
) } const createChildren = (): VNode => { this.onRender?.() return h( 'div', mergeProps(this.$attrs, { role: 'none', ref: 'wrapperRef', class: [ `${mergedClsPrefix}-scrollbar`, this.themeClass, rtlEnabled && `${mergedClsPrefix}-scrollbar--rtl` ], style: this.cssVars, onMouseenter: triggerDisplayManually ? undefined : this.handleMouseEnterWrapper, onMouseleave: triggerDisplayManually ? undefined : this.handleMouseLeaveWrapper }), [ this.container ? ( $slots.default?.() ) : (
{{ default: () => (
{$slots}
) }}
), internalHoistYRail ? null : createYRail(undefined, undefined), xScrollable && (
{h( (triggerIsNone ? Wrapper : Transition) as any, triggerIsNone ? null : { name: 'fade-in-transition' }, { default: () => this.needXBar && this.isShowXBar && !this.isIos ? (
) : null } )}
) ] ) } const scrollbarNode = this.container ? ( createChildren() ) : ( {{ default: createChildren }} ) if (internalHoistYRail) { return ( {scrollbarNode} {createYRail(this.themeClass, this.cssVars)} ) } else { return scrollbarNode } } }) type NativeScrollbarProps = Omit type MergedProps = Partial export default Scrollbar export const XScrollbar: new () => { $props: MergedProps } = Scrollbar as any ================================================ FILE: src/_internal/scrollbar/src/styles/index.cssr.ts ================================================ import { fadeInTransition } from '../../../../_styles/transitions/fade-in.cssr' import { c, cB, cE, cM } from '../../../../_utils/cssr' // vars: // --n-scrollbar-bezier // --n-scrollbar-color // --n-scrollbar-color-hover // --n-scrollbar-width // --n-scrollbar-height // --n-scrollbar-border-radius // --n-scrollbar-rail-inset-horizontal // --n-scrollbar-rail-inset-vertical // --n-scrollbar-rail-color export default cB('scrollbar', ` overflow: hidden; position: relative; z-index: auto; height: 100%; width: 100%; `, [ c('>', [ cB('scrollbar-container', ` width: 100%; overflow: scroll; height: 100%; min-height: inherit; max-height: inherit; scrollbar-width: none; `, [ c('&::-webkit-scrollbar, &::-webkit-scrollbar-track-piece, &::-webkit-scrollbar-thumb', ` width: 0; height: 0; display: none; `), c('>', [ // We can't set overflow hidden since it affects positioning. cB('scrollbar-content', ` box-sizing: border-box; min-width: 100%; `) ]) ]) ]), c('>, +', [ cB('scrollbar-rail', ` position: absolute; pointer-events: none; user-select: none; background: var(--n-scrollbar-rail-color); -webkit-user-select: none; `, [ cM('horizontal', ` height: var(--n-scrollbar-height); `, [ c('>', [ cE('scrollbar', ` height: var(--n-scrollbar-height); border-radius: var(--n-scrollbar-border-radius); right: 0; `) ]) ]), cM('horizontal--top', ` top: var(--n-scrollbar-rail-top-horizontal-top); right: var(--n-scrollbar-rail-right-horizontal-top); bottom: var(--n-scrollbar-rail-bottom-horizontal-top); left: var(--n-scrollbar-rail-left-horizontal-top); `), cM('horizontal--bottom', ` top: var(--n-scrollbar-rail-top-horizontal-bottom); right: var(--n-scrollbar-rail-right-horizontal-bottom); bottom: var(--n-scrollbar-rail-bottom-horizontal-bottom); left: var(--n-scrollbar-rail-left-horizontal-bottom); `), cM('vertical', ` width: var(--n-scrollbar-width); `, [ c('>', [ cE('scrollbar', ` width: var(--n-scrollbar-width); border-radius: var(--n-scrollbar-border-radius); bottom: 0; `) ]) ]), cM('vertical--left', ` top: var(--n-scrollbar-rail-top-vertical-left); right: var(--n-scrollbar-rail-right-vertical-left); bottom: var(--n-scrollbar-rail-bottom-vertical-left); left: var(--n-scrollbar-rail-left-vertical-left); `), cM('vertical--right', ` top: var(--n-scrollbar-rail-top-vertical-right); right: var(--n-scrollbar-rail-right-vertical-right); bottom: var(--n-scrollbar-rail-bottom-vertical-right); left: var(--n-scrollbar-rail-left-vertical-right); `), cM('disabled', [ c('>', [ cE('scrollbar', 'pointer-events: none;') ]) ]), c('>', [ cE('scrollbar', ` z-index: 1; position: absolute; cursor: pointer; pointer-events: all; background-color: var(--n-scrollbar-color); transition: background-color .2s var(--n-scrollbar-bezier); `, [ fadeInTransition(), c('&:hover', 'background-color: var(--n-scrollbar-color-hover);') ]) ]) ]) ]) ]) ================================================ FILE: src/_internal/scrollbar/src/styles/rtl.cssr.ts ================================================ import { c, cB, cE, cM } from '../../../../_utils/cssr' export default cB('scrollbar', [ cM('rtl', ` direction: rtl; `, [ c('>', [ cB('scrollbar-rail', [ cM('horizontal', [ c('>', [ cE('scrollbar', ` left: 0; right: unset; `) ]) ]) ]) ]) ]) ]) ================================================ FILE: src/_internal/scrollbar/styles/common.ts ================================================ export const commonVars = { railInsetHorizontalBottom: 'auto 2px 4px 2px', railInsetHorizontalTop: '4px 2px auto 2px', railInsetVerticalRight: '2px 4px 2px auto', railInsetVerticalLeft: '2px auto 2px 4px', railColor: 'transparent' } ================================================ FILE: src/_internal/scrollbar/styles/dark.ts ================================================ import type { ScrollbarTheme } from './light' import { commonDark } from '../../../_styles/common' import { self } from './light' const scrollbarDark: ScrollbarTheme = { name: 'Scrollbar', common: commonDark, self } export default scrollbarDark ================================================ FILE: src/_internal/scrollbar/styles/index.ts ================================================ export { default as scrollbarDark } from './dark' export { default as scrollbarLight } from './light' export type { ScrollbarTheme, ScrollbarThemeVars } from './light' export { default as scrollbarRtl } from './rtl' ================================================ FILE: src/_internal/scrollbar/styles/light.ts ================================================ import type { Theme } from '../../../_mixins' import type { ThemeCommonVars } from '../../../_styles/common' import { commonLight } from '../../../_styles/common' import { commonVars } from './common' export function self(vars: ThemeCommonVars) { const { scrollbarColor, scrollbarColorHover, scrollbarHeight, scrollbarWidth, scrollbarBorderRadius } = vars return { ...commonVars, height: scrollbarHeight, width: scrollbarWidth, borderRadius: scrollbarBorderRadius, color: scrollbarColor, colorHover: scrollbarColorHover } } export type ScrollbarThemeVars = ReturnType const scrollbarLight: Theme<'Scrollbar', ScrollbarThemeVars> = { name: 'Scrollbar', common: commonLight, self } export default scrollbarLight export type ScrollbarTheme = typeof scrollbarLight ================================================ FILE: src/_internal/scrollbar/styles/rtl.ts ================================================ import type { RtlItem } from '../../../config-provider/src/internal-interface' import rtlStyle from '../src/styles/rtl.cssr' export const scrollbarRtl: RtlItem = { name: 'Scrollbar', style: rtlStyle } export default scrollbarRtl ================================================ FILE: src/_internal/scrollbar/tests/Scrollbar.spec.ts ================================================ import { mount } from '@vue/test-utils' import { NScrollbar } from '../index' describe('n-scrollbar', () => { it('should work with import on demand', () => { mount(NScrollbar) }) }) ================================================ FILE: src/_internal/scrollbar/tests/server.spec.tsx ================================================ import { setup } from '@css-render/vue3-ssr' import { renderToString } from '@vue/server-renderer' /** * @vitest-environment node */ import { createSSRApp, h } from 'vue' import { NScrollbar } from '../index' describe('server side rendering', () => { it('works', async () => { const app = createSSRApp(() => ) setup(app) try { await renderToString(app) } catch (e) { expect(e).not.toBeTruthy() } }) }) ================================================ FILE: src/_internal/select-menu/index.ts ================================================ export type { InternalSelectMenuRef } from './src/interface' export { default } from './src/SelectMenu' ================================================ FILE: src/_internal/select-menu/src/SelectGroupHeader.tsx ================================================ import type { TreeNode } from 'treemate' import type { PropType, Ref } from 'vue' import type { SelectGroupOption } from '../../../select/src/interface' import type { RenderLabelImpl, RenderOptionImpl } from './interface' import { defineComponent, h, inject } from 'vue' import { render } from '../../../_utils' import { internalSelectionMenuInjectionKey } from './interface' export default defineComponent({ name: 'NBaseSelectGroupHeader', props: { clsPrefix: { type: String, required: true }, tmNode: { type: Object as PropType>, required: true } }, setup() { const { renderLabelRef, renderOptionRef, labelFieldRef, nodePropsRef } = inject(internalSelectionMenuInjectionKey)! return { labelField: labelFieldRef, nodeProps: nodePropsRef, renderLabel: renderLabelRef as Ref, renderOption: renderOptionRef as Ref } }, render() { const { clsPrefix, renderLabel, renderOption, nodeProps, tmNode: { rawNode } } = this const attrs = nodeProps?.(rawNode) const children = renderLabel ? renderLabel(rawNode, false) : render(rawNode[this.labelField], rawNode, false) const node = (
{children}
) return rawNode.render ? rawNode.render({ node, option: rawNode }) : renderOption ? renderOption({ node, option: rawNode, selected: false }) : node } }) ================================================ FILE: src/_internal/select-menu/src/SelectMenu.tsx ================================================ import type { TreeNode } from 'treemate' import type { CSSProperties, PropType, WatchStopHandle } from 'vue' import type { VirtualListInst } from 'vueuc' import type { ThemeProps } from '../../../_mixins' import type { SelectGroupOption, SelectIgnoredOption, SelectOption, SelectTreeMate, Value } from '../../../select/src/interface' import type { ScrollbarInst } from '../../scrollbar' import type { ScrollbarProps } from '../../scrollbar/src/Scrollbar' import type { InternalSelectMenuTheme } from '../styles' import type { InternalExposedProps, NodeProps, RenderLabel, RenderOption, Size } from './interface' import { depx, getPadding, happensIn } from 'seemly' import { createIndexGetter } from 'treemate' import { computed, defineComponent, h, nextTick, onBeforeUnmount, onMounted, provide, ref, toRef, watch } from 'vue' import { VirtualList } from 'vueuc' import { useConfig, useRtl, useTheme, useThemeClass } from '../../../_mixins' import { resolveSlot, resolveWrappedSlot, useOnResize } from '../../../_utils' import { createKey } from '../../../_utils/cssr' import { NEmpty } from '../../../empty' import NFocusDetector from '../../focus-detector' import NInternalLoading from '../../loading' import { NScrollbar } from '../../scrollbar' import { internalSelectMenuLight } from '../styles' import { internalSelectionMenuBodyInjectionKey, internalSelectionMenuInjectionKey } from './interface' import NSelectGroupHeader from './SelectGroupHeader' import NSelectOption from './SelectOption' import style from './styles/index.cssr' export default defineComponent({ name: 'InternalSelectMenu', props: { ...(useTheme.props as ThemeProps), clsPrefix: { type: String, required: true }, scrollable: { type: Boolean, default: true }, treeMate: { type: Object as PropType, required: true }, multiple: Boolean, size: { type: String as PropType, default: 'medium' }, value: { type: [String, Number, Array] as PropType, default: null }, autoPending: Boolean, virtualScroll: { type: Boolean, default: true }, // show is used to toggle pending state initialization show: { type: Boolean, default: true }, labelField: { type: String, default: 'label' }, valueField: { type: String, default: 'value' }, loading: Boolean, focusable: Boolean, renderLabel: Function as PropType, renderOption: Function as PropType, nodeProps: Function as PropType, showCheckmark: { type: Boolean, default: true }, onMousedown: Function as PropType<(e: MouseEvent) => void>, onScroll: Function as PropType<(e: Event) => void>, onFocus: Function as PropType<(e: FocusEvent) => void>, onBlur: Function as PropType<(e: FocusEvent) => void>, onKeyup: Function as PropType<(e: KeyboardEvent) => void>, onKeydown: Function as PropType<(e: KeyboardEvent) => void>, onTabOut: Function as PropType<() => void>, onMouseenter: Function as PropType<(e: MouseEvent) => void>, onMouseleave: Function as PropType<(e: MouseEvent) => void>, onResize: Function as PropType<() => void>, resetMenuOnOptionsChange: { type: Boolean, default: true }, inlineThemeDisabled: Boolean, scrollbarProps: Object as PropType, // deprecated onToggle: Function as PropType<(tmNode: TreeNode) => void> }, setup(props) { const { mergedClsPrefixRef, mergedRtlRef, mergedComponentPropsRef } = useConfig(props) const rtlEnabledRef = useRtl( 'InternalSelectMenu', mergedRtlRef, mergedClsPrefixRef ) const themeRef = useTheme( 'InternalSelectMenu', '-internal-select-menu', style, internalSelectMenuLight, props, toRef(props, 'clsPrefix') ) const selfRef = ref(null) const virtualListRef = ref(null) const scrollbarRef = ref(null) const flattenedNodesRef = computed(() => props.treeMate.getFlattenedNodes()) const fIndexGetterRef = computed(() => createIndexGetter(flattenedNodesRef.value) ) const pendingNodeRef = ref | null>(null) function initPendingNode(): void { const { treeMate } = props let defaultPendingNode: TreeNode | null = null const { value } = props if (value === null) { defaultPendingNode = treeMate.getFirstAvailableNode() } else { if (props.multiple) { defaultPendingNode = treeMate.getNode( ((value as Array | null) || [])[ ((value as Array | null) || []).length - 1 ] ) } else { defaultPendingNode = treeMate.getNode(value as string | number) } if (!defaultPendingNode || defaultPendingNode.disabled) { defaultPendingNode = treeMate.getFirstAvailableNode() } } if (defaultPendingNode) { setPendingTmNode(defaultPendingNode) } else { setPendingTmNode(null) } } function clearPendingNodeIfInvalid(): void { const { value: pendingNode } = pendingNodeRef if (pendingNode && !props.treeMate.getNode(pendingNode.key)) { pendingNodeRef.value = null } } let initPendingNodeWatchStopHandle: WatchStopHandle | undefined watch( () => props.show, (show) => { if (show) { initPendingNodeWatchStopHandle = watch( () => props.treeMate, () => { if (props.resetMenuOnOptionsChange) { if (props.autoPending) { initPendingNode() } else { clearPendingNodeIfInvalid() } void nextTick(scrollToPendingNode) } else { clearPendingNodeIfInvalid() } }, { immediate: true } ) } else { initPendingNodeWatchStopHandle?.() } }, { immediate: true } ) onBeforeUnmount(() => { initPendingNodeWatchStopHandle?.() }) const itemSizeRef = computed(() => { return depx(themeRef.value.self[createKey('optionHeight', props.size)]) }) const paddingRef = computed(() => { return getPadding(themeRef.value.self[createKey('padding', props.size)]) }) const valueSetRef = computed(() => { if (props.multiple && Array.isArray(props.value)) { return new Set(props.value) } return new Set() }) const emptyRef = computed(() => { const tmNodes = flattenedNodesRef.value return tmNodes && tmNodes.length === 0 }) const mergedRenderEmptyRef = computed(() => { return mergedComponentPropsRef?.value?.Select?.renderEmpty }) function doToggle(tmNode: TreeNode): void { const { onToggle } = props if (onToggle) onToggle(tmNode) } function doScroll(e: Event): void { const { onScroll } = props if (onScroll) onScroll(e) } // required, scroller sync need to be triggered manually function handleVirtualListScroll(e: Event): void { scrollbarRef.value?.sync() doScroll(e) } function handleVirtualListResize(): void { scrollbarRef.value?.sync() } function getPendingTmNode(): TreeNode | null { const { value: pendingTmNode } = pendingNodeRef if (pendingTmNode) return pendingTmNode return null } function handleOptionMouseEnter( e: MouseEvent, tmNode: TreeNode ): void { if (tmNode.disabled) return setPendingTmNode(tmNode, false) } function handleOptionClick( e: MouseEvent, tmNode: TreeNode ): void { if (tmNode.disabled) return doToggle(tmNode) } // keyboard related methods function handleKeyUp(e: KeyboardEvent): void { if (happensIn(e, 'action')) return props.onKeyup?.(e) } function handleKeyDown(e: KeyboardEvent): void { if (happensIn(e, 'action')) return props.onKeydown?.(e) } function handleMouseDown(e: MouseEvent): void { props.onMousedown?.(e) if (props.focusable) return e.preventDefault() } function next(): void { const { value: pendingTmNode } = pendingNodeRef if (pendingTmNode) { setPendingTmNode(pendingTmNode.getNext({ loop: true }), true) } } function prev(): void { const { value: pendingTmNode } = pendingNodeRef if (pendingTmNode) { setPendingTmNode(pendingTmNode.getPrev({ loop: true }), true) } } function setPendingTmNode( tmNode: TreeNode | null, doScroll = false ): void { pendingNodeRef.value = tmNode if (doScroll) scrollToPendingNode() } function scrollToPendingNode(): void { const tmNode = pendingNodeRef.value if (!tmNode) return const fIndex = fIndexGetterRef.value(tmNode.key) if (fIndex === null) return if (props.virtualScroll) { virtualListRef.value?.scrollTo({ index: fIndex }) } else { scrollbarRef.value?.scrollTo({ index: fIndex, elSize: itemSizeRef.value }) } } function handleFocusin(e: FocusEvent): void { if (selfRef.value?.contains(e.target as Node | null)) { props.onFocus?.(e) } } function handleFocusout(e: FocusEvent): void { if (!selfRef.value?.contains(e.relatedTarget as Node | null)) { props.onBlur?.(e) } } provide(internalSelectionMenuInjectionKey, { handleOptionMouseEnter, handleOptionClick, valueSetRef, pendingTmNodeRef: pendingNodeRef, nodePropsRef: toRef(props, 'nodeProps'), showCheckmarkRef: toRef(props, 'showCheckmark'), multipleRef: toRef(props, 'multiple'), valueRef: toRef(props, 'value'), renderLabelRef: toRef(props, 'renderLabel'), renderOptionRef: toRef(props, 'renderOption'), labelFieldRef: toRef(props, 'labelField'), valueFieldRef: toRef(props, 'valueField') }) provide(internalSelectionMenuBodyInjectionKey, selfRef) onMounted(() => { const { value } = scrollbarRef if (value) value.sync() }) const cssVarsRef = computed(() => { const { size } = props const { common: { cubicBezierEaseInOut }, self: { height, borderRadius, color, groupHeaderTextColor, actionDividerColor, optionTextColorPressed, optionTextColor, optionTextColorDisabled, optionTextColorActive, optionOpacityDisabled, optionCheckColor, actionTextColor, optionColorPending, optionColorActive, loadingColor, loadingSize, optionColorActivePending, [createKey('optionFontSize', size)]: fontSize, [createKey('optionHeight', size)]: optionHeight, [createKey('optionPadding', size)]: optionPadding } } = themeRef.value return { '--n-height': height, '--n-action-divider-color': actionDividerColor, '--n-action-text-color': actionTextColor, '--n-bezier': cubicBezierEaseInOut, '--n-border-radius': borderRadius, '--n-color': color, '--n-option-font-size': fontSize, '--n-group-header-text-color': groupHeaderTextColor, '--n-option-check-color': optionCheckColor, '--n-option-color-pending': optionColorPending, '--n-option-color-active': optionColorActive, '--n-option-color-active-pending': optionColorActivePending, '--n-option-height': optionHeight, '--n-option-opacity-disabled': optionOpacityDisabled, '--n-option-text-color': optionTextColor, '--n-option-text-color-active': optionTextColorActive, '--n-option-text-color-disabled': optionTextColorDisabled, '--n-option-text-color-pressed': optionTextColorPressed, '--n-option-padding': optionPadding, '--n-option-padding-left': getPadding(optionPadding, 'left'), '--n-option-padding-right': getPadding(optionPadding, 'right'), '--n-loading-color': loadingColor, '--n-loading-size': loadingSize } }) const { inlineThemeDisabled } = props const themeClassHandle = inlineThemeDisabled ? useThemeClass( 'internal-select-menu', computed(() => props.size[0]), cssVarsRef, props ) : undefined const exposedProps: InternalExposedProps = { selfRef, next, prev, getPendingTmNode } useOnResize(selfRef, props.onResize) return { mergedTheme: themeRef, mergedClsPrefix: mergedClsPrefixRef, rtlEnabled: rtlEnabledRef, virtualListRef, scrollbarRef, itemSize: itemSizeRef, padding: paddingRef, flattenedNodes: flattenedNodesRef, empty: emptyRef, mergedRenderEmpty: mergedRenderEmptyRef, virtualListContainer() { const { value } = virtualListRef return value?.listElRef }, virtualListContent() { const { value } = virtualListRef return value?.itemsElRef }, doScroll, handleFocusin, handleFocusout, handleKeyUp, handleKeyDown, handleMouseDown, handleVirtualListResize, handleVirtualListScroll, cssVars: inlineThemeDisabled ? undefined : cssVarsRef, themeClass: themeClassHandle?.themeClass, onRender: themeClassHandle?.onRender, ...exposedProps } }, render() { const { $slots, virtualScroll, clsPrefix, mergedTheme, themeClass, onRender } = this onRender?.() return (
{resolveWrappedSlot( $slots.header, children => children && (
{children}
) )} {this.loading ? (
) : !this.empty ? ( {{ default: () => { return virtualScroll ? ( {{ default: ({ item: tmNode }: { item: TreeNode< SelectGroupOption | SelectOption | SelectIgnoredOption > }) => { return tmNode.isGroup ? ( } /> ) : tmNode.ignored ? null : ( } /> ) } }} ) : (
{this.flattenedNodes.map(tmNode => tmNode.isGroup ? ( } /> ) : ( } /> ) )}
) } }}
) : (
{resolveSlot($slots.empty, () => { return [ this.mergedRenderEmpty?.() || ( ) ] })}
)} {resolveWrappedSlot( $slots.action, children => children && [
{children}
, ] )}
) } }) ================================================ FILE: src/_internal/select-menu/src/SelectOption.tsx ================================================ import type { TreeNode } from 'treemate' import type { PropType, Ref, VNode } from 'vue' import type { SelectOption } from '../../../select/src/interface' import type { RenderLabelImpl, RenderOptionImpl } from './interface' import { useMemo } from 'vooks' import { defineComponent, h, inject, Transition } from 'vue' import { mergeEventHandlers, render } from '../../../_utils' import { NBaseIcon } from '../../icon' import { CheckmarkIcon } from '../../icons' import { internalSelectionMenuInjectionKey } from './interface' function renderCheckMark(show: boolean, clsPrefix: string): VNode { return ( {{ default: () => show ? ( {{ default: () => h(CheckmarkIcon) }} ) : null }} ) } export default defineComponent({ name: 'NBaseSelectOption', props: { clsPrefix: { type: String, required: true }, tmNode: { type: Object as PropType>, required: true } }, setup(props) { const { valueRef, pendingTmNodeRef, multipleRef, valueSetRef, renderLabelRef, renderOptionRef, labelFieldRef, valueFieldRef, showCheckmarkRef, nodePropsRef, handleOptionClick, handleOptionMouseEnter } = inject(internalSelectionMenuInjectionKey)! const isPendingRef = useMemo(() => { const { value: pendingTmNode } = pendingTmNodeRef if (!pendingTmNode) return false return props.tmNode.key === pendingTmNode.key }) function handleClick(e: MouseEvent): void { const { tmNode } = props if (tmNode.disabled) return handleOptionClick(e, tmNode) } function handleMouseEnter(e: MouseEvent): void { const { tmNode } = props if (tmNode.disabled) return handleOptionMouseEnter(e, tmNode) } function handleMouseMove(e: MouseEvent): void { const { tmNode } = props const { value: isPending } = isPendingRef if (tmNode.disabled || isPending) return handleOptionMouseEnter(e, tmNode) } return { multiple: multipleRef, isGrouped: useMemo(() => { const { tmNode } = props const { parent } = tmNode return parent && parent.rawNode.type === 'group' }), showCheckmark: showCheckmarkRef, nodeProps: nodePropsRef, isPending: isPendingRef, isSelected: useMemo(() => { const { value } = valueRef const { value: multiple } = multipleRef if (value === null) return false const optionValue = props.tmNode.rawNode[ valueFieldRef.value ] as NonNullable if (multiple) { const { value: valueSet } = valueSetRef return valueSet.has(optionValue) } else { return value === optionValue } }), labelField: labelFieldRef, renderLabel: renderLabelRef as Ref, renderOption: renderOptionRef as Ref, handleMouseMove, handleMouseEnter, handleClick } }, render() { const { clsPrefix, tmNode: { rawNode }, isSelected, isPending, isGrouped, showCheckmark, nodeProps, renderOption, renderLabel, handleClick, handleMouseEnter, handleMouseMove } = this const checkmark = renderCheckMark(isSelected, clsPrefix) const children = renderLabel ? [renderLabel(rawNode, isSelected), showCheckmark && checkmark] : [ render( rawNode[this.labelField] as SelectOption['label'], rawNode, isSelected ), showCheckmark && checkmark ] const attrs = nodeProps?.(rawNode) const node = (
{children}
) return rawNode.render ? rawNode.render({ node, option: rawNode, selected: isSelected }) : renderOption ? renderOption({ node, option: rawNode, selected: isSelected }) : node } }) ================================================ FILE: src/_internal/select-menu/src/interface.ts ================================================ import type { TreeNode } from 'treemate' import type { HTMLAttributes, Ref, UnwrapRef, VNode, VNodeChild } from 'vue' import type { SelectBaseOption, SelectGroupOption, SelectIgnoredOption } from '../../../select/src/interface' import { createInjectionKey } from '../../../_utils/vue/create-injection-key' export type Size = 'tiny' | 'small' | 'medium' | 'large' | 'huge' export type RenderLabel = ( option: SelectBaseOption & SelectGroupOption & SelectIgnoredOption, selected: boolean ) => VNodeChild export type RenderLabelImpl = ( option: SelectBaseOption | SelectGroupOption | SelectIgnoredOption, selected: boolean ) => VNodeChild export type RenderOption = (info: { node: VNode option: SelectBaseOption & SelectGroupOption & SelectIgnoredOption selected: boolean }) => VNodeChild export type RenderOptionImpl = (info: { node: VNode option: SelectBaseOption | SelectGroupOption | SelectIgnoredOption selected: boolean }) => VNodeChild export type NodeProps = ( option: SelectBaseOption | SelectGroupOption ) => HTMLAttributes & Record export interface InternalSelectMenuInjection { handleOptionMouseEnter: ( e: MouseEvent, tmNode: TreeNode ) => void handleOptionClick: (e: MouseEvent, tmNode: TreeNode) => void showCheckmarkRef: Ref valueSetRef: Ref> pendingTmNodeRef: Ref | null> multipleRef: Ref valueRef: Ref | null> renderLabelRef: Ref renderOptionRef: Ref labelFieldRef: Ref valueFieldRef: Ref nodePropsRef: Ref } export interface InternalExposedProps { selfRef: Ref getPendingTmNode: () => TreeNode | null prev: () => void next: () => void } export const internalSelectionMenuInjectionKey = createInjectionKey('n-internal-select-menu') export const internalSelectionMenuBodyInjectionKey = createInjectionKey< Ref >('n-internal-select-menu-body') export type InternalSelectMenuRef = UnwrapRef ================================================ FILE: src/_internal/select-menu/src/styles/index.cssr.ts ================================================ import { fadeInScaleUpTransition } from '../../../../_styles/transitions/fade-in-scale-up.cssr' import { c, cB, cE, cM, cNotM } from '../../../../_utils/cssr' // --n-loading-color // --n-loading-size // --n-option-padding-right export default cB('base-select-menu', ` line-height: 1.5; outline: none; z-index: 0; position: relative; border-radius: var(--n-border-radius); transition: background-color .3s var(--n-bezier), box-shadow .3s var(--n-bezier); background-color: var(--n-color); `, [ cB('scrollbar', ` max-height: var(--n-height); `), cB('virtual-list', ` max-height: var(--n-height); `), cB('base-select-option', ` min-height: var(--n-option-height); font-size: var(--n-option-font-size); display: flex; align-items: center; `, [ cE('content', ` z-index: 1; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; `) ]), cB('base-select-group-header', ` min-height: var(--n-option-height); font-size: .93em; display: flex; align-items: center; `), cB('base-select-menu-option-wrapper', ` position: relative; width: 100%; `), cE('loading, empty', ` display: flex; padding: 12px 32px; flex: 1; justify-content: center; `), cE('loading', ` color: var(--n-loading-color); font-size: var(--n-loading-size); `), cE('header', ` padding: 8px var(--n-option-padding-left); font-size: var(--n-option-font-size); transition: color .3s var(--n-bezier), border-color .3s var(--n-bezier); border-bottom: 1px solid var(--n-action-divider-color); color: var(--n-action-text-color); `), cE('action', ` padding: 8px var(--n-option-padding-left); font-size: var(--n-option-font-size); transition: color .3s var(--n-bezier), border-color .3s var(--n-bezier); border-top: 1px solid var(--n-action-divider-color); color: var(--n-action-text-color); `), cB('base-select-group-header', ` position: relative; cursor: default; padding: var(--n-option-padding); color: var(--n-group-header-text-color); `), cB('base-select-option', ` cursor: pointer; position: relative; padding: var(--n-option-padding); transition: color .3s var(--n-bezier), opacity .3s var(--n-bezier); box-sizing: border-box; color: var(--n-option-text-color); opacity: 1; `, [ cM('show-checkmark', ` padding-right: calc(var(--n-option-padding-right) + 20px); `), c('&::before', ` content: ""; position: absolute; left: 4px; right: 4px; top: 0; bottom: 0; border-radius: var(--n-border-radius); transition: background-color .3s var(--n-bezier); `), c('&:active', ` color: var(--n-option-text-color-pressed); `), cM('grouped', ` padding-left: calc(var(--n-option-padding-left) * 1.5); `), cM('pending', [ c('&::before', ` background-color: var(--n-option-color-pending); `) ]), cM('selected', ` color: var(--n-option-text-color-active); `, [ c('&::before', ` background-color: var(--n-option-color-active); `), cM('pending', [ c('&::before', ` background-color: var(--n-option-color-active-pending); `) ]) ]), cM('disabled', ` cursor: not-allowed; `, [ cNotM('selected', ` color: var(--n-option-text-color-disabled); `), cM('selected', ` opacity: var(--n-option-opacity-disabled); `) ]), cE('check', ` font-size: 16px; position: absolute; right: calc(var(--n-option-padding-right) - 4px); top: calc(50% - 7px); color: var(--n-option-check-color); transition: color .3s var(--n-bezier); `, [ fadeInScaleUpTransition({ enterScale: '0.5' }) ]) ]) ]) ================================================ FILE: src/_internal/select-menu/src/styles/rtl.cssr.ts ================================================ import { cB, cE, cM } from '../../../../_utils/cssr' export default cB('base-select-menu', [ cM('rtl', ` direction: rtl; `, [ cB('base-select-option', [ cE('check', ` right: unset; left: calc(var(--n-option-padding-right) - 4px); `), cM('show-checkmark', ` padding-left: calc(var(--n-option-padding-right) + 20px); padding-right: var(--n-option-padding-left); `) ]) ]) ]) ================================================ FILE: src/_internal/select-menu/styles/_common.ts ================================================ export default { height: 'calc(var(--n-option-height) * 7.6)', paddingTiny: '4px 0', paddingSmall: '4px 0', paddingMedium: '4px 0', paddingLarge: '4px 0', paddingHuge: '4px 0', optionPaddingTiny: '0 12px', optionPaddingSmall: '0 12px', optionPaddingMedium: '0 12px', optionPaddingLarge: '0 12px', optionPaddingHuge: '0 12px', loadingSize: '18px' } ================================================ FILE: src/_internal/select-menu/styles/dark.ts ================================================ import type { InternalSelectMenuTheme } from './light' import { commonDark } from '../../../_styles/common' import { emptyDark } from '../../../empty/styles' import { scrollbarDark } from '../../scrollbar/styles' import { self } from './light' const internalSelectMenuDark: InternalSelectMenuTheme = { name: 'InternalSelectMenu', common: commonDark, peers: { Scrollbar: scrollbarDark, Empty: emptyDark }, self } export default internalSelectMenuDark ================================================ FILE: src/_internal/select-menu/styles/index.ts ================================================ export { default as internalSelectMenuDark } from './dark' export { default as internalSelectMenuLight } from './light' export type { InternalSelectMenuTheme, InternalSelectMenuThemeVars } from './light' export { internalSelectMenuRtl } from './rtl' ================================================ FILE: src/_internal/select-menu/styles/light.ts ================================================ import type { ThemeCommonVars } from '../../../_styles/common' import { createTheme } from '../../../_mixins' import { commonLight } from '../../../_styles/common' import { emptyLight } from '../../../empty/styles' import { scrollbarLight } from '../../scrollbar/styles' import commonVariables from './_common' export function self(vars: ThemeCommonVars) { const { borderRadius, popoverColor, textColor3, dividerColor, textColor2, primaryColorPressed, textColorDisabled, primaryColor, opacityDisabled, hoverColor, fontSizeTiny, fontSizeSmall, fontSizeMedium, fontSizeLarge, fontSizeHuge, heightTiny, heightSmall, heightMedium, heightLarge, heightHuge } = vars return { ...commonVariables, optionFontSizeTiny: fontSizeTiny, optionFontSizeSmall: fontSizeSmall, optionFontSizeMedium: fontSizeMedium, optionFontSizeLarge: fontSizeLarge, optionFontSizeHuge: fontSizeHuge, optionHeightTiny: heightTiny, optionHeightSmall: heightSmall, optionHeightMedium: heightMedium, optionHeightLarge: heightLarge, optionHeightHuge: heightHuge, borderRadius, color: popoverColor, groupHeaderTextColor: textColor3, actionDividerColor: dividerColor, optionTextColor: textColor2, optionTextColorPressed: primaryColorPressed, optionTextColorDisabled: textColorDisabled, optionTextColorActive: primaryColor, optionOpacityDisabled: opacityDisabled, optionCheckColor: primaryColor, optionColorPending: hoverColor, optionColorActive: 'rgba(0, 0, 0, 0)', optionColorActivePending: hoverColor, actionTextColor: textColor2, loadingColor: primaryColor } } export type InternalSelectMenuThemeVars = ReturnType const internalSelectMenuLight = createTheme({ name: 'InternalSelectMenu', common: commonLight, peers: { Scrollbar: scrollbarLight, Empty: emptyLight }, self }) export default internalSelectMenuLight export type InternalSelectMenuTheme = typeof internalSelectMenuLight ================================================ FILE: src/_internal/select-menu/styles/rtl.ts ================================================ import type { RtlItem } from '../../../config-provider/src/internal-interface' import rtlStyle from '../src/styles/rtl.cssr' export const internalSelectMenuRtl: RtlItem = { name: 'InternalSelectMenu', style: rtlStyle } ================================================ FILE: src/_internal/selection/index.ts ================================================ export { default } from './src/Selection' export type { InternalSelectionInst } from './src/Selection' ================================================ FILE: src/_internal/selection/src/Selection.tsx ================================================ import type { CSSProperties, InputHTMLAttributes, PropType, VNode } from 'vue' import type { VOverflowInst } from 'vueuc' import type { ThemeProps } from '../../../_mixins' import type { FormValidationStatus } from '../../../form/src/public-types' import type { PopoverProps } from '../../../popover' import type { SelectBaseOption } from '../../../select/src/interface' import type { TagRef } from '../../../tag/src/Tag' import type { RenderLabel, RenderLabelImpl } from '../../select-menu/src/interface' import type { InternalSelectionTheme } from '../styles' import type { RenderTag } from './interface' import { getPadding } from 'seemly' import { computed, defineComponent, Fragment, h, nextTick, onMounted, ref, toRef, watch, watchEffect } from 'vue' import { VOverflow } from 'vueuc' import { useConfig, useRtl, useTheme, useThemeClass } from '../../../_mixins' import { createKey, getTitleAttribute, render, useOnResize, Wrapper } from '../../../_utils' import { NPopover } from '../../../popover' import { NTag } from '../../../tag' import Suffix from '../../suffix' import { internalSelectionLight } from '../styles' import style from './styles/index.cssr' export interface InternalSelectionInst { isComposing: boolean focus: () => void focusInput: () => void blur: () => void blurInput: () => void $el: HTMLElement } export default defineComponent({ name: 'InternalSelection', props: { ...(useTheme.props as ThemeProps), clsPrefix: { type: String, required: true }, bordered: { type: Boolean as PropType, default: undefined }, active: Boolean, pattern: { type: String, default: '' }, placeholder: String, selectedOption: { type: Object as PropType, default: null }, selectedOptions: { type: Array as PropType, default: null }, labelField: { type: String, default: 'label' }, valueField: { type: String, default: 'value' }, multiple: Boolean, filterable: Boolean, clearable: Boolean, disabled: Boolean, size: { type: String as PropType<'tiny' | 'small' | 'medium' | 'large'>, default: 'medium' }, loading: Boolean, autofocus: Boolean, showArrow: { type: Boolean, default: true }, inputProps: Object as PropType, focused: Boolean, renderTag: Function as PropType, onKeydown: Function as PropType<(e: KeyboardEvent) => void>, onClick: Function as PropType<(e: MouseEvent) => void>, onBlur: Function as PropType<(e: FocusEvent) => void>, onFocus: Function as PropType<(e: FocusEvent) => void>, onDeleteOption: Function as PropType<(option: SelectBaseOption) => void>, maxTagCount: [String, Number] as PropType, ellipsisTagPopoverProps: Object as PropType, onClear: Function as PropType<(e: MouseEvent) => void>, onPatternInput: Function as PropType<(e: InputEvent) => void>, onPatternFocus: Function as PropType<(e: FocusEvent) => void>, onPatternBlur: Function as PropType<(e: FocusEvent) => void>, renderLabel: Function as PropType, status: String as PropType, inlineThemeDisabled: Boolean, ignoreComposition: { type: Boolean, default: true }, onResize: Function as PropType<() => void> }, setup(props) { const { mergedClsPrefixRef, mergedRtlRef } = useConfig(props) const rtlEnabledRef = useRtl( 'InternalSelection', mergedRtlRef, mergedClsPrefixRef ) const patternInputMirrorRef = ref(null) const patternInputRef = ref(null) const selfRef = ref(null) const multipleElRef = ref(null) const singleElRef = ref(null) const patternInputWrapperRef = ref(null) const counterRef = ref(null) const counterWrapperRef = ref(null) const overflowRef = ref(null) const inputTagElRef = ref(null) const showTagsPopoverRef = ref(false) const patternInputFocusedRef = ref(false) const hoverRef = ref(false) const themeRef = useTheme( 'InternalSelection', '-internal-selection', style, internalSelectionLight, props, toRef(props, 'clsPrefix') ) const mergedClearableRef = computed(() => { return ( props.clearable && !props.disabled && (hoverRef.value || props.active) ) }) const filterablePlaceholderRef = computed(() => { return props.selectedOption ? props.renderTag ? props.renderTag({ option: props.selectedOption, handleClose: () => {} }) : props.renderLabel ? props.renderLabel(props.selectedOption as never, true) : render( props.selectedOption[props.labelField], props.selectedOption, true ) : props.placeholder }) const labelRef = computed(() => { const option = props.selectedOption if (!option) return undefined return option[props.labelField] }) const selectedRef = computed(() => { if (props.multiple) { return !!( Array.isArray(props.selectedOptions) && props.selectedOptions.length ) } else { return props.selectedOption !== null } }) function syncMirrorWidth(): void { const { value: patternInputMirrorEl } = patternInputMirrorRef if (patternInputMirrorEl) { const { value: patternInputEl } = patternInputRef if (patternInputEl) { patternInputEl.style.width = `${patternInputMirrorEl.offsetWidth}px` if (props.maxTagCount !== 'responsive') { overflowRef.value?.sync({ showAllItemsBeforeCalculate: false }) } } } } function hideInputTag(): void { const { value: inputTagEl } = inputTagElRef if (inputTagEl) inputTagEl.style.display = 'none' } function showInputTag(): void { const { value: inputTagEl } = inputTagElRef if (inputTagEl) inputTagEl.style.display = 'inline-block' } watch(toRef(props, 'active'), (value) => { if (!value) hideInputTag() }) watch(toRef(props, 'pattern'), () => { if (props.multiple) { void nextTick(syncMirrorWidth) } }) function doFocus(e: FocusEvent): void { const { onFocus } = props if (onFocus) onFocus(e) } function doBlur(e: FocusEvent): void { const { onBlur } = props if (onBlur) onBlur(e) } function doDeleteOption(value: SelectBaseOption): void { const { onDeleteOption } = props if (onDeleteOption) onDeleteOption(value) } function doClear(e: MouseEvent): void { const { onClear } = props if (onClear) onClear(e) } function doPatternInput(value: InputEvent): void { const { onPatternInput } = props if (onPatternInput) onPatternInput(value) } function handleFocusin(e: FocusEvent): void { if ( !e.relatedTarget || !selfRef.value?.contains(e.relatedTarget as Node) ) { doFocus(e) } } function handleFocusout(e: FocusEvent): void { if (selfRef.value?.contains(e.relatedTarget as Node)) return doBlur(e) } function handleClear(e: MouseEvent): void { doClear(e) } function handleMouseEnter(): void { hoverRef.value = true } function handleMouseLeave(): void { hoverRef.value = false } function handleMouseDown(e: MouseEvent): void { if (!props.active || !props.filterable) return if (e.target === patternInputRef.value) return e.preventDefault() } function handleDeleteOption(option: SelectBaseOption): void { doDeleteOption(option) } const isComposingRef = ref(false) function handlePatternKeyDown(e: KeyboardEvent): void { if (e.key === 'Backspace' && !isComposingRef.value) { if (!props.pattern.length) { const { selectedOptions } = props if (selectedOptions?.length) { handleDeleteOption(selectedOptions[selectedOptions.length - 1]) } } } } // the composition end is later than its input so we can cached the event // and return the input event let cachedInputEvent: InputEvent | null = null function handlePatternInputInput(e: InputEvent): void { // we should sync mirror width here const { value: patternInputMirrorEl } = patternInputMirrorRef if (patternInputMirrorEl) { const inputText: string = (e.target as any).value patternInputMirrorEl.textContent = inputText syncMirrorWidth() } if (props.ignoreComposition) { if (!isComposingRef.value) { doPatternInput(e) } else { cachedInputEvent = e } } else { doPatternInput(e) } } function handleCompositionStart(): void { isComposingRef.value = true } function handleCompositionEnd(): void { isComposingRef.value = false if (props.ignoreComposition) { doPatternInput(cachedInputEvent!) } cachedInputEvent = null } function handlePatternInputFocus(e: FocusEvent): void { patternInputFocusedRef.value = true props.onPatternFocus?.(e) } function handlePatternInputBlur(e: FocusEvent): void { patternInputFocusedRef.value = false props.onPatternBlur?.(e) } function blur(): void { if (props.filterable) { patternInputFocusedRef.value = false patternInputWrapperRef.value?.blur() patternInputRef.value?.blur() } else if (props.multiple) { const { value: multipleEl } = multipleElRef multipleEl?.blur() } else { const { value: singleEl } = singleElRef singleEl?.blur() } } function focus(): void { if (props.filterable) { patternInputFocusedRef.value = false patternInputWrapperRef.value?.focus() } else if (props.multiple) { multipleElRef.value?.focus() } else { singleElRef.value?.focus() } } function focusInput(): void { const { value: patternInputEl } = patternInputRef if (patternInputEl) { showInputTag() patternInputEl.focus() } } function blurInput(): void { const { value: patternInputEl } = patternInputRef if (patternInputEl) { patternInputEl.blur() } } function updateCounter(count: number): void { const { value } = counterRef if (value) { value.setTextContent(`+${count}`) } } function getCounter(): HTMLElement | null { const { value } = counterWrapperRef return value } function getTail(): HTMLElement | null { return patternInputRef.value } let enterTimerId: number | null = null function clearEnterTimer(): void { if (enterTimerId !== null) window.clearTimeout(enterTimerId) } function handleMouseEnterCounter(): void { if (props.active) return clearEnterTimer() enterTimerId = window.setTimeout(() => { if (selectedRef.value) { showTagsPopoverRef.value = true } }, 100) } function handleMouseLeaveCounter(): void { clearEnterTimer() } function onPopoverUpdateShow(show: boolean): void { if (!show) { clearEnterTimer() showTagsPopoverRef.value = false } } watch(selectedRef, (value) => { if (!value) { showTagsPopoverRef.value = false } }) onMounted(() => { watchEffect(() => { const patternInputWrapperEl = patternInputWrapperRef.value if (!patternInputWrapperEl) return if (props.disabled) { patternInputWrapperEl.removeAttribute('tabindex') } else { patternInputWrapperEl.tabIndex = patternInputFocusedRef.value ? -1 : 0 } }) }) useOnResize(selfRef, props.onResize) const { inlineThemeDisabled } = props const cssVarsRef = computed(() => { const { size } = props const { common: { cubicBezierEaseInOut }, self: { fontWeight, borderRadius, color, placeholderColor, textColor, paddingSingle, paddingMultiple, caretColor, colorDisabled, textColorDisabled, placeholderColorDisabled, colorActive, boxShadowFocus, boxShadowActive, boxShadowHover, border, borderFocus, borderHover, borderActive, arrowColor, arrowColorDisabled, loadingColor, // form warning colorActiveWarning, boxShadowFocusWarning, boxShadowActiveWarning, boxShadowHoverWarning, borderWarning, borderFocusWarning, borderHoverWarning, borderActiveWarning, // form error colorActiveError, boxShadowFocusError, boxShadowActiveError, boxShadowHoverError, borderError, borderFocusError, borderHoverError, borderActiveError, // clear clearColor, clearColorHover, clearColorPressed, clearSize, // arrow arrowSize, [createKey('height', size)]: height, [createKey('fontSize', size)]: fontSize } } = themeRef.value const paddingSingleDiscrete = getPadding(paddingSingle) const paddingMultipleDiscrete = getPadding(paddingMultiple) return { '--n-bezier': cubicBezierEaseInOut, '--n-border': border, '--n-border-active': borderActive, '--n-border-focus': borderFocus, '--n-border-hover': borderHover, '--n-border-radius': borderRadius, '--n-box-shadow-active': boxShadowActive, '--n-box-shadow-focus': boxShadowFocus, '--n-box-shadow-hover': boxShadowHover, '--n-caret-color': caretColor, '--n-color': color, '--n-color-active': colorActive, '--n-color-disabled': colorDisabled, '--n-font-size': fontSize, '--n-height': height, '--n-padding-single-top': paddingSingleDiscrete.top, '--n-padding-multiple-top': paddingMultipleDiscrete.top, '--n-padding-single-right': paddingSingleDiscrete.right, '--n-padding-multiple-right': paddingMultipleDiscrete.right, '--n-padding-single-left': paddingSingleDiscrete.left, '--n-padding-multiple-left': paddingMultipleDiscrete.left, '--n-padding-single-bottom': paddingSingleDiscrete.bottom, '--n-padding-multiple-bottom': paddingMultipleDiscrete.bottom, '--n-placeholder-color': placeholderColor, '--n-placeholder-color-disabled': placeholderColorDisabled, '--n-text-color': textColor, '--n-text-color-disabled': textColorDisabled, '--n-arrow-color': arrowColor, '--n-arrow-color-disabled': arrowColorDisabled, '--n-loading-color': loadingColor, // form warning '--n-color-active-warning': colorActiveWarning, '--n-box-shadow-focus-warning': boxShadowFocusWarning, '--n-box-shadow-active-warning': boxShadowActiveWarning, '--n-box-shadow-hover-warning': boxShadowHoverWarning, '--n-border-warning': borderWarning, '--n-border-focus-warning': borderFocusWarning, '--n-border-hover-warning': borderHoverWarning, '--n-border-active-warning': borderActiveWarning, // form error '--n-color-active-error': colorActiveError, '--n-box-shadow-focus-error': boxShadowFocusError, '--n-box-shadow-active-error': boxShadowActiveError, '--n-box-shadow-hover-error': boxShadowHoverError, '--n-border-error': borderError, '--n-border-focus-error': borderFocusError, '--n-border-hover-error': borderHoverError, '--n-border-active-error': borderActiveError, // clear '--n-clear-size': clearSize, '--n-clear-color': clearColor, '--n-clear-color-hover': clearColorHover, '--n-clear-color-pressed': clearColorPressed, // arrow-size '--n-arrow-size': arrowSize, // font-weight '--n-font-weight': fontWeight } }) const themeClassHandle = inlineThemeDisabled ? useThemeClass( 'internal-selection', computed(() => { return props.size[0] }), cssVarsRef, props ) : undefined return { mergedTheme: themeRef, mergedClearable: mergedClearableRef, mergedClsPrefix: mergedClsPrefixRef, rtlEnabled: rtlEnabledRef, patternInputFocused: patternInputFocusedRef, filterablePlaceholder: filterablePlaceholderRef, label: labelRef, selected: selectedRef, showTagsPanel: showTagsPopoverRef, isComposing: isComposingRef, // dom ref counterRef, counterWrapperRef, patternInputMirrorRef, patternInputRef, selfRef, multipleElRef, singleElRef, patternInputWrapperRef, overflowRef, inputTagElRef, handleMouseDown, handleFocusin, handleClear, handleMouseEnter, handleMouseLeave, handleDeleteOption, handlePatternKeyDown, handlePatternInputInput, handlePatternInputBlur, handlePatternInputFocus, handleMouseEnterCounter, handleMouseLeaveCounter, handleFocusout, handleCompositionEnd, handleCompositionStart, onPopoverUpdateShow, focus, focusInput, blur, blurInput, updateCounter, getCounter, getTail, renderLabel: props.renderLabel as RenderLabelImpl, cssVars: inlineThemeDisabled ? undefined : cssVarsRef, themeClass: themeClassHandle?.themeClass, onRender: themeClassHandle?.onRender } }, render() { const { status, multiple, size, disabled, filterable, maxTagCount, bordered, clsPrefix, ellipsisTagPopoverProps, onRender, renderTag, renderLabel } = this onRender?.() const maxTagCountResponsive = maxTagCount === 'responsive' const maxTagCountNumeric = typeof maxTagCount === 'number' const useMaxTagCount = maxTagCountResponsive || maxTagCountNumeric const suffix = ( {{ default: () => ( {{ default: () => this.$slots.arrow?.() }} ) }} ) let body: JSX.Element if (multiple) { const { labelField } = this const createTag = (option: SelectBaseOption): JSX.Element => (
{renderTag ? ( renderTag({ option, handleClose: () => { this.handleDeleteOption(option) } }) ) : ( { this.handleDeleteOption(option) }} internalCloseIsButtonTag={false} internalCloseFocusable={false} > {{ default: () => renderLabel ? renderLabel(option, true) : render(option[labelField], option, true) }} )}
) const createOriginalTagNodes = (): VNode[] => (maxTagCountNumeric ? this.selectedOptions!.slice(0, maxTagCount) : this.selectedOptions! ).map(createTag) const input = filterable ? (
{this.pattern}
) : null // May Overflow const renderCounter = maxTagCountResponsive ? () => (
) : undefined let counter: JSX.Element | undefined if (maxTagCountNumeric) { const rest = this.selectedOptions!.length - maxTagCount if (rest > 0) { counter = (
{{ default: () => `+${rest}` }}
) } } const tags = maxTagCountResponsive ? ( filterable ? ( {{ default: createOriginalTagNodes, counter: renderCounter, tail: () => input }} ) : ( {{ default: createOriginalTagNodes, counter: renderCounter }} ) ) : maxTagCountNumeric && counter ? ( createOriginalTagNodes().concat(counter) ) : ( createOriginalTagNodes() ) const renderPopover = useMaxTagCount ? (): JSX.Element => (
{maxTagCountResponsive ? createOriginalTagNodes() : this.selectedOptions!.map(createTag)}
) : undefined const popoverProps = useMaxTagCount ? ({ show: this.showTagsPanel, trigger: 'hover', overlap: true, placement: 'top', width: 'trigger', onUpdateShow: this.onPopoverUpdateShow, theme: this.mergedTheme.peers.Popover, themeOverrides: this.mergedTheme.peerOverrides.Popover, ...ellipsisTagPopoverProps } as const) : null const showPlaceholder = this.selected ? false : this.active ? !this.pattern && !this.isComposing : true const placeholder = showPlaceholder ? (
{this.placeholder}
) : null const popoverTrigger = filterable ? (
{tags} {maxTagCountResponsive ? null : input} {suffix}
) : (
{tags} {suffix}
) body = ( <> {useMaxTagCount ? ( {{ trigger: () => popoverTrigger, default: renderPopover }} ) : ( popoverTrigger )} {placeholder} ) } else { if (filterable) { const hasInput = this.pattern || this.isComposing const showPlaceholder = this.active ? !hasInput : !this.selected const showSelectedLabel = this.active ? false : this.selected body = (
{showSelectedLabel ? (
{renderTag ? renderTag({ option: this.selectedOption!, handleClose: () => {} }) : renderLabel ? renderLabel(this.selectedOption!, true) : render(this.label, this.selectedOption, true)}
) : null} {showPlaceholder ? (
{this.filterablePlaceholder}
) : null} {suffix}
) } else { body = (
{this.label !== undefined ? (
{renderTag ? renderTag({ option: this.selectedOption!, handleClose: () => {} }) : renderLabel ? renderLabel(this.selectedOption!, true) : render(this.label, this.selectedOption, true)}
) : (
{this.placeholder}
)} {suffix}
) } } return (
{body} {bordered ? (
) : null} {bordered ? (
) : null}
) } }) ================================================ FILE: src/_internal/selection/src/interface.ts ================================================ import type { VNodeChild } from 'vue' import type { SelectOption } from '../../../select/src/interface' export type RenderTag = (props: { option: SelectOption handleClose: () => void }) => VNodeChild ================================================ FILE: src/_internal/selection/src/styles/index.cssr.ts ================================================ import { c, cB, cE, cM, cNotM } from '../../../../_utils/cssr' // vars: // --n-bezier // --n-border // --n-border-active // --n-border-focus // --n-border-hover // --n-border-radius // --n-box-shadow-active // --n-box-shadow-focus // --n-box-shadow-hover // --n-caret-color // --n-color // --n-color-active // --n-color-disabled // --n-font-size // --n-height // --n-padding-single // --n-padding-multiple // --n-placeholder-color // --n-placeholder-color-disabled // --n-text-color // --n-text-color-disabled // --n-arrow-color // --n-arrow-size // --n-loading-color // --n-font-weight // ...clear vars // ...form item vars export default c([ cB('base-selection', ` --n-padding-single: var(--n-padding-single-top) var(--n-padding-single-right) var(--n-padding-single-bottom) var(--n-padding-single-left); --n-padding-multiple: var(--n-padding-multiple-top) var(--n-padding-multiple-right) var(--n-padding-multiple-bottom) var(--n-padding-multiple-left); position: relative; z-index: auto; box-shadow: none; width: 100%; max-width: 100%; display: inline-block; vertical-align: bottom; border-radius: var(--n-border-radius); min-height: var(--n-height); line-height: 1.5; font-size: var(--n-font-size); `, [ cB('base-loading', ` color: var(--n-loading-color); `), cB('base-selection-tags', 'min-height: var(--n-height);'), cE('border, state-border', ` position: absolute; left: 0; right: 0; top: 0; bottom: 0; pointer-events: none; border: var(--n-border); border-radius: inherit; transition: box-shadow .3s var(--n-bezier), border-color .3s var(--n-bezier); `), cE('state-border', ` z-index: 1; border-color: #0000; `), cB('base-suffix', ` cursor: pointer; position: absolute; top: 50%; transform: translateY(-50%); right: 10px; `, [ cE('arrow', ` font-size: var(--n-arrow-size); color: var(--n-arrow-color); transition: color .3s var(--n-bezier); `) ]), cB('base-selection-overlay', ` display: flex; align-items: center; white-space: nowrap; pointer-events: none; position: absolute; top: 0; right: 0; bottom: 0; left: 0; padding: var(--n-padding-single); transition: color .3s var(--n-bezier); `, [ cE('wrapper', ` flex-basis: 0; flex-grow: 1; overflow: hidden; text-overflow: ellipsis; `) ]), cB('base-selection-placeholder', ` color: var(--n-placeholder-color); `, [ cE('inner', ` max-width: 100%; overflow: hidden; `) ]), cB('base-selection-tags', ` cursor: pointer; outline: none; box-sizing: border-box; position: relative; z-index: auto; display: flex; padding: var(--n-padding-multiple); flex-wrap: wrap; align-items: center; width: 100%; vertical-align: bottom; background-color: var(--n-color); border-radius: inherit; transition: color .3s var(--n-bezier), box-shadow .3s var(--n-bezier), background-color .3s var(--n-bezier); `), cB('base-selection-label', ` height: var(--n-height); display: inline-flex; width: 100%; vertical-align: bottom; cursor: pointer; outline: none; z-index: auto; box-sizing: border-box; position: relative; transition: color .3s var(--n-bezier), box-shadow .3s var(--n-bezier), background-color .3s var(--n-bezier); border-radius: inherit; background-color: var(--n-color); align-items: center; `, [ cB('base-selection-input', ` font-size: inherit; line-height: inherit; outline: none; cursor: pointer; box-sizing: border-box; border:none; width: 100%; padding: var(--n-padding-single); background-color: #0000; color: var(--n-text-color); transition: color .3s var(--n-bezier); caret-color: var(--n-caret-color); `, [ cE('content', ` text-overflow: ellipsis; overflow: hidden; white-space: nowrap; `) ]), cE('render-label', ` color: var(--n-text-color); `) ]), cNotM('disabled', [ c('&:hover', [ cE('state-border', ` box-shadow: var(--n-box-shadow-hover); border: var(--n-border-hover); `) ]), cM('focus', [ cE('state-border', ` box-shadow: var(--n-box-shadow-focus); border: var(--n-border-focus); `) ]), cM('active', [ cE('state-border', ` box-shadow: var(--n-box-shadow-active); border: var(--n-border-active); `), cB('base-selection-label', 'background-color: var(--n-color-active);'), cB('base-selection-tags', 'background-color: var(--n-color-active);') ]) ]), cM('disabled', 'cursor: not-allowed;', [ cE('arrow', ` color: var(--n-arrow-color-disabled); `), cB('base-selection-label', ` cursor: not-allowed; background-color: var(--n-color-disabled); `, [ cB('base-selection-input', ` cursor: not-allowed; color: var(--n-text-color-disabled); `), cE('render-label', ` color: var(--n-text-color-disabled); `) ]), cB('base-selection-tags', ` cursor: not-allowed; background-color: var(--n-color-disabled); `), cB('base-selection-placeholder', ` cursor: not-allowed; color: var(--n-placeholder-color-disabled); `) ]), cB('base-selection-input-tag', ` height: calc(var(--n-height) - 6px); line-height: calc(var(--n-height) - 6px); outline: none; display: none; position: relative; margin-bottom: 3px; max-width: 100%; vertical-align: bottom; `, [ cE('input', ` font-size: inherit; font-family: inherit; min-width: 1px; padding: 0; background-color: #0000; outline: none; border: none; max-width: 100%; overflow: hidden; width: 1em; line-height: inherit; cursor: pointer; color: var(--n-text-color); caret-color: var(--n-caret-color); `), cE('mirror', ` position: absolute; left: 0; top: 0; white-space: pre; visibility: hidden; user-select: none; -webkit-user-select: none; opacity: 0; `) ]), ['warning', 'error'].map(status => cM(`${status}-status`, [ cE('state-border', `border: var(--n-border-${status});`), cNotM('disabled', [ c('&:hover', [ cE('state-border', ` box-shadow: var(--n-box-shadow-hover-${status}); border: var(--n-border-hover-${status}); `) ]), cM('active', [ cE('state-border', ` box-shadow: var(--n-box-shadow-active-${status}); border: var(--n-border-active-${status}); `), cB('base-selection-label', `background-color: var(--n-color-active-${status});`), cB('base-selection-tags', `background-color: var(--n-color-active-${status});`) ]), cM('focus', [ cE('state-border', ` box-shadow: var(--n-box-shadow-focus-${status}); border: var(--n-border-focus-${status}); `) ]) ]) ])) ]), cB('base-selection-popover', ` margin-bottom: -3px; display: flex; flex-wrap: wrap; margin-right: -8px; `), cB('base-selection-tag-wrapper', ` max-width: 100%; display: inline-flex; padding: 0 7px 3px 0; `, [ c('&:last-child', 'padding-right: 0;'), cB('tag', ` font-size: 14px; max-width: 100%; `, [ cE('content', ` line-height: 1.25; text-overflow: ellipsis; overflow: hidden; `) ]) ]) ]) ================================================ FILE: src/_internal/selection/src/styles/rtl.cssr.ts ================================================ import { cB, cM } from '../../../../_utils/cssr' export default cB('base-selection', [ cM('rtl', ` direction: rtl; --n-padding-single: var(--n-padding-single-top) var(--n-padding-single-left) var(--n-padding-single-bottom) var(--n-padding-single-right); --n-padding-multiple: var(--n-padding-multiple-top) var(--n-padding-multiple-left) var(--n-padding-multiple-bottom) var(--n-padding-multiple-right); `, [ cB('base-suffix', ` right: unset; left: 10px; `) ]) ]) ================================================ FILE: src/_internal/selection/styles/_common.ts ================================================ export default { paddingSingle: '0 26px 0 12px', paddingMultiple: '3px 26px 0 12px', clearSize: '16px', arrowSize: '16px' } ================================================ FILE: src/_internal/selection/styles/dark.ts ================================================ import type { InternalSelectionTheme } from './light' import { changeColor } from 'seemly' import { commonDark } from '../../../_styles/common' import { popoverDark } from '../../../popover/styles' import commonVars from './_common' const internalSelectionDark: InternalSelectionTheme = { name: 'InternalSelection', common: commonDark, peers: { Popover: popoverDark }, self(vars) { const { borderRadius, textColor2, textColorDisabled, inputColor, inputColorDisabled, primaryColor, primaryColorHover, warningColor, warningColorHover, errorColor, errorColorHover, iconColor, iconColorDisabled, clearColor, clearColorHover, clearColorPressed, placeholderColor, placeholderColorDisabled, fontSizeTiny, fontSizeSmall, fontSizeMedium, fontSizeLarge, heightTiny, heightSmall, heightMedium, heightLarge, fontWeight } = vars return { ...commonVars, fontWeight, fontSizeTiny, fontSizeSmall, fontSizeMedium, fontSizeLarge, heightTiny, heightSmall, heightMedium, heightLarge, borderRadius, // default textColor: textColor2, textColorDisabled, placeholderColor, placeholderColorDisabled, color: inputColor, colorDisabled: inputColorDisabled, colorActive: changeColor(primaryColor, { alpha: 0.1 }), border: '1px solid #0000', borderHover: `1px solid ${primaryColorHover}`, borderActive: `1px solid ${primaryColor}`, borderFocus: `1px solid ${primaryColorHover}`, boxShadowHover: 'none', boxShadowActive: `0 0 8px 0 ${changeColor(primaryColor, { alpha: 0.4 })}`, boxShadowFocus: `0 0 8px 0 ${changeColor(primaryColor, { alpha: 0.4 })}`, caretColor: primaryColor, arrowColor: iconColor, arrowColorDisabled: iconColorDisabled, loadingColor: primaryColor, // warning borderWarning: `1px solid ${warningColor}`, borderHoverWarning: `1px solid ${warningColorHover}`, borderActiveWarning: `1px solid ${warningColor}`, borderFocusWarning: `1px solid ${warningColorHover}`, boxShadowHoverWarning: 'none', boxShadowActiveWarning: `0 0 8px 0 ${changeColor(warningColor, { alpha: 0.4 })}`, boxShadowFocusWarning: `0 0 8px 0 ${changeColor(warningColor, { alpha: 0.4 })}`, colorActiveWarning: changeColor(warningColor, { alpha: 0.1 }), caretColorWarning: warningColor, // error borderError: `1px solid ${errorColor}`, borderHoverError: `1px solid ${errorColorHover}`, borderActiveError: `1px solid ${errorColor}`, borderFocusError: `1px solid ${errorColorHover}`, boxShadowHoverError: 'none', boxShadowActiveError: `0 0 8px 0 ${changeColor(errorColor, { alpha: 0.4 })}`, boxShadowFocusError: `0 0 8px 0 ${changeColor(errorColor, { alpha: 0.4 })}`, colorActiveError: changeColor(errorColor, { alpha: 0.1 }), caretColorError: errorColor, clearColor, clearColorHover, clearColorPressed } } } export default internalSelectionDark ================================================ FILE: src/_internal/selection/styles/index.ts ================================================ export { default as internalSelectionDark } from './dark' export { default as internalSelectionLight } from './light' export type { InternalSelectionTheme, InternalSelectionThemeVars } from './light' export { internalSelectionRtl } from './rtl' ================================================ FILE: src/_internal/selection/styles/light.ts ================================================ import type { ThemeCommonVars } from '../../../_styles/common' import { changeColor } from 'seemly' import { createTheme } from '../../../_mixins' import { commonLight } from '../../../_styles/common' import { popoverLight } from '../../../popover/styles' import commonVariables from './_common' function self(vars: ThemeCommonVars) { const { borderRadius, textColor2, textColorDisabled, inputColor, inputColorDisabled, primaryColor, primaryColorHover, warningColor, warningColorHover, errorColor, errorColorHover, borderColor, iconColor, iconColorDisabled, clearColor, clearColorHover, clearColorPressed, placeholderColor, placeholderColorDisabled, fontSizeTiny, fontSizeSmall, fontSizeMedium, fontSizeLarge, heightTiny, heightSmall, heightMedium, heightLarge, fontWeight } = vars return { ...commonVariables, fontSizeTiny, fontSizeSmall, fontSizeMedium, fontSizeLarge, heightTiny, heightSmall, heightMedium, heightLarge, borderRadius, fontWeight, // default textColor: textColor2, textColorDisabled, placeholderColor, placeholderColorDisabled, color: inputColor, colorDisabled: inputColorDisabled, colorActive: inputColor, border: `1px solid ${borderColor}`, borderHover: `1px solid ${primaryColorHover}`, borderActive: `1px solid ${primaryColor}`, borderFocus: `1px solid ${primaryColorHover}`, boxShadowHover: 'none', boxShadowActive: `0 0 0 2px ${changeColor(primaryColor, { alpha: 0.2 })}`, boxShadowFocus: `0 0 0 2px ${changeColor(primaryColor, { alpha: 0.2 })}`, caretColor: primaryColor, arrowColor: iconColor, arrowColorDisabled: iconColorDisabled, loadingColor: primaryColor, // warning borderWarning: `1px solid ${warningColor}`, borderHoverWarning: `1px solid ${warningColorHover}`, borderActiveWarning: `1px solid ${warningColor}`, borderFocusWarning: `1px solid ${warningColorHover}`, boxShadowHoverWarning: 'none', boxShadowActiveWarning: `0 0 0 2px ${changeColor(warningColor, { alpha: 0.2 })}`, boxShadowFocusWarning: `0 0 0 2px ${changeColor(warningColor, { alpha: 0.2 })}`, colorActiveWarning: inputColor, caretColorWarning: warningColor, // error borderError: `1px solid ${errorColor}`, borderHoverError: `1px solid ${errorColorHover}`, borderActiveError: `1px solid ${errorColor}`, borderFocusError: `1px solid ${errorColorHover}`, boxShadowHoverError: 'none', boxShadowActiveError: `0 0 0 2px ${changeColor(errorColor, { alpha: 0.2 })}`, boxShadowFocusError: `0 0 0 2px ${changeColor(errorColor, { alpha: 0.2 })}`, colorActiveError: inputColor, caretColorError: errorColor, clearColor, clearColorHover, clearColorPressed } } export type InternalSelectionThemeVars = ReturnType const internalSelectionLight = createTheme({ name: 'InternalSelection', common: commonLight, peers: { Popover: popoverLight }, self }) export default internalSelectionLight export type InternalSelectionTheme = typeof internalSelectionLight ================================================ FILE: src/_internal/selection/styles/rtl.ts ================================================ import type { RtlItem } from '../../../config-provider/src/internal-interface' import rtlStyle from '../src/styles/rtl.cssr' export const internalSelectionRtl: RtlItem = { name: 'InternalSelection', style: rtlStyle } ================================================ FILE: src/_internal/slot-machine/index.ts ================================================ export { default } from './src/SlotMachine' ================================================ FILE: src/_internal/slot-machine/src/SlotMachine.tsx ================================================ import { computed, defineComponent, h, ref, toRef, TransitionGroup, watch } from 'vue' import { useStyle } from '../../../_mixins' import NFadeInExpandTransition from '../../fade-in-expand-transition' import SlotMachineNumber from './SlotMachineNumber' import style from './styles/index.cssr' export default defineComponent({ name: 'BaseSlotMachine', props: { clsPrefix: { type: String, required: true }, value: { type: [Number, String], default: 0 }, max: { type: Number, default: undefined }, appeared: { type: Boolean, required: true } }, setup(props) { useStyle('-base-slot-machine', style, toRef(props, 'clsPrefix')) const oldValueRef = ref() const newValueRef = ref() const numbersRef = computed(() => { if (typeof props.value === 'string') return [] if (props.value < 1) return [0] const numbers: number[] = [] let value = props.value if (props.max !== undefined) { value = Math.min(props.max, value) } while (value >= 1) { numbers.push(value % 10) value /= 10 value = Math.floor(value) } numbers.reverse() return numbers }) watch(toRef(props, 'value'), (value, oldValue) => { if (typeof value === 'string') { newValueRef.value = undefined oldValueRef.value = undefined } else { if (typeof oldValue === 'string') { newValueRef.value = value oldValueRef.value = undefined } else { newValueRef.value = value oldValueRef.value = oldValue } } }) return () => { const { value, clsPrefix } = props return typeof value === 'number' ? ( {{ default: () => numbersRef.value.map((number, i) => ( )) }} {{ default: () => props.max !== undefined && props.max < value ? ( ) : null }} ) : ( {value} ) } } }) ================================================ FILE: src/_internal/slot-machine/src/SlotMachineNumber.tsx ================================================ import type { PropType } from 'vue' import { computed, defineComponent, h, nextTick, ref, toRef, watch } from 'vue' export default defineComponent({ name: 'SlotMachineNumber', props: { clsPrefix: { type: String, required: true }, value: { // could be '+', 1, 2, ... type: [Number, String] as PropType, required: true }, oldOriginalNumber: { type: Number, default: undefined }, newOriginalNumber: { type: Number, default: undefined } }, setup(props) { const numberRef = ref(null) const oldNumberRef = ref(props.value) const newNumberRef = ref(props.value) const scrollAnimationDirectionRef = ref<'up' | 'down'>('up') const activeRef = ref(false) const newNumberScrollAnimationClassRef = computed(() => { return activeRef.value ? `${props.clsPrefix}-base-slot-machine-current-number--${scrollAnimationDirectionRef.value}-scroll` : null }) const oldNumberScrollAnimationClassRef = computed(() => { return activeRef.value ? `${props.clsPrefix}-base-slot-machine-old-number--${scrollAnimationDirectionRef.value}-scroll` : null }) // BUG: may be typescript bug watch(toRef(props, 'value'), (value, oldValue) => { oldNumberRef.value = oldValue newNumberRef.value = value void nextTick(scroll) }) function scroll(): void { const newOriginalNumber = props.newOriginalNumber const oldOriginalNumber = props.oldOriginalNumber if (oldOriginalNumber === undefined || newOriginalNumber === undefined) { return } if (newOriginalNumber > oldOriginalNumber) { scrollByDir('up') } else if (oldOriginalNumber > newOriginalNumber) { scrollByDir('down') } } function scrollByDir(dir: 'up' | 'down'): void { scrollAnimationDirectionRef.value = dir activeRef.value = false void nextTick(() => { void numberRef.value?.offsetWidth activeRef.value = true }) } return () => { const { clsPrefix } = props return ( {oldNumberRef.value !== null ? ( {oldNumberRef.value} ) : null} {newNumberRef.value} {oldNumberRef.value !== null ? ( {oldNumberRef.value} ) : null} ) } } }) ================================================ FILE: src/_internal/slot-machine/src/styles/index.cssr.ts ================================================ import { fadeInWidthExpandTransition } from '../../../../_styles/transitions/fade-in-width-expand.cssr' import { fadeUpWidthExpandTransition } from '../../../../_styles/transitions/fade-up-width-expand.cssr' import { c, cB, cE, cM } from '../../../../_utils/cssr' // ease-out: cubic-bezier(0, 0, .2, 1) export default c([ c('@keyframes n-base-slot-machine-fade-up-in', ` from { transform: translateY(60%); opacity: 0; } to { transform: translateY(0); opacity: 1; } `), c('@keyframes n-base-slot-machine-fade-down-in', ` from { transform: translateY(-60%); opacity: 0; } to { transform: translateY(0); opacity: 1; } `), c('@keyframes n-base-slot-machine-fade-up-out', ` from { transform: translateY(0%); opacity: 1; } to { transform: translateY(-60%); opacity: 0; } `), c('@keyframes n-base-slot-machine-fade-down-out', ` from { transform: translateY(0%); opacity: 1; } to { transform: translateY(60%); opacity: 0; } `), cB('base-slot-machine', ` overflow: hidden; white-space: nowrap; display: inline-block; height: 18px; line-height: 18px; `, [ cB('base-slot-machine-number', ` display: inline-block; position: relative; height: 18px; width: .6em; max-width: .6em; `, [ fadeUpWidthExpandTransition({ duration: '.2s' }), // use 0s, not 0 fadeInWidthExpandTransition({ duration: '.2s', delay: '0s' }), cB('base-slot-machine-old-number', ` display: inline-block; opacity: 0; position: absolute; left: 0; right: 0; `, [ cM('top', { transform: 'translateY(-100%)' }), cM('bottom', { transform: 'translateY(100%)' }), cM('down-scroll', { animation: 'n-base-slot-machine-fade-down-out .2s cubic-bezier(0, 0, .2, 1)', animationIterationCount: 1 }), cM('up-scroll', { animation: 'n-base-slot-machine-fade-up-out .2s cubic-bezier(0, 0, .2, 1)', animationIterationCount: 1 }) ]), cB('base-slot-machine-current-number', ` display: inline-block; position: absolute; left: 0; top: 0; bottom: 0; right: 0; opacity: 1; transform: translateY(0); width: .6em; `, [ cM('down-scroll', { animation: 'n-base-slot-machine-fade-down-in .2s cubic-bezier(0, 0, .2, 1)', animationIterationCount: 1 }), cM('up-scroll', { animation: 'n-base-slot-machine-fade-up-in .2s cubic-bezier(0, 0, .2, 1)', animationIterationCount: 1 }), cE('inner', ` display: inline-block; position: absolute; right: 0; top: 0; width: .6em; `, [ cM('not-number', ` right: unset; left: 0; `) ]) ]) ]) ]) ]) ================================================ FILE: src/_internal/suffix/index.ts ================================================ export { default } from './src/Suffix' ================================================ FILE: src/_internal/suffix/src/Suffix.tsx ================================================ import type { PropType } from 'vue' import { defineComponent, h } from 'vue' import { resolveSlot } from '../../../_utils/vue' import NBaseClear from '../../clear' import { NBaseIcon } from '../../icon' import { ChevronDownIcon } from '../../icons' import NBaseLoading from '../../loading' export default defineComponent({ name: 'InternalSelectionSuffix', props: { clsPrefix: { type: String, required: true }, showArrow: { type: Boolean, default: undefined }, showClear: { type: Boolean, default: undefined }, loading: { type: Boolean, default: false }, onClear: Function as PropType<(e: MouseEvent) => void> }, setup(props, { slots }) { return () => { const { clsPrefix } = props return ( {{ default: () => props.showArrow ? ( {{ placeholder: () => ( {{ default: () => resolveSlot(slots.default, () => [ ]) }} ) }} ) : null }} ) } } }) ================================================ FILE: src/_internal/wave/index.ts ================================================ export { default } from './src/Wave' export type { BaseWaveRef } from './src/Wave' ================================================ FILE: src/_internal/wave/src/Wave.tsx ================================================ import { defineComponent, h, nextTick, onBeforeUnmount, ref, toRef } from 'vue' import { useStyle } from '../../../_mixins' import style from './styles/index.cssr' export interface BaseWaveRef { play: () => void } export default defineComponent({ name: 'BaseWave', props: { clsPrefix: { type: String, required: true } }, setup(props) { useStyle('-base-wave', style, toRef(props, 'clsPrefix')) const selfRef = ref(null) const activeRef = ref(false) let animationTimerId: number | null = null onBeforeUnmount(() => { if (animationTimerId !== null) { window.clearTimeout(animationTimerId) } }) return { active: activeRef, selfRef, play() { if (animationTimerId !== null) { window.clearTimeout(animationTimerId) activeRef.value = false animationTimerId = null } void nextTick(() => { void selfRef.value?.offsetHeight activeRef.value = true animationTimerId = window.setTimeout(() => { activeRef.value = false animationTimerId = null }, 1000) }) } } }, render() { const { clsPrefix } = this return (
) } }) ================================================ FILE: src/_internal/wave/src/styles/index.cssr.ts ================================================ import { cB } from '../../../../_utils/cssr/index' export default cB('base-wave', ` position: absolute; left: 0; right: 0; top: 0; bottom: 0; border-radius: inherit; `) ================================================ FILE: src/_mixins/common.ts ================================================ export const cssrAnchorMetaName = 'naive-ui-style' ================================================ FILE: src/_mixins/index.ts ================================================ export { defaultClsPrefix, default as useConfig } from './use-config' export { useThemeClass } from './use-css-vars-class' export { default as useFormItem } from './use-form-item' export { default as useHljs } from './use-hljs' export type { Hljs } from './use-hljs' export { default as useLocale } from './use-locale' export { useRtl } from './use-rtl' export { default as useStyle } from './use-style' export { createTheme, default as useTheme } from './use-theme' export type { MergedTheme, Theme, ThemeProps, ThemePropsReactive } from './use-theme' ================================================ FILE: src/_mixins/use-config.ts ================================================ import type { ComputedRef, Ref } from 'vue' import type { Breakpoints, GlobalComponentConfig, RtlEnabledState } from '../config-provider/src/internal-interface' import { computed, inject, shallowRef } from 'vue' import { configProviderInjectionKey } from '../config-provider/src/context' type UseConfigProps = Readonly<{ bordered?: boolean [key: string]: unknown }> export const defaultClsPrefix = 'n' export default function useConfig( props: UseConfigProps = {}, options: { defaultBordered?: boolean } = { defaultBordered: true } ): { inlineThemeDisabled: boolean | undefined mergedRtlRef: Ref | undefined mergedBorderedRef: ComputedRef mergedClsPrefixRef: Ref mergedBreakpointsRef: Ref | undefined mergedComponentPropsRef: Ref | undefined namespaceRef: ComputedRef } { const NConfigProvider = inject(configProviderInjectionKey, null) return { // NConfigProvider, inlineThemeDisabled: NConfigProvider?.inlineThemeDisabled, mergedRtlRef: NConfigProvider?.mergedRtlRef, mergedComponentPropsRef: NConfigProvider?.mergedComponentPropsRef, mergedBreakpointsRef: NConfigProvider?.mergedBreakpointsRef, mergedBorderedRef: computed(() => { const { bordered } = props if (bordered !== undefined) return bordered return ( NConfigProvider?.mergedBorderedRef.value ?? options.defaultBordered ?? true ) }), mergedClsPrefixRef: NConfigProvider ? NConfigProvider.mergedClsPrefixRef : shallowRef(defaultClsPrefix), namespaceRef: computed(() => NConfigProvider?.mergedNamespaceRef.value) } } export function useMergedClsPrefix(): Ref { const NConfigProvider = inject(configProviderInjectionKey, null) return NConfigProvider ? NConfigProvider.mergedClsPrefixRef : shallowRef(defaultClsPrefix) } ================================================ FILE: src/_mixins/use-css-vars-class.ts ================================================ import type { ComputedRef, Ref } from 'vue' import { useSsrAdapter } from '@css-render/vue3-ssr' import { hash } from 'css-render' import { inject, ref, watchEffect } from 'vue' import { throwError } from '../_utils' import { c } from '../_utils/cssr' import { configProviderInjectionKey } from '../config-provider/src/context' export function useThemeClass( componentName: string, hashRef: Ref | undefined, cssVarsRef: ComputedRef> | undefined, props: { themeOverrides?: unknown, builtinThemeOverrides?: unknown } ): { themeClass: Ref onRender: () => void } { if (!cssVarsRef) throwError('useThemeClass', 'cssVarsRef is not passed') const NConfigProvider = inject(configProviderInjectionKey, null) const mergedThemeHashRef = NConfigProvider?.mergedThemeHashRef const styleMountTarget = NConfigProvider?.styleMountTarget const themeClassRef = ref('') const ssrAdapter = useSsrAdapter() let renderCallback: (() => void) | undefined const hashClassPrefix = `__${componentName}` const mountStyle = (): void => { let finalThemeHash = hashClassPrefix const hashValue = hashRef ? hashRef.value : undefined const themeHash = mergedThemeHashRef?.value if (themeHash) finalThemeHash += `-${themeHash}` if (hashValue) finalThemeHash += `-${hashValue}` const { themeOverrides, builtinThemeOverrides } = props if (themeOverrides) { finalThemeHash += `-${hash(JSON.stringify(themeOverrides))}` } if (builtinThemeOverrides) { finalThemeHash += `-${hash(JSON.stringify(builtinThemeOverrides))}` } themeClassRef.value = finalThemeHash renderCallback = () => { const cssVars = cssVarsRef.value let style = '' for (const key in cssVars) { style += `${key}: ${cssVars[key]};` } c(`.${finalThemeHash}`, style).mount({ id: finalThemeHash, ssr: ssrAdapter, parent: styleMountTarget }) renderCallback = undefined } } watchEffect(() => { mountStyle() }) return { themeClass: themeClassRef, onRender: () => { renderCallback?.() } } } ================================================ FILE: src/_mixins/use-form-item.ts ================================================ import type { ComputedRef, Ref } from 'vue' import type { FormItemSize, FormValidationStatus } from '../form/src/public-types' import { computed, inject, onBeforeUnmount, provide } from 'vue' import { createInjectionKey } from '../_utils' type AllowedSize = 'tiny' | 'small' | 'medium' | 'large' | 'huge' | number export interface FormItemInjection { path: Ref disabled: Ref mergedSize: ComputedRef mergedValidationStatus: ComputedRef restoreValidation: () => void handleContentBlur: () => void handleContentFocus: () => void handleContentInput: () => void handleContentChange: () => void } export const formItemInjectionKey = createInjectionKey('n-form-item') interface UseFormItemOptions { defaultSize?: FormItemSize mergedSize?: (formItem: FormItemInjection | null) => T mergedDisabled?: (formItem: FormItemInjection | null) => boolean } interface UseFormItemProps { size?: T disabled?: boolean status?: FormValidationStatus } export interface UseFormItem { mergedSizeRef: ComputedRef mergedDisabledRef: ComputedRef mergedStatusRef: ComputedRef nTriggerFormBlur: () => void nTriggerFormChange: () => void nTriggerFormFocus: () => void nTriggerFormInput: () => void } export default function useFormItem( props: UseFormItemProps, { defaultSize = 'medium', mergedSize, mergedDisabled }: UseFormItemOptions = {} ): UseFormItem { const NFormItem = inject(formItemInjectionKey, null) provide(formItemInjectionKey, null) const mergedSizeRef = computed( mergedSize ? () => mergedSize(NFormItem) : () => { const { size } = props if (size) return size if (NFormItem) { const { mergedSize } = NFormItem if (mergedSize.value !== undefined) { return mergedSize.value as T } } return defaultSize as T } ) const mergedDisabledRef = computed( mergedDisabled ? () => mergedDisabled(NFormItem) : () => { const { disabled } = props if (disabled !== undefined) { return disabled } if (NFormItem) { return NFormItem.disabled.value } return false } ) const mergedStatusRef = computed(() => { const { status } = props if (status) return status return NFormItem?.mergedValidationStatus.value }) onBeforeUnmount(() => { if (NFormItem) { NFormItem.restoreValidation() } }) return { mergedSizeRef, mergedDisabledRef, mergedStatusRef, nTriggerFormBlur() { if (NFormItem) { NFormItem.handleContentBlur() } }, nTriggerFormChange() { if (NFormItem) { NFormItem.handleContentChange() } }, nTriggerFormFocus() { if (NFormItem) { NFormItem.handleContentFocus() } }, nTriggerFormInput() { if (NFormItem) { NFormItem.handleContentInput() } } } } ================================================ FILE: src/_mixins/use-hljs.ts ================================================ import type { HLJSApi } from 'highlight.js' import type { ComputedRef, Ref } from 'vue' import { computed, inject, watchEffect } from 'vue' import { warn } from '../_utils' import { configProviderInjectionKey } from '../config-provider/src/context' interface UseHljsProps { hljs?: unknown [key: string]: unknown } export interface Hljs { highlight: HLJSApi['highlight'] getLanguage: HLJSApi['getLanguage'] } export default function useHljs( props: UseHljsProps, shouldHighlightRef?: Ref ): ComputedRef { const NConfigProvider = inject(configProviderInjectionKey, null) if (__DEV__) { const warnHljs = (): void => { if (!props.hljs && !NConfigProvider?.mergedHljsRef.value) { warn('code', 'hljs is not set.') } } if (!shouldHighlightRef) { warnHljs() } else { watchEffect(() => { if (shouldHighlightRef.value) { warnHljs() } }) } } return computed(() => { return (props.hljs as any) || NConfigProvider?.mergedHljsRef.value }) } ================================================ FILE: src/_mixins/use-locale.ts ================================================ import type { Ref } from 'vue' import type { NLocale } from '../locales/common/enUS' import type { NDateLocale } from '../locales/date/enUS' import { computed, inject } from 'vue' import { configProviderInjectionKey } from '../config-provider/src/context' import { dateEnUS, enUS } from '../locales' export default function useLocale( ns: T ): { localeRef: Ref dateLocaleRef: Ref } { const { mergedLocaleRef, mergedDateLocaleRef } = inject(configProviderInjectionKey, null) || {} const localeRef = computed(() => { return mergedLocaleRef?.value?.[ns] ?? enUS[ns] }) const dateLocaleRef = computed(() => { return mergedDateLocaleRef?.value ?? dateEnUS }) return { dateLocaleRef, localeRef } } ================================================ FILE: src/_mixins/use-rtl.ts ================================================ import type { Ref } from 'vue' import type { RtlEnabledState, RtlItem } from '../config-provider/src/internal-interface' import { useSsrAdapter } from '@css-render/vue3-ssr' import { exists } from 'css-render' import { computed, inject, onBeforeMount, watchEffect } from 'vue' import { configProviderInjectionKey } from '../config-provider/src/context' import { cssrAnchorMetaName } from './common' export function useRtl( mountId: string, rtlStateRef: Ref | undefined, clsPrefixRef: Ref ): Ref | undefined { if (!rtlStateRef) return undefined const ssrAdapter = useSsrAdapter() const componentRtlStateRef = computed(() => { const { value: rtlState } = rtlStateRef if (!rtlState) { return undefined } const componentRtlState = rtlState[mountId as keyof RtlEnabledState] if (!componentRtlState) { return undefined } return componentRtlState }) const NConfigProvider = inject(configProviderInjectionKey, null) const mountStyle = (): void => { watchEffect(() => { const { value: clsPrefix } = clsPrefixRef const id = `${clsPrefix}${mountId}Rtl` // if it already exists, we only need to watch clsPrefix, although in most // of time it's unnecessary... However we can at least listen less // handlers, which is great. if (exists(id, ssrAdapter)) return const { value: componentRtlState } = componentRtlStateRef if (!componentRtlState) return componentRtlState.style.mount({ id, head: true, anchorMetaName: cssrAnchorMetaName, props: { bPrefix: clsPrefix ? `.${clsPrefix}-` : undefined }, ssr: ssrAdapter, parent: NConfigProvider?.styleMountTarget }) }) } if (ssrAdapter) { mountStyle() } else { onBeforeMount(mountStyle) } return componentRtlStateRef } ================================================ FILE: src/_mixins/use-style.ts ================================================ import type { CNode } from 'css-render' import type { Ref } from 'vue' import { useSsrAdapter } from '@css-render/vue3-ssr' import { inject, onBeforeMount } from 'vue' import globalStyle from '../_styles/global/index.cssr' import { throwError } from '../_utils' import { configProviderInjectionKey } from '../config-provider/src/context' import { cssrAnchorMetaName } from './common' export default function useStyle( mountId: string, style: CNode, clsPrefixRef: Ref ): void { if (!style) { if (__DEV__) throwError('use-style', 'No style is specified.') return } const ssrAdapter = useSsrAdapter() const NConfigProvider = inject(configProviderInjectionKey, null) const mountStyle = (): void => { const clsPrefix = clsPrefixRef.value style.mount({ id: clsPrefix === undefined ? mountId : clsPrefix + mountId, head: true, anchorMetaName: cssrAnchorMetaName, props: { bPrefix: clsPrefix ? `.${clsPrefix}-` : undefined }, ssr: ssrAdapter, parent: NConfigProvider?.styleMountTarget }) if (!NConfigProvider?.preflightStyleDisabled) { globalStyle.mount({ id: 'n-global', head: true, anchorMetaName: cssrAnchorMetaName, ssr: ssrAdapter, parent: NConfigProvider?.styleMountTarget }) } } if (ssrAdapter) { mountStyle() } else { onBeforeMount(mountStyle) } } ================================================ FILE: src/_mixins/use-theme.ts ================================================ import type { CNode } from 'css-render' import type { ComputedRef, PropType, Ref } from 'vue' import type { ThemeCommonVars } from '../_styles/common' import type { GlobalTheme } from '../config-provider' import { useSsrAdapter } from '@css-render/vue3-ssr' import { merge } from 'lodash-es' import { computed, inject, onBeforeMount } from 'vue' import globalStyle from '../_styles/global/index.cssr' import { configProviderInjectionKey } from '../config-provider/src/context' import { cssrAnchorMetaName } from './common' export interface Theme, R = any> { name: N common?: ThemeCommonVars peers?: R self?: (vars: ThemeCommonVars) => T } export interface ThemeProps { theme: PropType themeOverrides: PropType> builtinThemeOverrides: PropType> } export interface ThemePropsReactive { theme?: T themeOverrides?: ExtractThemeOverrides builtinThemeOverrides?: ExtractThemeOverrides } export type ExtractThemeVars = T extends Theme ? unknown extends U // self is undefined, ThemeVars is unknown ? Record : U : Record export type ExtractPeerOverrides = T extends Theme ? { peers?: { [k in keyof V]?: ExtractThemeOverrides } } : T // V is peers theme export type ExtractMergedPeerOverrides = T extends Theme ? { [k in keyof V]?: ExtractPeerOverrides } : T export type ExtractThemeOverrides = Partial> & ExtractPeerOverrides & { common?: Partial } export function createTheme( theme: Theme ): Theme { return theme } type UseThemeProps = Readonly<{ theme?: T | undefined themeOverrides?: ExtractThemeOverrides builtinThemeOverrides?: ExtractThemeOverrides }> export type MergedTheme = T extends Theme ? { common: ThemeCommonVars self: V peers: W peerOverrides: ExtractMergedPeerOverrides } : T function useTheme( resolveId: Exclude, mountId: string, style: CNode | undefined, defaultTheme: Theme, props: UseThemeProps>, clsPrefixRef: Ref | undefined ): ComputedRef>> { const ssrAdapter = useSsrAdapter() const NConfigProvider = inject(configProviderInjectionKey, null) if (style) { const mountStyle = (): void => { const clsPrefix = clsPrefixRef?.value style.mount({ id: clsPrefix === undefined ? mountId : clsPrefix + mountId, head: true, props: { bPrefix: clsPrefix ? `.${clsPrefix}-` : undefined }, anchorMetaName: cssrAnchorMetaName, ssr: ssrAdapter, parent: NConfigProvider?.styleMountTarget }) if (!NConfigProvider?.preflightStyleDisabled) { globalStyle.mount({ id: 'n-global', head: true, anchorMetaName: cssrAnchorMetaName, ssr: ssrAdapter, parent: NConfigProvider?.styleMountTarget }) } } if (ssrAdapter) { mountStyle() } else { onBeforeMount(mountStyle) } } const mergedThemeRef = computed(() => { // keep props to make theme overrideable const { theme: { common: selfCommon, self, peers = {} } = {}, themeOverrides: selfOverrides = {} as ExtractThemeOverrides< Theme >, builtinThemeOverrides: builtinOverrides = {} as ExtractThemeOverrides< Theme > } = props const { common: selfCommonOverrides, peers: peersOverrides } = selfOverrides const { common: globalCommon = undefined, [resolveId]: { common: globalSelfCommon = undefined, self: globalSelf = undefined, peers: globalPeers = {} } = {} } = NConfigProvider?.mergedThemeRef.value || {} const { common: globalCommonOverrides = undefined, [resolveId]: globalSelfOverrides = {} } = NConfigProvider?.mergedThemeOverridesRef.value || {} const { common: globalSelfCommonOverrides, peers: globalPeersOverrides = {} } = globalSelfOverrides const mergedCommon = merge( {}, selfCommon || globalSelfCommon || globalCommon || defaultTheme.common, globalCommonOverrides, globalSelfCommonOverrides, selfCommonOverrides ) const mergedSelf = merge( // {}, executed every time, no need for empty obj (self || globalSelf || defaultTheme.self)?.(mergedCommon) as T, builtinOverrides, globalSelfOverrides, selfOverrides ) return { common: mergedCommon, self: mergedSelf, peers: merge({}, defaultTheme.peers, globalPeers, peers), peerOverrides: merge( {}, builtinOverrides.peers, globalPeersOverrides, peersOverrides ) } }) return mergedThemeRef } useTheme.props = { theme: Object, themeOverrides: Object, builtinThemeOverrides: Object } as const /** * props.theme (Theme): * { * common: CommonThemeVars, * self(): ThemeVars, * peers: { Component: Theme } * } * provider.theme: * { * common: CommonThemeVars, * Button: Theme * ... * } * defaultTheme: * { * common: CommonThemeVars, * self(): ThemeVars, * peers: { Component: Theme } * } * * props.themeOverrides (ThemeOverrides): * { * common: CommonThemeVars, * peers: { Component: ThemeOverrides }, * ...ThemeVars * } * provider.themeOverrides: * { * common: CommonThemeVars, * Component: ThemeOverrides * ... * } * * mergedTheme: * { * common: CommonThemeVars, * self: ThemeVars, * peers: { Component: Theme }, * overrides: { Component: ThemeOverrides } * } */ export default useTheme ================================================ FILE: src/_styles/common/_common.ts ================================================ export default { fontFamily: 'v-sans, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"', fontFamilyMono: 'v-mono, SFMono-Regular, Menlo, Consolas, Courier, monospace', fontWeight: '400', fontWeightStrong: '500', cubicBezierEaseInOut: 'cubic-bezier(.4, 0, .2, 1)', cubicBezierEaseOut: 'cubic-bezier(0, 0, .2, 1)', cubicBezierEaseIn: 'cubic-bezier(.4, 0, 1, 1)', borderRadius: '3px', borderRadiusSmall: '2px', fontSize: '14px', fontSizeMini: '12px', fontSizeTiny: '12px', fontSizeSmall: '14px', fontSizeMedium: '14px', fontSizeLarge: '15px', fontSizeHuge: '16px', lineHeight: '1.6', heightMini: '16px', // private now, it's too small heightTiny: '22px', heightSmall: '28px', heightMedium: '34px', heightLarge: '40px', heightHuge: '46px' } ================================================ FILE: src/_styles/common/dark.ts ================================================ import type { ThemeCommonVars } from './light' import { composite, rgba, scaleColor } from 'seemly' import commonVariables from './_common' const base = { neutralBase: '#000', neutralInvertBase: '#fff', neutralTextBase: '#fff', neutralPopover: 'rgb(72, 72, 78)', neutralCard: 'rgb(24, 24, 28)', neutralModal: 'rgb(44, 44, 50)', neutralBody: 'rgb(16, 16, 20)', alpha1: '0.9', alpha2: '0.82', alpha3: '0.52', alpha4: '0.38', alpha5: '0.28', alphaClose: '0.52', alphaDisabled: '0.38', alphaDisabledInput: '0.06', alphaPending: '0.09', alphaTablePending: '0.06', alphaTableStriped: '0.05', alphaPressed: '0.05', alphaAvatar: '0.18', alphaRail: '0.2', alphaProgressRail: '0.12', alphaBorder: '0.24', alphaDivider: '0.09', alphaInput: '0.1', alphaAction: '0.06', alphaTab: '0.04', alphaScrollbar: '0.2', alphaScrollbarHover: '0.3', alphaCode: '0.12', alphaTag: '0.2', // primary primaryHover: '#7fe7c4', primaryDefault: '#63e2b7', primaryActive: '#5acea7', primarySuppl: 'rgb(42, 148, 125)', // info infoHover: '#8acbec', infoDefault: '#70c0e8', infoActive: '#66afd3', infoSuppl: 'rgb(56, 137, 197)', // error errorHover: '#e98b8b', errorDefault: '#e88080', errorActive: '#e57272', errorSuppl: 'rgb(208, 58, 82)', // warning warningHover: '#f5d599', warningDefault: '#f2c97d', warningActive: '#e6c260', warningSuppl: 'rgb(240, 138, 0)', // success successHover: '#7fe7c4', successDefault: '#63e2b7', successActive: '#5acea7', successSuppl: 'rgb(42, 148, 125)' } const baseBackgroundRgb = rgba(base.neutralBase) const baseInvertBackgroundRgb = rgba(base.neutralInvertBase) const overlayPrefix = `rgba(${baseInvertBackgroundRgb.slice(0, 3).join(', ')}, ` function overlay(alpha: number | string): string { return `${overlayPrefix + String(alpha)})` } function neutral(alpha: number | string): string { const overlayRgba = Array.from(baseInvertBackgroundRgb) overlayRgba[3] = Number(alpha) return composite( baseBackgroundRgb, overlayRgba as [number, number, number, number] ) } const derived: ThemeCommonVars = { name: 'common' as const, ...commonVariables, baseColor: base.neutralBase, // primary color primaryColor: base.primaryDefault, primaryColorHover: base.primaryHover, primaryColorPressed: base.primaryActive, primaryColorSuppl: base.primarySuppl, // info color infoColor: base.infoDefault, infoColorHover: base.infoHover, infoColorPressed: base.infoActive, infoColorSuppl: base.infoSuppl, // success color successColor: base.successDefault, successColorHover: base.successHover, successColorPressed: base.successActive, successColorSuppl: base.successSuppl, // warning color warningColor: base.warningDefault, warningColorHover: base.warningHover, warningColorPressed: base.warningActive, warningColorSuppl: base.warningSuppl, // error color errorColor: base.errorDefault, errorColorHover: base.errorHover, errorColorPressed: base.errorActive, errorColorSuppl: base.errorSuppl, // text color textColorBase: base.neutralTextBase, textColor1: overlay(base.alpha1), textColor2: overlay(base.alpha2), textColor3: overlay(base.alpha3), // textColor4: overlay(base.alpha4), // disabled, placeholder, icon // textColor5: overlay(base.alpha5), textColorDisabled: overlay(base.alpha4), placeholderColor: overlay(base.alpha4), placeholderColorDisabled: overlay(base.alpha5), iconColor: overlay(base.alpha4), iconColorDisabled: overlay(base.alpha5), iconColorHover: overlay(Number(base.alpha4) * 1.25), iconColorPressed: overlay(Number(base.alpha4) * 0.8), opacity1: base.alpha1, opacity2: base.alpha2, opacity3: base.alpha3, opacity4: base.alpha4, opacity5: base.alpha5, dividerColor: overlay(base.alphaDivider), borderColor: overlay(base.alphaBorder), // close closeIconColorHover: overlay(Number(base.alphaClose)), closeIconColor: overlay(Number(base.alphaClose)), closeIconColorPressed: overlay(Number(base.alphaClose)), closeColorHover: 'rgba(255, 255, 255, .12)', closeColorPressed: 'rgba(255, 255, 255, .08)', // clear clearColor: overlay(base.alpha4), clearColorHover: scaleColor(overlay(base.alpha4), { alpha: 1.25 }), clearColorPressed: scaleColor(overlay(base.alpha4), { alpha: 0.8 }), scrollbarColor: overlay(base.alphaScrollbar), scrollbarColorHover: overlay(base.alphaScrollbarHover), scrollbarWidth: '5px', scrollbarHeight: '5px', scrollbarBorderRadius: '5px', progressRailColor: overlay(base.alphaProgressRail), railColor: overlay(base.alphaRail), popoverColor: base.neutralPopover, tableColor: base.neutralCard, cardColor: base.neutralCard, modalColor: base.neutralModal, bodyColor: base.neutralBody, tagColor: neutral(base.alphaTag), avatarColor: overlay(base.alphaAvatar), invertedColor: base.neutralBase, inputColor: overlay(base.alphaInput), codeColor: overlay(base.alphaCode), tabColor: overlay(base.alphaTab), actionColor: overlay(base.alphaAction), tableHeaderColor: overlay(base.alphaAction), hoverColor: overlay(base.alphaPending), tableColorHover: overlay(base.alphaTablePending), tableColorStriped: overlay(base.alphaTableStriped), pressedColor: overlay(base.alphaPressed), opacityDisabled: base.alphaDisabled, inputColorDisabled: overlay(base.alphaDisabledInput), buttonColor2: 'rgba(255, 255, 255, .08)', buttonColor2Hover: 'rgba(255, 255, 255, .12)', buttonColor2Pressed: 'rgba(255, 255, 255, .08)', boxShadow1: '0 1px 2px -2px rgba(0, 0, 0, .24), 0 3px 6px 0 rgba(0, 0, 0, .18), 0 5px 12px 4px rgba(0, 0, 0, .12)', boxShadow2: '0 3px 6px -4px rgba(0, 0, 0, .24), 0 6px 12px 0 rgba(0, 0, 0, .16), 0 9px 18px 8px rgba(0, 0, 0, .10)', boxShadow3: '0 6px 16px -9px rgba(0, 0, 0, .08), 0 9px 28px 0 rgba(0, 0, 0, .05), 0 12px 48px 16px rgba(0, 0, 0, .03)' } export default derived ================================================ FILE: src/_styles/common/index.ts ================================================ export { default as commonDark } from './dark' export { default as commonLight } from './light' export type { ThemeCommonVars } from './light' ================================================ FILE: src/_styles/common/light.ts ================================================ import { composite, rgba, scaleColor } from 'seemly' import commonVariables from './_common' const base = { neutralBase: '#FFF', neutralInvertBase: '#000', neutralTextBase: '#000', neutralPopover: '#fff', neutralCard: '#fff', neutralModal: '#fff', neutralBody: '#fff', alpha1: '0.82', alpha2: '0.72', alpha3: '0.38', alpha4: '0.24', // disabled text, placeholder, icon alpha5: '0.18', // disabled placeholder alphaClose: '0.6', alphaDisabled: '0.5', alphaDisabledInput: '0.02', alphaPending: '0.05', alphaTablePending: '0.02', alphaPressed: '0.07', alphaAvatar: '0.2', alphaRail: '0.14', alphaProgressRail: '.08', alphaBorder: '0.12', alphaDivider: '0.06', alphaInput: '0', alphaAction: '0.02', alphaTab: '0.04', alphaScrollbar: '0.25', alphaScrollbarHover: '0.4', alphaCode: '0.05', alphaTag: '0.02', // primary primaryHover: '#36ad6a', primaryDefault: '#18a058', primaryActive: '#0c7a43', primarySuppl: '#36ad6a', // info infoHover: '#4098fc', infoDefault: '#2080f0', infoActive: '#1060c9', infoSuppl: '#4098fc', // error errorHover: '#de576d', errorDefault: '#d03050', errorActive: '#ab1f3f', errorSuppl: '#de576d', // warning warningHover: '#fcb040', warningDefault: '#f0a020', warningActive: '#c97c10', warningSuppl: '#fcb040', // success successHover: '#36ad6a', successDefault: '#18a058', successActive: '#0c7a43', successSuppl: '#36ad6a' } const baseBackgroundRgb = rgba(base.neutralBase) const baseInvertBackgroundRgb = rgba(base.neutralInvertBase) const overlayPrefix = `rgba(${baseInvertBackgroundRgb.slice(0, 3).join(', ')}, ` function overlay(alpha: string | number) { return `${overlayPrefix + String(alpha)})` } function neutral(alpha: string | number) { const overlayRgba = Array.from(baseInvertBackgroundRgb) overlayRgba[3] = Number(alpha) return composite( baseBackgroundRgb, overlayRgba as [number, number, number, number] ) } const derived = { name: 'common' as const, ...commonVariables, baseColor: base.neutralBase, // primary color primaryColor: base.primaryDefault, primaryColorHover: base.primaryHover, primaryColorPressed: base.primaryActive, primaryColorSuppl: base.primarySuppl, // info color infoColor: base.infoDefault, infoColorHover: base.infoHover, infoColorPressed: base.infoActive, infoColorSuppl: base.infoSuppl, // success color successColor: base.successDefault, successColorHover: base.successHover, successColorPressed: base.successActive, successColorSuppl: base.successSuppl, // warning color warningColor: base.warningDefault, warningColorHover: base.warningHover, warningColorPressed: base.warningActive, warningColorSuppl: base.warningSuppl, // error color errorColor: base.errorDefault, errorColorHover: base.errorHover, errorColorPressed: base.errorActive, errorColorSuppl: base.errorSuppl, // text color textColorBase: base.neutralTextBase, textColor1: 'rgb(31, 34, 37)', textColor2: 'rgb(51, 54, 57)', textColor3: 'rgb(118, 124, 130)', // textColor4: neutral(base.alpha4), // disabled, placeholder, icon // textColor5: neutral(base.alpha5), textColorDisabled: neutral(base.alpha4), placeholderColor: neutral(base.alpha4), placeholderColorDisabled: neutral(base.alpha5), iconColor: neutral(base.alpha4), iconColorHover: scaleColor(neutral(base.alpha4), { lightness: 0.75 }), iconColorPressed: scaleColor(neutral(base.alpha4), { lightness: 0.9 }), iconColorDisabled: neutral(base.alpha5), opacity1: base.alpha1, opacity2: base.alpha2, opacity3: base.alpha3, opacity4: base.alpha4, opacity5: base.alpha5, dividerColor: 'rgb(239, 239, 245)', borderColor: 'rgb(224, 224, 230)', // close closeIconColor: neutral(Number(base.alphaClose)), closeIconColorHover: neutral(Number(base.alphaClose)), closeIconColorPressed: neutral(Number(base.alphaClose)), closeColorHover: 'rgba(0, 0, 0, .09)', closeColorPressed: 'rgba(0, 0, 0, .13)', // clear clearColor: neutral(base.alpha4), clearColorHover: scaleColor(neutral(base.alpha4), { lightness: 0.75 }), clearColorPressed: scaleColor(neutral(base.alpha4), { lightness: 0.9 }), scrollbarColor: overlay(base.alphaScrollbar), scrollbarColorHover: overlay(base.alphaScrollbarHover), scrollbarWidth: '5px', scrollbarHeight: '5px', scrollbarBorderRadius: '5px', progressRailColor: neutral(base.alphaProgressRail), railColor: 'rgb(219, 219, 223)', popoverColor: base.neutralPopover, tableColor: base.neutralCard, cardColor: base.neutralCard, modalColor: base.neutralModal, bodyColor: base.neutralBody, tagColor: '#eee', avatarColor: neutral(base.alphaAvatar), invertedColor: 'rgb(0, 20, 40)', inputColor: neutral(base.alphaInput), codeColor: 'rgb(244, 244, 248)', tabColor: 'rgb(247, 247, 250)', actionColor: 'rgb(250, 250, 252)', tableHeaderColor: 'rgb(250, 250, 252)', hoverColor: 'rgb(243, 243, 245)', // use color with alpha since it can be nested with header filter & sorter effect tableColorHover: 'rgba(0, 0, 100, 0.03)', tableColorStriped: 'rgba(0, 0, 100, 0.02)', pressedColor: 'rgb(237, 237, 239)', opacityDisabled: base.alphaDisabled, inputColorDisabled: 'rgb(250, 250, 252)', // secondary button color // can also be used in tertiary button & quaternary button buttonColor2: 'rgba(46, 51, 56, .05)', buttonColor2Hover: 'rgba(46, 51, 56, .09)', buttonColor2Pressed: 'rgba(46, 51, 56, .13)', boxShadow1: '0 1px 2px -2px rgba(0, 0, 0, .08), 0 3px 6px 0 rgba(0, 0, 0, .06), 0 5px 12px 4px rgba(0, 0, 0, .04)', boxShadow2: '0 3px 6px -4px rgba(0, 0, 0, .12), 0 6px 16px 0 rgba(0, 0, 0, .08), 0 9px 28px 8px rgba(0, 0, 0, .05)', boxShadow3: '0 6px 16px -9px rgba(0, 0, 0, .08), 0 9px 28px 0 rgba(0, 0, 0, .05), 0 12px 48px 16px rgba(0, 0, 0, .03)' } export default derived export type ThemeCommonVars = typeof derived ================================================ FILE: src/_styles/global/index.cssr.ts ================================================ import { c } from '../../_utils/cssr' import commonVariables from '../common/_common' const { fontSize, fontFamily, lineHeight } = commonVariables // All the components need the style // It is static and won't be changed in the app's lifetime // If user want to overrides it they need to use `n-global-style` is provided // // Technically we can remove font-size & font-family & line-height to make // it pure. However the coding cost doesn't worth it. // // -webkit-tap-hilight-color: // https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-tap-highlight-color // In some android devices, there will be the style. export default c('body', ` margin: 0; font-size: ${fontSize}; font-family: ${fontFamily}; line-height: ${lineHeight}; -webkit-text-size-adjust: 100%; -webkit-tap-highlight-color: transparent; `, [ c('input', ` font-family: inherit; font-size: inherit; `) ]) ================================================ FILE: src/_styles/transitions/fade-down.cssr.ts ================================================ import type { CNode } from 'css-render' import { c } from '../../_utils/cssr' import commonVariables from '../common/_common' const { cubicBezierEaseInOut } = commonVariables interface FadeDownTransitionOptions { name?: string fromOffset?: string enterDuration?: string leaveDuration?: string enterCubicBezier?: string leaveCubicBezier?: string } export function fadeDownTransition({ name = 'fade-down', fromOffset = '-4px', enterDuration = '.3s', leaveDuration = '.3s', enterCubicBezier = cubicBezierEaseInOut, leaveCubicBezier = cubicBezierEaseInOut }: FadeDownTransitionOptions = {}): CNode[] { return [ c(`&.${name}-transition-enter-from, &.${name}-transition-leave-to`, { opacity: 0, transform: `translateY(${fromOffset})` }), c(`&.${name}-transition-enter-to, &.${name}-transition-leave-from`, { opacity: 1, transform: 'translateY(0)' }), c(`&.${name}-transition-leave-active`, { transition: `opacity ${leaveDuration} ${leaveCubicBezier}, transform ${leaveDuration} ${leaveCubicBezier}` }), c(`&.${name}-transition-enter-active`, { transition: `opacity ${enterDuration} ${enterCubicBezier}, transform ${enterDuration} ${enterCubicBezier}` }) ] } ================================================ FILE: src/_styles/transitions/fade-in-height-expand.cssr.ts ================================================ import type { CNode } from 'css-render' import { c } from '../../_utils/cssr' import commonVariables from '../common/_common' const { cubicBezierEaseInOut, cubicBezierEaseOut, cubicBezierEaseIn } = commonVariables interface FadeInHeightExpandTransitionOption { overflow?: string duration?: string originalTransition?: string leavingDelay?: string foldPadding?: boolean enterToProps?: Record | undefined leaveToProps?: Record | undefined reverse?: boolean } export function fadeInHeightExpandTransition({ overflow = 'hidden', duration = '.3s', originalTransition = '', leavingDelay = '0s', foldPadding = false, enterToProps = undefined, leaveToProps = undefined, reverse = false }: FadeInHeightExpandTransitionOption = {}): CNode[] { const enterClass = reverse ? 'leave' : 'enter' const leaveClass = reverse ? 'enter' : 'leave' return [ c(`&.fade-in-height-expand-transition-${leaveClass}-from, &.fade-in-height-expand-transition-${enterClass}-to`, { ...enterToProps, opacity: 1 }), c(`&.fade-in-height-expand-transition-${leaveClass}-to, &.fade-in-height-expand-transition-${enterClass}-from`, { ...leaveToProps, opacity: 0, marginTop: '0 !important', marginBottom: '0 !important', paddingTop: foldPadding ? '0 !important' : undefined, paddingBottom: foldPadding ? '0 !important' : undefined }), c(`&.fade-in-height-expand-transition-${leaveClass}-active`, ` overflow: ${overflow}; transition: max-height ${duration} ${cubicBezierEaseInOut} ${leavingDelay}, opacity ${duration} ${cubicBezierEaseOut} ${leavingDelay}, margin-top ${duration} ${cubicBezierEaseInOut} ${leavingDelay}, margin-bottom ${duration} ${cubicBezierEaseInOut} ${leavingDelay}, padding-top ${duration} ${cubicBezierEaseInOut} ${leavingDelay}, padding-bottom ${duration} ${cubicBezierEaseInOut} ${leavingDelay} ${originalTransition ? `,${originalTransition}` : ''} `), c(`&.fade-in-height-expand-transition-${enterClass}-active`, ` overflow: ${overflow}; transition: max-height ${duration} ${cubicBezierEaseInOut}, opacity ${duration} ${cubicBezierEaseIn}, margin-top ${duration} ${cubicBezierEaseInOut}, margin-bottom ${duration} ${cubicBezierEaseInOut}, padding-top ${duration} ${cubicBezierEaseInOut}, padding-bottom ${duration} ${cubicBezierEaseInOut} ${originalTransition ? `,${originalTransition}` : ''} `) ] } ================================================ FILE: src/_styles/transitions/fade-in-scale-up.cssr.ts ================================================ import type { CNode } from 'css-render' import { c } from '../../_utils/cssr' import commonVariables from '../common/_common' const { cubicBezierEaseIn, cubicBezierEaseOut } = commonVariables interface FadeInScaleUpTransitionOptions { transformOrigin?: string duration?: string enterScale?: string originalTransform?: string originalTransition?: string } export function fadeInScaleUpTransition({ transformOrigin = 'inherit', duration = '.2s', enterScale = '.9', originalTransform = '', originalTransition = '' }: FadeInScaleUpTransitionOptions = {}): CNode[] { return [ c('&.fade-in-scale-up-transition-leave-active', { transformOrigin, transition: `opacity ${duration} ${cubicBezierEaseIn}, transform ${duration} ${cubicBezierEaseIn} ${ originalTransition && `,${originalTransition}` }` }), c('&.fade-in-scale-up-transition-enter-active', { transformOrigin, transition: `opacity ${duration} ${cubicBezierEaseOut}, transform ${duration} ${cubicBezierEaseOut} ${ originalTransition && `,${originalTransition}` }` }), c('&.fade-in-scale-up-transition-enter-from, &.fade-in-scale-up-transition-leave-to', { opacity: 0, transform: `${originalTransform} scale(${enterScale})` }), c('&.fade-in-scale-up-transition-leave-from, &.fade-in-scale-up-transition-enter-to', { opacity: 1, transform: `${originalTransform} scale(1)` }) ] } ================================================ FILE: src/_styles/transitions/fade-in-width-expand.cssr.ts ================================================ import type { CNode } from 'css-render' import { c } from '../../_utils/cssr' import commonVariables from '../common/_common' const { cubicBezierEaseInOut } = commonVariables interface FadeInWidthExpandTransition { duration?: string delay?: string } export function fadeInWidthExpandTransition({ duration = '.2s', delay = '.1s' }: FadeInWidthExpandTransition = {}): CNode[] { return [ c('&.fade-in-width-expand-transition-leave-from, &.fade-in-width-expand-transition-enter-to', { opacity: 1 }), c('&.fade-in-width-expand-transition-leave-to, &.fade-in-width-expand-transition-enter-from', ` opacity: 0!important; margin-left: 0!important; margin-right: 0!important; `), c('&.fade-in-width-expand-transition-leave-active', ` overflow: hidden; transition: opacity ${duration} ${cubicBezierEaseInOut}, max-width ${duration} ${cubicBezierEaseInOut} ${delay}, margin-left ${duration} ${cubicBezierEaseInOut} ${delay}, margin-right ${duration} ${cubicBezierEaseInOut} ${delay}; `), c('&.fade-in-width-expand-transition-enter-active', ` overflow: hidden; transition: opacity ${duration} ${cubicBezierEaseInOut} ${delay}, max-width ${duration} ${cubicBezierEaseInOut}, margin-left ${duration} ${cubicBezierEaseInOut}, margin-right ${duration} ${cubicBezierEaseInOut}; `) ] } ================================================ FILE: src/_styles/transitions/fade-in.cssr.ts ================================================ import type { CNode } from 'css-render' import { c } from '../../_utils/cssr' import commonVariables from '../common/_common' const { cubicBezierEaseInOut } = commonVariables interface FadeInTransitionOptions { name?: string enterDuration?: string leaveDuration?: string enterCubicBezier?: string leaveCubicBezier?: string } export function fadeInTransition({ name = 'fade-in', enterDuration = '0.2s', leaveDuration = '0.2s', enterCubicBezier = cubicBezierEaseInOut, leaveCubicBezier = cubicBezierEaseInOut }: FadeInTransitionOptions = {}): CNode[] { return [ c(`&.${name}-transition-enter-active`, { transition: `all ${enterDuration} ${enterCubicBezier}!important` }), c(`&.${name}-transition-leave-active`, { transition: `all ${leaveDuration} ${leaveCubicBezier}!important` }), c(`&.${name}-transition-enter-from, &.${name}-transition-leave-to`, { opacity: 0 }), c(`&.${name}-transition-leave-from, &.${name}-transition-enter-to`, { opacity: 1 }) ] } ================================================ FILE: src/_styles/transitions/fade-up-width-expand.cssr.ts ================================================ import type { CNode } from 'css-render' import { c } from '../../_utils/cssr' import commonVariables from '../common/_common' const { cubicBezierEaseOut } = commonVariables interface FadeUpWidthExpandTransition { duration?: string } export function fadeUpWidthExpandTransition({ duration = '.2s' }: FadeUpWidthExpandTransition = {}): CNode[] { return [ c('&.fade-up-width-expand-transition-leave-active', { transition: ` opacity ${duration} ${cubicBezierEaseOut}, max-width ${duration} ${cubicBezierEaseOut}, transform ${duration} ${cubicBezierEaseOut} ` }), c('&.fade-up-width-expand-transition-enter-active', { transition: ` opacity ${duration} ${cubicBezierEaseOut}, max-width ${duration} ${cubicBezierEaseOut}, transform ${duration} ${cubicBezierEaseOut} ` }), c('&.fade-up-width-expand-transition-enter-to', { opacity: 1, transform: 'translateX(0) translateY(0)' }), c('&.fade-up-width-expand-transition-enter-from', { maxWidth: '0 !important', opacity: 0, transform: 'translateY(60%)' }), c('&.fade-up-width-expand-transition-leave-from', { opacity: 1, transform: 'translateY(0)' }), c('&.fade-up-width-expand-transition-leave-to', { maxWidth: '0 !important', opacity: 0, transform: 'translateY(60%)' }) ] } ================================================ FILE: src/_styles/transitions/icon-switch.cssr.ts ================================================ import type { CNode } from 'css-render' import { c } from '../../_utils/cssr' import commonVariables from '../common/_common' const { cubicBezierEaseInOut } = commonVariables interface IconSwitchTransitionOptions { originalTransform?: string left?: string | number top?: string | number transition?: string } export function iconSwitchTransition({ originalTransform = '', left = 0, top = 0, transition = `all .3s ${cubicBezierEaseInOut} !important` }: IconSwitchTransitionOptions = {}): CNode[] { return [ c('&.icon-switch-transition-enter-from, &.icon-switch-transition-leave-to', { transform: `${originalTransform} scale(0.75)`, left, top, opacity: 0 }), c('&.icon-switch-transition-enter-to, &.icon-switch-transition-leave-from', { transform: `scale(1) ${originalTransform}`, left, top, opacity: 1 }), c('&.icon-switch-transition-enter-active, &.icon-switch-transition-leave-active', { transformOrigin: 'center', position: 'absolute', left, top, transition }) ] } ================================================ FILE: src/_styles/transitions/slide-in-from-bottom.ts ================================================ import type { CNode } from 'css-render' import { c } from '../../_utils/cssr' import commonVariables from '../common/_common' const { cubicBezierEaseIn, cubicBezierEaseOut } = commonVariables interface SlideInFromBottomTransitionOptions { duration?: string leaveDuration?: string name?: string } export function slideInFromBottomTransition({ duration = '0.3s', leaveDuration = '0.2s', name = 'slide-in-from-bottom' }: SlideInFromBottomTransitionOptions = {}): CNode[] { return [ c(`&.${name}-transition-leave-active`, { transition: `transform ${leaveDuration} ${cubicBezierEaseIn}` }), c(`&.${name}-transition-enter-active`, { transition: `transform ${duration} ${cubicBezierEaseOut}` }), c(`&.${name}-transition-enter-to`, { transform: 'translateY(0)' }), c(`&.${name}-transition-enter-from`, { transform: 'translateY(100%)' }), c(`&.${name}-transition-leave-from`, { transform: 'translateY(0)' }), c(`&.${name}-transition-leave-to`, { transform: 'translateY(100%)' }) ] } ================================================ FILE: src/_styles/transitions/slide-in-from-left.ts ================================================ import type { CNode } from 'css-render' import { c } from '../../_utils/cssr' import commonVariables from '../common/_common' const { cubicBezierEaseIn, cubicBezierEaseOut } = commonVariables interface SlideInFromLeftTransitionOptions { duration?: string leaveDuration?: string name?: string } export function slideInFromLeftTransition({ duration = '0.3s', leaveDuration = '0.2s', name = 'slide-in-from-left' }: SlideInFromLeftTransitionOptions = {}): CNode[] { return [ c(`&.${name}-transition-leave-active`, { transition: `transform ${leaveDuration} ${cubicBezierEaseIn}` }), c(`&.${name}-transition-enter-active`, { transition: `transform ${duration} ${cubicBezierEaseOut}` }), c(`&.${name}-transition-enter-to`, { transform: 'translateX(0)' }), c(`&.${name}-transition-enter-from`, { transform: 'translateX(-100%)' }), c(`&.${name}-transition-leave-from`, { transform: 'translateX(0)' }), c(`&.${name}-transition-leave-to`, { transform: 'translateX(-100%)' }) ] } ================================================ FILE: src/_styles/transitions/slide-in-from-right.ts ================================================ import type { CNode } from 'css-render' import { c } from '../../_utils/cssr' import commonVariables from '../common/_common' const { cubicBezierEaseIn, cubicBezierEaseOut } = commonVariables interface SlideInFromRightTransitionOptions { duration?: string leaveDuration?: string name?: string } export function slideInFromRightTransition({ duration = '0.3s', leaveDuration = '0.2s', name = 'slide-in-from-right' }: SlideInFromRightTransitionOptions = {}): CNode[] { return [ c(`&.${name}-transition-leave-active`, { transition: `transform ${leaveDuration} ${cubicBezierEaseIn}` }), c(`&.${name}-transition-enter-active`, { transition: `transform ${duration} ${cubicBezierEaseOut}` }), c(`&.${name}-transition-enter-to`, { transform: 'translateX(0)' }), c(`&.${name}-transition-enter-from`, { transform: 'translateX(100%)' }), c(`&.${name}-transition-leave-from`, { transform: 'translateX(0)' }), c(`&.${name}-transition-leave-to`, { transform: 'translateX(100%)' }) ] } ================================================ FILE: src/_styles/transitions/slide-in-from-top.ts ================================================ import type { CNode } from 'css-render' import { c } from '../../_utils/cssr' import commonVariables from '../common/_common' const { cubicBezierEaseIn, cubicBezierEaseOut } = commonVariables interface SlideInFromTopTransitionOptions { duration?: string leaveDuration?: string name?: string } export function slideInFromTopTransition({ duration = '0.3s', leaveDuration = '0.2s', name = 'slide-in-from-top' }: SlideInFromTopTransitionOptions = {}): CNode[] { return [ c(`&.${name}-transition-leave-active`, { transition: `transform ${leaveDuration} ${cubicBezierEaseIn}` }), c(`&.${name}-transition-enter-active`, { transition: `transform ${duration} ${cubicBezierEaseOut}` }), c(`&.${name}-transition-enter-to`, { transform: 'translateY(0)' }), c(`&.${name}-transition-enter-from`, { transform: 'translateY(-100%)' }), c(`&.${name}-transition-leave-from`, { transform: 'translateY(0)' }), c(`&.${name}-transition-leave-to`, { transform: 'translateY(-100%)' }) ] } ================================================ FILE: src/_utils/color/index.ts ================================================ import { composite } from 'seemly' export function createHoverColor(rgb: string): string { return composite(rgb, [255, 255, 255, 0.16]) } export function createPressedColor(rgb: string): string { return composite(rgb, [0, 0, 0, 0.12]) } ================================================ FILE: src/_utils/composable/index.ts ================================================ export { useAdjustedTo } from './use-adjusted-to' export { useInjectionCollection, useInjectionElementCollection, useInjectionInstanceCollection } from './use-collection' export { useDeferredTrue } from './use-deferred-true' export { useHoudini } from './use-houdini' export { useIsComposing } from './use-is-composing' export { lockHtmlScrollRightCompensationRef, useLockHtmlScroll } from './use-lock-html-scroll' export { useReactivated } from './use-reactivated' export { useOnResize } from './use-resize' ================================================ FILE: src/_utils/composable/use-adjusted-to.ts ================================================ import type { ComponentPublicInstance, ComputedRef, PropType } from 'vue' import { off, on } from 'evtd' import { useMemo } from 'vooks' import { inject, onBeforeUnmount, onMounted, ref } from 'vue' import { internalSelectionMenuBodyInjectionKey } from '../../_internal/select-menu/src/interface' import { drawerBodyInjectionKey } from '../../drawer/src/interface' import { modalBodyInjectionKey } from '../../modal/src/interface' import { popoverBodyInjectionKey } from '../../popover/src/interface' interface UseAdjustedToProps { to?: string | HTMLElement | boolean [key: string]: unknown } const teleportDisabled = '__disabled__' function useAdjustedTo( props: UseAdjustedToProps ): ComputedRef { const modal = inject(modalBodyInjectionKey, null) const drawer = inject(drawerBodyInjectionKey, null) const popover = inject(popoverBodyInjectionKey, null) const selectMenu = inject(internalSelectionMenuBodyInjectionKey, null) const fullscreenElementRef = ref() if (typeof document !== 'undefined') { fullscreenElementRef.value = document.fullscreenElement const handleFullscreenChange = (): void => { fullscreenElementRef.value = document.fullscreenElement } onMounted(() => { on('fullscreenchange', document, handleFullscreenChange) }) onBeforeUnmount(() => { off('fullscreenchange', document, handleFullscreenChange) }) } return useMemo(() => { const { to } = props if (to !== undefined) { if (to === false) return teleportDisabled if (to === true) return fullscreenElementRef.value || 'body' return to } if (modal?.value) { return (modal.value as ComponentPublicInstance).$el ?? modal.value } if (drawer?.value) return drawer.value if (popover?.value) return popover.value if (selectMenu?.value) return selectMenu.value return to ?? (fullscreenElementRef.value || 'body') }) } // teleport disabled key useAdjustedTo.tdkey = teleportDisabled useAdjustedTo.propTo = { type: [String, Object, Boolean] as PropType, default: undefined } export { useAdjustedTo } ================================================ FILE: src/_utils/composable/use-browser-location.ts ================================================ import type { Ref } from 'vue' import { onMounted, onUnmounted, ref } from 'vue' import { isBrowser } from '../env/is-browser' export interface IWindowLocation { hash?: string host?: string hostname?: string href?: string origin?: string pathname?: string port?: string protocol?: string search?: string } export function useBrowserLocation( customWindow = isBrowser ? window : null ): Ref { const getWindowLocation = (): IWindowLocation => { const { hash, host, hostname, href, origin, pathname, port, protocol, search } = customWindow?.location || {} return { hash, host, hostname, href, origin, pathname, port, protocol, search } } const locationState = ref(getWindowLocation()) const updateLocation = (): void => { locationState.value = getWindowLocation() } onMounted(() => { if (customWindow) { customWindow.addEventListener('popstate', updateLocation) customWindow.addEventListener('hashchange', updateLocation) } }) onUnmounted(() => { if (customWindow) { customWindow.removeEventListener('popstate', updateLocation) customWindow.removeEventListener('hashchange', updateLocation) } }) return locationState } ================================================ FILE: src/_utils/composable/use-collection.ts ================================================ import type { InjectionKey, Ref } from 'vue' import { getCurrentInstance, inject, onBeforeUnmount, onMounted, watch } from 'vue' // injection.collection { // key1: [insta, instb] // key2: [instc] // } export function useInjectionInstanceCollection( injectionName: string | InjectionKey, collectionKey: string, registerKeyRef: Ref ): void { const injection = inject> | null>( injectionName, null ) if (injection === null) return const vm = getCurrentInstance()?.proxy watch(registerKeyRef, registerInstance) registerInstance(registerKeyRef.value) onBeforeUnmount(() => { registerInstance(undefined, registerKeyRef.value) }) function registerInstance(key?: string, oldKey?: string): void { if (!injection) return const collection = injection[collectionKey] if (oldKey !== undefined) removeInstance(collection, oldKey) if (key !== undefined) addInstance(collection, key) } function removeInstance( collection: Record, key: string ): void { if (!collection[key]) collection[key] = [] collection[key].splice( collection[key].findIndex(instance => instance === vm), 1 ) } function addInstance(collection: Record, key: string): void { if (!collection[key]) collection[key] = [] if (!~collection[key].findIndex(instance => instance === vm)) { collection[key].push(vm) } } } // injection.collection { // key1: [insta.value, instb.value] // key2: [instc.value] // } export function useInjectionCollection( injectionName: string | InjectionKey, collectionKey: string, valueRef: Ref ): void { const injection = inject | null>(injectionName, null) if (injection === null) return if (!(collectionKey in injection)) { injection[collectionKey] = [] } injection[collectionKey].push(valueRef.value) watch(valueRef, (value, prevValue) => { const collectionArray = injection[collectionKey] const index = collectionArray.findIndex( collectionValue => collectionValue === prevValue ) if (~index) collectionArray.splice(index, 1) collectionArray.push(value) }) onBeforeUnmount(() => { const collectionArray = injection[collectionKey] const index = collectionArray.findIndex( collectionValue => collectionValue === valueRef.value ) if (~index) collectionArray.splice(index, 1) }) } // injection.collection { // key1: [insta.$el, instb.$el] // key2: [instc.$el] // } export function useInjectionElementCollection( injectionName: string | InjectionKey, collectionKey: string, getElement: () => HTMLElement | null ): void { const injection = inject | null>( injectionName, null ) if (injection === null) return if (!(collectionKey in injection)) { injection[collectionKey] = [] } onMounted(() => { const el = getElement() if (!el) return injection[collectionKey].push(el) }) onBeforeUnmount(() => { const collectionArray = injection[collectionKey] const element = getElement() const index = collectionArray.findIndex( collectionElement => collectionElement === element ) if (~index) collectionArray.splice(index, 1) }) } ================================================ FILE: src/_utils/composable/use-deferred-true.ts ================================================ import type { Ref } from 'vue' import { ref, watch } from 'vue' export function useDeferredTrue( valueRef: Ref, delay: number, shouldDelayRef: Ref ): Ref { if (!delay) return valueRef const delayedRef = ref(valueRef.value) let timerId: number | null = null watch(valueRef, (value) => { if (timerId !== null) window.clearTimeout(timerId) if (value === true) { if (shouldDelayRef && !shouldDelayRef.value) { delayedRef.value = true } else { timerId = window.setTimeout(() => { delayedRef.value = true }, delay) } } else { delayedRef.value = false } }) return delayedRef } ================================================ FILE: src/_utils/composable/use-houdini.ts ================================================ import { isBrowser } from '../env/is-browser' let houdiniRegistered = false export function useHoudini(): void { if (!isBrowser) return if (!window.CSS) return if (!houdiniRegistered) { houdiniRegistered = true if ('registerProperty' in window?.CSS) { try { ;(CSS as any).registerProperty({ name: '--n-color-start', syntax: '', inherits: false, initialValue: '#0000' }) ;(CSS as any).registerProperty({ name: '--n-color-end', syntax: '', inherits: false, initialValue: '#0000' }) } catch {} } } } ================================================ FILE: src/_utils/composable/use-is-composing.ts ================================================ import type { Ref } from 'vue' import { onBeforeMount, onBeforeUnmount, ref } from 'vue' import { isBrowser } from '../env/is-browser' const isComposingRef = ref(false) function compositionStartHandler(): void { isComposingRef.value = true } function compositionEndHandler(): void { isComposingRef.value = false } let mountedCount = 0 export function useIsComposing(): Ref { if (isBrowser) { onBeforeMount(() => { if (!mountedCount) { window.addEventListener('compositionstart', compositionStartHandler) window.addEventListener('compositionend', compositionEndHandler) } mountedCount++ }) onBeforeUnmount(() => { if (mountedCount <= 1) { window.removeEventListener('compositionstart', compositionStartHandler) window.removeEventListener('compositionend', compositionEndHandler) mountedCount = 0 } else { mountedCount-- } }) } return isComposingRef } ================================================ FILE: src/_utils/composable/use-lock-html-scroll.ts ================================================ import type { Ref, WatchStopHandle } from 'vue' import { onBeforeUnmount, onMounted, ref, watch } from 'vue' let lockCount = 0 let originalMarginRight: string = '' let originalOverflow: string = '' let originalOverflowX: string = '' let originalOverflowY: string = '' export const lockHtmlScrollRightCompensationRef = ref('0px') export function useLockHtmlScroll(lockRef: Ref): void { // not browser if (typeof document === 'undefined') return const el = document.documentElement let watchStopHandle: WatchStopHandle | undefined let activated = false const unlock = (): void => { el.style.marginRight = originalMarginRight el.style.overflow = originalOverflow el.style.overflowX = originalOverflowX el.style.overflowY = originalOverflowY lockHtmlScrollRightCompensationRef.value = '0px' } onMounted(() => { watchStopHandle = watch( lockRef, (value) => { if (value) { if (!lockCount) { const scrollbarWidth = window.innerWidth - el.offsetWidth if (scrollbarWidth > 0) { originalMarginRight = el.style.marginRight el.style.marginRight = `${scrollbarWidth}px` lockHtmlScrollRightCompensationRef.value = `${scrollbarWidth}px` } originalOverflow = el.style.overflow originalOverflowX = el.style.overflowX originalOverflowY = el.style.overflowY el.style.overflow = 'hidden' el.style.overflowX = 'hidden' el.style.overflowY = 'hidden' } activated = true lockCount++ } else { lockCount-- if (!lockCount) { unlock() } activated = false } }, { immediate: true } ) }) onBeforeUnmount(() => { watchStopHandle?.() if (activated) { lockCount-- if (!lockCount) { unlock() } activated = false } }) } ================================================ FILE: src/_utils/composable/use-reactivated.ts ================================================ import { onActivated, onDeactivated } from 'vue' export function useReactivated(callback: () => void): { isDeactivated: boolean } { const isDeactivatedRef = { isDeactivated: false } let activateStateInitialized = false onActivated(() => { isDeactivatedRef.isDeactivated = false if (!activateStateInitialized) { activateStateInitialized = true return } callback() }) onDeactivated(() => { isDeactivatedRef.isDeactivated = true if (!activateStateInitialized) { activateStateInitialized = true } }) return isDeactivatedRef } ================================================ FILE: src/_utils/composable/use-resize.ts ================================================ import type { Ref } from 'vue' import { onBeforeUnmount, onMounted, watch } from 'vue' import { resizeObserverManager } from 'vueuc' export function useOnResize( elRef: Ref, onResize: (() => void) | undefined ): void { // it needn't be reactive since it's for internal usage if (onResize) { onMounted(() => { const { value: el } = elRef if (el) { resizeObserverManager.registerHandler(el, onResize) } }) // avoid memory leak watch( elRef, (_, oldEl) => { if (oldEl) { resizeObserverManager.unregisterHandler(oldEl) } }, { deep: false } ) onBeforeUnmount(() => { const { value: el } = elRef if (el) { resizeObserverManager.unregisterHandler(el) } }) } } ================================================ FILE: src/_utils/css/color-to-class.ts ================================================ export function color2Class(color: string): string { return color.replace(/#|\(|\)|,|\s|\./g, '_') } ================================================ FILE: src/_utils/css/format-length.ts ================================================ const pureNumberRegex = /^(\d|\.)+$/ const numberRegex = /(\d|\.)+/ interface FormatLengthOptions { c?: number offset?: number attachPx?: boolean } export function formatLength< T extends number | string | null | undefined | any >( length: T, { c = 1, offset = 0, attachPx = true }: FormatLengthOptions = {} ): T extends null ? null : T extends undefined ? undefined : T extends string | number ? string : T { if (typeof length === 'number') { const result = (length + offset) * c if (result === 0) return '0' as any return `${result}px` as any } else if (typeof length === 'string') { if (pureNumberRegex.test(length)) { const result = (Number(length) + offset) * c if (attachPx) { if (result === 0) return '0' as any return `${result}px` as any } else { return `${result}` as any } } else { const result = numberRegex.exec(length) if (!result) return length as any return length.replace( numberRegex, String((Number(result[0]) + offset) * c) ) as any } } return length as any } ================================================ FILE: src/_utils/css/index.ts ================================================ export { color2Class } from './color-to-class' export { formatLength } from './format-length' export { rtlInset } from './rtl-inset' ================================================ FILE: src/_utils/css/rtl-inset.ts ================================================ import { getPadding } from 'seemly' export function rtlInset(inset: string): string { const { left, right, top, bottom } = getPadding(inset) return `${top} ${left} ${bottom} ${right}` } ================================================ FILE: src/_utils/cssr/index.ts ================================================ import type { CNode, CProperties } from 'css-render' import { plugin as BemPlugin } from '@css-render/plugin-bem' import { CssRender } from 'css-render' const namespace = 'n' const prefix = `.${namespace}-` const elementPrefix = '__' const modifierPrefix = '--' const cssr = CssRender() const plugin = BemPlugin({ blockPrefix: prefix, elementPrefix, modifierPrefix }) cssr.use(plugin) const { c, find } = cssr const { cB, cE, cM, cNotM } = plugin function insideModal(style: CNode): CNode { return c( ({ props: { bPrefix } }) => `${bPrefix || prefix}modal, ${bPrefix || prefix}drawer`, [style] ) } function insidePopover(style: CNode): CNode { return c(({ props: { bPrefix } }) => `${bPrefix || prefix}popover`, [style]) } function asModal(style: CProperties): CNode { return c(({ props: { bPrefix } }) => `&${bPrefix || prefix}modal`, style) } // child block const cCB: typeof cB = ((...args: any[]) => { return c('>', [(cB as any)(...args)]) }) as any function createKey

( prefix: P, suffix: S ): S extends 'default' ? P : `${P}${Capitalize}` { return (prefix + (suffix === 'default' ? '' : suffix.replace(/^[a-z]/, startChar => startChar.toUpperCase()))) as any } export { asModal, c, cB, cCB, cE, cM, cNotM, createKey, find, insideModal, insidePopover, namespace, prefix } ================================================ FILE: src/_utils/dom/download.ts ================================================ export function download(url: string | null, name: string | undefined): void { if (!url) return const a = document.createElement('a') a.href = url if (name !== undefined) { a.download = name } document.body.appendChild(a) a.click() document.body.removeChild(a) } export function publicDownload(url: string, name: string | undefined): void { download(url, name) } ================================================ FILE: src/_utils/dom/index.ts ================================================ export { download } from './download' export { isDocument } from './is-document' ================================================ FILE: src/_utils/dom/is-document.ts ================================================ export function isDocument(node: Node): node is Document { return node.nodeName === '#document' } ================================================ FILE: src/_utils/env/browser.ts ================================================ import { isBrowser } from './is-browser' export const isChrome = isBrowser && 'chrome' in window export const isFirefox = isBrowser && navigator.userAgent.includes('Firefox') export const isSafari = isBrowser && navigator.userAgent.includes('Safari') && !isChrome ================================================ FILE: src/_utils/env/is-browser.ts ================================================ export const isBrowser = typeof document !== 'undefined' && typeof window !== 'undefined' ================================================ FILE: src/_utils/env/is-jsdom.ts ================================================ let _isJsdom: boolean | undefined export function isJsdom(): boolean { if (_isJsdom === undefined) { _isJsdom = navigator.userAgent.includes('Node.js') || navigator.userAgent.includes('jsdom') } return _isJsdom } ================================================ FILE: src/_utils/env/is-native-lazy-load.ts ================================================ import { isBrowser } from './is-browser' export const isImageSupportNativeLazy = isBrowser && 'loading' in document.createElement('img') ================================================ FILE: src/_utils/event/index.ts ================================================ const eventSet = new WeakSet() export function markEventEffectPerformed(event: Event): void { eventSet.add(event) } export function eventEffectNotPerformed(event: Event): boolean { return !eventSet.has(event) } ================================================ FILE: src/_utils/index.ts ================================================ export * from './composable' export { color2Class, formatLength, rtlInset } from './css' export { createKey } from './cssr' export * from './dom' export { isBrowser } from './env/is-browser' export { isJsdom } from './env/is-jsdom' export { eventEffectNotPerformed, markEventEffectPerformed } from './event' export { getTitleAttribute, isArrayShallowEqual, largerSize, smallerSize, throwError, warn, warnOnce } from './naive' export type { ExtractInternalPropTypes, ExtractPublicPropTypes, Mutable } from './naive' export type * from './ts/ts' export { call, createDataKey, createInjectionKey, createRefSetter, flatten, getFirstSlotVNode, getFirstSlotVNodeWithTypedProps, getSlot, getVNodeChildren, isNodeVShowFalse, isSlotEmpty, keep, keysOf, mergeEventHandlers, omit, render, resolveSlot, resolveSlotWithTypedProps, resolveWrappedSlot, resolveWrappedSlotWithProps, Wrapper } from './vue' export type { MaybeArray } from './vue' ================================================ FILE: src/_utils/naive/attribute.ts ================================================ import type { HTMLAttributes } from 'vue' export function getTitleAttribute(value: unknown): HTMLAttributes['title'] { switch (typeof value) { case 'string': // The empty string should also be reset to undefined. return value || undefined case 'number': return String(value) default: return undefined } } ================================================ FILE: src/_utils/naive/extract-public-props.ts ================================================ import type { ExtractPropTypes } from 'vue' import type { useTheme } from '../../_mixins' type themePropKeys = keyof typeof useTheme.props type RemoveReadonly = { -readonly [key in keyof T]: T[key] } export type ExtractPublicPropTypes = Omit< Partial>>, | Exclude | Extract > export type ExtractInternalPropTypes = Partial> ================================================ FILE: src/_utils/naive/index.ts ================================================ export { getTitleAttribute } from './attribute' export type { ExtractInternalPropTypes, ExtractPublicPropTypes } from './extract-public-props' export type { Mutable } from './mutable' export { largerSize, smallerSize } from './prop' export { isArrayShallowEqual } from './value' export { throwError, warn, warnOnce } from './warn' ================================================ FILE: src/_utils/naive/mutable.ts ================================================ export type Mutable = T extends Record ? { -readonly [P in keyof T]: T[P] extends ReadonlyArray ? Array> : Mutable } : T ================================================ FILE: src/_utils/naive/prop.ts ================================================ const smallerSizeMap = { tiny: 'mini', small: 'tiny', medium: 'small', large: 'medium', huge: 'large' } as const const largerSizeMap = { tiny: 'small', small: 'medium', medium: 'large', large: 'huge' } as const type SmallerSizeMap = typeof smallerSizeMap type SmallerSize = keyof SmallerSizeMap type LargerSizeMap = typeof largerSizeMap type LargerSize = keyof LargerSizeMap export function largerSize(size: T): LargerSizeMap[T] { const result = largerSizeMap[size] if (result === undefined) { throw new Error(`${size} has no larger size.`) } return result } export function smallerSize(size: T): SmallerSizeMap[T] { const result = smallerSizeMap[size] if (result === undefined) { throw new Error(`${size} has no smaller size.`) } return result } ================================================ FILE: src/_utils/naive/value.ts ================================================ export function isArrayShallowEqual>( array1: T | null, array2: T | null ): boolean { if (array1 === null && array2 === null) return true if (array1 === null || array2 === null) return false if (array1.length === array2.length) { for (let i = 0; i < array1.length; ++i) { if (array1[i] !== array2[i]) { return false } } return true } return false } ================================================ FILE: src/_utils/naive/warn.ts ================================================ const warnedMessages = new Set() export function warnOnce(location: string, message: string): void { const mergedMessage = `[naive/${location}]: ${message}` if (warnedMessages.has(mergedMessage)) return warnedMessages.add(mergedMessage) console.error(mergedMessage) } export function warn(location: string, message: string): void { console.error(`[naive/${location}]: ${message}`) } export function error(location: string, message: string, error: unknown): void { console.error(`[naive/${location}]: ${message}`, error) } export function throwError(location: string, message: string): never { throw new Error(`[naive/${location}]: ${message}`) } ================================================ FILE: src/_utils/tests/index.spec.ts ================================================ import type { VNode } from 'vue' import { h, isVNode } from 'vue' import { createHoverColor, createPressedColor } from '../color' import { call, createDataKey, formatLength, getTitleAttribute, keep, keysOf, largerSize, omit, render, smallerSize } from '../index' describe('color', () => { it('should work with createHoverColor', () => { expect(createHoverColor('#666666')).toBe('rgba(126, 126, 126, 1)') expect(createHoverColor('rgb(42, 148, 125)')).toBe('rgba(76, 165, 146, 1)') }) it('should work with createPressedColor', () => { expect(createPressedColor('#666666')).toBe('rgba(90, 90, 90, 1)') expect(createPressedColor('rgb(42, 148, 125)')).toBe( 'rgba(37, 130, 110, 1)' ) }) }) describe('css', () => { it('should work with formatLength', () => { expect(formatLength(7)).toBe('7px') expect(formatLength(2, { offset: 3 })).toBe('5px') expect(formatLength(3, { offset: 4, c: 2 })).toBe('14px') expect(formatLength('3')).toBe('3px') expect(formatLength('3', { attachPx: false })).toBe('3') expect(formatLength('2', { offset: 3 })).toBe('5px') expect(formatLength('3', { offset: 4, c: 2 })).toBe('14px') expect(formatLength('4px')).toBe('4px') expect(formatLength('2px', { offset: 3 })).toBe('5px') expect(formatLength('3px', { offset: 4, c: 2 })).toBe('14px') }) }) describe('naive', () => { it('should work with getTitleAttribute', () => { expect(getTitleAttribute(7)).toBe('7') expect(getTitleAttribute('test')).toBe('test') expect(getTitleAttribute([])).toBe(undefined) expect(getTitleAttribute({})).toBe(undefined) expect(getTitleAttribute(() => '')).toBe(undefined) }) it('should work with largerSize', () => { expect(largerSize('tiny')).toBe('small') expect(largerSize('small')).toBe('medium') expect(largerSize('medium')).toBe('large') expect(largerSize('large')).toBe('huge') }) it('should work with smallerSize', () => { expect(smallerSize('huge')).toBe('large') expect(smallerSize('large')).toBe('medium') expect(smallerSize('medium')).toBe('small') expect(smallerSize('small')).toBe('tiny') }) }) describe('vue', () => { it('should work with call', () => { let testValue = 0 let testValue2 = 0 function testFunction1(): void { testValue = testValue + 1 } function testFunction2(v: number): void { testValue = testValue + v + 2 } function testFunction3(v: number): void { testValue2 = testValue2 + v + 3 } call(testFunction1) expect(testValue).toBe(1) testValue = 0 call(testFunction2, 1) expect(testValue).toBe(3) testValue = 0 call([testFunction2, testFunction3], 1) expect(testValue).toBe(3) expect(testValue2).toBe(4) }) it('should work with createDataKey', () => { expect(createDataKey('1')).toBe('s-1') expect(createDataKey(1)).toBe('n-1') }) it('should work with keep', () => { const test = { c: 3 } const test2 = { a: 1, b: 2, d: test } expect(JSON.stringify(keep(test, ['c']))).toBe(JSON.stringify({ c: 3 })) expect(JSON.stringify(keep(test2, ['a', 'b', 'd']))).toBe( JSON.stringify({ a: 1, b: 2, d: { c: 3 } }) ) expect(JSON.stringify(keep(test2, ['a', 'b', 'd'], { a: 4, e: 5 }))).toBe( JSON.stringify({ a: 4, b: 2, d: { c: 3 }, e: 5 }) ) }) it('should work with keysOf', () => { const test = { c: 3 } const test2 = { a: 1, b: 2, d: test } expect(keysOf(test).toString()).toBe(['c'].toString()) expect(keysOf(test2).toString()).toBe(['a', 'b', 'd'].toString()) }) it('should work with omit', () => { const test = { c: 3 } const test2 = { a: 1, b: 2, d: test } expect(JSON.stringify(omit(test2, ['a']))).toBe( JSON.stringify({ b: 2, d: { c: 3 } }) ) expect(JSON.stringify(omit(test2, ['a', 'd']))).toBe( JSON.stringify({ b: 2 }) ) expect(JSON.stringify(omit(test2, ['b'], { a: 4, b: 5 }))).toBe( JSON.stringify({ a: 4, d: { c: 3 }, b: 5 }) ) }) it('should work with render', () => { function testFunction(value: string): VNode { return h(value, null, { default: () => 'test' }) } expect(isVNode(render('test'))).toBe(true) expect(isVNode(render(123))).toBe(true) expect(isVNode(render(testFunction, 'div'))).toBe(true) expect(isVNode(render({ a: 1 }))).toBe(false) expect(render({ a: 1 })).toBe(null) expect(isVNode(render(['1']))).toBe(false) expect(render(['1'])).toBe(null) }) }) ================================================ FILE: src/_utils/ts/ts.ts ================================================ export type ThemeRelatedProps = | 'theme' | 'themeOverrides' | 'builtinThemeOverrides' export type Equal = (() => T extends X ? 1 : 2) extends () => T extends Y ? 1 : 2 ? true : false export type Expect = T ================================================ FILE: src/_utils/vue/call.ts ================================================ type AnyFunction = (...args: any[]) => any function call(funcs: MaybeArray<() => void>): void function call(funcs: MaybeArray<(a1: A1) => void>, a1: A1): void function call( funcs: MaybeArray<(a1: A1, a2: A2) => void>, a1: A1, a2: A2 ): void function call( funcs: MaybeArray<(a1: A1, a2: A2, a3: A3) => void>, a1: A1, a2: A2, a3: A3 ): void function call( funcs: MaybeArray<(a1: A1, a2: A2, a3: A3, a4: A4) => void>, a1: A1, a2: A2, a3: A3, a4: A4 ): void function call( funcs: AnyFunction[] | AnyFunction, ...args: A ): void { if (Array.isArray(funcs)) { funcs.forEach(func => (call as any)(func, ...args)) } else { return funcs(...args) } } export { call } export type MaybeArray = T | T[] ================================================ FILE: src/_utils/vue/create-data-key.ts ================================================ export function createDataKey(key: string | number): string { return typeof key === 'string' ? `s-${key}` : `n-${key}` } ================================================ FILE: src/_utils/vue/create-injection-key.ts ================================================ import type { InjectionKey } from 'vue' export function createInjectionKey(key: string): InjectionKey { return key as any } ================================================ FILE: src/_utils/vue/create-ref-setter.ts ================================================ import type { Ref } from 'vue' export function createRefSetter(ref: Ref): any { return (inst: { $el: HTMLElement | null } | null) => { if (inst) { ref.value = inst.$el } else { ref.value = null } } } ================================================ FILE: src/_utils/vue/flatten.ts ================================================ import type { VNode, VNodeChild } from 'vue' import { Comment, createTextVNode, Fragment } from 'vue' // o(n) flatten export function flatten( vNodes: VNodeChild[], filterCommentNode: boolean = true, result: VNode[] = [] ): VNode[] { vNodes.forEach((vNode) => { if (vNode === null) return if (typeof vNode !== 'object') { if (typeof vNode === 'string' || typeof vNode === 'number') { result.push(createTextVNode(String(vNode))) } return } if (Array.isArray(vNode)) { flatten(vNode, filterCommentNode, result) return } if (vNode.type === Fragment) { if (vNode.children === null) return if (Array.isArray(vNode.children)) { flatten(vNode.children, filterCommentNode, result) } // rawSlot } else { if (vNode.type === Comment && filterCommentNode) return result.push(vNode) } }) return result } ================================================ FILE: src/_utils/vue/get-first-slot-vnode.ts ================================================ import type { Slots, VNode } from 'vue' import { warn } from '../naive' import { flatten } from './flatten' export function getFirstSlotVNode( slots: Slots, slotName = 'default', props: unknown = undefined ): VNode | null { const slot = slots[slotName] if (!slot) { warn('getFirstSlotVNode', `slot[${slotName}] is empty`) return null } const slotContent = flatten(slot(props)) // vue will normalize the slot, so slot must be an array if (slotContent.length === 1) { return slotContent[0] } else { warn('getFirstSlotVNode', `slot[${slotName}] should have exactly one child`) return null } } export function getFirstSlotVNodeWithTypedProps( slotName: string, slot: ((props: T) => VNode[]) | undefined, props: T ): VNode | null { if (!slot) { return null } const slotContent = flatten(slot(props)) // vue will normalize the slot, so slot must be an array if (slotContent.length === 1) { return slotContent[0] } else { warn('getFirstSlotVNode', `slot[${slotName}] should have exactly one child`) return null } } ================================================ FILE: src/_utils/vue/get-slot.ts ================================================ import type { ComponentPublicInstance, VNodeChild } from 'vue' export function getSlot( instance: ComponentPublicInstance, slotName = 'default', fallback: VNodeChild[] = [] ): VNodeChild[] { const slots = instance.$slots const slot = slots[slotName] if (slot === undefined) return fallback return slot() } ================================================ FILE: src/_utils/vue/get-v-node-children.ts ================================================ import type { VNode } from 'vue' export function getVNodeChildren( vNode: VNode, slotName = 'default', fallback: VNode[] = [] ): VNode[] { const { children } = vNode if ( children !== null && typeof children === 'object' && !Array.isArray(children) ) { const slot = children[slotName] if (typeof slot === 'function') { return slot() } } return fallback } ================================================ FILE: src/_utils/vue/index.ts ================================================ export { call } from './call' export type { MaybeArray } from './call' export { createDataKey } from './create-data-key' export { createInjectionKey } from './create-injection-key' export { createRefSetter } from './create-ref-setter' export { flatten } from './flatten' export { getFirstSlotVNode, getFirstSlotVNodeWithTypedProps } from './get-first-slot-vnode' export { getSlot } from './get-slot' export { getVNodeChildren } from './get-v-node-children' export { isNodeVShowFalse } from './is-node-v-show-false' export { keep } from './keep' export { keysOf } from './keysOf' export { mergeEventHandlers } from './merge-handlers' export { omit } from './omit' export { render } from './render' export { isSlotEmpty, resolveSlot, resolveSlotWithTypedProps, resolveWrappedSlot, resolveWrappedSlotWithProps } from './resolve-slot' export { Wrapper } from './wrapper' ================================================ FILE: src/_utils/vue/is-node-v-show-false.ts ================================================ import type { VNode } from 'vue' import { vShow } from 'vue' export function isNodeVShowFalse(vNode: VNode): boolean { const showDir = vNode.dirs?.find(({ dir }) => dir === vShow) return !!(showDir && showDir.value === false) } ================================================ FILE: src/_utils/vue/keep.ts ================================================ export function keep( object: T, keys: K[] = [], rest?: R ): Pick & R { const keepedObject: any = {} keys.forEach((key) => { keepedObject[key] = (object as any)[key] }) return Object.assign(keepedObject, rest) } ================================================ FILE: src/_utils/vue/keysOf.ts ================================================ export function keysOf>( obj: T ): Array { return Object.keys(obj) as any } ================================================ FILE: src/_utils/vue/merge-handlers.ts ================================================ export function mergeEventHandlers( handlers: Array void)> ): undefined | ((e: T) => void) { const filteredHandlers = handlers.filter(handler => handler !== undefined) if (filteredHandlers.length === 0) return undefined if (filteredHandlers.length === 1) return filteredHandlers[0] return (e) => { handlers.forEach((handler) => { if (handler) { handler(e) } }) } } ================================================ FILE: src/_utils/vue/omit.ts ================================================ export function omit>( object: T, keys: K[] = [], rest?: R ): Omit & (R extends undefined ? Record : R) { const omitedObject: any = {} const originalKeys = Object.getOwnPropertyNames(object) originalKeys.forEach((originalKey) => { if (!(keys as string[]).includes(originalKey)) { omitedObject[originalKey] = object[originalKey as keyof T] } }) return Object.assign(omitedObject, rest) } ================================================ FILE: src/_utils/vue/render.ts ================================================ import type { VNodeChild } from 'vue' import { createTextVNode } from 'vue' export function render( r: | string | number | undefined | null | ((...args: [...T]) => VNodeChild) | unknown, ...args: [...T] ): VNodeChild { if (typeof r === 'function') { return r(...args) } else if (typeof r === 'string') { return createTextVNode(r) } else if (typeof r === 'number') { return createTextVNode(String(r)) } else { return null } } ================================================ FILE: src/_utils/vue/resolve-slot.ts ================================================ import type { Slot, VNodeArrayChildren, VNodeChild } from 'vue' import { Comment, Fragment, isVNode } from 'vue' export function ensureValidVNode( vnodes: VNodeArrayChildren ): VNodeArrayChildren | null { return vnodes.some((child) => { if (!isVNode(child)) { return true } if (child.type === Comment) { return false } if ( child.type === Fragment && !ensureValidVNode(child.children as VNodeArrayChildren) ) { return false } return true }) ? vnodes : null } /** * We shouldn't use the following functions with slot flags `_: 1, 2, 3` */ export function resolveSlot( slot: Slot | undefined, fallback: () => VNodeArrayChildren ): VNodeArrayChildren { return (slot && ensureValidVNode(slot())) || fallback() } export function resolveSlotWithTypedProps( slot: Slot | undefined, props: T, fallback: (props: T) => VNodeArrayChildren ): VNodeArrayChildren { return (slot && ensureValidVNode(slot(props))) || fallback(props) } /** * Resolve slot with wrapper if content exists, no fallback */ export function resolveWrappedSlot( slot: Slot | undefined, wrapper: (children: VNodeArrayChildren | null) => VNodeChild ): VNodeChild { const children = slot && ensureValidVNode(slot()) return wrapper(children || null) } /* * Resolve slot with wrapper if content exists, no fallback */ export function resolveWrappedSlotWithProps( slot: Slot | undefined, props: any, wrapper: (children: VNodeArrayChildren | null) => VNodeChild ): VNodeChild { const children = slot && ensureValidVNode(slot(props)) return wrapper(children || null) } export function isSlotEmpty(slot: Slot | undefined): boolean { return !(slot && ensureValidVNode(slot())) } ================================================ FILE: src/_utils/vue/wrapper.tsx ================================================ import { defineComponent } from 'vue' export const Wrapper = defineComponent({ render() { return this.$slots.default?.() } }) ================================================ FILE: src/affix/demos/enUS/basic.demo.vue ================================================ # Basic Affix has `trigger-top`, `top`, `trigger-bottom` and `bottom`. `trigger-top` is top affixing trigger point. `top` is the style `top` value after top affixing is trigger. `trigger-bottom` and `bottom` work in similar way. ================================================ FILE: src/affix/demos/enUS/index.demo-entry.md ================================================ # Affix Affix can make content stick to fixed places when scrolling. It's similar to `position: sticky` but can do more things. ## Demos ```demo basic.vue position.vue ``` ## API ### Affix Props | Name | Type | Default | Description | | --- | --- | --- | --- | | bottom | `number` | `undefined` | The css bottom property after trigger bottom affix. (if not set, use `trigger-bottom` prop) | | listen-to | `string \| HTMLElement \| Document \| Window \| (() => HTMLElement)` | `document` | The scrolling element to listen scrolling. | | trigger-bottom | `number` | `undefined` | The distance px to bottom of target to trigger bottom affix. (if not set, use `bottom` prop) | | trigger-top | `number` | `undefined` | The distance px to top of target to trigger top affix. (if not set, use `top` prop) | | position | `'fixed' \| 'absolute'` | `'fixed'` | CSS position of the affix. | | top | `number` | `undefined` | The css top property after trigger top affix. (if not set, use `trigger-top` prop) | ================================================ FILE: src/affix/demos/enUS/position.demo.vue ================================================ # Position Affix can be `absolute` or `fixed` positioned. You may need some css tricks to make it works as following. By default position is set to `fixed`, because in most cases scrolled element is `document`. ================================================ FILE: src/affix/demos/zhCN/basic.demo.vue ================================================ # 基础用法 Affix 有 `trigger-top`、`top`、`trigger-bottom` 和 `bottom` 属性。`trigger-top` 是顶部固定的触发距离,`top` 是在触发顶部固定之后 CSS 的 `top` 值。`trigger-bottom` 和 `bottom` 类似。 ================================================ FILE: src/affix/demos/zhCN/index.demo-entry.md ================================================ # 固钉 Affix Affix 可以让内容在页面滚动的时候固定在一个位置,它和 `position: sticky` 有那么点像不过可以做更多事。 ## 演示 ```demo basic.vue position.vue ``` ## API ### Affix Props | 名称 | 类型 | 默认值 | 描述 | | --- | --- | --- | --- | | bottom | `number` | `undefined` | 在触发底部固定后 Affix 的 CSS bottom 属性(如果没设定,会使用 `trigger-bottom` 代替) | | listen-to | `string \| HTMLElement \| Document \| Window \| (() => HTMLElement)` | `document` | 需要监听滚动的元素 | | trigger-bottom | `number` | `undefined` | 触发底部固定时,Affix 和目标元素元素的底部距离(如果没设定,会使用 `bottom` 代替) | | trigger-top | `number` | `undefined` | 触发顶部固定时,Affix 和目标元素元素的顶部距离(如果没设定,会使用 `top` 代替) | | position | `'fixed' \| 'absolute'` | `'fixed'` | Affix 的 CSS position | | top | `number` | `undefined` | 在触发顶部固定后 Affix 的 CSS top 属性(如果没设定,会使用 `trigger-top` 代替) | ================================================ FILE: src/affix/demos/zhCN/position.demo.vue ================================================ # 位置 Affix 可以 `absolute` 或者 `fixed` 定位。你可能还需要写一些额外的 CSS 才能让达到例子的效果。 默认情况下位置是 `fixed`,因为大多数情况下,滚动的元素是 `document`。 ================================================ FILE: src/affix/index.ts ================================================ export { affixProps, default as NAffix } from './src/Affix' export type { AffixProps } from './src/Affix' ================================================ FILE: src/affix/src/Affix.tsx ================================================ import type { CSSProperties, PropType } from 'vue' import type { ExtractPublicPropTypes } from '../../_utils' import type { ScrollTarget } from './utils' import { beforeNextFrameOnce, unwrapElement } from 'seemly' import { computed, defineComponent, h, onBeforeUnmount, onMounted, ref } from 'vue' import { useConfig, useStyle } from '../../_mixins' import { keysOf, warn } from '../../_utils' import style from './styles/index.cssr' import { getRect, getScrollTop } from './utils' export const affixProps = { listenTo: [String, Object, Function] as PropType< string | ScrollTarget | (() => HTMLElement) | undefined >, top: Number, bottom: Number, triggerTop: Number, triggerBottom: Number, position: { type: String as PropType<'fixed' | 'absolute'>, default: 'fixed' }, // deprecated offsetTop: { type: Number as PropType, validator: () => { if (__DEV__) { warn( 'affix', '`offset-top` is deprecated, please use `trigger-top` instead.' ) } return true }, default: undefined }, offsetBottom: { type: Number as PropType, validator: () => { if (__DEV__) { warn( 'affix', '`offset-bottom` is deprecated, please use `trigger-bottom` instead.' ) } return true }, default: undefined }, target: { type: Function as unknown as PropType<(() => HTMLElement) | undefined>, validator: () => { if (__DEV__) { warn('affix', '`target` is deprecated, please use `listen-to` instead.') } return true }, default: undefined } } as const export const affixPropKeys = keysOf(affixProps) export type AffixProps = ExtractPublicPropTypes export default defineComponent({ name: 'Affix', props: affixProps, setup(props) { const { mergedClsPrefixRef } = useConfig(props) useStyle('-affix', style, mergedClsPrefixRef) let scrollTarget: ScrollTarget | null = null const stickToTopRef = ref(false) const stickToBottomRef = ref(false) const bottomAffixedTriggerScrollTopRef = ref(null) const topAffixedTriggerScrollTopRef = ref(null) const affixedRef = computed(() => { return stickToBottomRef.value || stickToTopRef.value }) const mergedOffsetTopRef = computed(() => { return props.triggerTop ?? props.offsetTop ?? props.top }) const mergedTopRef = computed(() => { return props.top ?? props.triggerTop ?? props.offsetTop }) const mergedBottomRef = computed(() => { return props.bottom ?? props.triggerBottom ?? props.offsetBottom }) const mergedOffsetBottomRef = computed(() => { return props.triggerBottom ?? props.offsetBottom ?? props.bottom }) const selfRef = ref(null) const init = (): void => { const { target: getScrollTarget, listenTo } = props if (getScrollTarget) { // deprecated scrollTarget = getScrollTarget() } else if (listenTo) { scrollTarget = unwrapElement(listenTo) } else { scrollTarget = document } if (scrollTarget) { scrollTarget.addEventListener('scroll', handleScroll) handleScroll() } else if (__DEV__) { warn('affix', 'Target to be listened to is not valid.') } } function handleScroll(): void { beforeNextFrameOnce(_handleScroll) } function _handleScroll(): void { const { value: selfEl } = selfRef if (!scrollTarget || !selfEl) return const scrollTop = getScrollTop(scrollTarget) if (affixedRef.value) { if ( topAffixedTriggerScrollTopRef.value !== null && scrollTop < topAffixedTriggerScrollTopRef.value ) { stickToTopRef.value = false topAffixedTriggerScrollTopRef.value = null } if ( bottomAffixedTriggerScrollTopRef.value !== null && scrollTop > bottomAffixedTriggerScrollTopRef.value ) { stickToBottomRef.value = false bottomAffixedTriggerScrollTopRef.value = null } return } const containerRect = getRect(scrollTarget) const affixRect = selfEl.getBoundingClientRect() const pxToTop = affixRect.top - containerRect.top const pxToBottom = containerRect.bottom - affixRect.bottom const mergedOffsetTop = mergedOffsetTopRef.value const mergedOffsetBottom = mergedOffsetBottomRef.value if (mergedOffsetTop !== undefined && pxToTop <= mergedOffsetTop) { stickToTopRef.value = true topAffixedTriggerScrollTopRef.value = scrollTop - (mergedOffsetTop - pxToTop) } else { stickToTopRef.value = false topAffixedTriggerScrollTopRef.value = null } if ( mergedOffsetBottom !== undefined && pxToBottom <= mergedOffsetBottom ) { stickToBottomRef.value = true bottomAffixedTriggerScrollTopRef.value = scrollTop + mergedOffsetBottom - pxToBottom } else { stickToBottomRef.value = false bottomAffixedTriggerScrollTopRef.value = null } } onMounted(() => { init() }) onBeforeUnmount(() => { if (!scrollTarget) return scrollTarget.removeEventListener('scroll', handleScroll) }) return { selfRef, affixed: affixedRef, mergedClsPrefix: mergedClsPrefixRef, mergedstyle: computed(() => { const style: CSSProperties = {} if ( stickToTopRef.value && mergedOffsetTopRef.value !== undefined && mergedTopRef.value !== undefined ) { style.top = `${mergedTopRef.value}px` } if ( stickToBottomRef.value && mergedOffsetBottomRef.value !== undefined && mergedBottomRef.value !== undefined ) { style.bottom = `${mergedBottomRef.value}px` } return style }) } }, render() { const { mergedClsPrefix } = this return (

) } }) ================================================ FILE: src/affix/src/styles/index.cssr.ts ================================================ import { cB, cM } from '../../../_utils/cssr' export default cB('affix', [ cM('affixed', { position: 'fixed' }, [ cM('absolute-positioned', { position: 'absolute' }) ]) ]) ================================================ FILE: src/affix/src/utils.ts ================================================ export type ScrollTarget = Window | Document | HTMLElement export function getScrollTop(target: ScrollTarget): number { return target instanceof HTMLElement ? target.scrollTop : window.scrollY } export function getRect(target: ScrollTarget): { top: number, bottom: number } { return target instanceof HTMLElement ? target.getBoundingClientRect() : { top: 0, bottom: window.innerHeight } } ================================================ FILE: src/affix/tests/Affix.spec.ts ================================================ import { mount } from '@vue/test-utils' import { sleep } from 'seemly' import { h } from 'vue' import { NAffix } from '../index' async function makeScroll( dom: Element, name: 'scrollTop', offset: number ): Promise { const eventTarget = dom === document.documentElement ? window : dom dom[name] = offset eventTarget.dispatchEvent( new CustomEvent('scroll', { detail: { target: { [name]: offset } } }) ) await sleep(100) } describe('n-affix', () => { it('should work with import on demand', () => { mount(NAffix) }) it('should work with `top` prop', async () => { const wrapper = mount(NAffix, { attachTo: document.body, props: { top: 120 }, slots: { default: () => { return h('div', {}, 'content') } } }) expect(wrapper.find('.n-affix--fixed').exists()).toBe(false) await makeScroll(document.documentElement, 'scrollTop', 200) expect(wrapper.attributes('style')).toContain('top: 120px;') wrapper.unmount() }) it('should work with `position` prop', async () => { const wrapper = mount(NAffix, { props: { position: 'absolute' }, slots: { default: () => { return h('div', {}, 'content') } } }) expect(wrapper.find('.n-affix--absolute-positioned').exists()).not.toBe( null ) wrapper.unmount() }) }) ================================================ FILE: src/affix/tests/server.spec.tsx ================================================ import { setup } from '@css-render/vue3-ssr' import { renderToString } from '@vue/server-renderer' /** * @vitest-environment node */ import { createSSRApp, h } from 'vue' import { NAffix } from '../..' describe('server side rendering', () => { it('works', async () => { const app = createSSRApp(() => ) setup(app) try { await renderToString(app) } catch (e) { expect(e).not.toBeTruthy() } }) }) ================================================ FILE: src/alert/demos/enUS/basic.demo.vue ================================================ # Basic ================================================ FILE: src/alert/demos/enUS/bordered.demo.vue ================================================ # Bordered ================================================ FILE: src/alert/demos/enUS/closable.demo.vue ================================================ # Closable ================================================ FILE: src/alert/demos/enUS/icon.demo.vue ================================================ # Icon ================================================ FILE: src/alert/demos/enUS/index.demo-entry.md ================================================ # Alert According to my experience, the most frequent usage of it may be requesting for disabling AdBlocks. ## Demos ```demo basic.vue bordered.vue closable.vue icon.vue no-icon.vue marquee.vue ``` ## API ### Alert Props | Name | Type | Default | Description | Version | | --- | --- | --- | --- | --- | | bordered | `boolean` | `true` | Whether the alert can show border. | 2.32.2 | | closable | `boolean` | `false` | Whether the alert can be closed. | | | show-icon | `boolean` | `true` | Whether to show the icon of alert. | | | title | `string` | `undefined` | Title of the alert. | | | type | `'default' \| 'info' \| 'success' \| 'warning' \| 'error'` | `'default'` | Alert type. | | | on-after-leave | `Function` | `undefined` | Callback function executed when the alert disappears. | | | on-close | `() => boolean \| Promise \| any` | `() => true` | The callback function executed when the close icon is clicked. | | ### Alert Slots | Name | Parameters | Description | | ------- | ---------- | --------------------------------------- | | default | `()` | The content of the alert. | | header | `()` | The content placed in the alert header. | | icon | `()` | Icon displayed in the alert. | ================================================ FILE: src/alert/demos/enUS/marquee.demo.vue ================================================ # Marquee You can use `n-marquee` to achieve marquee effect. ================================================ FILE: src/alert/demos/enUS/no-icon.demo.vue ================================================ # No icon ================================================ FILE: src/alert/demos/zhCN/basic.demo.vue ================================================ # 基础用法 ================================================ FILE: src/alert/demos/zhCN/bordered.demo.vue ================================================ # 无边框 ================================================ FILE: src/alert/demos/zhCN/closable.demo.vue ================================================ # 可以关掉 ================================================ FILE: src/alert/demos/zhCN/empty-debug.demo.vue ================================================ # Empty debug ================================================ FILE: src/alert/demos/zhCN/icon.demo.vue ================================================ # 图标 ================================================ FILE: src/alert/demos/zhCN/index.demo-entry.md ================================================ # 警示信息 Alert 根据我的经验,这东西使用最频繁的场景是让你关掉 AdBlocks。 ## 演示 ```demo basic.vue bordered.vue closable.vue icon.vue no-icon.vue marquee.vue rtl-debug.vue empty-debug.vue ``` ## API ### Alert Props | 名称 | 类型 | 默认值 | 说明 | 版本 | | --- | --- | --- | --- | --- | | bordered | `boolean` | `true` | alert 是否显示边框 | 2.32.2 | | closable | `boolean` | `false` | alert 信息是否可以关掉 | | | show-icon | `boolean` | `true` | alert 是否展示 icon | | | title | `string` | `undefined` | alert 的 title 信息 | | | type | `'default' \| 'info' \| 'success' \| 'warning' \| 'error'` | `'default'` | alert 的类型 | | | on-after-leave | `Function` | `undefined` | alert 消失时执行的回调函数 | | | on-close | `() => boolean \| Promise \| any` | `() => true` | 点击 close icon 时执行的回调函数 | | ### Alert Slots | 名称 | 参数 | 说明 | | ------- | ---- | ------------------------------ | | default | `()` | alert 的内容 | | header | `()` | alert 的 header 部分填充的内容 | | icon | `()` | alert 的 icon 部分填充的内容 | ================================================ FILE: src/alert/demos/zhCN/marquee.demo.vue ================================================ # 跑马灯 你可以配合 `n-marquee` 实现轮播的效果。 ================================================ FILE: src/alert/demos/zhCN/no-icon.demo.vue ================================================ # 没有图标 ================================================ FILE: src/alert/demos/zhCN/rtl-debug.demo.vue ================================================ # Rtl Debug ================================================ FILE: src/alert/index.ts ================================================ export { alertProps, default as NAlert } from './src/Alert' export type { AlertProps, AlertSlots } from './src/Alert' ================================================ FILE: src/alert/src/Alert.tsx ================================================ import type { HTMLAttributes, PropType, SlotsType, VNode } from 'vue' import type { ThemeProps } from '../../_mixins' import type { ExtractPublicPropTypes } from '../../_utils' import type { AlertTheme } from '../styles' import { getMargin } from 'seemly' import { computed, defineComponent, h, mergeProps, ref, watchEffect } from 'vue' import { NBaseClose, NBaseIcon, NFadeInExpandTransition } from '../../_internal' import { ErrorIcon, InfoIcon, SuccessIcon, WarningIcon } from '../../_internal/icons' import { useConfig, useTheme, useThemeClass } from '../../_mixins' import { useRtl } from '../../_mixins/use-rtl' import { createKey, resolveSlot, resolveWrappedSlot, warnOnce } from '../../_utils' import { alertLight } from '../styles' import style from './styles/index.cssr' export const alertProps = { ...(useTheme.props as ThemeProps), title: String, showIcon: { type: Boolean, default: true }, type: { type: String as PropType< 'info' | 'warning' | 'error' | 'success' | 'default' >, default: 'default' }, bordered: { type: Boolean, default: true }, closable: Boolean, onClose: Function, onAfterLeave: Function, /** @deprecated */ onAfterHide: Function } export type AlertProps = ExtractPublicPropTypes export interface AlertSlots { default?: () => VNode[] icon?: () => VNode[] header?: () => VNode[] } export default defineComponent({ name: 'Alert', inheritAttrs: false, props: alertProps, slots: Object as SlotsType, setup(props) { if (__DEV__) { watchEffect(() => { if (props.onAfterHide !== undefined) { warnOnce( 'alert', '`on-after-hide` is deprecated, please use `on-after-leave` instead.' ) } }) } const { mergedClsPrefixRef, mergedBorderedRef, inlineThemeDisabled, mergedRtlRef } = useConfig(props) const themeRef = useTheme( 'Alert', '-alert', style, alertLight, props, mergedClsPrefixRef ) const rtlEnabledRef = useRtl('Alert', mergedRtlRef, mergedClsPrefixRef) const cssVarsRef = computed(() => { const { common: { cubicBezierEaseInOut }, self } = themeRef.value const { fontSize, borderRadius, titleFontWeight, lineHeight, iconSize, iconMargin, iconMarginRtl, closeIconSize, closeBorderRadius, closeSize, closeMargin, closeMarginRtl, padding } = self const { type } = props const { left, right } = getMargin(iconMargin) return { '--n-bezier': cubicBezierEaseInOut, '--n-color': self[createKey('color', type)], '--n-close-icon-size': closeIconSize, '--n-close-border-radius': closeBorderRadius, '--n-close-color-hover': self[createKey('closeColorHover', type)], '--n-close-color-pressed': self[createKey('closeColorPressed', type)], '--n-close-icon-color': self[createKey('closeIconColor', type)], '--n-close-icon-color-hover': self[createKey('closeIconColorHover', type)], '--n-close-icon-color-pressed': self[createKey('closeIconColorPressed', type)], '--n-icon-color': self[createKey('iconColor', type)], '--n-border': self[createKey('border', type)], '--n-title-text-color': self[createKey('titleTextColor', type)], '--n-content-text-color': self[createKey('contentTextColor', type)], '--n-line-height': lineHeight, '--n-border-radius': borderRadius, '--n-font-size': fontSize, '--n-title-font-weight': titleFontWeight, '--n-icon-size': iconSize, '--n-icon-margin': iconMargin, '--n-icon-margin-rtl': iconMarginRtl, '--n-close-size': closeSize, '--n-close-margin': closeMargin, '--n-close-margin-rtl': closeMarginRtl, '--n-padding': padding, '--n-icon-margin-left': left, '--n-icon-margin-right': right } }) const themeClassHandle = inlineThemeDisabled ? useThemeClass( 'alert', computed(() => { return props.type[0] }), cssVarsRef, props ) : undefined const visibleRef = ref(true) const doAfterLeave = (): void => { const { onAfterLeave, onAfterHide // deprecated } = props if (onAfterLeave) onAfterLeave() if (onAfterHide) onAfterHide() } const handleCloseClick = (): void => { void Promise.resolve(props.onClose?.()).then((result) => { if (result === false) return visibleRef.value = false }) } const handleAfterLeave = (): void => { doAfterLeave() } return { rtlEnabled: rtlEnabledRef, mergedClsPrefix: mergedClsPrefixRef, mergedBordered: mergedBorderedRef, visible: visibleRef, handleCloseClick, handleAfterLeave, mergedTheme: themeRef, cssVars: inlineThemeDisabled ? undefined : cssVarsRef, themeClass: themeClassHandle?.themeClass, onRender: themeClassHandle?.onRender } }, render() { this.onRender?.() return ( {{ default: () => { const { mergedClsPrefix, $slots } = this const attrs: HTMLAttributes = { class: [ `${mergedClsPrefix}-alert`, this.themeClass, this.closable && `${mergedClsPrefix}-alert--closable`, this.showIcon && `${mergedClsPrefix}-alert--show-icon`, // fix: https://github.com/tusen-ai/naive-ui/issues/4588 !this.title && this.closable && `${mergedClsPrefix}-alert--right-adjust`, this.rtlEnabled && `${mergedClsPrefix}-alert--rtl` ], style: this.cssVars as any, role: 'alert' } return this.visible ? (
{this.closable && ( )} {this.bordered && (
)} {this.showIcon && ( )}
{resolveWrappedSlot($slots.header, (children) => { const mergedChildren = children || this.title return mergedChildren ? (
{mergedChildren}
) : null })} {$slots.default && (
{$slots}
)}
) : null } }} ) } }) ================================================ FILE: src/alert/src/styles/index.cssr.ts ================================================ import { fadeInHeightExpandTransition } from '../../../_styles/transitions/fade-in-height-expand.cssr' import { c, cB, cE, cM } from '../../../_utils/cssr' // vars: // --n-bezier // --n-color // --n-close-color-hover // --n-close-color-pressed // --n-close-icon-color // --n-close-icon-color-hover // --n-close-icon-color-pressed // --n-icon-color // --n-border // --n-title-text-color // --n-content-text-color // --n-line-height // --n-border-radius // --n-font-size // --n-title-font-weight // --n-icon-size // --n-icon-margin // --n-close-size // --n-close-icon-size // --n-close-margin // --n-padding // --n-icon-margin-left // --n-icon-margin-right export default cB('alert', ` line-height: var(--n-line-height); border-radius: var(--n-border-radius); position: relative; transition: background-color .3s var(--n-bezier); background-color: var(--n-color); text-align: start; word-break: break-word; `, [ cE('border', ` border-radius: inherit; position: absolute; left: 0; right: 0; top: 0; bottom: 0; transition: border-color .3s var(--n-bezier); border: var(--n-border); pointer-events: none; `), cM('closable', [ cB('alert-body', [ cE('title', ` padding-right: 24px; `) ]) ]), cE('icon', { color: 'var(--n-icon-color)' }), cB('alert-body', { padding: 'var(--n-padding)' }, [ cE('title', { color: 'var(--n-title-text-color)' }), cE('content', { color: 'var(--n-content-text-color)' }) ]), fadeInHeightExpandTransition({ originalTransition: 'transform .3s var(--n-bezier)', enterToProps: { transform: 'scale(1)' }, leaveToProps: { transform: 'scale(0.9)' } }), cE('icon', ` position: absolute; left: 0; top: 0; align-items: center; justify-content: center; display: flex; width: var(--n-icon-size); height: var(--n-icon-size); font-size: var(--n-icon-size); margin: var(--n-icon-margin); `), cE('close', ` transition: color .3s var(--n-bezier), background-color .3s var(--n-bezier); position: absolute; right: 0; top: 0; margin: var(--n-close-margin); `), cM('show-icon', [ cB('alert-body', { paddingLeft: 'calc(var(--n-icon-margin-left) + var(--n-icon-size) + var(--n-icon-margin-right))' }) ]), // fix: https://github.com/tusen-ai/naive-ui/issues/4588 cM('right-adjust', [ cB('alert-body', { paddingRight: 'calc(var(--n-close-size) + var(--n-padding) + 2px)' }) ]), cB('alert-body', ` border-radius: var(--n-border-radius); transition: border-color .3s var(--n-bezier); `, [ cE('title', ` transition: color .3s var(--n-bezier); font-size: 16px; line-height: 19px; font-weight: var(--n-title-font-weight); `, [ c('& +', [ cE('content', { marginTop: '9px' }) ]) ]), cE('content', { transition: 'color .3s var(--n-bezier)', fontSize: 'var(--n-font-size)' }) ]), cE('icon', { transition: 'color .3s var(--n-bezier)' }) ]) ================================================ FILE: src/alert/src/styles/rtl.cssr.ts ================================================ import { cB, cE, cM } from '../../../_utils/cssr' export default cB('alert', [ cM('rtl', ` direction: rtl; `, [ cE('icon', ` left: unset; right: 0; margin: var(--n-icon-margin-rtl); `), cM('show-icon', [ cB('alert-body', ` padding-left: var(--n-padding); padding-right: calc(var(--n-icon-margin-left) + var(--n-icon-size) + var(--n-icon-margin-right)); `) ]), cE('close', ` position: absolute; right: unset; left: 0; margin: var(--n-close-margin-rtl); `) ]) ]) ================================================ FILE: src/alert/styles/_common.ts ================================================ export default { iconMargin: '11px 8px 0 12px', iconMarginRtl: '11px 12px 0 8px', iconSize: '24px', closeIconSize: '16px', closeSize: '20px', closeMargin: '13px 14px 0 0', closeMarginRtl: '13px 0 0 14px', padding: '13px' } ================================================ FILE: src/alert/styles/dark.ts ================================================ import type { AlertTheme } from './light' import { changeColor } from 'seemly' import { commonDark } from '../../_styles/common' import commonVars from './_common' const alertDark: AlertTheme = { name: 'Alert', common: commonDark, self(vars) { const { lineHeight, borderRadius, fontWeightStrong, dividerColor, inputColor, textColor1, textColor2, closeColorHover, closeColorPressed, closeIconColor, closeIconColorHover, closeIconColorPressed, infoColorSuppl, successColorSuppl, warningColorSuppl, errorColorSuppl, fontSize } = vars return { ...commonVars, fontSize, lineHeight, titleFontWeight: fontWeightStrong, borderRadius, border: `1px solid ${dividerColor}`, color: inputColor, titleTextColor: textColor1, iconColor: textColor2, contentTextColor: textColor2, closeBorderRadius: borderRadius, closeColorHover, closeColorPressed, closeIconColor, closeIconColorHover, closeIconColorPressed, borderInfo: `1px solid ${changeColor(infoColorSuppl, { alpha: 0.35 })}`, colorInfo: changeColor(infoColorSuppl, { alpha: 0.25 }), titleTextColorInfo: textColor1, iconColorInfo: infoColorSuppl, contentTextColorInfo: textColor2, closeColorHoverInfo: closeColorHover, closeColorPressedInfo: closeColorPressed, closeIconColorInfo: closeIconColor, closeIconColorHoverInfo: closeIconColorHover, closeIconColorPressedInfo: closeIconColorPressed, borderSuccess: `1px solid ${changeColor(successColorSuppl, { alpha: 0.35 })}`, colorSuccess: changeColor(successColorSuppl, { alpha: 0.25 }), titleTextColorSuccess: textColor1, iconColorSuccess: successColorSuppl, contentTextColorSuccess: textColor2, closeColorHoverSuccess: closeColorHover, closeColorPressedSuccess: closeColorPressed, closeIconColorSuccess: closeIconColor, closeIconColorHoverSuccess: closeIconColorHover, closeIconColorPressedSuccess: closeIconColorPressed, borderWarning: `1px solid ${changeColor(warningColorSuppl, { alpha: 0.35 })}`, colorWarning: changeColor(warningColorSuppl, { alpha: 0.25 }), titleTextColorWarning: textColor1, iconColorWarning: warningColorSuppl, contentTextColorWarning: textColor2, closeColorHoverWarning: closeColorHover, closeColorPressedWarning: closeColorPressed, closeIconColorWarning: closeIconColor, closeIconColorHoverWarning: closeIconColorHover, closeIconColorPressedWarning: closeIconColorPressed, borderError: `1px solid ${changeColor(errorColorSuppl, { alpha: 0.35 })}`, colorError: changeColor(errorColorSuppl, { alpha: 0.25 }), titleTextColorError: textColor1, iconColorError: errorColorSuppl, contentTextColorError: textColor2, closeColorHoverError: closeColorHover, closeColorPressedError: closeColorPressed, closeIconColorError: closeIconColor, closeIconColorHoverError: closeIconColorHover, closeIconColorPressedError: closeIconColorPressed } } } export default alertDark ================================================ FILE: src/alert/styles/index.ts ================================================ export { default as alertDark } from './dark' export { default as alertLight } from './light' export type { AlertTheme, AlertThemeVars } from './light' export { alertRtl } from './rtl' ================================================ FILE: src/alert/styles/light.ts ================================================ import type { Theme } from '../../_mixins/use-theme' import type { ThemeCommonVars } from '../../_styles/common' import { changeColor, composite } from 'seemly' import { commonLight } from '../../_styles/common' import commonVars from './_common' function self(vars: ThemeCommonVars) { const { lineHeight, borderRadius, fontWeightStrong, baseColor, dividerColor, actionColor, textColor1, textColor2, closeColorHover, closeColorPressed, closeIconColor, closeIconColorHover, closeIconColorPressed, infoColor, successColor, warningColor, errorColor, fontSize } = vars return { ...commonVars, fontSize, lineHeight, titleFontWeight: fontWeightStrong, borderRadius, border: `1px solid ${dividerColor}`, color: actionColor, titleTextColor: textColor1, iconColor: textColor2, contentTextColor: textColor2, closeBorderRadius: borderRadius, closeColorHover, closeColorPressed, closeIconColor, closeIconColorHover, closeIconColorPressed, borderInfo: `1px solid ${composite( baseColor, changeColor(infoColor, { alpha: 0.25 }) )}`, colorInfo: composite(baseColor, changeColor(infoColor, { alpha: 0.08 })), titleTextColorInfo: textColor1, iconColorInfo: infoColor, contentTextColorInfo: textColor2, closeColorHoverInfo: closeColorHover, closeColorPressedInfo: closeColorPressed, closeIconColorInfo: closeIconColor, closeIconColorHoverInfo: closeIconColorHover, closeIconColorPressedInfo: closeIconColorPressed, borderSuccess: `1px solid ${composite( baseColor, changeColor(successColor, { alpha: 0.25 }) )}`, colorSuccess: composite( baseColor, changeColor(successColor, { alpha: 0.08 }) ), titleTextColorSuccess: textColor1, iconColorSuccess: successColor, contentTextColorSuccess: textColor2, closeColorHoverSuccess: closeColorHover, closeColorPressedSuccess: closeColorPressed, closeIconColorSuccess: closeIconColor, closeIconColorHoverSuccess: closeIconColorHover, closeIconColorPressedSuccess: closeIconColorPressed, borderWarning: `1px solid ${composite( baseColor, changeColor(warningColor, { alpha: 0.33 }) )}`, colorWarning: composite( baseColor, changeColor(warningColor, { alpha: 0.08 }) ), titleTextColorWarning: textColor1, iconColorWarning: warningColor, contentTextColorWarning: textColor2, closeColorHoverWarning: closeColorHover, closeColorPressedWarning: closeColorPressed, closeIconColorWarning: closeIconColor, closeIconColorHoverWarning: closeIconColorHover, closeIconColorPressedWarning: closeIconColorPressed, borderError: `1px solid ${composite( baseColor, changeColor(errorColor, { alpha: 0.25 }) )}`, colorError: composite(baseColor, changeColor(errorColor, { alpha: 0.08 })), titleTextColorError: textColor1, iconColorError: errorColor, contentTextColorError: textColor2, closeColorHoverError: closeColorHover, closeColorPressedError: closeColorPressed, closeIconColorError: closeIconColor, closeIconColorHoverError: closeIconColorHover, closeIconColorPressedError: closeIconColorPressed } } export type AlertThemeVars = ReturnType const alertLight: Theme<'Alert', AlertThemeVars> = { name: 'Alert', common: commonLight, self } export default alertLight export type AlertTheme = typeof alertLight ================================================ FILE: src/alert/styles/rtl.ts ================================================ import type { RtlItem } from '../../config-provider/src/internal-interface' import rtlStyle from '../src/styles/rtl.cssr' export const alertRtl: RtlItem = { name: 'Alert', style: rtlStyle } ================================================ FILE: src/alert/tests/Alert.spec.ts ================================================ import { IosAirplane } from '@vicons/ionicons4' import { mount } from '@vue/test-utils' import { h } from 'vue' import { NIcon } from '../../icon' import { NAlert } from '../index' describe('n-alert', () => { it('should work with import on demand', () => { mount(NAlert) }) it('should have a role of "alert"', () => { const wrapper = mount(NAlert) expect(wrapper.find('.n-alert').attributes('role')).toBe('alert') wrapper.unmount() }) it('should add the right aria', () => { const wrapper = mount(NAlert) expect(wrapper.find('.n-alert__icon').attributes('aria-hidden')).toBe( 'true' ) wrapper.unmount() }) it('shouldnt have default title', () => { const wrapper = mount(NAlert) expect(wrapper.find('.n-alert-body__title').exists()).toBe(false) wrapper.unmount() }) it('should have designated title', () => { const title = 'sometimes naïve' const wrapper = mount(NAlert, { props: { title } }) expect(wrapper.find('.n-alert-body__title').text()).toBe(title) wrapper.unmount() }) it('should work with type prop', async () => { ;(['info', 'success', 'warning', 'error'] as const).forEach((type) => { const wrapper = mount(NAlert, { props: { type } }) expect(wrapper.find('.n-alert').attributes('style')).toMatchSnapshot() wrapper.unmount() }) }) it('should work with `bordered` prop', async () => { const wrapper = mount(NAlert) const body = wrapper.find('.n-alert-body') expect(body.classes()).toContain('n-alert-body--bordered') await wrapper.setProps({ bordered: false }) expect(body.classes()).not.toContain('n-alert-body--bordered') wrapper.unmount() }) it('should work with `default` slot', () => { const wrapper = mount(NAlert, { slots: { default: () => 'default' } }) expect(wrapper.find('.n-alert-body__content').exists()).toBe(true) expect(wrapper.find('.n-alert-body__content').text()).toBe('default') wrapper.unmount() }) it('should work with `icon` slot', async () => { const wrapper = mount(NAlert, { slots: { icon: () => h(NIcon, null, { default: () => h(IosAirplane) }) } }) expect(wrapper.findComponent(NIcon).exists()).toBe(true) wrapper.unmount() }) it('should work with `header` slot', async () => { const wrapper = mount(NAlert, { slots: { header: () => 'test-header' } }) expect(wrapper.find('.n-alert-body__title').text()).toBe('test-header') wrapper.unmount() }) it('shouldnt be closable by default', () => { const wrapper = mount(NAlert) expect(wrapper.find('.n-base-close.n-alert__close').exists()).toBe(false) wrapper.unmount() }) it('should be closable when designated', () => { const wrapper = mount(NAlert, { props: { closable: true } }) expect(wrapper.find('.n-base-close.n-alert__close').exists()).toBe(true) wrapper.unmount() }) it('should show icon by default', () => { const wrapper = mount(NAlert) expect(wrapper.find('.n-alert__icon').exists()).toBe(true) wrapper.unmount() }) it('should hide icon when designated', () => { const wrapper = mount(NAlert, { props: { showIcon: false } }) expect(wrapper.find('.n-alert__icon').exists()).toBe(false) wrapper.unmount() }) it('shouldn\'t closed when on-close prop returns false', async () => { const wrapper = mount(NAlert, { props: { closable: true, onClose: () => false } }) const closeBtn = wrapper.find('.n-base-close.n-alert__close') await closeBtn.trigger('click') expect(wrapper.find('.n-base-close.n-alert__close').exists()).toBe(true) wrapper.unmount() }) it('should trigger callback when closed', async () => { const handleCloseClick = vi.fn() const handleOnAfterLeave = vi.fn() // https://github.com/vuejs/test-utils/issues/1912#issuecomment-1351054542 const rafSpy = vi .spyOn(window, 'requestAnimationFrame') .mockImplementation((cb: FrameRequestCallback): number => { cb(0) return 0 }) const wrapper = mount(NAlert, { props: { closable: true, onClose: handleCloseClick, onAfterLeave: handleOnAfterLeave }, global: { stubs: { Transition: false, TransitionGroup: false } } }) const closeBtn = wrapper.find('.n-base-close.n-alert__close') expect(closeBtn.exists()).toBe(true) await closeBtn.trigger('click') expect(wrapper.emitted()).toHaveProperty('click') expect(handleCloseClick).toHaveBeenCalled() expect(handleOnAfterLeave).toHaveBeenCalled() wrapper.unmount() rafSpy.mockRestore() }) }) ================================================ FILE: src/alert/tests/__snapshots__/Alert.spec.ts.snap ================================================ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`n-alert > should work with type prop 1`] = `"--n-bezier: cubic-bezier(.4, 0, .2, 1); --n-color: rgba(237, 245, 254, 1); --n-close-icon-size: 16px; --n-close-border-radius: 3px; --n-close-color-hover: rgba(0, 0, 0, .09); --n-close-color-pressed: rgba(0, 0, 0, .13); --n-close-icon-color: rgba(102, 102, 102, 1); --n-close-icon-color-hover: rgba(102, 102, 102, 1); --n-close-icon-color-pressed: rgba(102, 102, 102, 1); --n-icon-color: #2080f0; --n-border: 1px solid rgba(199, 223, 251, 1); --n-title-text-color: rgb(31, 34, 37); --n-content-text-color: rgb(51, 54, 57); --n-line-height: 1.6; --n-border-radius: 3px; --n-font-size: 14px; --n-title-font-weight: 500; --n-icon-size: 24px; --n-icon-margin: 11px 8px 0 12px; --n-icon-margin-rtl: 11px 12px 0 8px; --n-close-size: 20px; --n-close-margin: 13px 14px 0 0; --n-close-margin-rtl: 13px 0 0 14px; --n-padding: 13px; --n-icon-margin-left: 12px; --n-icon-margin-right: 8px;"`; exports[`n-alert > should work with type prop 2`] = `"--n-bezier: cubic-bezier(.4, 0, .2, 1); --n-color: rgba(237, 247, 242, 1); --n-close-icon-size: 16px; --n-close-border-radius: 3px; --n-close-color-hover: rgba(0, 0, 0, .09); --n-close-color-pressed: rgba(0, 0, 0, .13); --n-close-icon-color: rgba(102, 102, 102, 1); --n-close-icon-color-hover: rgba(102, 102, 102, 1); --n-close-icon-color-pressed: rgba(102, 102, 102, 1); --n-icon-color: #18a058; --n-border: 1px solid rgba(197, 231, 213, 1); --n-title-text-color: rgb(31, 34, 37); --n-content-text-color: rgb(51, 54, 57); --n-line-height: 1.6; --n-border-radius: 3px; --n-font-size: 14px; --n-title-font-weight: 500; --n-icon-size: 24px; --n-icon-margin: 11px 8px 0 12px; --n-icon-margin-rtl: 11px 12px 0 8px; --n-close-size: 20px; --n-close-margin: 13px 14px 0 0; --n-close-margin-rtl: 13px 0 0 14px; --n-padding: 13px; --n-icon-margin-left: 12px; --n-icon-margin-right: 8px;"`; exports[`n-alert > should work with type prop 3`] = `"--n-bezier: cubic-bezier(.4, 0, .2, 1); --n-color: rgba(254, 247, 237, 1); --n-close-icon-size: 16px; --n-close-border-radius: 3px; --n-close-color-hover: rgba(0, 0, 0, .09); --n-close-color-pressed: rgba(0, 0, 0, .13); --n-close-icon-color: rgba(102, 102, 102, 1); --n-close-icon-color-hover: rgba(102, 102, 102, 1); --n-close-icon-color-pressed: rgba(102, 102, 102, 1); --n-icon-color: #f0a020; --n-border: 1px solid rgba(250, 224, 181, 1); --n-title-text-color: rgb(31, 34, 37); --n-content-text-color: rgb(51, 54, 57); --n-line-height: 1.6; --n-border-radius: 3px; --n-font-size: 14px; --n-title-font-weight: 500; --n-icon-size: 24px; --n-icon-margin: 11px 8px 0 12px; --n-icon-margin-rtl: 11px 12px 0 8px; --n-close-size: 20px; --n-close-margin: 13px 14px 0 0; --n-close-margin-rtl: 13px 0 0 14px; --n-padding: 13px; --n-icon-margin-left: 12px; --n-icon-margin-right: 8px;"`; exports[`n-alert > should work with type prop 4`] = `"--n-bezier: cubic-bezier(.4, 0, .2, 1); --n-color: rgba(251, 238, 241, 1); --n-close-icon-size: 16px; --n-close-border-radius: 3px; --n-close-color-hover: rgba(0, 0, 0, .09); --n-close-color-pressed: rgba(0, 0, 0, .13); --n-close-icon-color: rgba(102, 102, 102, 1); --n-close-icon-color-hover: rgba(102, 102, 102, 1); --n-close-icon-color-pressed: rgba(102, 102, 102, 1); --n-icon-color: #d03050; --n-border: 1px solid rgba(243, 203, 211, 1); --n-title-text-color: rgb(31, 34, 37); --n-content-text-color: rgb(51, 54, 57); --n-line-height: 1.6; --n-border-radius: 3px; --n-font-size: 14px; --n-title-font-weight: 500; --n-icon-size: 24px; --n-icon-margin: 11px 8px 0 12px; --n-icon-margin-rtl: 11px 12px 0 8px; --n-close-size: 20px; --n-close-margin: 13px 14px 0 0; --n-close-margin-rtl: 13px 0 0 14px; --n-padding: 13px; --n-icon-margin-left: 12px; --n-icon-margin-right: 8px;"`; ================================================ FILE: src/alert/tests/server.spec.tsx ================================================ import { setup } from '@css-render/vue3-ssr' import { renderToString } from '@vue/server-renderer' /** * @vitest-environment node */ import { createSSRApp, h } from 'vue' import { NAlert } from '../..' describe('server side rendering', () => { it('works', async () => { const app = createSSRApp(() => ) setup(app) try { await renderToString(app) } catch (e) { expect(e).not.toBeTruthy() } }) }) ================================================ FILE: src/anchor/demos/enUS/affix.demo.vue ================================================ # Affix When in affix mode, Anchor can recieve addition props as same as Affix. ================================================ FILE: src/anchor/demos/enUS/basic.demo.vue ================================================ # Basic ================================================ FILE: src/anchor/demos/enUS/ignore-gap.demo.vue ================================================ # Ignore gap ================================================ FILE: src/anchor/demos/enUS/index.demo-entry.md ================================================ # Anchor Tell you where you are. ## Demos ```demo basic.vue ignore-gap.vue affix.vue scrollto.vue ``` ## API ### Anchor Props | Name | Type | Default | Description | | --- | --- | --- | --- | | affix | `boolean` | `false` | If it works like an affix. If set to `true`, it will receive props from [affix](affix#Affix-Props). | | bound | `number` | `12` | The height of the border when scrolling. | | ignore-gap | `boolean` | `false` | If set to `true`, it will be displayed on the exact href. | | offset-target | `string \| HTMLElement \| Window \| Document \| (() => HTMLElement)` | `document` | The element or selector used to calc offset of link elements. If you are not scrolling the entire document but only a part of it, you may need to set this. | | show-rail | `boolean` | `true` | Whether to show the sider rail. | | show-background | `boolean` | `true` | Whether to show background of links. | | type | `'rail' \| 'block'` | `'rail'` | The type to use. | ### AnchorLink Props | Name | Type | Default | Description | | ----- | -------- | ----------- | ------------------- | | href | `string` | `undefined` | The target of link | | title | `string` | `undefined` | The content of link | ### AnchorLink Slots | Name | Parameters | Description | Version | | ----- | ---------- | ---------------------- | ------- | | title | `()` | The title of the link. | 2.42.0 | ### Anchor Methods | Name | Type | Description | | --- | --- | --- | | scrollTo | `(href: string) => void` | Manually scroll to the specific position. | ================================================ FILE: src/anchor/demos/enUS/scrollto.demo.vue ================================================ # Scroll to ================================================ FILE: src/anchor/demos/zhCN/affix.demo.vue ================================================ # 固定 在固定模式下,Anchor 还接受和 Affix 一样的 Props。 ================================================ FILE: src/anchor/demos/zhCN/basic.demo.vue ================================================ # 基础用法 ================================================ FILE: src/anchor/demos/zhCN/ignore-gap.demo.vue ================================================ # 忽略间隔 ================================================ FILE: src/anchor/demos/zhCN/index.demo-entry.md ================================================ # 侧边导航 Anchor 你永远的指路明灯。 ## 演示 ```demo basic.vue ignore-gap.vue affix.vue scrollto.vue max-height-debug.vue ``` ## API ### Anchor Props | 名称 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | affix | `boolean` | `false` | Anchor 是否像 Affix 一样展示,如果设定为 `true`,它还会接受 [Affix](affix#Affix-Props) 的 Props | | bound | `number` | `12` | 元素开始触发 anchor 的偏移量 | | ignore-gap | `boolean` | `false` | 如果设定为 `true`, 导航将显示在准确的 href 区域 | | offset-target | `string \| HTMLElement \| Window \| Document \| (() => HTMLElement)` | `document` | 计算偏移位置相对的元素或选择器。如果你滚动的不是整个文档而只是其中的一部分,那你有可能要设定这个 | | show-rail | `boolean` | `true` | 是否展示侧面的轨道 | | show-background | `boolean` | `true` | 是否展示 link 的背景 | | type | `'rail' \| 'block'` | `'rail'` | Anchor 的风格,`'block'` 为块状风格,`'rail'` 为轨道风格 | ### AnchorLink Props | 名称 | 类型 | 默认值 | 说明 | | ----- | -------- | ----------- | -------- | | href | `string` | `undefined` | 锚点链接 | | title | `stirng` | `undefined` | 锚点标题 | ### AnchorLink Slots | 名称 | 参数 | 说明 | 版本 | | ----- | ---- | -------- | ------ | | title | `()` | 锚点标题 | 2.42.0 | ### Anchor Methods | 名称 | 类型 | 说明 | | -------- | ------------------------ | ---------------------- | | scrollTo | `(href: string) => void` | 手动触发到指定滚动位置 | ================================================ FILE: src/anchor/demos/zhCN/max-height-debug.demo.vue ================================================ # 高度限定 ================================================ FILE: src/anchor/demos/zhCN/scrollto.demo.vue ================================================ # 滚动到 ================================================ FILE: src/anchor/index.ts ================================================ export { anchorProps, default as NAnchor } from './src/AnchorAdapter' export type { AnchorInst, AnchorProps } from './src/AnchorAdapter' export { anchorLinkProps, default as NAnchorLink } from './src/Link' export type { AnchorLinkProps } from './src/Link' export type * from './src/public-types' ================================================ FILE: src/anchor/src/AnchorAdapter.tsx ================================================ import type { CSSProperties } from 'vue' import type { ThemeProps } from '../../_mixins' import type { ExtractPublicPropTypes } from '../../_utils' import type { AnchorTheme } from '../styles' import type { BaseAnchorInst } from './BaseAnchor' import { computed, defineComponent, h, ref } from 'vue' import { useConfig, useTheme, useThemeClass } from '../../_mixins' import { keep } from '../../_utils' import { NAffix } from '../../affix' import { affixPropKeys, affixProps } from '../../affix/src/Affix' import { anchorLight } from '../styles' import NBaseAnchor, { baseAnchorPropKeys, baseAnchorProps } from './BaseAnchor' import style from './styles/index.cssr' export interface AnchorInst { scrollTo: (href: string) => void } export const anchorProps = { ...(useTheme.props as ThemeProps), affix: Boolean, ...affixProps, ...baseAnchorProps } as const export type AnchorProps = ExtractPublicPropTypes export default defineComponent({ name: 'Anchor', props: anchorProps, setup(props, { slots }) { const { mergedClsPrefixRef, inlineThemeDisabled } = useConfig(props) const themeRef = useTheme( 'Anchor', '-anchor', style, anchorLight, props, mergedClsPrefixRef ) const anchorRef = ref(null) const cssVarsRef = computed(() => { const { self: { railColor, linkColor, railColorActive, linkTextColor, linkTextColorHover, linkTextColorPressed, linkTextColorActive, linkFontSize, railWidth, linkPadding, borderRadius }, common: { cubicBezierEaseInOut } } = themeRef.value return { '--n-link-border-radius': borderRadius, '--n-link-color': linkColor, '--n-link-font-size': linkFontSize, '--n-link-text-color': linkTextColor, '--n-link-text-color-hover': linkTextColorHover, '--n-link-text-color-active': linkTextColorActive, '--n-link-text-color-pressed': linkTextColorPressed, '--n-link-padding': linkPadding, '--n-bezier': cubicBezierEaseInOut, '--n-rail-color': railColor, '--n-rail-color-active': railColorActive, '--n-rail-width': railWidth } }) const themeClassHandle = inlineThemeDisabled ? useThemeClass('anchor', undefined, cssVarsRef, props) : undefined return { scrollTo(href: string) { anchorRef.value?.setActiveHref(href) }, renderAnchor: () => { themeClassHandle?.onRender() return ( {slots} ) } } }, render() { return !this.affix ? ( this.renderAnchor() ) : ( {{ default: this.renderAnchor }} ) } }) ================================================ FILE: src/anchor/src/BaseAnchor.tsx ================================================ import type { PropType } from 'vue' import type { OffsetTarget } from './utils' import { unwrapElement } from 'seemly' import { onFontsReady } from 'vooks' import { computed, defineComponent, h, nextTick, onBeforeUnmount, onMounted, provide, ref, toRef, watch } from 'vue' import { NScrollbar } from '../../_internal' import { keysOf } from '../../_utils' import { anchorInjectionKey } from './Link' import { getOffset } from './utils' interface LinkInfo { top: number height: number href: string } export interface BaseAnchorInst { setActiveHref: (href: string) => void } export const baseAnchorProps = { type: { type: String as PropType<'block' | 'rail'>, default: 'rail' }, showRail: { type: Boolean, default: true }, showBackground: { type: Boolean, default: true }, bound: { type: Number, default: 12 }, internalScrollable: Boolean, ignoreGap: Boolean, offsetTarget: [String, Object, Function] as PropType< string | OffsetTarget | (() => HTMLElement) > } as const export const baseAnchorPropKeys = keysOf(baseAnchorProps) export default defineComponent({ name: 'BaseAnchor', props: { ...baseAnchorProps, mergedClsPrefix: { type: String, required: true } }, setup(props) { const collectedLinkHrefs: string[] = [] const titleEls: HTMLElement[] = [] const activeHrefRef = ref(null) const slotRef = ref(null) const barRef = ref(null) const selfRef = ref(null) let skipScrollHandling = false const isBlockTypeRef = computed(() => { return props.type === 'block' }) const mergedShowRailRef = computed(() => { return !isBlockTypeRef.value && props.showRail }) function disableTransitionOneTick(): void { const { value: barEl } = barRef const { value: slotEl } = slotRef if (barEl) { barEl.style.transition = 'none' } if (slotEl) { slotEl.style.transition = 'none' } if (titleEls) { titleEls.forEach((titleEl) => { titleEl.style.transition = 'none' }) } void nextTick(() => { const { value: nextBarEl } = barRef const { value: nextSlotEl } = slotRef if (nextBarEl) { void nextBarEl.offsetWidth nextBarEl.style.transition = '' } if (nextSlotEl) { void nextSlotEl.offsetWidth nextSlotEl.style.transition = '' } if (titleEls) { titleEls.forEach((titleEl) => { void titleEl.offsetWidth titleEl.style.transition = '' }) } }) } function updateBarPosition( linkTitleEl: HTMLElement, transition = true ): void { const { value: barEl } = barRef const { value: slotEl } = slotRef const { value: selfEl } = selfRef if (!selfEl || !barEl) return if (!transition) { barEl.style.transition = 'none' if (slotEl) slotEl.style.transition = 'none' } const { offsetHeight, offsetWidth } = linkTitleEl const { top: linkTitleClientTop, left: linkTitleClientLeft } = linkTitleEl.getBoundingClientRect() const { top: anchorClientTop, left: anchorClientLeft } = selfEl.getBoundingClientRect() const offsetTop = linkTitleClientTop - anchorClientTop const offsetLeft = linkTitleClientLeft - anchorClientLeft barEl.style.top = `${offsetTop}px` barEl.style.height = `${offsetHeight}px` if (slotEl) { slotEl.style.top = `${offsetTop}px` slotEl.style.height = `${offsetHeight}px` slotEl.style.maxWidth = `${offsetWidth + offsetLeft}px` } void barEl.offsetHeight if (slotEl) void slotEl.offsetHeight if (!transition) { barEl.style.transition = '' if (slotEl) slotEl.style.transition = '' } } let currentThrottleTimerId: ReturnType | undefined let hasTrailingThrottledTask = false let isInThrottledPeriod = false const handleScroll = () => { if (isInThrottledPeriod) { hasTrailingThrottledTask = true } else { if (skipScrollHandling) { return } _handleScroll(true) isInThrottledPeriod = true clearTimeout(currentThrottleTimerId) currentThrottleTimerId = setTimeout(() => { isInThrottledPeriod = false if (hasTrailingThrottledTask) { hasTrailingThrottledTask = false handleScroll() } }, 128) } } function setActiveHref(href: string, transition = true): void { const idMatchResult = /^#([^#]+)$/.exec(href) if (!idMatchResult) return const linkEl = document.getElementById(idMatchResult[1]) if (!linkEl) return skipScrollHandling = true activeHrefRef.value = href linkEl.scrollIntoView() if (!transition) { disableTransitionOneTick() } hasTrailingThrottledTask = false setTimeout(() => { skipScrollHandling = false }, 0) } function _handleScroll(transition = true): void { const links: LinkInfo[] = [] const offsetTarget = unwrapElement(props.offsetTarget ?? document) collectedLinkHrefs.forEach((href) => { const idMatchResult = /#([^#]+)$/.exec(href) if (!idMatchResult) return const linkEl = document.getElementById(idMatchResult[1]) if (linkEl && offsetTarget) { const { top, height } = getOffset(linkEl, offsetTarget) links.push({ top, height, href }) } }) links.sort((a, b) => { // ascend top if (a.top > b.top) { return 1 // descend height } else if (a.top === b.top && a.height < b.height) { return -1 } return -1 }) const currentActiveHref = activeHrefRef.value const { bound, ignoreGap } = props const activeLink = links.reduce((prevLink: LinkInfo | null, link) => { if (link.top + link.height < 0) { if (ignoreGap) { return link } else { return prevLink } } if (link.top <= bound) { if (prevLink === null) { return link } else if (link.top === prevLink.top) { if (link.href === currentActiveHref) { return link } else { return prevLink } } else if (link.top > prevLink.top) { return link } else { return prevLink } } return prevLink }, null) if (!transition) disableTransitionOneTick() if (activeLink) { activeHrefRef.value = activeLink.href } else { activeHrefRef.value = null } } provide(anchorInjectionKey, { activeHref: activeHrefRef, mergedClsPrefix: toRef(props, 'mergedClsPrefix'), updateBarPosition, setActiveHref, collectedLinkHrefs, titleEls }) onMounted(() => { document.addEventListener('scroll', handleScroll, true) setActiveHref(window.location.hash) _handleScroll(false) }) onFontsReady(() => { setActiveHref(window.location.hash) _handleScroll(false) }) onBeforeUnmount(() => { clearTimeout(currentThrottleTimerId) document.removeEventListener('scroll', handleScroll, true) }) watch(activeHrefRef, (value) => { if (value === null) { const { value: slotEl } = slotRef if (slotEl && !isBlockTypeRef.value) { slotEl.style.maxWidth = '0' } } }) return { selfRef, barRef, slotRef, setActiveHref, activeHref: activeHrefRef, isBlockType: isBlockTypeRef, mergedShowRail: mergedShowRailRef } }, render() { const { mergedClsPrefix, mergedShowRail, isBlockType, $slots } = this const Anchor = (
{mergedShowRail && this.showBackground ? (