@canlooks/can-ui 0.0.77 → 0.0.79

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.
Files changed (29) hide show
  1. package/dist/cjs/components/cascade/cascade.js +1 -1
  2. package/dist/cjs/components/dateTimePicker/timer.js +1 -1
  3. package/dist/cjs/components/inputBase/inputBase.js +2 -5
  4. package/dist/cjs/components/optionsBase/optionsBase.d.ts +4 -0
  5. package/dist/cjs/components/optionsBase/optionsBase.js +11 -10
  6. package/dist/cjs/components/popper/popperContext.d.ts +1 -1
  7. package/dist/cjs/components/popper/popperContext.js +3 -3
  8. package/dist/cjs/components/select/select.js +1 -1
  9. package/dist/cjs/components/textarea/textarea.d.ts +8 -2
  10. package/dist/cjs/components/textarea/textarea.js +3 -2
  11. package/dist/cjs/components/treeSelect/treeSelect.js +1 -1
  12. package/dist/cjs/extensions/reactiveForm/reactiveForm.d.ts +2 -2
  13. package/dist/cjs/utils/utils.d.ts +7 -2
  14. package/dist/cjs/utils/utils.js +17 -3
  15. package/dist/esm/components/cascade/cascade.js +2 -2
  16. package/dist/esm/components/dateTimePicker/timer.js +2 -2
  17. package/dist/esm/components/inputBase/inputBase.js +3 -6
  18. package/dist/esm/components/optionsBase/optionsBase.d.ts +4 -0
  19. package/dist/esm/components/optionsBase/optionsBase.js +13 -12
  20. package/dist/esm/components/popper/popperContext.d.ts +1 -1
  21. package/dist/esm/components/popper/popperContext.js +2 -2
  22. package/dist/esm/components/select/select.js +2 -2
  23. package/dist/esm/components/textarea/textarea.d.ts +8 -2
  24. package/dist/esm/components/textarea/textarea.js +3 -2
  25. package/dist/esm/components/treeSelect/treeSelect.js +2 -2
  26. package/dist/esm/extensions/reactiveForm/reactiveForm.d.ts +2 -2
  27. package/dist/esm/utils/utils.d.ts +7 -2
  28. package/dist/esm/utils/utils.js +16 -3
  29. package/package.json +1 -1
@@ -60,7 +60,7 @@ loading, options, labelKey = 'label', primaryKey = 'value', childrenKey = 'child
60
60
  setInnerOptions(await loadOptions(searchValue, parent));
61
61
  }, loading);
