plain-design 3.0.1 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plain-design",
3
- "version": "3.0.1",
3
+ "version": "3.0.2",
4
4
  "description": "",
5
5
  "main": "dist/plain-design.min.js",
6
6
  "module": "dist/plain-design.commonjs.min.js",
@@ -46,7 +46,7 @@
46
46
  "@babel/preset-env": "^7.13.15",
47
47
  "@babel/preset-react": "^7.13.13",
48
48
  "@babel/preset-typescript": "^7.13.0",
49
- "@peryl/react-compose": "0.0.225",
49
+ "@peryl/react-compose": "0.0.229",
50
50
  "@peryl/utils": "0.1.73",
51
51
  "@types/classnames": "^2.2.11",
52
52
  "@types/node": "^20.8.6",
@@ -3,10 +3,17 @@ import {$message} from "../$message";
3
3
  import i18n from "../i18n";
4
4
  import $configuration from "../$configuration";
5
5
  import {createSimpleDate} from "../createSimpleDate";
6
+ import {createEffects} from "@peryl/utils/createEffects";
7
+ import {addElementListener} from "@peryl/utils/addElementListener";
8
+ import {addWindowListener} from "@peryl/utils/addWindowListener";
9
+ import {delay} from "@peryl/utils/delay";
6
10
 
11
+ /*单个文件对象数据类型*/
7
12
  export type FileServiceSingleFile = File & { calcSize: number, data?: any }
13
+ /*文件自定义校验函数类型*/
8
14
  export type FileServiceValidator = (file: FileServiceSingleFile) => boolean | undefined
9
15
 
