plain-design 1.0.0-beta.78 → 1.0.0-beta.79

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,220 @@
1
+ @include comp(dialog) {
2
+ &.#{componentName(search-service)} {
3
+ align-items: flex-start;
4
+ padding: 80px 0;
5
+
6
+ .dialog-content {
7
+ overflow: hidden;
8
+ }
9
+
10
+ @include comp(empty) {
11
+ min-height: 100px;
12
+ }
13
+
14
+ .search-service-input-box {
15
+ @include comp(input) {
16
+ border-color: plv(primary-6) !important;
17
+
18
+ .input-box {
19
+ color: plv(primary-6) !important;
20
+ font-size: 1.2em;
21
+ height: 2.5em !important;
22
+ }
23
+
24
+ .input-prefix-icon {
25
+ width: 2em;
26
+ font-size: 1.6em;
27
+ display: flex;
28
+ align-items: center;
29
+ justify-content: center;
30
+ color: plv(primary-6);
31
+ position: relative;
32
+ left: 0.15em;
33
+ }
34
+ .input-suffix-icon-wrapper {
35
+ width: 1.5em;
36
+ font-size: 1.6em;
37
+ }
38
+ @include comp(loading) {
39
+ }
40
+ }
41
+ }
42
+
43
+ .search-service-foot {
44
+ display: flex;
45
+ align-items: center;
46
+ justify-content: center;
47
+ font-size: 0.8em;
48
+ color: plv(text-3);
49
+ border-top: solid 1px plv(border-color);
50
+
51
+ & > * + * {
52
+ display: inline-block;
53
+ margin-left: 1em;
54
+ }
55
+
56
+ svg {
57
+ position: relative;
58
+ top: -1px;
59
+ }
60
+ }
61
+
62
+ .search-service-option-virtual-list {
63
+ height: 50vh;
64
+ }
65
+
66
+ .search-service-option-item {
67
+ user-select: none;
68
+
69
+ .search-service-option-item-default {
70
+ text-align: left;
71
+ box-sizing: border-box;
72
+
73
+ &[data-service-item-type="group"] {
74
+ .search-service-option-item-default-title {
75
+ font-weight: 600;
76
+ color: plv(primary-6);
77
+ font-size: 1em;
78
+ }
79
+ }
80
+
81
+ &:not([data-service-item-type="group"]) {
82
+ background-color: plv(bg-4);
83
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.25);
84
+ transition: all ease 150ms;
85
+
86
+ .search-service-option-item-default-box {
87
+ display: flex;
88
+ align-items: center;
89
+
90
+ .search-service-option-item-default-label {
91
+ flex: 1;
92
+ display: flex;
93
+ flex-direction: column;
94
+ }
95
+
96
+ .search-service-option-item-default-button {
97
+ opacity: 0;
98
+ display: flex;
99
+ align-items: center;
100
+ }
101
+ }
102
+ }
103
+
104
+ }
105
+
106
+ &[data-active=true] {
107
+ .search-service-option-item-default {
108
+ &:not([data-service-item-type="group"]) {
109
+ background-color: plv(primary-6);
110
+
111
+ svg, .search-service-option-item-default-label {
112
+ color: plv(pbfc);
113
+
114
+ span {
115
+ color: plv(pbfc) !important;
116
+ }
117
+ }
118
+
119
+ .search-service-option-item-default-button {
120
+ opacity: 1;
121
+ }
122
+ }
123
+ }
124
+ }
125
+
126
+ .search-service-option-item-default-button {
127
+ padding: 0.2em;
128
+
129
+ &[data-search-button="favorite"], &[data-search-button="remove"] {
130
+ &:hover {
131
+ background-color: plv(primary-8);
132
+ border-radius: 99px;
133
+ }
134
+ }
135
+
136
+ &[data-search-button="favorite"] {
137
+ &:hover {
138
+ path {
139
+ fill: plv(pbfc)
140
+ }
141
+ }
142
+ }
143
+
144
+ & + .search-service-option-item-default-button {
145
+ margin-left: 0.1em !important;
146
+ }
147
+ }
148
+ }
149
+ }
150
+
151
+ @include comp(search-service-panel) {
152
+ @include sizeMixin(box, ()) {
153
+ .search-service-input-box {
154
+ padding: $margin;
155
+ @include comp(input) {
156
+ border-radius: calc(#{$border-radius} / 1.5);
157
+ }
158
+ }
159
+ .search-service-foot {
160
+ padding: calc(#{$margin});
161
+ }
162
+
163
+ .search-service-option-item {
164
+ width: calc(100% - (#{$margin} * 2));
165
+ margin-left: $margin;
166
+ padding-bottom: 8px;
167
+
168
+ .search-service-option-item-default {
169
+ border-radius: calc(#{$border-radius} / 1.5);
170
+
171
+ &:not([data-service-item-type="group"]) {
172
+ padding: calc(#{$margin} / 2);
173
+ cursor: pointer;
174
+
175
+ .search-service-option-item-default-box {
176
+ min-height: calc(3.5em - #{$margin} / 2);
177
+
178
+ & > * + * {
179
+ margin-left: 0.5em;
180
+ }
181
+
182
+ .search-service-option-item-default-tree {
183
+ width: 1.8em;
184
+ height: 3.2em;
185
+ }
186
+
187
+ .search-service-option-item-default-label {
188
+
189
+ & > span:nth-child(1) {
190
+ color: plv(text-1);
191
+ }
192
+
193
+ & > span:nth-child(2) {
194
+ color: plv(text-3);
195
+ font-size: 0.8em;
196
+ margin-top: 0.25em;
197
+ }
198
+ }
199
+ }
200
+ }
201
+ }
202
+ }
203
+ .search-service-option-item-default {
204
+ &[data-service-item-type="group"] {
205
+ .search-service-option-item-default-title {
206
+ }
207
+ }
208
+ }
209
+
210
+ .search-service-empty {
211
+ padding-bottom: $margin;
212
+ margin: $margin 0;
213
+
214
+ svg + div {
215
+ margin-top: $margin;
216
+ }
217
+ }
218
+ }
219
+ }
220
+ }
@@ -0,0 +1,112 @@
1
+ import {iMouseEvent} from "plain-design-composition";
2
+
3
+ export interface iSearchDataRender {
4
+ (dataMeta: iSearchDataMeta): any;
5
+ }
6
+
7
+ export interface iSearchDataMeta {
8
+ title?: string,
9
+ desc?: string,
10
+ type: 'group' | 'page' | 'header' | 'sub_header' | 'content' | 'favorite' | 'history',
11
+ data?: any
12
+ }
13
+
14
+ export interface iSearchServiceDefaultConfig {
15
+ width: number,
16
+ render?: iSearchDataRender,
17
+ placeholder: string,
18
+ footer?: () => any,
19
+ cacheName?: string,
20
+ }
21
+
22
+ export interface iSearchServiceCustomConfig {
23
+ getData: (searchKey: string) => Promise<iSearchDataMeta[]>,
24
+ onSelect: (data: iSearchDataMeta) => void,
25
+ }
26
+
27
+ export type iSearchServiceConfig = iSearchServiceDefaultConfig & iSearchServiceCustomConfig
28
+
29
+ export const SearchType2Icon: Record<iSearchDataMeta['type'], () => any> = {
30
+ group: () => null,
31
+ page: () => (
32
+ <svg width="20" height="20" viewBox="0 0 20 20">
33
+ <path d="M17 6v12c0 .52-.2 1-1 1H4c-.7 0-1-.33-1-1V2c0-.55.42-1 1-1h8l5 5zM14 8h-3.13c-.51 0-.87-.34-.87-.87V4" stroke="currentColor" fill="none" fillRule="evenodd" strokeLinejoin="round"></path>
34
+ </svg>
35
+ ),
36
+ header: () => (
37
+ <svg width="20" height="20" viewBox="0 0 20 20">
38
+ <path d="M13 13h4-4V8H7v5h6v4-4H7V8H3h4V3v5h6V3v5h4-4v5zm-6 0v4-4H3h4z" stroke="currentColor" fill="none" fillRule="evenodd" strokeLinecap="round" strokeLinejoin="round"></path>
39
+ </svg>
40
+ ),
41
+ sub_header: () => (
42
+ <svg width="20" height="20" viewBox="0 0 20 20">
43
+ <path d="M13 13h4-4V8H7v5h6v4-4H7V8H3h4V3v5h6V3v5h4-4v5zm-6 0v4-4H3h4z" stroke="currentColor" fill="none" fillRule="evenodd" strokeLinecap="round" strokeLinejoin="round"></path>
44
+ </svg>
45
+ ),
46
+ content: () => (
47
+ <svg width="20" height="20" viewBox="0 0 20 20">
48
+ <path d="M17 5H3h14zm0 5H3h14zm0 5H3h14z" stroke="currentColor" fill="none" fillRule="evenodd" strokeLinejoin="round"></path>
49
+ </svg>
50
+ ),
51
+ history: () => (
52
+ <svg width="20" height="20" viewBox="0 0 20 20">
53
+ <g stroke="currentColor" fill="none" fillRule="evenodd" strokeLinecap="round" strokeLinejoin="round">
54
+ <path d="M3.18 6.6a8.23 8.23 0 1112.93 9.94h0a8.23 8.23 0 01-11.63 0"></path>
55
+ <path d="M6.44 7.25H2.55V3.36M10.45 6v5.6M10.45 11.6L13 13"></path>
56
+ </g>
57
+ </svg>
58
+ ),
59
+ favorite: () => (
60
+ <svg width="20" height="20" viewBox="0 0 20 20">
61
+ <path d="M10 14.2L5 17l1-5.6-4-4 5.5-.7 2.5-5 2.5 5 5.6.8-4 4 .9 5.5z" stroke="currentColor" fill="none" fillRule="evenodd" strokeLinejoin="round"></path>
62
+ </svg>
63
+ ),
64
+ };
65
+
66
+ export const SearchTreeIcon = {
67
+ normal: () => (<svg className="search-service-option-item-default-tree" viewBox="0 0 24 54">
68
+ <g stroke="currentColor" fill="none" fillRule="evenodd" strokeLinecap="round" strokeLinejoin="round">
69
+ <path d="M8 6v42M20 27H8.3"></path>
70
+ </g>
71
+ </svg>),
72
+ last: () => (
73
+ <svg className="search-service-option-item-default-tree" viewBox="0 0 24 54">
74
+ <g stroke="currentColor" fill="none" fillRule="evenodd" strokeLinecap="round" strokeLinejoin="round">
75
+ <path d="M8 6v21M20 27H8.3"></path>
76
+ </g>
77
+ </svg>
78
+ )
79
+ };
80
+
81
+ export const SearchOptionButton = {
82
+ normal: (onClick?: (e: iMouseEvent) => void) => {
83
+ return (
84
+ <div key="normal" className="search-service-option-item-default-button" data-search-button="normal" onClick={onClick}>
85
+ <svg width="20" height="20" viewBox="0 0 20 20">
86
+ <g stroke="currentColor" fill="none" fillRule="evenodd" strokeLinecap="round" strokeLinejoin="round">
87
+ <path d="M18 3v4c0 2-2 4-4 4H2"></path>
88
+ <path d="M8 17l-6-6 6-6"></path>
89
+ </g>
90
+ </svg>
91
+ </div>
92
+ );
93
+ },
94
+ favorite: (onClick?: (e: iMouseEvent) => void) => {
95
+ return (
96
+ <div key="favorite" className="search-service-option-item-default-button" data-search-button="favorite" onClick={onClick}>
97
+ <svg width="20" height="20" viewBox="0 0 20 20">
98
+ <path d="M10 14.2L5 17l1-5.6-4-4 5.5-.7 2.5-5 2.5 5 5.6.8-4 4 .9 5.5z" stroke="currentColor" fill="none" fillRule="evenodd" strokeLinejoin="round"></path>
99
+ </svg>
100
+ </div>
101
+ );
102
+ },
103
+ remove: (onClick?: (e: iMouseEvent) => void) => {
104
+ return (
105
+ <div key="remove" className="search-service-option-item-default-button" data-search-button="remove" onClick={onClick}>
106
+ <svg width="20" height="20" viewBox="0 0 20 20">
107
+ <path d="M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z" stroke="currentColor" fill="none" fillRule="evenodd" strokeLinecap="round" strokeLinejoin="round"></path>
108
+ </svg>
109
+ </div>
110
+ );
111
+ },
112
+ };
@@ -1,4 +1,4 @@
1
- import {computed, designComponent, Fragment, getComponentCls, iHTMLDivElement, mergeAttrs, useClasses, useRefs, useStyles} from "plain-design-composition";
1
+ import {computed, designComponent, Fragment, getComponentCls, iHTMLDivElement, mergeAttrs, onMounted, useClasses, useRefs, useStyles} from "plain-design-composition";
2
2
  import './index.scss';
3
3
  import {ThemeStatus, useStyle} from "../../uses/useStyle";
4
4
  import {useEdit} from "../../uses/useEdit";
@@ -17,6 +17,7 @@ import {useFocusHandler} from "../../uses/useFocusHandler";
17
17
  import {useSuggestionInput} from "./useSuggestionInput";
18
18
  import {useMultipleModel} from "../../uses/useMultipleModel";
19
19
  import {useParentPopupId} from "../usePopup/utils/popup.utils";
20
+ import {delay} from "plain-utils/utils/delay";
20
21
 
21
22
  export const Input = designComponent({
22
23
  name: '-input',
@@ -106,6 +107,13 @@ export const Input = designComponent({
106
107
  hooks.onInput.use(val => emit.onInput(val));
107
108
  hooks.onSuggestion.use(data => emit.onSuggestion(data));
108
109
 
110
+ onMounted(async () => {
111
+ await delay(78);
112
+ if (attrs.autoFocus) {
113
+ refs.input?.focus();
114
+ }
115
+ });
116
+
109
117
  return {
110
118
  refer: {
111
119
  model,
@@ -26,10 +26,11 @@ export const InputPropsOption = {
26
26
  multipleSeparator: { default: /[\s\n,,]/ }, // 多值输入的时候的分隔符。类型为字符串或者正则表达式;有这个分隔符的话,会自动按照这个分隔符对输入的文本分割
27
27
  fixedWidth: { type: Boolean }, // InputGroup下是否固定宽度
28
28
  bare: { type: Boolean }, // 去掉包裹节点
29
+ loadingType: { type: String }, // loading类型
29
30
 
30
31
  /*clear*/
31
32
  noClear: { type: Boolean }, // 去掉清空按钮
32
- clearHandler: { type: Function as PropType<(e: iMouseEvent) => void> }, // 自定义处理清空逻辑
33
+ clearHandler: { type: Function as PropType<(e: iMouseEvent) => void> }, // 自定义处理清空逻辑
33
34
 
34
35
  /*textarea*/
35
36
  textarea: { type: Boolean }, // 当前是否为文本域输入框
@@ -59,7 +59,7 @@ export function useInputSuffixIcon({ slots, hooks, props, editComputed, model, e
59
59
  <span className="input-suffix-icon-wrapper" onMouseDown={handler.onMousedownSuffix}>
60
60
  {hasSuffixIcon.value && slots.suffixIconContent(<Icon icon={props.suffixIcon} className="input-suffix-icon"/>)}
61
61
  {hasSuffixClear.value && (<Icon icon="pi-close-circle-fill" className="input-suffix-clear"/>)}
62
- {editComputed.value.loading && <Loading/>}
62
+ {editComputed.value.loading && <Loading type={props.loadingType}/>}
63
63
  </span>
64
64
  );
65
65
 
@@ -8,6 +8,7 @@ import {VerticalScrollbar} from "./VerticalScrollbar";
8
8
  import {HorizontalScrollbar} from "./HorizontalScrollbar";
9
9
  import {ResizeDetectFuncParam, useResizeDetector} from "../../directives/ResizeDetector";
10
10
  import {createScrollUtils} from "../createScrollUtils";
11
+ import {getRectAutoFormat} from "plain-utils/dom/getRectAutoFormat";
11
12
 
12
13
  export enum PLAIN_SCROLL_VERTICAL_POSITION {
13
14
  top = 'top',
@@ -213,6 +214,39 @@ export const Scroll = designComponent({
213
214
  autoScrollLeft() {scrollUtils.autoScrollLeft();},
214
215
  autoScrollRight() {scrollUtils.autoScrollRight();},
215
216
  stopAutoScroll() {scrollUtils.stopAutoScroll();},
217
+ showElement: (elementSelector: string | HTMLElement): boolean => {
218
+ if (!refs.host || !refs.content) {return false;}
219
+ const selectElement = typeof elementSelector === "string" ? refs.content?.querySelector(elementSelector) : elementSelector;
220
+
221
+ if (selectElement == null) {return false;}
222
+ const selectElementRect = getRectAutoFormat(selectElement as any);
223
+ const contentElementRect = getRectAutoFormat(refs.content);
224
+ const hostElementRect = getRectAutoFormat(refs.host);
225
+
226
+ /*vertical*/
227
+ if (selectElementRect.bottom >= hostElementRect.bottom) {
228
+ /*在下面*/
229
+ const offsetTop = selectElementRect.top - contentElementRect.top;
230
+ methods.scrollTop(offsetTop - (hostElementRect.height - selectElementRect.height));
231
+ } else if (selectElementRect.top <= hostElementRect.top) {
232
+ /*在上面*/
233
+ const offsetTop = selectElementRect.top - contentElementRect.top;
234
+ methods.scrollTop(offsetTop);
235
+ }
236
+
237
+ /*horizontal*/
238
+ if (selectElementRect.right >= hostElementRect.right) {
239
+ /*在右边*/
240
+ const offsetLeft = selectElementRect.left - contentElementRect.left;
241
+ methods.scrollLeft(offsetLeft - (hostElementRect.width - selectElementRect.width));
242
+ } else if (selectElementRect.left <= hostElementRect.left) {
243
+ /*在左边*/
244
+ const offsetLeft = selectElementRect.left - contentElementRect.left;
245
+ methods.scrollLeft(offsetLeft);
246
+ }
247
+
248
+ return true;
249
+ },
216
250
  /**
217
251
  * 禁用list的队列动画,300ms后恢复正常
218
252
  * @author 韦胜健
@@ -58,6 +58,9 @@ export function usePlcTreeRenderNode(
58
58
  */
59
59
  const renderTreeNode = (renderScope: iTableCellRenderScope) => {
60
60
  const treeNode = treeCore.flatNodeComputedData.value[renderScope.node.state.index];
61
+ if (!treeNode) {
62
+ return null;
63
+ }
61
64
  return (
62
65
  <RenderPlcTreeNode
63
66
  treeCore={treeCore}
@@ -189,6 +189,14 @@ export type {
189
189
  iPlainResponseDataType,
190
190
  iHttpResponseDataType
191
191
  } from './components/createHttp/http.utils';
192
+ export type {
193
+ iSearchServiceCustomConfig,
194
+ iSearchServiceConfig,
195
+ iSearchServiceDefaultConfig,
196
+ iSearchDataMeta,
197
+ iSearchDataRender,
198
+ } from './components/$search/search.utils';
199
+ export {$search} from './components/$search';
192
200
 
193
201
  export {Address} from './components/Address';
194
202
  export {AddressCascade} from './components/AddressCascade';
@@ -305,5 +305,13 @@ export const EnUsLocale: tZhCnLocale = {
305
305
  洋红: 'Magenta',
306
306
  极昼: 'Light',
307
307
  黑夜: 'Dark',
308
- }
308
+ },
309
+ search: {
310
+ select: 'Select',
311
+ switch: 'Switch',
312
+ noHistory: 'No Search History',
313
+ noMatch: 'There are no search result that can match "{val}"',
314
+ searchHistory: 'Search History',
315
+ favorite: 'Favorite'
316
+ },
309
317
  };
@@ -115,8 +115,8 @@ export const ZhCnLocale = {
115
115
  save: '保存',
116
116
  done: '完成',
117
117
  },
118
- yes:'是',
119
- no:'否',
118
+ yes: '是',
119
+ no: '否',
120
120
  },
121
121
  formatter: {
122
122
  week: 'gggg年第ww周',
@@ -303,7 +303,15 @@ export const ZhCnLocale = {
303
303
  洋红: '洋红',
304
304
  极昼: '极昼',
305
305
  黑夜: '黑夜',
306
- }
306
+ },
307
+ search: {
308
+ select: '选择',
309
+ switch: '切换',
310
+ noHistory: '没有搜索历史',
311
+ noMatch: '无法找到搜索结果 "{val}"',
312
+ searchHistory: '搜索历史',
313
+ favorite: '收藏'
314
+ },
307
315
  } as const;
308
316
 
309
317
  type ZhCnLocaleUtils<T> = { [k in keyof T]: T[k] extends string ? string : ZhCnLocaleUtils<T[k]> }
@@ -0,0 +1,38 @@
1
+ export function createListUtils<T>(
2
+ {
3
+ getList,
4
+ current,
5
+ }: {
6
+ getList: () => T[],
7
+ current: () => number | undefined | null,
8
+ }
9
+ ) {
10
+ return {
11
+ prevIndex: () => {
12
+ const list = getList();
13
+ if (!list.length) {
14
+ return -1;
15
+ }
16
+ let curIdx = current();
17
+ if (curIdx == null || curIdx - 1 < 0) {
18
+ curIdx = list.length - 1;
19
+ } else {
20
+ curIdx = curIdx - 1;
21
+ }
22
+ return curIdx;
23
+ },
24
+ nextIndex: () => {
25
+ const list = getList();
26
+ if (!list.length) {
27
+ return -1;
28
+ }
29
+ let curIdx = current();
30
+ if (curIdx == null || curIdx + 1 > list.length - 1) {
31
+ curIdx = 0;
32
+ } else {
33
+ curIdx = curIdx + 1;
34
+ }
35
+ return curIdx;
36
+ },
37
+ };
38
+ }