diff --git a/.eslintrc.js b/.eslintrc.js index 12abdbcae4fd5df18ae6d86694fa8ebad068f487..3da2f8038e830ccdbd9d559118c4e74b0baf58b9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -25,6 +25,7 @@ module.exports = { 'no-undef': 2, 'vue/max-attributes-per-line': 'off', 'vue/no-multiple-template-root': 'off', + 'vue/script-setup-uses-vars': 'off', '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/member-delimiter-style': [ 'error', diff --git a/devui/carousel/carousel.scss b/devui/carousel/carousel.scss index 855639f1dbd45e223dbc17126552eded087e2c4f..2cbf5013ffc423e12ab3b415ec4877b74de8beff 100644 --- a/devui/carousel/carousel.scss +++ b/devui/carousel/carousel.scss @@ -70,7 +70,6 @@ display: flex; justify-content: center; width: 100%; - list-style: none; &.bottom { diff --git a/devui/carousel/carousel.tsx b/devui/carousel/carousel.tsx index d5e5f08445ba8bf67d94a54a51ad53b1bdaebc7e..db977d77e97110723fbb146090ef18eb7219311a 100644 --- a/devui/carousel/carousel.tsx +++ b/devui/carousel/carousel.tsx @@ -1,3 +1,4 @@ +/* eslint-disable */ import { defineComponent, ref, watch, onMounted, Fragment, Comment } from 'vue'; import { carouselProps, DotTrigger } from './types'; diff --git a/devui/loading/__tests__/loading.spec.ts b/devui/loading/__tests__/loading.spec.ts index b2fb756c35e99bc5327c4dffc9934b216715b188..be8b2b5fb731299bc7b61c1752d13d77916ae039 100644 --- a/devui/loading/__tests__/loading.spec.ts +++ b/devui/loading/__tests__/loading.spec.ts @@ -1,3 +1,4 @@ +/* eslint-disable */ import { mount } from '@vue/test-utils'; import { ref, Ref, nextTick, h, shallowReactive } from 'vue'; import loading from '../index'; diff --git a/devui/loading/src/directive.ts b/devui/loading/src/directive.ts index 0927697b13a4724ea4b53097ad504eb94c3b1e34..a99daebe5bd1907060acae378e30d4124c20ea88 100644 --- a/devui/loading/src/directive.ts +++ b/devui/loading/src/directive.ts @@ -1,3 +1,4 @@ +/* eslint-disable */ import { defineComponent } from 'vue' import Loading from './loading' import { LoadingProps, BindingType, TargetHTMLElement } from './types' diff --git a/devui/loading/src/loading.scss b/devui/loading/src/loading.scss index 5233d84b1a306de3a1760be23568ca9fc378adbf..2e9fbef5101c3d6df424b8f95e2dbcac79357de1 100644 --- a/devui/loading/src/loading.scss +++ b/devui/loading/src/loading.scss @@ -2,16 +2,21 @@ @keyframes devui-busy-spinner-anim { 0% { - transform:rotate(0deg) - scale(1) + transform: + rotate(0deg) + scale(1); } + 50% { - transform:rotate(135deg) - scale(1.5) + transform: + rotate(135deg) + scale(1.5); } + to { - transform:rotate(270deg) - scale(1) + transform: + rotate(270deg) + scale(1); } } @@ -22,11 +27,13 @@ bottom: 0; top: 0; background-color: $devui-line; - opacity: .3; + opacity: 0.3; } + .devui-loading-wrapper { text-align: center; } + .devui-loading--full { position: fixed; left: 0; @@ -35,9 +42,11 @@ top: 0; z-index: 9999; } + .devui-loading--hidden { overflow: hidden; } + .devui-loading-text { vertical-align: super; margin-left: 10px; @@ -48,15 +57,17 @@ position: absolute; transform: translate(-50%, -50%); padding: 12px 14px; - background: var(--devui-base-bg, #fff); + background: var(--devui-base-bg, #ffffff); border-radius: var(--devui-border-radius-card, 6px); } + .devui-busy-default-spinner { position: relative; display: inline-block; width: 15px; height: 15px; animation: devui-busy-spinner-anim 1s linear infinite; + div { position: absolute; left: 44.5%; @@ -65,28 +76,32 @@ height: 6px; border-radius: 50%; } + .devui-loading-bar1 { top: 0; left: 0; background: #5e7ce0; - background: var(--devui-brand,#5e7ce0); + background: var(--devui-brand, #5e7ce0); } + .devui-loading-bar2 { top: 0; left: 9px; background: #859bff; - background: var(--devui-brand-foil,#859bff); + background: var(--devui-brand-foil, #859bff); } + .devui-loading-bar3 { top: 9px; left: 0; background: #859bff; - background: var(--devui-brand-foil,#859bff); + background: var(--devui-brand-foil, #859bff); } + .devui-loading-bar4 { top: 9px; left: 9px; background: #5e7ce0; - background: var(--devui-brand,#5e7ce0); + background: var(--devui-brand, #5e7ce0); } -} \ No newline at end of file +} diff --git a/devui/loading/src/loading.tsx b/devui/loading/src/loading.tsx index 64414cb170d0f212069a3666bdd8bfa73d415f76..60234c0a460ca58b4b38b916211015f5ed7582a2 100644 --- a/devui/loading/src/loading.tsx +++ b/devui/loading/src/loading.tsx @@ -4,7 +4,7 @@ import { componentProps, ComponentProps } from './types' import './loading.scss'; export default defineComponent({ - name: 'd-loading', + name: 'DLoading', inheritAttrs: false, props: componentProps, setup(props: ComponentProps) { diff --git a/devui/loading/src/service.ts b/devui/loading/src/service.ts index 355f6a8c371318132166ecc28290244e366d2659..2c8642de85c211ba0d2dafe2d2a8b111c9d19e01 100644 --- a/devui/loading/src/service.ts +++ b/devui/loading/src/service.ts @@ -1,3 +1,4 @@ +/* eslint-disable */ import { defineComponent } from 'vue' import { createComponent } from './component' import Loading from './loading' diff --git a/devui/select/__tests__/select.spec.ts b/devui/select/__tests__/select.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..d7fcaa9b39297c6d2396525af914a21455b5a150 --- /dev/null +++ b/devui/select/__tests__/select.spec.ts @@ -0,0 +1,114 @@ +import { mount } from '@vue/test-utils'; +import { ref, reactive, nextTick } from 'vue'; +import DSelect from '../src/select'; + +describe('select', () => { + it('select render work', async () => { + const value = ref(1); + const options = reactive([1, 2, 'string']); + const wrapper = mount({ + components: { DSelect }, + template: ``, + setup() { + return { + value, + options, + }; + }, + }); + const container = wrapper.find('.devui-select'); + let dropdown = wrapper.find('.devui-select-dropdown'); + let listItems = wrapper.findAll('.devui-select-item'); + const input = wrapper.find('.devui-select-input'); + const arrow = wrapper.find('.devui-select-arrow'); + + expect(container.exists()).toBeTruthy(); + expect(dropdown.isVisible()).toBeFalsy(); + expect(arrow.isVisible()).toBeTruthy(); + expect(listItems.length).toBe(3); + expect(listItems[0].classes()).toContain('active'); + expect(input.attributes('placeholder')).toBe('这是默认选择框'); + expect(input.element.value).toBe('1'); + + await input.trigger('click'); + await nextTick(); + // isVisible不会自动更新需要重新获取 + dropdown = wrapper.find('.devui-select-dropdown'); + expect(dropdown.isVisible()).toBeTruthy(); + expect(container.classes()).toContain('devui-select-open'); + + await listItems[2].trigger('click'); + await nextTick(); + + // isVisible不会自动更新需要重新获取 + dropdown = wrapper.find('.devui-select-dropdown'); + expect(value.value).toBe('string'); + expect(dropdown.isVisible()).toBeFalsy(); + expect(input.element.value).toBe('string'); + // class不会自动更新需要重新获取 + listItems = wrapper.findAll('.devui-select-item'); + expect(listItems[2].classes()).toContain('active'); + }); + + it('select size and overview work', async () => { + const wrapper = mount(DSelect, { + props: { + size: 'sm', + overview: 'underlined', + }, + }); + + let container = wrapper.find('.devui-select'); + expect(container.classes()).toContain('devui-select-sm'); + expect(container.classes()).toContain('devui-select-underlined'); + + await wrapper.setProps({ + size: 'lg', + overview: 'border', + }); + + container = wrapper.find('.devui-select'); + expect(container.classes()).toContain('devui-select-lg'); + expect(container.classes()).not.toContain('devui-select-underlined'); + }); + + it('select events work', async () => { + const value = ref(2); + const options = reactive([6, 2, 'test']); + const toggleChange = jest.fn(); + const valueChange = jest.fn(); + const wrapper = mount({ + components: { DSelect }, + template: ` + + `, + setup() { + return { + value, + options, + toggleChange, + valueChange, + }; + }, + }); + + const input = wrapper.find('.devui-select-input'); + await input.trigger('click'); + + expect(toggleChange).toBeCalledTimes(1); + expect(valueChange).toBeCalledTimes(0); + expect(value.value).toBe(2); + + const listItems = wrapper.findAll('.devui-select-item'); + await listItems[2].trigger('click'); + + expect(toggleChange).toBeCalledTimes(2); + expect(valueChange).toBeCalledTimes(1); + expect(value.value).toBe('test'); + }); +}); diff --git a/devui/select/demo/select-demo.tsx b/devui/select/demo/select-demo.tsx deleted file mode 100644 index ef734f005bcf4f60c88f7e84536cec2e3cb16fb1..0000000000000000000000000000000000000000 --- a/devui/select/demo/select-demo.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { defineComponent } from 'vue' - -export default defineComponent({ - name: 'd-select-demo', - props: { - }, - setup(props, ctx) { - return () => { - return
devui-select-demo
- } - } -}) \ No newline at end of file diff --git a/devui/select/demo/select.route.ts b/devui/select/demo/select.route.ts deleted file mode 100644 index c6565f5588821128aa0d9c80e69f9bd8022d4fc3..0000000000000000000000000000000000000000 --- a/devui/select/demo/select.route.ts +++ /dev/null @@ -1,15 +0,0 @@ -import SelectDemoComponent from './select-demo' -import DevUIApiComponent from '../../shared/devui-api/devui-api' - -import ApiCn from '../doc/api-cn.md' -import ApiEn from '../doc/api-en.md' -const routes = [ - { path: '', redirectTo: 'demo' }, - { path: 'demo', component: SelectDemoComponent}, - { path: 'api', component: DevUIApiComponent, meta: { - 'zh-cn': ApiCn, - 'en-us': ApiEn - }} -] - -export default routes diff --git a/devui/select/doc/api-cn.md b/devui/select/doc/api-cn.md deleted file mode 100644 index 7b6472a5255ae0adf75e4d644daedf993316d222..0000000000000000000000000000000000000000 --- a/devui/select/doc/api-cn.md +++ /dev/null @@ -1,96 +0,0 @@ -# 如何使用 - -在 module 中引入: - -```typescript -import { SelectModule } from 'ng-devui/select'; -``` - -在页面中使用: - -```html - -``` - -## d-select - -### d-select 参数 - -| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | -| :----------------------------------------------: | :-------------------------------------------------: | :----------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | -| options | `array` | [] | 可选, 和 searchFn 互斥,两者必须有且只有一个。下拉选项资源`string` `object` | [基本用法](demo#basic-usage) | -| isSearch | `boolean` | false | 可选,是否支持过滤搜索 | [使用对象](demo#object-filter) | -| scrollHight | `string` | '300px' | 可选,下拉菜单高度,建议使用 px 作为高度单位 | -| highlightItemClass | `string` | 'bg-grey' | 可选,下拉高亮 css | -| filterKey | `string` | -- | 当传入资源 options 类型为 object 时,必选,针对传入资源 options 的每项对应字段做过滤操作 | [使用对象](demo#object-filter) | -| multiple | `boolean` | false | 可选,是否支持多选 | [自定义搜索功能](demo#custom-search) | -| isSelectAll | `boolean` | false | 可选,是否显示全选 | [全选下拉选项](demo#select-all) | -| readonly | `boolean` | true | 可选,是否可以输入 | [标签化](demo#labelization) | -| size | `string` | '' | 可选,下拉选框尺寸,有三种选择`'lg'`,`''`,`'sm'` | [基本用法](demo#basic-usage) | -| disabled | `boolean` | false | 可选,是否禁用下拉框 | [禁用](demo#disabled) | -| placeholder | `string` | 'Please Input keywords' | 可选,输入框的 placeholder | [基本用法](demo#basic-usage) | -| searchPlaceholder | `string` | '' | 可选,搜索功能输入框的 placeholder | [自定义搜索功能](demo#custom-search) | -| searchFn | `function` | -- | 可选,搜索函数,当需要自定义下拉选择过滤规则时可以使用 | [自定义搜索功能](demo#custom-search) | -| valueParser | `function` | -- | 可选,决定选择框文字如何显示,默认显示 filterKey 字段或者本身的值 | -| formatter | `function` | -- | 可选,决定下拉框每项文字如何显示,默认显示 filterKey 字段或者本身的值 | -| direction | `'up'\|'down'\|'auto'` | '' | 可选,下拉选框尺寸,有三种选择`'up'`,`'down'`,`'auto'` | [禁用](demo#disabled) | -| overview | `string` | 'border' | 可选,决定选择框样式显示,默认有边框`'border'`,`'underlined'` | [基本用法](demo#basic-usage) | -| enableLazyLoad | `boolean` | false | 可选,是否支持数据懒加载,用于滚动到底部时动态请求数据 | [虚拟滚动 或 懒加载](demo#lazy-load-virtual-scroll) | -| extraConfig | `object` | N/A | 可选, 可输入配置项 参考示例 | [自定义模板](demo#select-template) | -| extraConfig.labelization | `object` | N/A | 可选, 标签化多选结果的配置项,参考示例 | [标签化](demo#labelization) | -| extraConfig.labelization.enable | `boolean` | false | 可选下的必填参数, 是否启用标签化,使用必须置为 true,参考示例 | [标签化](demo#labelization) | -| extraConfig.labelization.overflow | `string` | '' | 可选, 多个标签超出容器时候的预处理行为,值为`'normal' \| 'scroll-y' \| 'multiple-line' \| 'string'` 对应默认隐藏,纵向滚动、自动变多行和自定义类 | [标签化](demo#labelization) | -| extraConfig.labelization.containerMaxHeight | `string` | '1.8em' | 可选, 限制容器最高高度。 多行模式下默认不限制高度,单行模式下默认为 1.8em | -| ~~extraConfig.labelization.containnerMaxHeight~~ | `string` | '1.8em' | 已废弃, 限制容器最高高度。 多行模式下默认不限制高度,单行模式下默认为 1.8em, 请使用`extraConfig.labelization.containerMaxHeight` | -| extraConfig.labelization.labelMaxWidth | `string` | '100%' | 可选下, 限制标签宽度,默认值为行宽的 100% | -| extraConfig.selectedItemWithTemplate | `object` | N/A | 可选,在单选情况下,显示选项使用了 template 的情况下,顶部选中的内容是否也以 template 展示,参考示例 | [自定义模板](demo#select-template) | -| extraConfig.selectedItemWithTemplate.enable | `boolean` | -- | 可选下的必填参数, 是否启用选中项使用模板,使用必须置为 true,参考示例 | [自定义模板](demo#select-template) | -| optionDisabledKey | `string` | '' | 可选,禁用单个选项;当传入资源 options 类型为`Object`,比如设置为`'disabled'`,则当对象的 disabled 属性为 true 时,该选项将禁用;当设置为`''`时不禁用单个选项 | [禁用](demo#disabled) | -| optionImmutableKey | `string` | '' | 可选,禁用单个选项;当传入资源 options 类型为`Object`,比如设置为`'immutable'`,则当对象的 immutable 属性为 true 时,该选项将禁被禁止变更;当设置为`''`时不生效 | [禁用](demo#disabled) | -| noResultItemTemplate | `TemplateRef` | -- | 可选,没有匹配项的展示结果 | -| keepMultipleOrder | `string` | 'user-select' | 可选,`'user-select' \| 'origin'`,配置多选的时候是否维持原数组排序还是用户选择的顺序排序,默认是用户顺序 | [设置已选项顺序源数组顺序或选中顺序](demo#multi-keep-order) | -| customViewTemplate | `TemplateRef` | -- | 可选,支持自定义区域显示内容定制,可以使用 choose 来选择某项,choose 需要传两个必填参数,第一个为选择的选项,第二个为选项在列表的 index 值,event 参数选填,若不填请自行处理冒泡,详见 demo | [自定义区域](demo#custom-area) | -| customViewDirection | `'bottom' \| 'right'\| 'left'` | 'bottom' | 可选, customViewTemplate 所处的相对下拉列表的位置 | [自定义区域方向和选中](demo#custom-area-direction) | -| appendToBody | `boolean` | false | 可选, true 会被附加到 body | [附着到 Body 上](demo#append-to-body) | -| appendToBodyDirections | `Array` | `['rightDown', 'leftDown', 'rightUp', 'leftUp']` | 可选,方向数组优先采用数组里靠前的位置,AppendToBodyDirection 和 ConnectedPosition 请参考 dropdown | [自定义区域方向和选中](demo#custom-area-direction) | -| autoFocus | `boolean` | false | 可选,是否自动聚焦 | -| toggleOnFocus | `boolean` | false | 可选,是否 focus 自动展开下拉列表 | -| width | `number` | -- | 可选,搭配 appendToBody 使用,设置下拉宽度 | [自定义区域方向和选中](demo#custom-area-direction) | -| virtualScroll | `boolean` | false | 可选,是否虚拟滚动,大数据量场景试用 | [虚拟滚动 或 懒加载](demo#lazy-load-virtual-scroll) | -| allowClear | `boolean` | false | 可选, 配置是否允许清空选值,仅单选场景适用 | [允许清空值](demo#allow-clear-value) | -| inputItemTemplate | `TemplateRef` | -- | 可选,自定义模板,若传入,会忽略 ContentChild | | -| ~~~notAutoScroll~~~ | `boolean` | false | `待改名`~~~可选,自动聚焦的时候,自动滚动到 select 位置~~~ | -| templateItemSize | `number` | false | `待完善`可选,模板单条高度, appendToBody 必须为 true | -| loadingTemplateRef | `TemplateRef` | -- | 可选,自定义 loading 模板 | [虚拟滚动 或 懒加载](demo#lazy-load-virtual-scroll) | - -### d-select 事件 - -| 事件 | 类型 | 说明 | 跳转 Demo | -| :----------: | :-------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | -| valueChange | `EventEmitter\|any>` | 可选,输出函数,当选中某个选项项后,将会调用此函数,参数为当前选择项的值 | -| toggleChange | `EventEmitter` | 可选,输出函数,下拉打开关闭 toggle 事件 | [异步加载显示加载中](demo#async-loading) | -| loadMore | `EventEmitter<{instance: SelectComponent, event: ScrollEvent}>` | 懒加载触发事件,配合`enableLazyLoad`使用,使用`$event.instance.loadFinish()`结束本次加载, event 为懒加载监听的滚动事件,参考 dLazyLoad | [虚拟滚动 或 懒加载](demo#lazy-load-virtual-scroll) | - -注意: 使用 appendToBody 后需要在有滚动条的地方使用`cdkScrollable` - -```terminal -npm install @angular/cdk --save -``` - -```TypeScript -import { ScrollDispatchModule } from '@angular/cdk/scrolling'; - -@NgModule({ - imports: [ - // ... - ScrollDispatchModule, - // ... - ] -}) -``` - -```html -
- -
-``` diff --git a/devui/select/doc/api-en.md b/devui/select/doc/api-en.md deleted file mode 100644 index 4001d3fd9b4bfbc27290384afff519157d0e91af..0000000000000000000000000000000000000000 --- a/devui/select/doc/api-en.md +++ /dev/null @@ -1,95 +0,0 @@ -# How to use - -Import into module: - -```typescript -import { SelectModule } from 'ng-devui/select'; -``` - -In the page: - -```html - -``` - -## d-select - -### d-select Parameter - -| Parameter | Type | Default | Description | Jump to Demo | -| :----------------------------------------------: | :-------------------------------------------------: | :-------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -| options | `array` | [] | Optional. This parameter and searchFn are mutually exclusive. Only one of them is required. Resource of the drop-down list box: `string` `object` | [Basic Usage](demo#basic-usage) | -| isSearch | `boolean` | false | Optional. indicating whether to support filtering search. | [Use object](demo#object-filter) | -| scrollHight | `string` | '300px' | Optional. Height of the drop-down list box. You are advised to use px as the height unit. | -| highlightItemClass | `string` | 'bg-grey' | Optional. The drop-down list box is highlighted. css | -| filterKey | `string` | -- | Optional. This parameter is required when the input resource options type is object. Filter the fields corresponding to the input resource options. | [Use object](demo#object-filter) | -| multiple | `boolean` | false | Optional. indicating whether to support multiple selections. | [Custom Search](demo#custom-search) | -| isSelectAll | `boolean` | false | Optional. Whether to display all options. | [Select All](demo#select-all) | -| readonly | `boolean` | true | Optional. Whether the value can be entered | [Tagged](demo#labelization) | -| size | `string` | '' | Optional. Size of the drop-down list box. The options are as follows: `'lg'`, `''`, and `'sm'` | [Basic Usage](demo#basic-usage) | -| disabled | `boolean` | false | Optional. indicating whether to disable the drop-down list box. | [Disable](demo#disabled) | -| placeholder | `string` | 'Please Input keywords' | Optional. This parameter specifies the placeholder of the input box. | [Basic Usage](demo#basic-usage) | -| searchPlaceholder | `string` | '' | Optional. placeholder in the search text box. | [Custom Search](demo#custom-search) | -| searchFn | `function` | -- | Optional. Search function. You can use this function when you need to customize filtering rules from the drop-down list box. | [Custom Search](demo#custom-search) | -| valueParser | `function` | -- | Optional. This parameter determines how to display the text in the selection box. By default, the filterKey field or its value is displayed. | -| formatter | `function` | -- | Optional. This parameter determines how to display each text in the drop-down list box. By default, the filterKey field or its value is displayed. | -| direction | `'up'\|'down'\|'auto'` | 'down' | Optional. The options are as follows: up, down, and auto. | [Disabled](demo#disabled) | -| overview | `string` | 'border' | Optional. This parameter specifies the selection box style. By default, the border is `'border'`,`'underlined'` | [Basic Usage](demo#basic-usage) | -| enableLazyLoad | `boolean` | false | Optional. Whether to support data lazy loading. It is used to dynamically request data when scrolling to the bottom. | [Virtual scrolling or lazy loading](demo#lazy-load-virtual-scroll) | -| extraConfig | `object` | N/A | Optional. You can enter configuration items. Example | [Customized template](demo#select-template) | -| extraConfig.labelization | `object` | N/A | Optional. It is a configuration item for labeling the multi-selection result. For details, see. | [Labeling](demo#labelization) | -| extraConfig.labelization.enable | `boolean` | false | Indicates whether to enable tagging. This parameter is mandatory under . The value must be true. For details, see the example | [Labeling](demo#labelization) | -| extraConfig.labelization.overflow | `string` | '' | Optional. Preprocessing behavior when multiple tags exceed the container. The value is `'normal'\| 'scroll-y' \| 'multiple-line' \| 'string'`, indicating that the tag is hidden by default, vertical scrolling, automatic multi-line, and custom class | [labeling](demo#labelization) | -| extraConfig.labelization.containerMaxHeight | `string` | '1.8em ' | Specifies the maximum height of the container. This parameter is optional. By default, the height is not limited in multi-line mode. In single-line mode, the height is 1.8em by default. | -| ~~extraConfig.labelization.containnerMaxHeight~~ | `string` | '1.8em' | `Deprecated`. specifies the maximum height of a container. By default, the height is not limited in multi-line mode. In single-line mode, the height is 1.8 em by default. Use `extraConfig.labelization.containerMaxHeight` | . | -| extraConfig.labelization.labelMaxWidth | `string` | '100% ' | Optional. Limit the label width. The default value is 100% of the row width. | -| extraConfig.selectedItemWithTemplate | `object` | N/A | Optional. When a single option is selected and the display option is set to template, check whether the selected content is displayed in the template format. For details, see the example. | [Customized template](demo#select-template) | -| extraConfig.selectedItemWithTemplate.enable | `boolean` | -- | This parameter is required under labelization. It specifies whether to enable the selected items to use the template. The value must be true. For details, see Example | [Customized Template](demo#select-template) | -| optionDisabledKey | `string` | '' | Optional. A single option is disabled. If the input resource options type is Object, for example, Disabled, and the disabled attribute of the object is true, this option is disabled. When this parameter is set to `''`, a single option is not disabled. | [Disabled](demo#disabled) | -| optionImmutableKey | `string` | '' | Optional. A single option is disabled. If the input resource option type is Object, for example, immutable, and the immutable attribute of the object is true, the option cannot be changed. This parameter does not take effect when it is set to `''`. | [Disabled](demo#disabled) | -| noResultItemTemplate | `TemplateRef` | -- | Optional. No matching result is displayed. | -| keepMultipleOrder | `string` | 'user-select' | Optional. `'user-select' \| 'origin'` indicates whether to maintain the original array or user-selected sequence when multiple selections are performed, the default value is the user order. | [Sets the selected order source array order or selection order](demo#multi-keep-order) | -| customViewTemplate | `TemplateRef` | -- | Optional. Content displayed in the customized area can be customized. You can choose to select an item. Two mandatory parameters need to be transferred. The first parameter is the selected option, the second parameter is the index value of the option in the list, and the event parameter is Optional. if this parameter is left empty, handle the pop-up. For details, see demo | [Custom Area](demo#custom-area) | -| customViewDirection | `'bottom' \|'right'\|'left'` | 'bottom' | customViewTemplate position in the relative drop-down list box | [Customizing Area Orientation and Selecting](demo#custom-area-direction) | -| appendToBody | `boolean` | false | Optional. true: The value is attached to the body. | [Attach to the body](demo#append-to-body) | -| appendToBodyDirections | `Array` | `['rightDown','leftDown','rightUp','leftUp']` | Optional. The first position in the array is preferred for the direction array, for details about AppendToBodyDirection and ConnectedPosition, see dropdown | [CCustomizing Area Orientation and Selecting](demo#custom-area-direction) | -| autoFocus | `boolean` | false | Optional. Whether to enable auto-focus. | -| toggleOnFocus | `boolean` | false | Optional. indicating whether to automatically expand the drop-down list box by focusing. | -| width | `number` | -- | Optional. This parameter is used with appendToBody to set the drop-down width. | [Customizing Area Orientation and Selecting](demo#custom-area-direction) | -| virtualScroll | `boolean` | false | Optional. Whether to use virtual scrolling. This parameter is used in scenarios with a large amount of data. | [Virtual scrolling or lazy loading](demo#lazy-load-virtual-scroll) | -| allowClear | `boolean` | false | Optional specifies whether to clear the selected value. This parameter applies only to single-choice scenarios. | [Allowed to clear value](demo#allow-clear-value) | -| inputItemTemplate | `TemplateRef` | -- | Optional. Customized template. If this parameter is transferred, ContentChild is ignored. | | -| ~~~notAutoScroll~~~ | `boolean` | false | `To be renamed`~~~ Optional. When autofocus is enabled, the system automatically scrolls to the select position.~~~ | -| templateItemSize | `number` | false | `To be improved` Optional. The height of a single template is required. appendToBody must be true. | -| loadingTemplateRef | `TemplateRef` | -- | Optional. Customized loading template | [Virtual scrolling or lazy loading](demo#lazy-load-virtual-scroll) | - -### d-select event - -| Event | Type | Description | Jump to Demo | -| :----------: | :-------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | -| valueChange | `EventEmitter\|any>` | Optional. output function. This function is invoked after an option is selected. The parameter is the value of the current option. | -| toggleChange | `EventEmitter` | Optional. output function. It is optional. It is used to enable or disable the toggle event. | [Asynchronous loading indicates that the toggle event is being loaded](demo#async-loading) | -| loadMore | `EventEmitter<{instance: SelectComponent, event: ScrollEvent}>` | lazy loading trigger event. This event is used together with `enableLazyLoad` and `$event.instance.loadFinish()` is used to end the loading. event indicates the lazy loading listening scrolling event. For details, see dLazyLoad | [Virtual scrolling or lazy loading](demo#lazy-load-virtual-scroll) | . | - -Note: After appendToBody is used, use `cdkScrollable` where the scroll bar exists. - -```terminal -npm install @angular/cdk --save -``` - -```TypeScript -import {ScrollDispatchModule} from '@angular/cdk/scrolling'; -@NgModule({ -imports: [ -//... -ScrollDispatchModule, -//... -] -}) -``` - -```html -
- -
-``` diff --git a/devui/select/index.ts b/devui/select/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..160de4aab8aa0ab3594ab1a68b3bc0af2220587e --- /dev/null +++ b/devui/select/index.ts @@ -0,0 +1,10 @@ +import { App } from 'vue' +import Select from './src/select' + +Select.install = function(Vue: App) { + Vue.component(Select.name, Select) +}; + +Select.version = '0.0.1' + +export default Select diff --git a/devui/select/select.tsx b/devui/select/select.tsx deleted file mode 100644 index e011365d12a0dbb5f05e92e1df2b56a332bba62f..0000000000000000000000000000000000000000 --- a/devui/select/select.tsx +++ /dev/null @@ -1,13 +0,0 @@ - -import { defineComponent } from 'vue' - -export default defineComponent({ - name: 'd-select', - props: { - }, - setup(props, ctx) { - return () => { - return
devui-select
- } - } -}) \ No newline at end of file diff --git a/devui/select/src/select.scss b/devui/select/src/select.scss new file mode 100644 index 0000000000000000000000000000000000000000..f520b07dc28a7aadcb23db0fb55822eaea3efde1 --- /dev/null +++ b/devui/select/src/select.scss @@ -0,0 +1,188 @@ +@import '../../style/mixins/index'; +@import '../../style/theme/color'; +@import '../../style/theme/corner'; + +$border-change-time: 300ms; +$border-change-function: cubic-bezier(0.645, 0.045, 0.355, 1); +$select-input-height-sm: 24px; +$select-input-height-md: 28px; +$select-input-height-lg: 44px; +$select-arrow-width: 28px; +$transition-base-time: 0.25s; +$select-dropdown-max-height: 300px; +$select-item-font-size: var(--devui-font-size, 12px); +$select-item-min-height: 36px; + +@mixin border-transition { + transition: border-color $border-change-time $border-change-function; +} + +.devui-select { + position: relative; + width: 100%; +} + +.devui-select-underlined { + border-bottom: 1px solid $devui-form-control-line; + @include border-transition(); + + &:not([disabled]):not(.disabled) { + &:hover { + border-color: $devui-form-control-line-hover; + } + + &.devui-select-open { + border-color: $devui-form-control-line-active; + } + } + + .devui-select-input { + border: none; + } +} + +.devui-select-open { + .devui-select-arrow { + transform: rotate3d(0, 0, 1, 180deg); + } +} + +.devui-select-selection { + position: relative; +} + +.devui-select-input { + cursor: pointer; + width: 100%; + height: $select-input-height-md; + padding: 4px $select-arrow-width 4px 10px; + color: $devui-text; + vertical-align: middle; + border: 1px solid $devui-form-control-line; + border-radius: $devui-border-radius; + outline: none; + background-color: $devui-base-bg; + @include border-transition(); + + &:not([disabled]):not(.disabled) { + &:hover { + border-color: $devui-form-control-line-hover; + } + + &:focus { + border-color: $devui-form-control-line-active; + } + } + + &[disabled], + &.disabled { + &:hover { + cursor: not-allowed; + background-color: $devui-disabled-bg; + border-color: $devui-disabled-line; + color: $devui-disabled-text; + } + } + + &.devui-select-input-lg { + height: $select-input-height-lg; + } + + &.devui-select-input-sm { + height: $select-input-height-sm; + } +} + +.devui-select-arrow { + position: absolute; + right: 0; + height: 100%; + width: $select-arrow-width; + display: inline-flex; + justify-content: center; + align-items: center; + transform: rotate3d(0, 0, 1, 0deg); + transition: transform $transition-base-time ease-out; +} + +.devui-select-dropdown { + position: absolute; + width: calc(100% - 2px); + overflow: auto; + top: 100%; + left: 0; + margin: 5px 0; + border-radius: $devui-border-radius; + background: $devui-base-bg; + box-shadow: 0 2px 5px 0 $devui-shadow; + z-index: 999; +} + +.devui-select-dropdown-list { + max-height: $select-dropdown-max-height; + width: 100%; + overflow-y: auto; + padding: 0; + margin: 0; +} + +.devui-select-item { + font-size: $select-item-font-size; + display: block; + min-height: $select-item-min-height; + line-height: 1.5; + width: 100%; + padding: 10px; + clear: both; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + border: 0; + color: $devui-text; + cursor: pointer; + + &:hover:not(.active) { + color: $devui-list-item-hover-text; + background-color: $devui-list-item-hover-bg; + } + + &.active { + color: $devui-list-item-active-text; + background-color: $devui-list-item-active-bg; + } +} + +.devui-scrollbar { + &::-webkit-scrollbar { + width: 8px; + height: 8px; + } + + &::-webkit-scrollbar-corner { + background-color: transparent; + } + + &::-webkit-scrollbar-thumb { + border-radius: 8px; + background-color: var(--devui-line, #adb0b8); + } + + &::-webkit-scrollbar-track { + background-color: transparent; + } +} + +.fade-enter-active, +.fade-leave-active { + transform: scale3d(0, 1, 0, 0.9999) translate3d(0, 1, 0, 0); + transform-origin: 0 0%; + opacity: 1; + transition: transform, opacity $transition-base-time ease-out; +} + +.fade-enter-from, +.fade-leave-to { + transform: scale3d(0, 1, 0, 0) translate3d(0, 1, 0, -4px); + opacity: 0; + transition: transform, opacity $transition-base-time ease-in; +} diff --git a/devui/select/src/select.tsx b/devui/select/src/select.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b164187c6c3c1829ea41cdccccea570eca7e255b --- /dev/null +++ b/devui/select/src/select.tsx @@ -0,0 +1,119 @@ +import { defineComponent, ref, Transition, toRefs } from 'vue'; +import { selectProps, SelectProps, OptionItem } from './use-select'; +import DIcon from '../../icon/src/icon'; +import { className } from './utils'; +import './select.scss'; + +export default defineComponent({ + name: 'DSelect', + props: selectProps, + emits: ['toggleChange', 'valueChange', 'update:modelValue'], + setup(props: SelectProps, ctx) { + const isOpen = ref(false); + function toggleChange(bool: boolean) { + isOpen.value = bool; + ctx.emit('toggleChange', bool); + } + + const inputValue = ref(props.modelValue + ''); + initInputValue(); + + function initInputValue() { + props.options.forEach((item) => { + if (typeof item === 'object' && item.value === props.modelValue) { + inputValue.value = item.name; + } + }); + } + + function valueChange(item: OptionItem, index: number) { + const value = typeof item === 'object' ? item.value : item; + inputValue.value = getInputValue(item); + ctx.emit('update:modelValue', value); + ctx.emit('valueChange', item, index); + toggleChange(false); + } + + function getItemClassName(item: OptionItem) { + const value = typeof item === 'object' ? item.value : item; + return className('devui-select-item', { + active: value === props.modelValue, + }); + } + + function getInputValue(item: OptionItem) { + const value = typeof item === 'object' ? item.name : item; + return value + ''; + } + + return { + isOpen, + inputValue, + valueChange, + toggleChange, + getItemClassName, + ...toRefs(props), + }; + }, + render() { + const { + options, + isOpen, + inputValue, + size, + placeholder, + overview, + valueChange, + toggleChange, + getItemClassName, + } = this; + + const selectClassName = className('devui-select', { + 'devui-select-open': isOpen, + 'devui-select-lg': size === 'lg', + 'devui-select-sm': size === 'sm', + 'devui-select-underlined': overview === 'underlined', + }); + + const inputClassName = className('devui-select-input', { + 'devui-select-input-lg': size === 'lg', + 'devui-select-input-sm': size === 'sm', + }); + + return ( +
+
+ toggleChange(!isOpen)} + onBlur={() => toggleChange(false)} + /> + + + +
+ +
+
    + {options.map((item, i) => ( +
  • { + valueChange(item, i); + }} + class={getItemClassName(item)} + key={i} + > + {typeof item === 'object' ? item.name : item} +
  • + ))} +
+
+
+
+ ); + }, +}); diff --git a/devui/select/src/use-select.ts b/devui/select/src/use-select.ts new file mode 100644 index 0000000000000000000000000000000000000000..60b40ee1e6ce3d782246a1991d801ece7447056c --- /dev/null +++ b/devui/select/src/use-select.ts @@ -0,0 +1,46 @@ +import { PropType, ExtractPropTypes } from 'vue'; + +export interface OptionObjectItem { + name: string + value: string | number + [key: string]: any +} +export type OptionItem = number | string | OptionObjectItem; +export type Options = Array; + +export const selectProps = { + modelValue: { + type: [String, Number] as PropType, + default: '', + }, + 'onUpdate:modelValue': { + type: Function as PropType<(val: string | number) => void>, + default: undefined, + }, + options: { + type: Array as PropType, + default: () => [], + }, + size: { + type: String as PropType<'sm' | 'md' | 'lg'>, + default: 'md', + }, + overview: { + type: String as PropType<'border' | 'underlined'>, + default: 'border', + }, + placeholder: { + type: String, + default: '请选择', + }, + onToggleChange: { + type: Function as PropType<(bool: boolean) => void>, + default: undefined, + }, + onValueChange: { + type: Function as PropType<(item: OptionItem, index: number) => void>, + default: undefined, + }, +} as const; + +export type SelectProps = ExtractPropTypes; diff --git a/devui/select/src/utils.ts b/devui/select/src/utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..3092da7789a04ffc4321311f268cccd1b802bb9b --- /dev/null +++ b/devui/select/src/utils.ts @@ -0,0 +1,19 @@ +/** + * 动态获取class字符串 + * @param classStr 是一个字符串,固定的class名 + * @param classOpt 是一个对象,key表示class名,value为布尔值,true则添加,否则不添加 + * @returns 最终的class字符串 + */ +export function className( + classStr: string, + classOpt?: { [key: string]: boolean; } +): string { + let classname = classStr; + if (typeof classOpt === 'object') { + Object.keys(classOpt).forEach((key) => { + classOpt[key] && (classname += ` ${key}`); + }); + } + + return classname; +} diff --git a/sites/.vitepress/config/sidebar.ts b/sites/.vitepress/config/sidebar.ts index e98545224338c6552904b293921b37e8debdb97e..2d639b4ff14139647fa4aa011514bbdb89df9a46 100644 --- a/sites/.vitepress/config/sidebar.ts +++ b/sites/.vitepress/config/sidebar.ts @@ -26,6 +26,7 @@ const sidebar = { children: [ { text: 'Checkbox 复选框', link: '/components/checkbox/' }, { text: 'Radio 单选框', link: '/components/radio/' }, + { text: 'Select 下拉选择框', link: '/components/select/' }, { text: 'Switch 开关', link: '/components/switch/' }, { text: 'TagsInput 标签输入', link: '/components/tags-input/' }, { text: 'TextInput 文本框', link: '/components/text-input/' } diff --git a/sites/components/select/index.md b/sites/components/select/index.md new file mode 100644 index 0000000000000000000000000000000000000000..4af9aadcfef8cf06b7909c8073c71cb2810e9873 --- /dev/null +++ b/sites/components/select/index.md @@ -0,0 +1,88 @@ +# Select 下拉选择框 + +用于从列表中选择单个或者多个数据 + +### 何时使用 + +需要从列表中选择单个或者多个数据 + +### 基本用法 + +#### Large + +
+ +
+ +#### Middle + +
+ +
+ +#### Small + +
+ +
+ +#### Underlined + +
+ +
+ +```html + + + + + + + +``` + +