16
+ /*选择文件时的参数类型*/
10
17
  export type FileServiceChooseFileConfig = {
11
18
  multiple?: boolean, // 是否多选
12
19
  accept?: string, // 选择的文件类型, input组件的accept属性值
@@ -14,6 +21,7 @@ export type FileServiceChooseFileConfig = {
14
21
  max?: number, // 最大文件大小
15
22
  }
16
23
 
24
+ /*文件上传失败时的数据类型*/
17
25
  export type FileServiceUploadErrorEvent = Error & {
18
26
  status: number,
19
27
  method: string,
@@ -21,6 +29,7 @@ export type FileServiceUploadErrorEvent = Error & {
21
29
  config: FileServiceUploadConfig,
22
30
  }
23
31
 
32
+ /*文件上传方法参数类型*/
24
33
  export type FileServiceUploadConfig = {
25
34
  action: string, // 上传地址
26
35
  file: File | File[], // 上传的文件
@@ -33,6 +42,7 @@ export type FileServiceUploadConfig = {
33
42
  onError?: (e: FileServiceUploadErrorEvent | ProgressEvent) => void,
34
43
  }
35
44
 
45
+ /*选择文件类型的内置文件类型别名*/
36
46
  export const FileServiceDefaultAccept = {
37
47
  // image: 'image/gif, image/jpeg, image/png, image/jpg',
38
48
  image: 'bmp,jpg,jpeg,png,tif,gif,pcx,tga,exif,fpx,svg,psd,cdr,pcd,dxf,ufo,eps,ai,raw,WMF,webp,avif'.split(',').map(i => `image/${i}`).join(', '),
@@ -41,6 +51,9 @@ export const FileServiceDefaultAccept = {
41
51
 
42
52
  export function createFileService() {
43
53
 
54
+ /*选择文件副作用对象,每次选择文件之后都需要清理掉选择文件时产生的副作用*/
55
+ const { effects: chooseEffects } = createEffects();
56
+
44
57
  /**
45
58
  * 选择文件
46
59
  * @author 韦胜健
@@ -48,51 +61,84 @@ export function createFileService() {
48
61
  */
49
62
  const chooseFile = (config?: FileServiceChooseFileConfig) => {
50
63
  config = config || {};
51
- const input = getInput();
52
-
53
- /*multiple*/
54
- if (config.multiple) {
55
- input.setAttribute('multiple', 'multiple');
56
- } else {
57
- input.removeAttribute('multiple');
58
- }
59
- /*accept*/
60
- if (!!config.accept) {
61
- const defaultInputAccept = FileServiceDefaultAccept[config.accept];
62
- input.setAttribute('accept', defaultInputAccept || config.accept);
63
- } else {
64
- input.removeAttribute('accept');
65
- }
64
+ chooseEffects.clear();
65
+ const inputElement = (() => {
66
+ const input = getInput();
67
+ /*multiple*/
68
+ if (config.multiple) {
69
+ input.setAttribute('multiple', 'multiple');
70
+ } else {
71
+ input.removeAttribute('multiple');
72
+ }
73
+ /*accept*/
74
+ if (!!config.accept) {
75
+ const defaultInputAccept = FileServiceDefaultAccept[config.accept];
76
+ input.setAttribute('accept', defaultInputAccept || config.accept);
77
+ } else {
78
+ input.removeAttribute('accept');
79
+ }
80
+ return input;
81
+ })();
66
82
  const dfd = defer<FileServiceSingleFile | FileServiceSingleFile[]>();
67
83
 
68
- if (!!input.__change) {
69
- input.value = null as any;
70
- input.removeEventListener('change', input.__change);
71
- }
72
- input.__change = (e) => {
73
- const targetFiles = (e as any).target.files as FileServiceSingleFile[];
74
- const files = [];
75
- for (let i = 0; i < targetFiles.length; i++) {
76
- const file = targetFiles[i];
77
- file.calcSize = Number((file.size / (1024 * 1024)).toFixed(2));
78
- if (!!config!.validator && !config!.validator(file)) return;
79
- if (!!config!.max && file.calcSize > config!.max) {
80
- return $message.error(
81
- i18n.$it('file.oversize', { filename: file.name, filesize: file.calcSize, max: config!.max }).d(`[${file.name}]大小为${file.calcSize}MB,超过最大限制${config!.max}MB`),
82
- { time: 5000 });
84
+ chooseEffects.clear();
85
+
86
+ const resolve = (value: FileServiceSingleFile | FileServiceSingleFile[]) => {
87
+ dfd.resolve(value);
88
+ chooseEffects.clear();
89
+ };
90
+
91
+ const reject = (reason: string) => {
92
+ dfd.reject(reason);
93
+ chooseEffects.clear();
94
+ };
95
+
96
+ const handlers = {
97
+ /*input变化之后说明用户选择了文件*/
98
+ onInputChange: (e: Event) => {
99
+ const targetFiles = (e as any).target.files as FileServiceSingleFile[];
100
+ const files = [];
101
+ for (let i = 0; i < targetFiles.length; i++) {
102
+ const file = targetFiles[i];
103
+ file.calcSize = Number((file.size / (1024 * 1024)).toFixed(2));
104
+ if (!!config!.validator && !config!.validator(file)) {
105
+ reject("File validator failed.");
106
+ return;
107
+ }
108
+ if (!!config!.max && file.calcSize > config!.max) {
109
+ reject("File size unavailable.");
110
+ return $message.error(
111
+ i18n.$it('file.oversize', { filename: file.name, filesize: file.calcSize, max: config!.max }).d(`[${file.name}]大小为${file.calcSize}MB,超过最大限制${config!.max}MB`),
112
+ { time: 5000 }
113
+ );
114
+ }
115
+ files.push(file);
83
116
  }
84
- files.push(file);
117
+ resolve(config!.multiple ? files : files[0]);
118
+ },
119
+ /*
120
+ window focus之后,不论用户选择了文件还是取消选择文件,
121
+ 都会触发浏览器窗口的focus方法,
122
+ 这里主要用来处理用户取消选择文件时的处理动作
123
+ 取消选择文件时调用dfd的reject方法
124
+ */
125
+ onWindowFocus: async () => {
126
+ await delay(300);
127
+ if (!inputElement.value) {dfd.reject('cancel');}
85
128
  }
86
- dfd.resolve(config!.multiple ? files : files[0]);
87
129
  };
88
- input.addEventListener('change', input.__change);
89
- input.click();
130
+
131
+ chooseEffects.push(addElementListener(inputElement, 'change', handlers.onInputChange));
132
+ chooseEffects.push(() => inputElement.value = '');
133
+ chooseEffects.push(addWindowListener('focus', handlers.onWindowFocus));
134
+
135
+ inputElement.click();
90
136
 
91
137
  return dfd.promise;
92
138
  };
93
139
 
94
140
  /**
95
- * 选择tupiuan
141
+ * 选择图片
96
142
  * @author 韦胜健
97
143
  * @date 2021/1/1 17:03
98
144
  */
@@ -207,8 +253,9 @@ export function createFileService() {
207
253
  };
208
254
  }
209
255
 
256
+ /*获取一个input type为file的input元素,用来选择文件*/
210
257
  const getInput = (() => {
211
- let input: HTMLInputElement & { __change?: (e: Event) => void };
258
+ let input: HTMLInputElement;
212
259
  return () => {
213
260
  if (!input) {
214
261
  input = document.createElement('input');
@@ -230,13 +277,11 @@ function getError(config: FileServiceUploadConfig, xhr: XMLHttpRequest) {
230
277
  } else {
231
278
  message = `file to post ${config.action} ${xhr.status}`;
232
279
  }
233
-
234
280
  const error = new Error(message) as FileServiceUploadErrorEvent;
235
281
  error.status = xhr.status;
236
282
  error.method = 'post';
237
283
  error.url = config.action;
238
284
  error.config = config;
239
-
240
285
  return error;
241
286
  }
242
287
 
@@ -11,7 +11,7 @@ export const Application = designComponent({
11
11
  name: 'application',
12
12
  props: {
13
13
  configuration: { type: Object as PropType<DeepPartial<iApplicationConfiguration>>, },
14
- defaultInputMode: { type: String as PropType<typeof InputMode.TYPE>, default: InputMode.flat },
14
+ defaultInputMode: { type: String as PropType<typeof InputMode.TYPE>, default: InputMode.stroke },
15
15
  defaultButtonMode: { type: String as PropType<typeof ThemeMode.TYPE>, default: ThemeMode.stroke },
16
16
  },
17
17
  slots: ['default'],
@@ -38,6 +38,7 @@
38
38
  @include statusMixin(button) {
39
39
  background-color: $color-6;
40
40
  border-color: $color-6;
41
+ outline-color: $color-6;
41
42
  color: plv(pbfc);
42
43
  @include hover(button) {
43
44
  background-color: $color-4;
@@ -60,6 +61,7 @@
60
61
  color: plv(text-2);
61
62
  background-color: $color-light-2;
62
63
  border-color: $color-light-2;
64
+ outline-color: $color-4;
63
65
 
64
66
  &:not(.button-status-secondary) {
65
67
  color: $color-6;
@@ -91,6 +93,7 @@
91
93
  @include statusMixin(button) {
92
94
  background-color: transparent;
93
95
  border-color: $color-6;
96
+ outline-color: $color-6;
94
97
  color: $color-6;
95
98
 
96
99
  @include active(button) {
@@ -113,6 +116,7 @@
113
116
  @include statusMixin(button) {
114
117
  background-color: plv(bg-2);
115
118
  border-color: plv(secondary-light-3);
119
+ outline-color: plv(primary-4);
116
120
  color: plv(text-2);
117
121
 
118
122
  @include active(button) {
@@ -4,13 +4,13 @@ import {EditProps, useEdit} from "../../uses/useEdit";
4
4
  import {StyleProps, ThemeMode, ThemeStatus, useStyle} from "../../uses/useStyle";
5
5
  import {throttle} from "@peryl/utils/throttle";
6
6
  import {unit} from "@peryl/utils/unit";
7
- import {useClickWave} from "../../directives/ClickWave";
8
7
  import {Icon} from "../Icon";
9
8
  import {Loading} from "../Loading";
10
9
  import {ButtonGroup} from "../ButtonGroup";
11
10
  import {AutoLoadingObserver} from "../AutoLoadingObserver";
12
11
  import {useCollapseStyles} from "../../uses/useCollapseStyles";
13
12
  import {createEffects} from "@peryl/utils/createEffects";
13
+ import {useClickRipple} from "../../directives/ClickRipple/useClickRipple";
14
14
 
15
15
  export const Button = designComponent({
16
16
  name: '-button',
@@ -132,7 +132,7 @@ export const Button = designComponent({
132
132
  style.width = unit(numberState.width);
133
133
  });
134
134
 
135
- useClickWave({ elGetter: () => refs.el, optionsGetter: () => ({ size: () => styleComputed.value.size, disabled: !editComputed.value.editable }), });
135
+ useClickRipple({ elGetter: () => refs.el, optionsGetter: () => ({ size: () => styleComputed.value.size, disabled: !editComputed.value.editable || mode.value === 'text' }), });
136
136
 
137
137
  const { loadingCollapse, iconCollapse } = (() => {
138
138
  const active = () => ({
@@ -235,7 +235,7 @@
235
235
  }
236
236
  &:not(.input-mode-text) {
237
237
  @include hover(input) {
238
- background-color: $color-light-3;
238
+ //background-color: $color-light-3;
239
239
  border-color: $color-light-4;
240
240
  }
241
241
  }
@@ -252,6 +252,15 @@
252
252
  }
253
253
  }
254
254
 
255
+ &.input-status-secondary {
256
+ &:not(.input-mode-text) {
257
+ @include hover(input) {
258
+ //background-color: $color-light-3;
259
+ border-color: plv(primary-6);
260
+ }
261
+ }
262
+ }
263
+
255
264
  /*input align*/
256
265
  /*@formatter:off*/
257
266
  &.input-align-left { .input-box{text-align: left} }
@@ -9,6 +9,7 @@
9
9
  cursor: pointer;
10
10
  user-select: none;
11
11
  transition: background-color ease 300ms;
12
+ border-radius: inherit;
12
13
 
13
14
  &:hover {
14
15
  background-color: plv(secondary-light-1);
@@ -4,7 +4,6 @@ import {useStyle} from "../../uses/useStyle";
4
4
  import {useEdit} from "../../uses/useEdit";
5
5
  import {roundFixed} from "@peryl/utils/roundFixed";
6
6
  import {eKeyboardKeys, getKeyNameByKeyboardEvent} from "@peryl/utils/keyboard";
7
- import {useClickWave} from "../../directives/ClickWave";
8
7
  import {NumberResize} from "./NumberResize";
9
8
  import {NumberButtonTypes} from "./number.utils";
10
9
  import {Input} from "../Input";
@@ -274,9 +273,6 @@ export function useInputNumberPublic(param: iInputNumberCompositionParam) {
274
273
  onInput: (val: any) => {hooks.onInput.exec(val);},
275
274
  };
276
275
 
277
- useClickWave({ elGetter: () => refs.add, optionsGetter: () => ({ disabled: !editComputed.value.editable }) });
278
- useClickWave({ elGetter: () => refs.sub, optionsGetter: () => ({ disabled: !editComputed.value.editable }) });
279
-
280
276
  /**
281
277
  * 点击加减按钮的时候阻止事件冒泡,避免触发打开suggestion面板
282
278
  * @author 韦胜健
@@ -25,7 +25,7 @@ export const Scroll = designComponent({
25
25
  scrollX: { type: Boolean }, // 可以横向滚动
26
26
  scrollY: { type: Boolean, default: true }, // 可以纵向滚动
27
27
  hideScrollbar: { type: Boolean }, // 隐藏滚动条
28
- alwaysShowScrollbar: { type: Boolean }, // 一直显示滚动条
28
+ alwaysShowScrollbar: { type: Boolean, default: true }, // 一直显示滚动条
29
29
  fitContentWidth: { type: Boolean }, // 适配内容宽度
30
30
  fitContentHeight: { type: Boolean }, // 适配内容高度
31
31
  fitHostWidth: { type: Boolean }, // 适配容器宽度
@@ -10,6 +10,7 @@ import ClientZoom from "../ClientZoom";
10
10
  import {addWindowListener} from '@peryl/utils/addWindowListener';
11
11
  import i18n from "../i18n";
12
12
  import Button from "../Button";
13
+ import {InputMode} from "../../uses/useStyle";
13
14
 
14
15
  export const ThemeEditor = designComponent({
15
16
  name: 'theme-editor',
@@ -28,7 +29,7 @@ export const ThemeEditor = designComponent({
28
29
  shape: PageThemeUtils.state.shape || 'square',
29
30
  zoom: PageThemeUtils.state.zoom || null,
30
31
  primaryKey: PageThemeUtils.state.primaryKey as string || 'default',
31
- inputMode: PageThemeUtils.state.inputMode || 'flat',
32
+ inputMode: PageThemeUtils.state.inputMode || InputMode.stroke,
32
33
  buttonMode: PageThemeUtils.state.buttonMode || 'flat',
33
34
  });
34
35
 
@@ -1,10 +1,10 @@
1
1
  import {computed, designComponent, getComponentCls, iFocusEvent, iKeyboardEvent, iMouseEvent, reactive, useClasses, useModel, useRefs} from "@peryl/react-compose";
2
- import {useClickWave} from "../../directives/ClickWave";
3
2
  import './toggle.scss';
4
3
  import {EditProps, useEdit} from "../../uses/useEdit";
5
4
  import {StyleProps, ThemeStatus, useStyle} from "../../uses/useStyle";
6
5
  import {eKeyboardKeys, getKeyNameByKeyboardEvent} from "@peryl/utils/keyboard";
7
6
  import i18n from "../i18n";
7
+ import {useClickRipple} from "../../directives/ClickRipple/useClickRipple";
8
8
 
9
9
  export const Toggle = designComponent({
10
10
  name: '-toggle',
@@ -90,7 +90,7 @@ export const Toggle = designComponent({
90
90
  blur: emit.onBlur,
91
91
  };
92
92
 
93
- useClickWave({ elGetter: () => refs.el, optionsGetter: () => ({ disabled: !editComputed.value.editable }) });
93
+ useClickRipple({ elGetter: () => refs.el, optionsGetter: () => ({ disabled: !editComputed.value.editable }) });
94
94
 
95
95
  return {
96
96
  refer: { refs },
@@ -114,6 +114,7 @@
114
114
  }
115
115
 
116
116
  @include statusMixin(toggle) {
117
+ outline-color: $color-6;
117
118
  &.toggle-on {
118
119
  background-color: $color-6;
119
120
  }
@@ -0,0 +1,16 @@
1
+ @include comp(wave-node) {
2
+ position: absolute;
3
+ inset: 0;
4
+ border-radius: inherit;
5
+ display: block;
6
+ pointer-events: none;
7
+ transition: all 300ms ease;
8
+ outline: solid 0;
9
+ outline-color: inherit;
10
+ opacity: 0.5;
11
+
12
+ &[wave-node-hide] {
13
+ outline-width: 6px;
14
+ opacity: 0.05;
15
+ }
16
+ }
@@ -0,0 +1,93 @@
1
+ import {getComponentCls, onBeforeUnmount, watch} from '@peryl/react-compose';
2
+ import './click-ripple.scss';
3
+ import {delay} from "@peryl/utils/delay";
4
+ import {ThemeSize} from "../../uses/useStyle";
5
+ import {createEffects} from "@peryl/utils/createEffects";
6
+ import {addClass} from "@peryl/utils/addClass";
7
+ import {hasClass} from "@peryl/utils/hasClass";
8
+ import {findParentElement} from "@peryl/utils/findParentElement";
9
+
10
+ export interface ClickWaveOptions {
11
+ disabled?: boolean,
12
+ size?: () => typeof ThemeSize.TYPE,
13
+ }
14
+
15
+ function createClickWaveManager(el: HTMLElement, o?: ClickWaveOptions | string) {
16
+ let option: ClickWaveOptions;
17
+
18
+ const updateOption = (o?: ClickWaveOptions | string) => {
19
+ o = o || {};
20
+ if (typeof o === "string") {o = { size: o as any };}
21
+ option = o;
22
+ };
23
+
24
+ updateOption(o);
25
+
26
+ const onClick = async (e: MouseEvent) => {
27
+ // console.log('click ripple', e.target);
28
+ if (option.disabled) return;
29
+ let target = e.target as HTMLElement;
30
+ let container: HTMLElement | null = null;
31
+ if (!hasClass(target, 'plain-click-node')) {
32
+ container = target.querySelector('.plain-click-node') as HTMLElement | null;
33
+ } else {
34
+ container = target;
35
+ }
36
+ if (!container) {
37
+ container = findParentElement(target, i => hasClass(i, 'plain-click-node'));
38
+ }
39
+ if (!container) {return;}
40
+
41
+ const spanElement = document.createElement('span');
42
+ addClass(spanElement, getComponentCls('wave-node'));
43
+ container.appendChild(spanElement);
44
+ await delay(0);
45
+ spanElement.setAttribute('wave-node-hide', '');
46
+ await delay(300);
47
+ container.removeChild(spanElement);
48
+ };
49
+
50
+ el.addEventListener('click', onClick, true);
51
+
52
+ return {
53
+ updateOption,
54
+ destroy: () => {el.removeEventListener('click', onClick, true);},
55
+ };
56
+ }
57
+
58
+ type WaveManager = ReturnType<typeof createClickWaveManager>
59
+
60
+ /**
61
+ * 代替 plain-ui for Vue3.0 中 v-click-wave 指令
62
+ * @author 韦胜健
63
+ * @date 2021/3/16 11:03
64
+ */
65
+ export function useClickRipple(
66
+ {
67
+ elGetter,
68
+ optionsGetter,
69
+ }: {
70
+ elGetter: () => HTMLElement | undefined | null,
71
+ optionsGetter: () => string | ClickWaveOptions,
72
+ }) {
73
+
74
+ let waveManager = null as null | WaveManager;
75
+ const { effects } = createEffects();
76
+
77
+ watch(elGetter, async el => {
78
+ await delay(0);
79
+ effects.clear();
80
+ if (!!el) {
81
+ const manager = createClickWaveManager(el, !!optionsGetter ? optionsGetter() : undefined);
82
+ waveManager = manager;
83
+ effects.push(() => {
84
+ manager.destroy();
85
+ waveManager = null;
86
+ });
87
+ }
88
+ }, { immediate: true });
89
+
90
+ !!optionsGetter && watch(optionsGetter, opt => {!!waveManager && waveManager.updateOption(opt);});
91
+
92
+ onBeforeUnmount(() => {effects.clear();});
93
+ }
@@ -1,8 +1,8 @@
1
1
  $waves: (
2
2
  large:6px,
3
- normal:4px,
4
- small:4px,
5
- mini:4px,
3
+ normal:3px,
4
+ small:3px,
5
+ mini:3px,
6
6
  );
7
7
 
8
8
  @each $key, $value in $waves {