62
62
  (0, react_1.useEffect)(() => {
63
- if (loadOptions && !searchable && !innerOpen.current && (0, utils_1.toArray)(innerValue)?.length) {
63
+ if (loadOptions && !searchable && !innerOpen.current && !(0, utils_1.isNoValue)(innerValue)) {
64
64
  // 第一次渲染就有value,需要请求数据
65
65
  innerLoadOptions(innerSearchValue.current);
66
66
  }
@@ -40,7 +40,7 @@ function TimerItem({ type, value, onChange }) {
40
40
  return false;
41
41
  };
42
42
  const scrollerRef = (0, react_1.useRef)(null);
43
- const selectedItemRef = (0, popper_1.useScrollToSelected)(scrollerRef);
43
+ const selectedItemRef = (0, popper_1.useScrollToTarget)(scrollerRef);
44
44
  const renderItems = (count) => {
45
45
  const ret = [];
46
46
  for (let i = 0; i < count; i++) {
@@ -57,13 +57,10 @@ min, max, step, precision, placeholder, disabled, readOnly, autoFocus, defaultVa
57
57
  }
58
58
  };
59
59
  const shouldRenderClearButton = () => {
60
- if (!clearable || disabled || readOnly || (0, utils_1.isUnset)(innerValue.current)) {
60
+ if (!clearable || disabled || readOnly) {
61
61
  return false;
62
62
  }
63
- if (Array.isArray(innerValue.current) || typeof innerValue.current === 'string') {
64
- return !!innerValue.current.length;
65
- }
66
- return true;
63
+ return (0, utils_1.isNoValue)(innerValue.current);
67
64
  };
68
65
  return ((0, jsx_runtime_1.jsxs)("div", { ...(0, utils_1.mergeComponentProps)(props, {
69
66
  ref: innerRef,
@@ -4,6 +4,10 @@ import { LoadingProps } from '../loading';
4
4
  import { OptionType } from '../selectionContext';
5
5
  import { Id } from '../../types';
6
6
  export interface MenuOptionType<V extends Id = Id> extends Omit<MenuItemProps, 'children'>, Omit<OptionType<V>, 'children'> {
7
+ /**
8
+ * 若指定为`true`,则弹框打开时会自动滚动到该选项,默认为`false`
9
+ */
10
+ scrollHere?: boolean;
7
11
  }
8
12
  export type OptionsBaseSharedProps<O extends MenuOptionType<V>, V extends Id = Id> = {
9
13
  showCheckbox?: boolean;
@@ -68,14 +68,14 @@ searchValue, selectedValue, onToggleSelected, ...props }) => {
68
68
  * 渲染部分
69
69
  */
70
70
  const scrollerRef = (0, react_1.useRef)(null);
71
- const selectedItemRef = (0, popper_1.useScrollToSelected)(scrollerRef);
71
+ const selectedItemRef = (0, popper_1.useScrollToTarget)(scrollerRef);
72
72
  const syncOnToggleSelected = (0, utils_1.useSync)(onToggleSelected);
73
73
  const renderedOptions = (0, react_1.useMemo)(() => {
74
74
  if (!filteredOptions?.length) {
75
75
  return (0, jsx_runtime_1.jsx)(placeholder_1.Placeholder, {});
76
76
  }
77
77
  const makeProps = (params) => ({
78
- ref: params.selected ? selectedItemRef : void 0,
78
+ ref: params.scrollHere || params.selected ? selectedItemRef : void 0,
79
79
  showCheckbox,
80
80
  selected: params.selected,
81
81
  focused: verticalIndex.current === params.index,
@@ -84,11 +84,11 @@ searchValue, selectedValue, onToggleSelected, ...props }) => {
84
84
  : params.label,
85
85
  onClick: e => {
86
86
  e.stopPropagation();
87
- params.opt.onClick?.(e);
87
+ params.opt?.onClick?.(e);
88
88
  syncOnToggleSelected.current?.(params.value, e);
89
89
  },
90
90
  onPointerEnter: e => {
91
- params.opt.onPointerEnter?.(e);
91
+ params.opt?.onPointerEnter?.(e);
92
92
  setVerticalIndex(-1);
93
93
  },
94
94
  children: null
@@ -97,13 +97,13 @@ searchValue, selectedValue, onToggleSelected, ...props }) => {
97
97
  return filteredOptions.map((opt, index) => {
98
98
  const value = opt[primaryKey];
99
99
  const label = opt[labelKey] ?? value;
100
- return ((0, jsx_runtime_1.jsx)(menuItem_1.MenuItem, { value: value, ...opt, ...makeProps({
101
- opt,
100
+ return ((0, jsx_runtime_1.jsx)(menuItem_1.MenuItem, { value: value, ...(0, utils_1.mergeComponentProps)(opt, makeProps({
102
101
  label,
103
102
  value,
104
103
  index,
105
- selected: (0, utils_1.isSelected)(value, selectedValue)
106
- }) }, opt.key ?? value ?? index));
104
+ selected: (0, utils_1.isSelected)(value, selectedValue),
105
+ scrollHere: opt.scrollHere
106
+ })) }, opt.key ?? value ?? index));
107
107
  });
108
108
  }
109
109
  // children
@@ -111,13 +111,14 @@ searchValue, selectedValue, onToggleSelected, ...props }) => {
111
111
  if (!(0, react_1.isValidElement)(c)) {
112
112
  return c;
113
113
  }
114
- const { value, label } = c.props;
114
+ const { value, label, scrollHere } = c.props;
115
115
  return (0, react_1.cloneElement)(c, makeProps({
116
116
  opt: c.props,
117
117
  label,
118
118
  value: value,
119
119
  index,
120
- selected: (0, utils_1.isSelected)(value, selectedValue)
120
+ selected: (0, utils_1.isSelected)(value, selectedValue),
121
+ scrollHere
121
122
  }));
122
123
  });
123
124
  }, [filteredOptions, selectedValue, verticalIndex.current, showCheckbox]);
@@ -17,4 +17,4 @@ export declare function usePopperContext(): {
17
17
  * 当弹框打开时,滚动至已选项
18
18
  * @returns {RefObject} ref
19
19
  */
20
- export declare function useScrollToSelected<T extends HTMLElement>(scrollerRef: RefObject<Element | null>): RefObject<T | null>;
20
+ export declare function useScrollToTarget<T extends HTMLElement>(scrollerRef: RefObject<Element | null>): RefObject<T | null>;
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PopperContext = void 0;
4
4
  exports.usePopperContext = usePopperContext;
5
- exports.useScrollToSelected = useScrollToSelected;
5
+ exports.useScrollToTarget = useScrollToTarget;
6
6
  const react_1 = require("react");
7
7
  exports.PopperContext = (0, react_1.createContext)({
8
8
  autoClose: false,
@@ -20,13 +20,13 @@ function usePopperContext() {
20
20
  * 当弹框打开时,滚动至已选项
21
21
  * @returns {RefObject} ref
22
22
  */
23
- function useScrollToSelected(scrollerRef) {
23
+ function useScrollToTarget(scrollerRef) {
24
24
  const ref = (0, react_1.useRef)(null);
25
25
  const { beforeOpenCallbacks } = usePopperContext();
26
26
  (0, react_1.useEffect)(() => {
27
27
  const beforeOpen = () => {
28
28
  if (ref.current && scrollerRef.current && scrollerRef.current.scrollHeight > scrollerRef.current.clientHeight) {
29
- scrollerRef.current.scrollTop = ref.current.offsetTop - scrollerRef.current.clientHeight / 2 + ref.current.clientHeight / 2;
29
+ scrollerRef.current.scrollTop = ref.current.offsetTop + ref.current.clientHeight / 2 - scrollerRef.current.clientHeight / 2;
30
30
  }
31
31
  };
32
32
  beforeOpenCallbacks.add(beforeOpen);
@@ -115,7 +115,7 @@ showCheckbox = !!multiple, loading, options, labelKey = 'label', primaryKey = 'v
115
115
  value: innerValue,
116
116
  onClear,
117
117
  onBlur
118
- }), "data-focused": innerOpen.current, children: inputBaseProps => (0, jsx_runtime_1.jsxs)("div", { className: select_style_1.classes.contentWrap, children: [!(0, utils_1.toArray)(innerValue)?.length
118
+ }), "data-focused": innerOpen.current, children: inputBaseProps => (0, jsx_runtime_1.jsxs)("div", { className: select_style_1.classes.contentWrap, children: [(0, utils_1.isNoValue)(innerValue)
119
119
  ? (0, jsx_runtime_1.jsx)("div", { className: select_style_1.classes.placeholder, children: props.placeholder ?? '请选择' })
120
120
  : (0, jsx_runtime_1.jsx)("div", { className: select_style_1.classes.backfill, children: renderBackfillFn() }), (0, jsx_runtime_1.jsx)("input", { size: 1, ...(0, utils_1.mergeComponentProps)(inputBaseProps, inputProps), "data-hidden": "true" }), (0, jsx_runtime_1.jsx)("div", { className: select_style_1.classes.arrow, "data-open": innerOpen.current, children: loading
121
121
  ? (0, jsx_runtime_1.jsx)(loadingIndicator_1.LoadingIndicator, {})
@@ -1,9 +1,15 @@
1
- import { Ref, ComponentProps } from 'react';
1
+ import { Ref, ComponentProps, CSSProperties } from 'react';
2
2
  import { InputBaseProps } from '../inputBase';
3
3
  export interface TextareaProps extends Omit<InputBaseProps<'textarea'>, 'children' | 'prefix' | 'suffix'> {
4
4
  textareaProps?: ComponentProps<'textarea'>;
5
5
  textareaRef?: Ref<HTMLTextAreaElement>;
6
6
  rows?: number;
7
7
  fullWidth?: boolean;
8
+ /**
9
+ * @enum {@link fullWidth} 为`true`时,默认为`vertical`
10
+ * @enum {@link fullWidth} 为`false`时,默认为`both`
11
+ * @enum 若设为`none`,则不可调整尺寸
12
+ */
13
+ resize?: CSSProperties['resize'];
8
14
  }
9
- export declare const Textarea: import("react").MemoExoticComponent<({ textareaProps, textareaRef, rows, fullWidth, ...props }: TextareaProps) => import("@emotion/react/jsx-runtime").JSX.Element>;
15
+ export declare const Textarea: import("react").MemoExoticComponent<({ textareaProps, textareaRef, rows, fullWidth, resize, ...props }: TextareaProps) => import("@emotion/react/jsx-runtime").JSX.Element>;
@@ -6,10 +6,11 @@ const react_1 = require("react");
6
6
  const inputBase_1 = require("../inputBase");
7
7
  const textarea_style_1 = require("./textarea.style");
8
8
  const utils_1 = require("../../utils");
9
- exports.Textarea = (0, react_1.memo)(({ textareaProps, textareaRef, rows, fullWidth = false, ...props }) => {
9
+ exports.Textarea = (0, react_1.memo)(({ textareaProps, textareaRef, rows, fullWidth = false, resize = fullWidth ? 'vertical' : 'both', ...props }) => {
10
10
  return ((0, jsx_runtime_1.jsx)(inputBase_1.InputBase, { ...props, css: textarea_style_1.style, className: (0, utils_1.clsx)(textarea_style_1.classes.root, props.className), "data-full-width": fullWidth, children: inputBaseProps => (0, jsx_runtime_1.jsx)("textarea", { ...(0, utils_1.mergeComponentProps)(inputBaseProps, {
11
11
  rows,
12
12
  ref: textareaRef,
13
- className: textarea_style_1.classes.textarea
13
+ className: textarea_style_1.classes.textarea,
14
+ style: { resize }
14
15
  }) }) }));
15
16
  });
@@ -74,7 +74,7 @@ placeholder = '请选择', autoFocus, clearable, onClear, ...props }) => {
74
74
  return ((0, jsx_runtime_1.jsx)(popper_1.Popper, { css: popper_style_1.popperStyle, open: innerOpen.current, onOpenChange: openChangeHandler, placement: "bottom", variant: "collapse", trigger: ['click', 'enter'], disabled: props.disabled || props.readOnly, sizeAdaptable: sizeAdaptable, content: (0, jsx_runtime_1.jsx)(tree_1.Tree, { ...props, nodes: options, value: innerValue, onChange: setInnerValue }), ...popperProps, popperRef: popperRef, onPointerDown: e => {
75
75
  popperProps?.onPointerDown?.(e);
76
76
  e.preventDefault();
77
- }, children: (0, jsx_runtime_1.jsx)(inputBase_1.InputBase, { clearable: !!props.multiple, css: treeSelect_style_1.style, className: (0, utils_1.clsx)(treeSelect_style_1.classes.root, props.className), "data-focused": open, value: innerValue, onClear: clearHandler, placeholder: placeholder, autoFocus: autoFocus, disabled: props.disabled, readOnly: props.readOnly, children: inputBaseProps => (0, jsx_runtime_1.jsxs)("div", { className: treeSelect_style_1.classes.contentWrap, children: [!(0, utils_1.toArray)(innerValue)?.length
77
+ }, children: (0, jsx_runtime_1.jsx)(inputBase_1.InputBase, { clearable: !!props.multiple, css: treeSelect_style_1.style, className: (0, utils_1.clsx)(treeSelect_style_1.classes.root, props.className), "data-focused": open, value: innerValue, onClear: clearHandler, placeholder: placeholder, autoFocus: autoFocus, disabled: props.disabled, readOnly: props.readOnly, children: inputBaseProps => (0, jsx_runtime_1.jsxs)("div", { className: treeSelect_style_1.classes.contentWrap, children: [(0, utils_1.isNoValue)(innerValue)
78
78
  ? (0, jsx_runtime_1.jsx)("div", { className: treeSelect_style_1.classes.placeholder, children: placeholder })
79
79
  : (0, jsx_runtime_1.jsx)("div", { className: treeSelect_style_1.classes.backfill, children: renderBackfillFn() }), (0, jsx_runtime_1.jsx)("input", { size: 1, ...(0, utils_1.mergeComponentProps)(inputBaseProps, inputProps), "data-hidden": "true" }), (0, jsx_runtime_1.jsx)("div", { className: treeSelect_style_1.classes.arrow, "data-open": open, children: loading
80
80
  ? (0, jsx_runtime_1.jsx)(loadingIndicator_1.LoadingIndicator, {})
@@ -22,9 +22,9 @@
22
22
  import { FormProps, FormRef } from '../../components/form';
23
23
  import { ReactElement, Ref } from 'react';
24
24
  import { ReactiveFormItem } from './reactiveFormItem';
25
- export type ReactiveFormPropsRef = Pick<FormRef, 'submit' | 'getFormErrors' | 'getFieldError' | 'resetForm' | 'resetField' | 'isFormTouched' | 'isFieldTouched'>;
25
+ export type ReactiveFormRef = Pick<FormRef, 'submit' | 'getFormErrors' | 'getFieldError' | 'resetForm' | 'resetField' | 'isFormTouched' | 'isFieldTouched'>;
26
26
  export interface ReactiveFormProps extends Omit<FormProps, 'ref' | 'initialValue' | 'onChange' | 'onFinish' | 'items'> {
27
- ref?: Ref<ReactiveFormPropsRef>;
27
+ ref?: Ref<ReactiveFormRef>;
28
28
  onChange?(): void;
29
29
  onFinish?(): void;
30
30
  }
@@ -80,13 +80,18 @@ export declare function isPromise<T>(it: any): it is Promise<T>;
80
80
  * @param promise
81
81
  */
82
82
  export declare function getPromiseState(promise: Promise<any>): Promise<'pending' | 'fulfilled' | 'rejected'>;
83
+ /**
84
+ * 判断表单控件是否为空值
85
+ * @param value
86
+ */
87
+ export declare function isNoValue(value: any): boolean;
83
88
  /**
84
89
  * 将节点用某个分隔符拼接起来,通常用于渲染多选项
85
90
  * @param arr
86
91
  * @param callback
87
- * @param seperator
92
+ * @param separator
88
93
  */
89
- export declare function joinNodes<T>(arr: T[], callback: (item: T, index: number) => ReactNode, seperator?: ReactNode): (string | number | bigint | boolean | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | import("react").ReactPortal | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | null | undefined)[];
94
+ export declare function joinNodes<T>(arr: T[], callback: (item: T, index: number) => ReactNode, separator?: ReactNode): (string | number | bigint | boolean | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | import("react").ReactPortal | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | null | undefined)[];
90
95
  /**
91
96
  * 修复数字输入框的值,包括最大值最小值限制,以及小数点精度
92
97
  * @param value
@@ -14,6 +14,7 @@ exports.cloneRef = cloneRef;
14
14
  exports.isUnset = isUnset;
15
15
  exports.isPromise = isPromise;
16
16
  exports.getPromiseState = getPromiseState;
17
+ exports.isNoValue = isNoValue;
17
18
  exports.joinNodes = joinNodes;
18
19
  exports.fixInputNumber = fixInputNumber;
19
20
  exports.isChildOf = isChildOf;
@@ -216,16 +217,29 @@ function getPromiseState(promise) {
216
217
  return res === s ? 'pending' : 'fulfilled';
217
218
  }).catch(() => 'rejected');
218
219
  }
220
+ /**
221
+ * 判断表单控件是否为空值
222
+ * @param value
223
+ */
224
+ function isNoValue(value) {
225
+ if (isUnset(value)) {
226
+ return true;
227
+ }
228
+ if (Array.isArray(value) || typeof value === 'string') {
229
+ return !value.length;
230
+ }
231
+ return false;
232
+ }
219
233
  /**
220
234
  * 将节点用某个分隔符拼接起来,通常用于渲染多选项
221
235
  * @param arr
222
236
  * @param callback
223
- * @param seperator
237
+ * @param separator
224
238
  */
225
- function joinNodes(arr, callback, seperator = ' / ') {
239
+ function joinNodes(arr, callback, separator = ' / ') {
226
240
  return arr.flatMap((item, index) => {
227
241
  return index > 0
228
- ? [seperator, callback(item, index)]
242
+ ? [separator, callback(item, index)]
229
243
  : callback(item, index);
230
244
  });
231
245
  }
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
2
2
  import { createContext, memo, useContext, useDeferredValue, useEffect, useMemo, useState } from 'react';
3
- import { useControlled, useLoading, useKeyboard, joinNodes, clsx, toArray, mergeComponentProps } from '../../utils';
3
+ import { useControlled, useLoading, useKeyboard, joinNodes, clsx, mergeComponentProps, isNoValue } from '../../utils';
4
4
  import { Input } from '../input';
5
5
  import { InputBase } from '../inputBase';
6
6
  import { Popper } from '../popper';
@@ -56,7 +56,7 @@ loading, options, labelKey = 'label', primaryKey = 'value', childrenKey = 'child
56
56
  setInnerOptions(await loadOptions(searchValue, parent));
57
57
  }, loading);
58
58
  useEffect(() => {
59
- if (loadOptions && !searchable && !innerOpen.current && toArray(innerValue)?.length) {
59
+ if (loadOptions && !searchable && !innerOpen.current && !isNoValue(innerValue)) {
60
60
  // 第一次渲染就有value,需要请求数据
61
61
  innerLoadOptions(innerSearchValue.current);
62
62
  }
@@ -3,7 +3,7 @@ import { memo, useRef } from 'react';
3
3
  import { classes, style } from './timer.style';
4
4
  import { MenuItem } from '../menuItem';
5
5
  import { useDateTimePickerContext } from './dateTimePicker';
6
- import { useScrollToSelected } from '../popper';
6
+ import { useScrollToTarget } from '../popper';
7
7
  export const Timer = memo(({ showHours, showMinutes, showSeconds, value, onChange }) => {
8
8
  return (_jsxs("div", { css: style, className: classes.root, children: [showHours && _jsx(TimerItem, { type: "hour", value: value, onChange: onChange }), showMinutes && _jsx(TimerItem, { type: "minute", value: value, onChange: onChange }), showSeconds && _jsx(TimerItem, { type: "second", value: value, onChange: onChange })] }));
9
9
  });
@@ -37,7 +37,7 @@ function TimerItem({ type, value, onChange }) {
37
37
  return false;
38
38
  };
39
39
  const scrollerRef = useRef(null);
40
- const selectedItemRef = useScrollToSelected(scrollerRef);
40
+ const selectedItemRef = useScrollToTarget(scrollerRef);
41
41
  const renderItems = (count) => {
42
42
  const ret = [];
43
43
  for (let i = 0; i < count; i++) {
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
2
2
  import { useImperativeHandle, useRef } from 'react';
3
3
  import { classes, useStyle } from './inputBase.style';
4
- import { fixInputNumber, isUnset, mergeComponentProps, useControlled } from '../../utils';
4
+ import { fixInputNumber, isNoValue, mergeComponentProps, useControlled } from '../../utils';
5
5
  import { useTheme } from '../theme';
6
6
  import { Button } from '../button';
7
7
  import { LoadingIndicator } from '../loadingIndicator';
@@ -54,13 +54,10 @@ min, max, step, precision, placeholder, disabled, readOnly, autoFocus, defaultVa
54
54
  }
55
55
  };
56
56
  const shouldRenderClearButton = () => {
57
- if (!clearable || disabled || readOnly || isUnset(innerValue.current)) {
57
+ if (!clearable || disabled || readOnly) {
58
58
  return false;
59
59
  }
60
- if (Array.isArray(innerValue.current) || typeof innerValue.current === 'string') {
61
- return !!innerValue.current.length;
62
- }
63
- return true;
60
+ return isNoValue(innerValue.current);
64
61
  };
65
62
  return (_jsxs("div", { ...mergeComponentProps(props, {
66
63
  ref: innerRef,
@@ -4,6 +4,10 @@ import { LoadingProps } from '../loading';
4
4
  import { OptionType } from '../selectionContext';
5
5
  import { Id } from '../../types';
6
6
  export interface MenuOptionType<V extends Id = Id> extends Omit<MenuItemProps, 'children'>, Omit<OptionType<V>, 'children'> {
7
+ /**
8
+ * 若指定为`true`,则弹框打开时会自动滚动到该选项,默认为`false`
9
+ */
10
+ scrollHere?: boolean;
7
11
  }
8
12
  export type OptionsBaseSharedProps<O extends MenuOptionType<V>, V extends Id = Id> = {
9
13
  showCheckbox?: boolean;
@@ -3,10 +3,10 @@ import { useMemo, memo, Children, isValidElement, cloneElement, useRef } from 'r
3
3
  import { Highlight } from '../highlight';
4
4
  import { MenuItem } from '../menuItem';
5
5
  import { Placeholder } from '../placeholder';
6
- import { clsx, isSelected, useKeyboard, useSync } from '../../utils';
6
+ import { clsx, isSelected, mergeComponentProps, useKeyboard, useSync } from '../../utils';
7
7
  import { classes, style } from './optionsBase.style';
8
8
  import { Loading } from '../loading';
9
- import { usePopperContext, useScrollToSelected } from '../popper';
9
+ import { usePopperContext, useScrollToTarget } from '../popper';
10
10
  export const OptionsBase = memo(({
11
11
  // 共享属性
12
12
  showCheckbox, loading, options, children, labelKey = 'label', primaryKey = 'value', searchTokenKey = 'searchToken', filterPredicate,
@@ -65,14 +65,14 @@ searchValue, selectedValue, onToggleSelected, ...props }) => {
65
65
  * 渲染部分
66
66
  */
67
67
  const scrollerRef = useRef(null);
68
- const selectedItemRef = useScrollToSelected(scrollerRef);
68
+ const selectedItemRef = useScrollToTarget(scrollerRef);
69
69
  const syncOnToggleSelected = useSync(onToggleSelected);
70
70
  const renderedOptions = useMemo(() => {
71
71
  if (!filteredOptions?.length) {
72
72
  return _jsx(Placeholder, {});
73
73
  }
74
74
  const makeProps = (params) => ({
75
- ref: params.selected ? selectedItemRef : void 0,
75
+ ref: params.scrollHere || params.selected ? selectedItemRef : void 0,
76
76
  showCheckbox,
77
77
  selected: params.selected,
78
78
  focused: verticalIndex.current === params.index,
@@ -81,11 +81,11 @@ searchValue, selectedValue, onToggleSelected, ...props }) => {
81
81
  : params.label,
82
82
  onClick: e => {
83
83
  e.stopPropagation();
84
- params.opt.onClick?.(e);
84
+ params.opt?.onClick?.(e);
85
85
  syncOnToggleSelected.current?.(params.value, e);
86
86
  },
87
87
  onPointerEnter: e => {
88
- params.opt.onPointerEnter?.(e);
88
+ params.opt?.onPointerEnter?.(e);
89
89
  setVerticalIndex(-1);
90
90
  },
91
91
  children: null
@@ -94,13 +94,13 @@ searchValue, selectedValue, onToggleSelected, ...props }) => {
94
94
  return filteredOptions.map((opt, index) => {
95
95
  const value = opt[primaryKey];
96
96
  const label = opt[labelKey] ?? value;
97
- return (_jsx(MenuItem, { value: value, ...opt, ...makeProps({
98
- opt,
97
+ return (_jsx(MenuItem, { value: value, ...mergeComponentProps(opt, makeProps({
99
98
  label,
100
99
  value,
101
100
  index,
102
- selected: isSelected(value, selectedValue)
103
- }) }, opt.key ?? value ?? index));
101
+ selected: isSelected(value, selectedValue),
102
+ scrollHere: opt.scrollHere
103
+ })) }, opt.key ?? value ?? index));
104
104
  });
105
105
  }
106
106
  // children
@@ -108,13 +108,14 @@ searchValue, selectedValue, onToggleSelected, ...props }) => {
108
108
  if (!isValidElement(c)) {
109
109
  return c;
110
110
  }
111
- const { value, label } = c.props;
111
+ const { value, label, scrollHere } = c.props;
112
112
  return cloneElement(c, makeProps({
113
113
  opt: c.props,
114
114
  label,
115
115
  value: value,
116
116
  index,
117
- selected: isSelected(value, selectedValue)
117
+ selected: isSelected(value, selectedValue),
118
+ scrollHere
118
119
  }));
119
120
  });
120
121
  }, [filteredOptions, selectedValue, verticalIndex.current, showCheckbox]);
@@ -17,4 +17,4 @@ export declare function usePopperContext(): {
17
17
  * 当弹框打开时,滚动至已选项
18
18
  * @returns {RefObject} ref
19
19
  */
20
- export declare function useScrollToSelected<T extends HTMLElement>(scrollerRef: RefObject<Element | null>): RefObject<T | null>;
20
+ export declare function useScrollToTarget<T extends HTMLElement>(scrollerRef: RefObject<Element | null>): RefObject<T | null>;
@@ -15,13 +15,13 @@ export function usePopperContext() {
15
15
  * 当弹框打开时,滚动至已选项
16
16
  * @returns {RefObject} ref
17
17
  */
18
- export function useScrollToSelected(scrollerRef) {
18
+ export function useScrollToTarget(scrollerRef) {
19
19
  const ref = useRef(null);
20
20
  const { beforeOpenCallbacks } = usePopperContext();
21
21
  useEffect(() => {
22
22
  const beforeOpen = () => {
23
23
  if (ref.current && scrollerRef.current && scrollerRef.current.scrollHeight > scrollerRef.current.clientHeight) {
24
- scrollerRef.current.scrollTop = ref.current.offsetTop - scrollerRef.current.clientHeight / 2 + ref.current.clientHeight / 2;
24
+ scrollerRef.current.scrollTop = ref.current.offsetTop + ref.current.clientHeight / 2 - scrollerRef.current.clientHeight / 2;
25
25
  }
26
26
  };
27
27
  beforeOpenCallbacks.add(beforeOpen);
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "@emotion/react/jsx-runtime";
2
2
  import { Children, isValidElement, memo, useCallback, useDeferredValue, useMemo, useRef } from 'react';
3
3
  import { InputBase } from '../inputBase';
4
- import { mergeComponentProps, toArray, useControlled } from '../../utils';
4
+ import { isNoValue, mergeComponentProps, useControlled } from '../../utils';
5
5
  import { classes, style } from './select.style';
6
6
  import { Popper } from '../popper';
7
7
  import { MenuItem } from '../menuItem';
@@ -112,7 +112,7 @@ showCheckbox = !!multiple, loading, options, labelKey = 'label', primaryKey = 'v
112
112
  value: innerValue,
113
113
  onClear,
114
114
  onBlur
115
- }), "data-focused": innerOpen.current, children: inputBaseProps => _jsxs("div", { className: classes.contentWrap, children: [!toArray(innerValue)?.length
115
+ }), "data-focused": innerOpen.current, children: inputBaseProps => _jsxs("div", { className: classes.contentWrap, children: [isNoValue(innerValue)
116
116
  ? _jsx("div", { className: classes.placeholder, children: props.placeholder ?? '请选择' })
117
117
  : _jsx("div", { className: classes.backfill, children: renderBackfillFn() }), _jsx("input", { size: 1, ...mergeComponentProps(inputBaseProps, inputProps), "data-hidden": "true" }), _jsx("div", { className: classes.arrow, "data-open": innerOpen.current, children: loading
118
118
  ? _jsx(LoadingIndicator, {})
@@ -1,9 +1,15 @@
1
- import { Ref, ComponentProps } from 'react';
1
+ import { Ref, ComponentProps, CSSProperties } from 'react';
2
2
  import { InputBaseProps } from '../inputBase';
3
3
  export interface TextareaProps extends Omit<InputBaseProps<'textarea'>, 'children' | 'prefix' | 'suffix'> {
4
4
  textareaProps?: ComponentProps<'textarea'>;
5
5
  textareaRef?: Ref<HTMLTextAreaElement>;
6
6
  rows?: number;
7
7
  fullWidth?: boolean;
8
+ /**
9
+ * @enum {@link fullWidth} 为`true`时,默认为`vertical`
10
+ * @enum {@link fullWidth} 为`false`时,默认为`both`
11
+ * @enum 若设为`none`,则不可调整尺寸
12
+ */
13
+ resize?: CSSProperties['resize'];
8
14
  }
9
- export declare const Textarea: import("react").MemoExoticComponent<({ textareaProps, textareaRef, rows, fullWidth, ...props }: TextareaProps) => import("@emotion/react/jsx-runtime").JSX.Element>;
15
+ export declare const Textarea: import("react").MemoExoticComponent<({ textareaProps, textareaRef, rows, fullWidth, resize, ...props }: TextareaProps) => import("@emotion/react/jsx-runtime").JSX.Element>;
@@ -3,10 +3,11 @@ import { memo } from 'react';
3
3
  import { InputBase } from '../inputBase';
4
4
  import { classes, style } from './textarea.style';
5
5
  import { clsx, mergeComponentProps } from '../../utils';
6
- export const Textarea = memo(({ textareaProps, textareaRef, rows, fullWidth = false, ...props }) => {
6
+ export const Textarea = memo(({ textareaProps, textareaRef, rows, fullWidth = false, resize = fullWidth ? 'vertical' : 'both', ...props }) => {
7
7
  return (_jsx(InputBase, { ...props, css: style, className: clsx(classes.root, props.className), "data-full-width": fullWidth, children: inputBaseProps => _jsx("textarea", { ...mergeComponentProps(inputBaseProps, {
8
8
  rows,
9
9
  ref: textareaRef,
10
- className: classes.textarea
10
+ className: classes.textarea,
11
+ style: { resize }
11
12
  }) }) }));
12
13
  });
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
2
2
  import { Children, isValidElement, memo, useMemo } from 'react';
3
3
  import { Popper } from '../popper';
4
4
  import { Tree, TreeNode } from '../tree';
5
- import { useControlled, toArray, clsx, mergeComponentProps } from '../../utils';
5
+ import { useControlled, toArray, clsx, mergeComponentProps, isNoValue } from '../../utils';
6
6
  import { InputBase } from '../inputBase';
7
7
  import { LoadingIndicator } from '../loadingIndicator';
8
8
  import { popperStyle } from '../popper/popper.style';
@@ -71,7 +71,7 @@ placeholder = '请选择', autoFocus, clearable, onClear, ...props }) => {
71
71
  return (_jsx(Popper, { css: popperStyle, open: innerOpen.current, onOpenChange: openChangeHandler, placement: "bottom", variant: "collapse", trigger: ['click', 'enter'], disabled: props.disabled || props.readOnly, sizeAdaptable: sizeAdaptable, content: _jsx(Tree, { ...props, nodes: options, value: innerValue, onChange: setInnerValue }), ...popperProps, popperRef: popperRef, onPointerDown: e => {
72
72
  popperProps?.onPointerDown?.(e);
73
73
  e.preventDefault();
74
- }, children: _jsx(InputBase, { clearable: !!props.multiple, css: style, className: clsx(classes.root, props.className), "data-focused": open, value: innerValue, onClear: clearHandler, placeholder: placeholder, autoFocus: autoFocus, disabled: props.disabled, readOnly: props.readOnly, children: inputBaseProps => _jsxs("div", { className: classes.contentWrap, children: [!toArray(innerValue)?.length
74
+ }, children: _jsx(InputBase, { clearable: !!props.multiple, css: style, className: clsx(classes.root, props.className), "data-focused": open, value: innerValue, onClear: clearHandler, placeholder: placeholder, autoFocus: autoFocus, disabled: props.disabled, readOnly: props.readOnly, children: inputBaseProps => _jsxs("div", { className: classes.contentWrap, children: [isNoValue(innerValue)
75
75
  ? _jsx("div", { className: classes.placeholder, children: placeholder })
76
76
  : _jsx("div", { className: classes.backfill, children: renderBackfillFn() }), _jsx("input", { size: 1, ...mergeComponentProps(inputBaseProps, inputProps), "data-hidden": "true" }), _jsx("div", { className: classes.arrow, "data-open": open, children: loading
77
77
  ? _jsx(LoadingIndicator, {})
@@ -22,9 +22,9 @@
22
22
  import { FormProps, FormRef } from '../../components/form';
23
23
  import { ReactElement, Ref } from 'react';
24
24
  import { ReactiveFormItem } from './reactiveFormItem';
25
- export type ReactiveFormPropsRef = Pick<FormRef, 'submit' | 'getFormErrors' | 'getFieldError' | 'resetForm' | 'resetField' | 'isFormTouched' | 'isFieldTouched'>;
25
+ export type ReactiveFormRef = Pick<FormRef, 'submit' | 'getFormErrors' | 'getFieldError' | 'resetForm' | 'resetField' | 'isFormTouched' | 'isFieldTouched'>;
26
26
  export interface ReactiveFormProps extends Omit<FormProps, 'ref' | 'initialValue' | 'onChange' | 'onFinish' | 'items'> {
27
- ref?: Ref<ReactiveFormPropsRef>;
27
+ ref?: Ref<ReactiveFormRef>;
28
28
  onChange?(): void;
29
29
  onFinish?(): void;
30
30
  }
@@ -80,13 +80,18 @@ export declare function isPromise<T>(it: any): it is Promise<T>;
80
80
  * @param promise
81
81
  */
82
82
  export declare function getPromiseState(promise: Promise<any>): Promise<'pending' | 'fulfilled' | 'rejected'>;
83
+ /**
84
+ * 判断表单控件是否为空值
85
+ * @param value
86
+ */
87
+ export declare function isNoValue(value: any): boolean;
83
88
  /**
84
89
  * 将节点用某个分隔符拼接起来,通常用于渲染多选项
85
90
  * @param arr
86
91
  * @param callback
87
- * @param seperator
92
+ * @param separator
88
93
  */
89
- export declare function joinNodes<T>(arr: T[], callback: (item: T, index: number) => ReactNode, seperator?: ReactNode): (string | number | bigint | boolean | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | import("react").ReactPortal | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | null | undefined)[];
94
+ export declare function joinNodes<T>(arr: T[], callback: (item: T, index: number) => ReactNode, separator?: ReactNode): (string | number | bigint | boolean | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | import("react").ReactPortal | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | null | undefined)[];
90
95
  /**
91
96
  * 修复数字输入框的值,包括最大值最小值限制,以及小数点精度
92
97
  * @param value
@@ -191,16 +191,29 @@ export function getPromiseState(promise) {
191
191
  return res === s ? 'pending' : 'fulfilled';
192
192
  }).catch(() => 'rejected');
193
193
  }
194
+ /**
195
+ * 判断表单控件是否为空值
196
+ * @param value
197
+ */
198
+ export function isNoValue(value) {
199
+ if (isUnset(value)) {
200
+ return true;
201
+ }
202
+ if (Array.isArray(value) || typeof value === 'string') {
203
+ return !value.length;
204
+ }
205
+ return false;
206
+ }
194
207
  /**
195
208
  * 将节点用某个分隔符拼接起来,通常用于渲染多选项
196
209
  * @param arr
197
210
  * @param callback
198
- * @param seperator
211
+ * @param separator
199
212
  */
200
- export function joinNodes(arr, callback, seperator = ' / ') {
213
+ export function joinNodes(arr, callback, separator = ' / ') {
201
214
  return arr.flatMap((item, index) => {
202
215
  return index > 0
203
- ? [seperator, callback(item, index)]
216
+ ? [separator, callback(item, index)]
204
217
  : callback(item, index);
205
218
  });
206
219
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canlooks/can-ui",
3
- "version": "0.0.77",
3
+ "version": "0.0.79",
4
4
  "author": "C.CanLiang <canlooks@gmail.com>",
5
5
  "description": "My ui framework",
6
6
  "license": "MIT",