@ioca/react 1.5.29 → 1.5.30

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/lib/index.js CHANGED
@@ -2,7 +2,7 @@ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
2
  import classNames from 'classnames';
3
3
  import { debounce, uid, crush, throttle } from 'radash';
4
4
  import { useState, useRef, useEffect, useCallback, useMemo, Children, cloneElement, createElement, isValidElement, memo, Fragment as Fragment$1, useTransition, forwardRef, useLayoutEffect, useContext, createContext, useImperativeHandle } from 'react';
5
- import { SkipPreviousRound, CloseRound, MinusRound, PlusRound, AddRound, InboxTwotone, UndoRound, RedoRound, FormatBoldRound, FormatItalicRound, FormatUnderlinedRound, StrikethroughSRound, ClearAllRound, PlayArrowRound, PauseRound, StopRound, VolumeDownRound, VolumeOffRound, FullscreenRound, FullscreenExitRound, FeedOutlined, AspectRatioRound, OpenInNewRound, FileDownloadOutlined, RotateRightRound, RotateLeftRound, KeyboardArrowLeftRound, KeyboardArrowRightRound, KeyboardDoubleArrowUpRound, SyncAltRound, VisibilityRound, VisibilityOffRound, MoreHorizRound, SearchRound, CheckRound, UnfoldMoreRound, CalendarMonthTwotone, AccessTimeRound, KeyboardArrowDownRound, MoveToInboxTwotone, OutboxTwotone, FilePresentOutlined, DriveFolderUploadOutlined } from '@ricons/material';
5
+ import { SkipPreviousRound, CloseRound, MinusRound, PlusRound, InboxTwotone, UndoRound, RedoRound, FormatBoldRound, FormatItalicRound, FormatUnderlinedRound, StrikethroughSRound, ClearAllRound, PlayArrowRound, PauseRound, StopRound, VolumeDownRound, VolumeOffRound, FullscreenRound, FullscreenExitRound, FeedOutlined, AspectRatioRound, OpenInNewRound, FileDownloadOutlined, RotateRightRound, RotateLeftRound, KeyboardArrowLeftRound, KeyboardArrowRightRound, KeyboardDoubleArrowUpRound, SyncAltRound, VisibilityRound, VisibilityOffRound, MoreHorizRound, SearchRound, CheckRound, UnfoldMoreRound, CalendarMonthTwotone, AccessTimeRound, AddRound, KeyboardArrowDownRound, MoveToInboxTwotone, OutboxTwotone, FilePresentOutlined, DriveFolderUploadOutlined } from '@ricons/material';
6
6
  import { createRoot } from 'react-dom/client';
7
7
  import { getScrollbarSize, List as List$2 } from 'react-window';
8
8
  import { createPortal, flushSync } from 'react-dom';
@@ -575,14 +575,6 @@ const Badge = (props) => {
575
575
  }), style: { fontSize: dotSize }, children: content })] }));
576
576
  };
577
577
 
578
- const Card = (props) => {
579
- const { hideShadow, border, className, children, header, footer, ...restProps } = props;
580
- return (jsxs("div", { className: classNames("i-card", className, {
581
- shadow: !hideShadow,
582
- "i-card-bordered": border,
583
- }), ...restProps, children: [header && jsx("div", { className: 'i-card-header', children: header }), children && jsx("div", { className: 'i-card-content', children: children }), footer && jsx("div", { className: 'i-card-footer', children: footer })] }));
584
- };
585
-
586
578
  function getPosition($source, $popup, options = {}) {
587
579
  const { refWindow, gap = 0, offset = 0, position = "top", align } = options;
588
580
  if (!$source || !$popup)
@@ -917,7 +909,7 @@ function Checkbox(props) {
917
909
  "i-input-inline": labelInline,
918
910
  }, className), ...restProps, children: [label && (jsxs("span", { className: "i-input-label-text", children: [required && jsx("span", { className: "error", children: "*" }), label, message && jsx("p", { className: "i-checkbox-message", children: message })] })), jsx("div", { className: classNames("i-checkbox-options", {
919
911
  "i-options-block": !optionInline,
920
- "i-checkbox-options-button": type === "button",
912
+ [`i-checkbox-options-${type}`]: true,
921
913
  }), children: formattedOptions.map((option) => {
922
914
  return (jsx(CheckboxItem, { name: name, value: selectedValues.includes(option.value), optionValue: option.value, type: type, disabled: disabled || option.disabled, onChange: (checked, e) => handleChange(checked, option, e), children: renderItem ?? option.label }, option.value));
923
915
  }) })] }));
@@ -1006,224 +998,6 @@ const Collapse = (props) => {
1006
998
  };
1007
999
  Collapse.Item = Item$5;
1008
1000
 
1009
- const Tag = (props) => {
1010
- const { dot, dotClass, outline, round, size = "normal", className, children, onClose, onClick, ...restProps } = props;
1011
- return (jsxs("span", { className: classNames("i-tag", {
1012
- "i-tag-outline": outline,
1013
- "i-tag-clickable": onClick,
1014
- [`i-tag-${size}`]: size !== "normal",
1015
- round,
1016
- }, className), onClick: onClick, ...restProps, children: [dot && jsx("span", { className: classNames("i-tag-dot", dotClass) }), children, onClose && (jsx(Helpericon, { active: true, className: "i-tag-close i-tag-hover-close", onClick: onClose }))] }));
1017
- };
1018
-
1019
- const CreateTag = memo(function CreateTag(props) {
1020
- const { isEditing, isLoading, createTagProps, tagProps, onBlur, onKeyDown, onStartCreate } = props;
1021
- if (isEditing) {
1022
- return (jsx(Tag, { ...createTagProps, className: classNames("i-pill", tagProps?.className, "i-pill-editing"), contentEditable: true, suppressContentEditableWarning: true, onBlur: () => onBlur(-1), onKeyDown: (e) => onKeyDown(e, -1), children: isLoading && jsx(Loading, { size: ".86em", className: "ml-4" }) }, "pill-editing"));
1023
- }
1024
- return (jsx(Tag, { ...createTagProps, className: classNames("i-pill", tagProps?.className, "i-pill-create"), onClick: onStartCreate, children: jsx("b", { children: "\uFF0B" }) }, "pill-create"));
1025
- });
1026
-
1027
- const TagItem = memo(function TagItem(props) {
1028
- const { item, index, isEditing, isLoading, tagProps, editable, readonly, renderItem, onClose, onClick, onBlur, onKeyDown } = props;
1029
- const isClickable = !isEditing && editable && !readonly;
1030
- if (renderItem) {
1031
- return renderItem({
1032
- value: item,
1033
- index,
1034
- editing: isEditing,
1035
- loading: isLoading,
1036
- readonly: !!readonly,
1037
- remove: () => onClose(index),
1038
- });
1039
- }
1040
- return (jsxs(Tag, { ...tagProps, className: classNames("i-pill", tagProps?.className, { "i-pill-editing": isEditing }), contentEditable: isEditing, suppressContentEditableWarning: true, onClose: !isEditing && !isLoading && !readonly ? () => onClose(index) : undefined, onClick: isClickable ? (e) => onClick(e, index) : undefined, onBlur: isEditing ? () => onBlur(index) : undefined, onKeyDown: isEditing ? (e) => onKeyDown(e, index) : undefined, children: [item, isLoading && jsx(Loading, { size: ".86em", className: "ml-4" })] }));
1041
- });
1042
-
1043
- function Pill(props) {
1044
- const { value = [], tagProps, max, icon = jsx(AddRound, {}), className, label, labelInline, readonly, editable, onChange, onUpdate, validator, format, renderItem, ...restProps } = props;
1045
- const [editingIndex, setEditingIndex] = useState(null);
1046
- const [loadingSet, setLoadingSet] = useState(new Set());
1047
- const instRef = useRef({
1048
- props,
1049
- editingIndex,
1050
- loadingSet,
1051
- setEditingIndex,
1052
- setLoadingSet,
1053
- });
1054
- instRef.current.props = props;
1055
- instRef.current.editingIndex = editingIndex;
1056
- instRef.current.loadingSet = loadingSet;
1057
- instRef.current.setEditingIndex = setEditingIndex;
1058
- instRef.current.setLoadingSet = setLoadingSet;
1059
- useEffect(() => {
1060
- if (editingIndex !== null) {
1061
- const el = document.querySelector(".i-pill-editing");
1062
- el?.focus();
1063
- }
1064
- }, [editingIndex]);
1065
- const cleanTagProps = useMemo(() => {
1066
- if (!tagProps)
1067
- return {};
1068
- const { onClose, dot, dotClass, ...rest } = tagProps;
1069
- return rest;
1070
- }, [tagProps]);
1071
- const handleClose = useCallback((index) => {
1072
- const inst = instRef.current;
1073
- if (inst.props.readonly)
1074
- return;
1075
- const hasAsync = !!inst.props.onUpdate;
1076
- if (hasAsync)
1077
- inst.setLoadingSet((prev) => new Set(prev).add(index));
1078
- setTimeout(async () => {
1079
- try {
1080
- const { props } = instRef.current;
1081
- const values = props.value ?? [];
1082
- const item = values[index];
1083
- if (item === undefined)
1084
- return;
1085
- const result = props.onUpdate?.(undefined, item, "delete");
1086
- if (result instanceof Promise) {
1087
- const ok = await result;
1088
- if (ok === false)
1089
- return;
1090
- }
1091
- const next = [...values];
1092
- next.splice(index, 1);
1093
- props.onChange?.(next);
1094
- }
1095
- finally {
1096
- if (hasAsync) {
1097
- instRef.current.setLoadingSet((prev) => {
1098
- const s = new Set(prev);
1099
- s.delete(index);
1100
- return s;
1101
- });
1102
- }
1103
- }
1104
- }, 0);
1105
- }, []);
1106
- const handleItemClick = useCallback((e, index) => {
1107
- if (e.target.closest(".i-helpericon"))
1108
- return;
1109
- const inst = instRef.current;
1110
- if (inst.props.readonly)
1111
- return;
1112
- if (index === -1 && inst.props.max !== undefined && (inst.props.value?.length ?? 0) >= inst.props.max)
1113
- return;
1114
- inst.setEditingIndex(index);
1115
- }, []);
1116
- const commitEdit = useCallback((index, text) => {
1117
- const inst = instRef.current;
1118
- const formatted = inst.props.format ? inst.props.format(text) : text;
1119
- const hasAsync = !!(inst.props.validator || inst.props.onUpdate);
1120
- if (hasAsync)
1121
- inst.setLoadingSet((prev) => new Set(prev).add(index));
1122
- setTimeout(async () => {
1123
- try {
1124
- const { props } = instRef.current;
1125
- if (props.validator) {
1126
- const valid = await Promise.resolve(props.validator(formatted));
1127
- if (!valid)
1128
- return;
1129
- }
1130
- const values = props.value ?? [];
1131
- if (index === -1) {
1132
- if (values.includes(formatted))
1133
- return;
1134
- const result = props.onUpdate?.(formatted, undefined, "create");
1135
- if (result instanceof Promise) {
1136
- const ok = await result;
1137
- if (ok === false)
1138
- return;
1139
- }
1140
- props.onChange?.([...values, formatted]);
1141
- }
1142
- else {
1143
- const oldValue = values[index];
1144
- if (oldValue === formatted)
1145
- return;
1146
- const result = props.onUpdate?.(formatted, oldValue, "update");
1147
- if (result instanceof Promise) {
1148
- const ok = await result;
1149
- if (ok === false)
1150
- return;
1151
- }
1152
- const next = [...values];
1153
- next[index] = formatted;
1154
- props.onChange?.(next);
1155
- }
1156
- }
1157
- finally {
1158
- if (hasAsync) {
1159
- instRef.current.setLoadingSet((prev) => {
1160
- const s = new Set(prev);
1161
- s.delete(index);
1162
- return s;
1163
- });
1164
- }
1165
- instRef.current.setEditingIndex(null);
1166
- }
1167
- }, 0);
1168
- }, []);
1169
- const handleBlur = useCallback((index) => {
1170
- const inst = instRef.current;
1171
- if (inst.loadingSet.has(index))
1172
- return;
1173
- const el = document.querySelector(".i-pill-editing");
1174
- const text = el?.textContent?.trim();
1175
- if (!text) {
1176
- if (index !== -1) {
1177
- handleClose(index);
1178
- }
1179
- else {
1180
- inst.setEditingIndex(null);
1181
- }
1182
- return;
1183
- }
1184
- commitEdit(index, text);
1185
- }, []);
1186
- const handleKeyDown = useCallback((e, index) => {
1187
- const inst = instRef.current;
1188
- if (inst.loadingSet.has(index))
1189
- return;
1190
- if (e.key === "Enter") {
1191
- e.preventDefault();
1192
- const text = e.currentTarget.textContent?.trim();
1193
- if (!text) {
1194
- if (index !== -1) {
1195
- handleClose(index);
1196
- }
1197
- else {
1198
- inst.setEditingIndex(null);
1199
- }
1200
- return;
1201
- }
1202
- commitEdit(index, text);
1203
- }
1204
- else if (e.key === "Escape") {
1205
- e.preventDefault();
1206
- if (index !== -1) {
1207
- const original = inst.props.value?.[index];
1208
- if (original !== undefined) {
1209
- e.currentTarget.textContent = original;
1210
- }
1211
- }
1212
- inst.setEditingIndex(null);
1213
- }
1214
- }, []);
1215
- const handleStartCreate = useCallback(() => {
1216
- const inst = instRef.current;
1217
- if (inst.props.readonly)
1218
- return;
1219
- if (inst.props.max !== undefined && (inst.props.value?.length ?? 0) >= inst.props.max)
1220
- return;
1221
- inst.setEditingIndex(-1);
1222
- }, []);
1223
- const canCreate = !readonly && (max === undefined || value.length < max);
1224
- return (jsxs("div", { className: classNames("i-pills i-input-label", { "i-input-inline": labelInline }, className), ...restProps, children: [label && jsx("span", { className: "i-input-label-text", children: label }), jsxs("div", { className: "i-pill-list", children: [value.map((item, i) => (jsx(TagItem, { item: item, index: i, isEditing: editingIndex === i, isLoading: loadingSet.has(i), tagProps: tagProps, editable: editable, readonly: readonly, renderItem: renderItem, onClose: handleClose, onClick: handleItemClick, onBlur: handleBlur, onKeyDown: handleKeyDown }, i))), canCreate && jsx(CreateTag, { isEditing: editingIndex === -1, isLoading: loadingSet.has(-1), createTagProps: cleanTagProps, tagProps: tagProps, onBlur: handleBlur, onKeyDown: handleKeyDown, onStartCreate: handleStartCreate })] })] }));
1225
- }
1226
-
1227
1001
  function Empty(props) {
1228
1002
  const { className, ...restProps } = props;
1229
1003
  return (jsx(InboxTwotone, { className: classNames("i-empty", className), ...restProps }));
@@ -3302,8 +3076,7 @@ class IFormInstance {
3302
3076
  for (let i = 1; i < parts.length; i++) {
3303
3077
  const ancestor = parts.slice(0, i).join(".");
3304
3078
  if (ancestor in this.data) {
3305
- console.warn(`[ioca-form] Field "${field}" conflicts with "${ancestor}". ` +
3306
- "Nested representation in form.get() may be inconsistent.");
3079
+ console.warn(`[ioca-form] Field "${field}" conflicts with "${ancestor}". ` + "Nested representation in form.get() may be inconsistent.");
3307
3080
  }
3308
3081
  }
3309
3082
  setDeep(this.data, field, value);
@@ -3355,7 +3128,7 @@ class IFormInstance {
3355
3128
  if (!field && o === undefined)
3356
3129
  return;
3357
3130
  const rule = {
3358
- validator: (v) => Array.isArray(v) ? v.length > 0 : ![undefined, null, ""].includes(v),
3131
+ validator: (v) => (Array.isArray(v) ? v.length > 0 : ![undefined, null, ""].includes(v)),
3359
3132
  };
3360
3133
  if (typeof o === "function")
3361
3134
  rule.validator = o;
@@ -3380,7 +3153,7 @@ class IFormInstance {
3380
3153
  });
3381
3154
  }
3382
3155
  });
3383
- return field ? isAllValid : isAllValid ? data : false;
3156
+ return isAllValid ? data : false;
3384
3157
  }
3385
3158
  }
3386
3159
  function useForm(form) {
@@ -3647,25 +3420,33 @@ function Modal(props) {
3647
3420
  if (!toggable.current)
3648
3421
  return;
3649
3422
  toggable.current = false;
3650
- if (!closable) {
3651
- setBounced(true);
3423
+ const canClose = typeof closable === 'function' ? closable() : closable;
3424
+ const exec = (result) => {
3425
+ if (!result) {
3426
+ setBounced(true);
3427
+ const timer = setTimeout(() => {
3428
+ setBounced(false);
3429
+ toggable.current = true;
3430
+ }, 400);
3431
+ return () => clearTimeout(timer);
3432
+ }
3433
+ setActive(false);
3434
+ updateVisible(mid, false);
3652
3435
  const timer = setTimeout(() => {
3653
- setBounced(false);
3436
+ if (!keepDOM)
3437
+ setShow(false);
3654
3438
  toggable.current = true;
3655
- }, 400);
3439
+ onVisibleChange?.(false);
3440
+ onClose?.();
3441
+ }, 240);
3656
3442
  return () => clearTimeout(timer);
3443
+ };
3444
+ if (canClose instanceof Promise) {
3445
+ canClose.then(exec);
3446
+ return;
3657
3447
  }
3658
- setActive(false);
3659
- updateVisible(mid, false);
3660
- const timer = setTimeout(() => {
3661
- if (!keepDOM)
3662
- setShow(false);
3663
- toggable.current = true;
3664
- onVisibleChange?.(false);
3665
- onClose?.();
3666
- }, 240);
3667
- return () => clearTimeout(timer);
3668
- }, [closable, keepDOM, onClose, onVisibleChange]);
3448
+ return exec(canClose);
3449
+ }, [closable, keepDOM, mid, onClose, onVisibleChange]);
3669
3450
  const handleBackdropClick = () => {
3670
3451
  backdropClosable && handleHide();
3671
3452
  };
@@ -3725,7 +3506,7 @@ function Modal(props) {
3725
3506
  e.stopPropagation();
3726
3507
  handleClick();
3727
3508
  onClick?.(e);
3728
- }, role: "dialog", "aria-modal": top, "data-mid": mid, ...restProps, children: jsxs(ModalContext.Provider, { value: true, children: [customized && children, !customized && (jsx(Content$2, { title: title, hideCloseButton: hideCloseButton, footer: footer, okButtonProps: okButtonProps, cancelButtonProps: cancelButtonProps, children: children, footerLeft: footerLeft, onOk: onOk, onClose: handleHide }))] }) }) }), getContainer());
3509
+ }, role: "dialog", "aria-modal": top, "data-mid": mid, ...restProps, children: jsxs(ModalContext.Provider, { value: true, children: [customized && children, !customized && jsx(Content$2, { title: title, hideCloseButton: hideCloseButton, footer: footer, okButtonProps: okButtonProps, cancelButtonProps: cancelButtonProps, children: children, footerLeft: footerLeft, onOk: onOk, onClose: handleHide })] }) }) }), getContainer());
3729
3510
  }
3730
3511
  Modal.useModal = useModal;
3731
3512
 
@@ -3739,7 +3520,17 @@ const HookModal = (props) => {
3739
3520
  },
3740
3521
  close: () => {
3741
3522
  state.visible = false;
3742
- if (mergedProps.closable ?? true)
3523
+ const canClose = typeof mergedProps.closable === 'function'
3524
+ ? mergedProps.closable()
3525
+ : (mergedProps.closable ?? true);
3526
+ if (canClose instanceof Promise) {
3527
+ canClose.then((result) => {
3528
+ if (!result)
3529
+ state.visible = true;
3530
+ });
3531
+ return;
3532
+ }
3533
+ if (canClose)
3743
3534
  return;
3744
3535
  Promise.resolve().then(() => {
3745
3536
  state.visible = true;
@@ -4385,7 +4176,7 @@ function InputContainer(props) {
4385
4176
  }
4386
4177
 
4387
4178
  const Number$1 = (props) => {
4388
- const { ref, label, name, value = "", labelInline, step = 1, min = -Infinity, max = Infinity, thousand, precision, type, className, width, status = "normal", append, border, underline, prepend, disabled, message, tip, hideControl, showMax, style, onChange, onEnter, onInput, onBlur, ...restProps } = props;
4179
+ const { ref, label, name, value = "", labelInline, step = 1, min = -Infinity, max = Infinity, thousand, precision, type, className, width, status = "normal", append, border = true, underline, prepend, disabled, message, tip, hideControl, showMax, style, onChange, onEnter, onInput, onBlur, ...restProps } = props;
4389
4180
  const [inputValue, setInputValue] = useState(value === undefined || value === null ? "" : String(value));
4390
4181
  const formatOut = (num) => {
4391
4182
  const v = clamp(num, min, max);
@@ -4398,9 +4189,7 @@ const Number$1 = (props) => {
4398
4189
  const body = negative ? s.slice(1) : s;
4399
4190
  const [integer, decimal] = body.split(".");
4400
4191
  const withThousand = integer.replace(/\B(?=(\d{3})+(?!\d))/g, thousand);
4401
- return decimal
4402
- ? `${negative ? "-" : ""}${withThousand}.${decimal}`
4403
- : `${negative ? "-" : ""}${withThousand}`;
4192
+ return decimal ? `${negative ? "-" : ""}${withThousand}.${decimal}` : `${negative ? "-" : ""}${withThousand}`;
4404
4193
  };
4405
4194
  const sanitizeNumberInput = (raw) => {
4406
4195
  const hasMinus = raw.startsWith("-");
@@ -4481,11 +4270,11 @@ const Number$1 = (props) => {
4481
4270
  [`i-input-${status}`]: status !== "normal",
4482
4271
  "i-input-borderless": !border,
4483
4272
  "i-input-underline": underline,
4484
- }), children: [prepend && jsx("div", { className: 'i-input-prepend', children: prepend }), !hideControl && !disabled && (jsx(Helpericon, { active: true, icon: jsx(MinusRound, {}), onClick: () => handleOperate(-step) })), jsx("input", { ...inputProps }), !hideControl && !disabled && (jsx(Helpericon, { active: true, icon: jsx(PlusRound, {}), onClick: () => handleOperate(step) })), showMax && max && !disabled && (jsx(Helpericon, { active: true, icon: jsx(KeyboardDoubleArrowUpRound, {}), onClick: handleMax })), append && jsx("div", { className: 'i-input-append', children: append })] }) }));
4273
+ }), children: [prepend && jsx("div", { className: "i-input-prepend", children: prepend }), !hideControl && !disabled && jsx(Helpericon, { active: true, icon: jsx(MinusRound, {}), onClick: () => handleOperate(-step) }), jsx("input", { ...inputProps }), !hideControl && !disabled && jsx(Helpericon, { active: true, icon: jsx(PlusRound, {}), onClick: () => handleOperate(step) }), showMax && max && !disabled && jsx(Helpericon, { active: true, icon: jsx(KeyboardDoubleArrowUpRound, {}), onClick: handleMax }), append && jsx("div", { className: "i-input-append", children: append })] }) }));
4485
4274
  };
4486
4275
 
4487
4276
  const Range = (props) => {
4488
- const { label, name, value, labelInline, min = -Infinity, max = Infinity, type, className, status = "normal", message, tip, append, prepend, step = 1, width, thousand, precision, hideControl, placeholder, border, underline, autoSwitch, onChange, onBlur, style, ...restProps } = props;
4277
+ const { label, name, value, labelInline, min = -Infinity, max = Infinity, type, className, status = "normal", message, tip, append, prepend, step = 1, width, thousand, precision, hideControl, placeholder, border = true, underline, autoSwitch, onChange, onBlur, style, ...restProps } = props;
4489
4278
  const [rangeValue, setRangeValue] = useState(value);
4490
4279
  const getRangeNumber = (v) => clamp(v, min, max);
4491
4280
  const getFormatNumber = (v) => formatNumber(v, { precision, thousand });
@@ -4545,11 +4334,11 @@ const Range = (props) => {
4545
4334
  [`i-input-${status}`]: status !== "normal",
4546
4335
  "i-input-borderless": !border,
4547
4336
  "i-input-underline": underline,
4548
- }), children: [prepend && jsx("div", { className: 'i-input-prepend', children: prepend }), !hideControl && (jsx(Helpericon, { active: true, icon: jsx(MinusRound, {}), onClick: (e) => handleOperate(e, -step, 0) })), jsx("input", { value: rangeValue?.[0] || "", placeholder: placeholder?.[0], ...inputProps, onBlur: handleBlur, onChange: (e) => handleChange(e, 0) }), !hideControl && (jsx(Helpericon, { active: true, icon: jsx(PlusRound, {}), onClick: (e) => handleOperate(e, step, 0) })), jsx(Helpericon, { active: true, icon: jsx(SyncAltRound, {}), style: { margin: 0 }, onClick: handleSwitch }), !hideControl && (jsx(Helpericon, { active: true, icon: jsx(MinusRound, {}), onClick: (e) => handleOperate(e, -step, 1) })), jsx("input", { value: rangeValue?.[1] || "", placeholder: placeholder?.[1], ...inputProps, onBlur: handleBlur, onChange: (e) => handleChange(e, 1) }), !hideControl && (jsx(Helpericon, { active: true, icon: jsx(PlusRound, {}), onClick: (e) => handleOperate(e, step, 1) })), append && jsx("div", { className: 'i-input-append', children: append })] }) }));
4337
+ }), children: [prepend && jsx("div", { className: "i-input-prepend", children: prepend }), !hideControl && jsx(Helpericon, { active: true, icon: jsx(MinusRound, {}), onClick: (e) => handleOperate(e, -step, 0) }), jsx("input", { value: rangeValue?.[0] || "", placeholder: placeholder?.[0], ...inputProps, onBlur: handleBlur, onChange: (e) => handleChange(e, 0) }), !hideControl && jsx(Helpericon, { active: true, icon: jsx(PlusRound, {}), onClick: (e) => handleOperate(e, step, 0) }), jsx(Helpericon, { active: true, icon: jsx(SyncAltRound, {}), style: { margin: 0 }, onClick: handleSwitch }), !hideControl && jsx(Helpericon, { active: true, icon: jsx(MinusRound, {}), onClick: (e) => handleOperate(e, -step, 1) }), jsx("input", { value: rangeValue?.[1] || "", placeholder: placeholder?.[1], ...inputProps, onBlur: handleBlur, onChange: (e) => handleChange(e, 1) }), !hideControl && jsx(Helpericon, { active: true, icon: jsx(PlusRound, {}), onClick: (e) => handleOperate(e, step, 1) }), append && jsx("div", { className: "i-input-append", children: append })] }) }));
4549
4338
  };
4550
4339
 
4551
4340
  const Textarea = (props) => {
4552
- const { ref, label, name, value = "", labelInline, className, status = "normal", message, tip, autoSize, border, width, style, resize, onChange, onEnter, ...restProps } = props;
4341
+ const { ref, label, name, value = "", labelInline, className, status = "normal", message, tip, autoSize, border = true, width, style, resize, onChange, onEnter, ...restProps } = props;
4553
4342
  const [textareaValue, setTextareaValue] = useState(value);
4554
4343
  const refTextarea = useRef(null);
4555
4344
  const syncTextareaHeight = () => {
@@ -4598,7 +4387,7 @@ const Textarea = (props) => {
4598
4387
  };
4599
4388
 
4600
4389
  const Input = ((props) => {
4601
- const { ref, type = "text", label, name, value = "", prepend, append, labelInline, className, status = "normal", message, tip, clear, width, hideVisible, border, underline, required, maxLength, onChange, onEnter, onClear, style, ...restProps } = props;
4390
+ const { ref, type = "text", label, name, value = "", prepend, append, labelInline, className, status = "normal", message, tip, clear, width, hideVisible, border = true, underline, required, maxLength, onChange, onEnter, onClear, style, ...restProps } = props;
4602
4391
  const [inputValue, setInputValue] = useState(value);
4603
4392
  const [inputType, setInputType] = useState(type);
4604
4393
  const [visible, setVisible] = useState(false);
@@ -4656,7 +4445,7 @@ const Input = ((props) => {
4656
4445
  [`i-input-${status}`]: status !== "normal",
4657
4446
  "i-input-borderless": !border,
4658
4447
  "i-input-underline": underline,
4659
- }), children: [prepend && jsx("div", { className: 'i-input-prepend', children: prepend }), jsx("input", { ...inputProps }), maxLength && inputValue?.length > 0 && (jsxs("span", { className: 'color-8 pr-4 font-sm', children: [inputValue.length, " / ", maxLength] })), jsx(Helpericon, { active: !!clearable || showHelper, icon: HelperIcon, className: classNames({ "i-helpericon-clear": isClearBtn }), onClick: handleHelperClick }), append && jsx("div", { className: 'i-input-append', children: append })] }) }));
4448
+ }), children: [prepend && jsx("div", { className: "i-input-prepend", children: prepend }), jsx("input", { ...inputProps }), maxLength && inputValue?.length > 0 && (jsxs("span", { className: "color-8 pr-4 font-sm", children: [inputValue.length, " / ", maxLength] })), jsx(Helpericon, { active: !!clearable || showHelper, icon: HelperIcon, className: classNames({ "i-helpericon-clear": isClearBtn }), onClick: handleHelperClick }), append && jsx("div", { className: "i-input-append", children: append })] }) }));
4660
4449
  });
4661
4450
  Input.Textarea = Textarea;
4662
4451
  Input.Number = Number$1;
@@ -4938,20 +4727,28 @@ const Pagination = (props) => {
4938
4727
  }), end < totalPage - 1 && renderEllipsis(), end < totalPage && (jsx(Page, { page: totalPage, onChange: handlePageChange, children: renderPage(totalPage) })), next && (jsx(Page, { page: page + 1, disabled: page === totalPage, onChange: handlePageChange, children: next }))] }));
4939
4728
  };
4940
4729
 
4730
+ const Tag = (props) => {
4731
+ const { dot, dotClass, outline, round, size = "normal", className, children, onClose, onClick, ...restProps } = props;
4732
+ return (jsxs("span", { className: classNames("i-tag", {
4733
+ "i-tag-outline": outline,
4734
+ "i-tag-clickable": onClick,
4735
+ [`i-tag-${size}`]: size !== "normal",
4736
+ round,
4737
+ }, className), onClick: onClick, ...restProps, children: [dot && jsx("span", { className: classNames("i-tag-dot", dotClass) }), children, onClose && (jsx(Helpericon, { active: true, className: "i-tag-close i-tag-hover-close", onClick: onClose }))] }));
4738
+ };
4739
+
4941
4740
  const Options = (props) => {
4942
- const { value: val, options, filter, filterPlaceholder, multiple, empty = jsx(Empty, {}), onSelect, onFilter, } = props;
4741
+ const { value: val, options, filter, filterPlaceholder, multiple, empty = jsx(Empty, {}), onSelect, onFilter } = props;
4943
4742
  return (jsxs("div", { className: classNames("i-select-options", {
4944
4743
  "i-select-options-multiple": multiple,
4945
- }), children: [filter && multiple && (jsxs("div", { className: 'i-select-filter', children: [jsx(Icon, { icon: jsx(SearchRound, {}), className: 'color-8 ml-8 my-auto', size: '1.2em' }), jsx("input", { type: 'text', className: 'i-input', placeholder: filterPlaceholder, onChange: onFilter })] })), options.length === 0 && empty, options.map((option, i) => {
4744
+ }), children: [filter && multiple && (jsxs("div", { className: "i-select-filter", children: [jsx(Icon, { icon: jsx(SearchRound, {}), className: "color-8 ml-8 my-auto", size: "1.2em" }), jsx("input", { type: "text", className: "i-input", placeholder: filterPlaceholder, onChange: onFilter })] })), options.length === 0 && empty, options.map((option, i) => {
4946
4745
  const { label, value, disabled } = option;
4947
- const isActive = multiple
4948
- ? val?.includes(value)
4949
- : val === value;
4950
- return (jsxs(List$1.Item, { active: isActive, type: 'option', onClick: () => onSelect?.(value, option), disabled: disabled, children: [multiple && (jsx(Icon, { icon: jsx(CheckRound, {}), className: 'i-select-option-check', size: '1em' })), label] }, value || i));
4746
+ const isActive = multiple ? val?.includes(value) : val === value;
4747
+ return (jsxs(List$1.Item, { active: isActive, type: "option", onClick: () => onSelect?.(value, option), disabled: disabled, children: [multiple && jsx(Icon, { icon: jsx(CheckRound, {}), className: "i-select-option-check", size: "1em" }), label] }, value || i));
4951
4748
  })] }));
4952
4749
  };
4953
4750
  const activeOptions = (options = [], value = [], max = 3) => {
4954
- const total = options.flatMap((opt) => value.includes(opt.value) ? [opt] : []);
4751
+ const total = options.flatMap((opt) => (value.includes(opt.value) ? [opt] : []));
4955
4752
  if (max >= total.length)
4956
4753
  return total;
4957
4754
  const rest = total.length - max;
@@ -4976,7 +4773,7 @@ const displayValue = (config) => {
4976
4773
  };
4977
4774
 
4978
4775
  const Select = (props) => {
4979
- const { ref, type = "text", name, label, value = "", placeholder, required, options = [], multiple, prepend, append, labelInline, style, className, message, status = "normal", hideClear, hideArrow, maxDisplay, border, filter, tip, filterPlaceholder = "...", popupProps, onSelect, onChange, ...restProps } = props;
4776
+ const { ref, type = "text", name, label, value = "", placeholder, required, options = [], multiple, prepend, append, labelInline, style, className, message, status = "normal", hideClear, hideArrow, maxDisplay, border = true, filter, tip, filterPlaceholder = "...", popupProps, onSelect, onChange, ...restProps } = props;
4980
4777
  const [filterValue, setFilterValue] = useState("");
4981
4778
  const [selectedValue, setSelectedValue] = useState(value);
4982
4779
  const [active, setActive] = useState(false);
@@ -4991,9 +4788,7 @@ const Select = (props) => {
4991
4788
  if (!fv || !filter)
4992
4789
  return formattedOptions;
4993
4790
  const lowerFv = fv.toLowerCase();
4994
- const filterFn = typeof filter === "function"
4995
- ? filter
4996
- : (opt) => opt._value.includes(lowerFv) || opt._label.includes(lowerFv);
4791
+ const filterFn = typeof filter === "function" ? filter : (opt) => opt._value.includes(lowerFv) || opt._label.includes(lowerFv);
4997
4792
  return formattedOptions.filter(filterFn);
4998
4793
  }, [formattedOptions, filter, filterValue]);
4999
4794
  const changeValue = (v) => {
@@ -5042,9 +4837,7 @@ const Select = (props) => {
5042
4837
  useEffect(() => {
5043
4838
  setSelectedValue(value);
5044
4839
  }, [value]);
5045
- const hasValue = multiple
5046
- ? selectedValue.length > 0
5047
- : !!selectedValue;
4840
+ const hasValue = multiple ? selectedValue.length > 0 : !!selectedValue;
5048
4841
  const clearable = !hideClear && active && hasValue;
5049
4842
  const text = message ?? tip;
5050
4843
  return (jsxs("label", { className: classNames("i-input-label", className, {
@@ -5061,7 +4854,7 @@ const Select = (props) => {
5061
4854
  multiple,
5062
4855
  maxDisplay,
5063
4856
  onSelect: handleSelect,
5064
- }) })) : (jsx("input", { className: "i-input i-select", placeholder: placeholder, readOnly: true }))) : null, !multiple && (jsx("input", { value: active ? filterValue : displayLabel, className: "i-input i-select", placeholder: displayLabel || placeholder, onChange: handleInputChange, readOnly: !filter })), jsx(Helpericon, { active: !hideArrow, icon: clearable ? undefined : jsx(UnfoldMoreRound, {}), onClick: handleHelperClick }), append] }) }), text && jsx("span", { className: "i-input-message", children: text })] }));
4857
+ }) })) : (jsx("input", { className: "i-input i-select", placeholder: placeholder, readOnly: true }))) : null, !multiple && jsx("input", { value: active ? filterValue : displayLabel, className: "i-input i-select", placeholder: displayLabel || placeholder, onChange: handleInputChange, readOnly: !filter }), jsx(Helpericon, { active: !hideArrow, icon: clearable ? undefined : jsx(UnfoldMoreRound, {}), onClick: handleHelperClick }), append] }) }), text && jsx("span", { className: "i-input-message", children: text })] }));
5065
4858
  };
5066
4859
 
5067
4860
  const ColorMethods = {
@@ -5531,6 +5324,214 @@ const DateRange = (props) => {
5531
5324
  return (jsx(Popup, { visible: active, trigger: 'click', position: 'bottom', arrow: false, align: 'start', onVisibleChange: handleVisibleChange, content: jsx(DoublePanel, { value: dayJsValue, weeks: weeks, unitYear: unitYear, unitMonth: unitMonth, renderDate: renderDate, renderMonth: renderMonth, renderYear: renderYear, disabledDate: disabledDate, onSelected: handleSelected }), children: jsx(Input, { value: inputValue, placeholder: placeholder, readOnly: true, clear: clear, onClear: handleClear, append: jsx(Icon, { icon: jsx(CalendarMonthTwotone, {}), className: 'i-datepicker-icon', size: '1em' }), className: classNames("i-datepicker-label", className), ...restProps }) }));
5532
5325
  };
5533
5326
 
5327
+ const CreateTag = memo(function CreateTag(props) {
5328
+ const { isEditing, isLoading, createTagProps, tagProps, onBlur, onKeyDown, onStartCreate } = props;
5329
+ if (isEditing) {
5330
+ return (jsx(Tag, { ...createTagProps, className: classNames("i-pill", tagProps?.className, "i-pill-editing"), contentEditable: true, suppressContentEditableWarning: true, onBlur: () => onBlur(-1), onKeyDown: (e) => onKeyDown(e, -1), children: isLoading && jsx(Loading, { size: ".86em", className: "ml-4" }) }, "pill-editing"));
5331
+ }
5332
+ return (jsx(Tag, { ...createTagProps, className: classNames("i-pill", tagProps?.className, "i-pill-create"), onClick: onStartCreate, children: jsx("b", { children: "\uFF0B" }) }, "pill-create"));
5333
+ });
5334
+
5335
+ const TagItem = memo(function TagItem(props) {
5336
+ const { item, index, isEditing, isLoading, tagProps, editable, readonly, renderItem, onClose, onClick, onBlur, onKeyDown } = props;
5337
+ const isClickable = !isEditing && editable && !readonly;
5338
+ if (renderItem) {
5339
+ return renderItem({
5340
+ value: item,
5341
+ index,
5342
+ editing: isEditing,
5343
+ loading: isLoading,
5344
+ readonly: !!readonly,
5345
+ remove: () => onClose(index),
5346
+ });
5347
+ }
5348
+ return (jsxs(Tag, { ...tagProps, className: classNames("i-pill", tagProps?.className, { "i-pill-editing": isEditing }), contentEditable: isEditing, suppressContentEditableWarning: true, onClose: !isEditing && !isLoading && !readonly ? () => onClose(index) : undefined, onClick: isClickable ? (e) => onClick(e, index) : undefined, onBlur: isEditing ? () => onBlur(index) : undefined, onKeyDown: isEditing ? (e) => onKeyDown(e, index) : undefined, children: [item, isLoading && jsx(Loading, { size: ".86em", className: "ml-4" })] }));
5349
+ });
5350
+
5351
+ function Pill(props) {
5352
+ const { value = [], tagProps, max, icon = jsx(AddRound, {}), className, label, labelInline, readonly, editable, onChange, onUpdate, validator, format, renderItem, hideCreate, ...restProps } = props;
5353
+ const [editingIndex, setEditingIndex] = useState(null);
5354
+ const [loadingSet, setLoadingSet] = useState(new Set());
5355
+ const instRef = useRef({
5356
+ props,
5357
+ editingIndex,
5358
+ loadingSet,
5359
+ setEditingIndex,
5360
+ setLoadingSet,
5361
+ });
5362
+ instRef.current.props = props;
5363
+ instRef.current.editingIndex = editingIndex;
5364
+ instRef.current.loadingSet = loadingSet;
5365
+ instRef.current.setEditingIndex = setEditingIndex;
5366
+ instRef.current.setLoadingSet = setLoadingSet;
5367
+ useEffect(() => {
5368
+ if (editingIndex !== null) {
5369
+ const el = document.querySelector(".i-pill-editing");
5370
+ el?.focus();
5371
+ }
5372
+ }, [editingIndex]);
5373
+ const cleanTagProps = useMemo(() => {
5374
+ if (!tagProps)
5375
+ return {};
5376
+ const { onClose, dot, dotClass, ...rest } = tagProps;
5377
+ return rest;
5378
+ }, [tagProps]);
5379
+ const handleClose = useCallback((index) => {
5380
+ const inst = instRef.current;
5381
+ if (inst.props.readonly)
5382
+ return;
5383
+ const hasAsync = !!inst.props.onUpdate;
5384
+ if (hasAsync)
5385
+ inst.setLoadingSet((prev) => new Set(prev).add(index));
5386
+ setTimeout(async () => {
5387
+ try {
5388
+ const { props } = instRef.current;
5389
+ const values = props.value ?? [];
5390
+ const item = values[index];
5391
+ if (item === undefined)
5392
+ return;
5393
+ const result = props.onUpdate?.(undefined, item, "delete");
5394
+ if (result instanceof Promise) {
5395
+ const ok = await result;
5396
+ if (ok === false)
5397
+ return;
5398
+ }
5399
+ const next = [...values];
5400
+ next.splice(index, 1);
5401
+ props.onChange?.(next);
5402
+ }
5403
+ finally {
5404
+ if (hasAsync) {
5405
+ instRef.current.setLoadingSet((prev) => {
5406
+ const s = new Set(prev);
5407
+ s.delete(index);
5408
+ return s;
5409
+ });
5410
+ }
5411
+ }
5412
+ }, 0);
5413
+ }, []);
5414
+ const handleItemClick = useCallback((e, index) => {
5415
+ if (e.target.closest(".i-helpericon"))
5416
+ return;
5417
+ const inst = instRef.current;
5418
+ if (inst.props.readonly)
5419
+ return;
5420
+ if (index === -1 && inst.props.max !== undefined && (inst.props.value?.length ?? 0) >= inst.props.max)
5421
+ return;
5422
+ inst.setEditingIndex(index);
5423
+ }, []);
5424
+ const commitEdit = useCallback((index, text) => {
5425
+ const inst = instRef.current;
5426
+ const formatted = inst.props.format ? inst.props.format(text) : text;
5427
+ const hasAsync = !!(inst.props.validator || inst.props.onUpdate);
5428
+ if (hasAsync)
5429
+ inst.setLoadingSet((prev) => new Set(prev).add(index));
5430
+ setTimeout(async () => {
5431
+ try {
5432
+ const { props } = instRef.current;
5433
+ if (props.validator) {
5434
+ const valid = await Promise.resolve(props.validator(formatted));
5435
+ if (!valid)
5436
+ return;
5437
+ }
5438
+ const values = props.value ?? [];
5439
+ if (index === -1) {
5440
+ if (values.includes(formatted))
5441
+ return;
5442
+ const result = props.onUpdate?.(formatted, undefined, "create");
5443
+ if (result instanceof Promise) {
5444
+ const ok = await result;
5445
+ if (ok === false)
5446
+ return;
5447
+ }
5448
+ props.onChange?.([...values, formatted]);
5449
+ }
5450
+ else {
5451
+ const oldValue = values[index];
5452
+ if (oldValue === formatted)
5453
+ return;
5454
+ const result = props.onUpdate?.(formatted, oldValue, "update");
5455
+ if (result instanceof Promise) {
5456
+ const ok = await result;
5457
+ if (ok === false)
5458
+ return;
5459
+ }
5460
+ const next = [...values];
5461
+ next[index] = formatted;
5462
+ props.onChange?.(next);
5463
+ }
5464
+ }
5465
+ finally {
5466
+ if (hasAsync) {
5467
+ instRef.current.setLoadingSet((prev) => {
5468
+ const s = new Set(prev);
5469
+ s.delete(index);
5470
+ return s;
5471
+ });
5472
+ }
5473
+ instRef.current.setEditingIndex(null);
5474
+ }
5475
+ }, 0);
5476
+ }, []);
5477
+ const handleBlur = useCallback((index) => {
5478
+ const inst = instRef.current;
5479
+ if (inst.loadingSet.has(index))
5480
+ return;
5481
+ const el = document.querySelector(".i-pill-editing");
5482
+ const text = el?.textContent?.trim();
5483
+ if (!text) {
5484
+ if (index !== -1) {
5485
+ handleClose(index);
5486
+ }
5487
+ else {
5488
+ inst.setEditingIndex(null);
5489
+ }
5490
+ return;
5491
+ }
5492
+ commitEdit(index, text);
5493
+ }, []);
5494
+ const handleKeyDown = useCallback((e, index) => {
5495
+ const inst = instRef.current;
5496
+ if (inst.loadingSet.has(index))
5497
+ return;
5498
+ if (e.key === "Enter") {
5499
+ e.preventDefault();
5500
+ const text = e.currentTarget.textContent?.trim();
5501
+ if (!text) {
5502
+ if (index !== -1) {
5503
+ handleClose(index);
5504
+ }
5505
+ else {
5506
+ inst.setEditingIndex(null);
5507
+ }
5508
+ return;
5509
+ }
5510
+ commitEdit(index, text);
5511
+ }
5512
+ else if (e.key === "Escape") {
5513
+ e.preventDefault();
5514
+ if (index !== -1) {
5515
+ const original = inst.props.value?.[index];
5516
+ if (original !== undefined) {
5517
+ e.currentTarget.textContent = original;
5518
+ }
5519
+ }
5520
+ inst.setEditingIndex(null);
5521
+ }
5522
+ }, []);
5523
+ const handleStartCreate = useCallback(() => {
5524
+ const inst = instRef.current;
5525
+ if (inst.props.readonly)
5526
+ return;
5527
+ if (inst.props.max !== undefined && (inst.props.value?.length ?? 0) >= inst.props.max)
5528
+ return;
5529
+ inst.setEditingIndex(-1);
5530
+ }, []);
5531
+ const canCreate = !hideCreate && !readonly && (max === undefined || value.length < max);
5532
+ return (jsxs("div", { className: classNames("i-pills i-input-label", { "i-input-inline": labelInline }, className), ...restProps, children: [label && jsx("span", { className: "i-input-label-text", children: label }), jsxs("div", { className: "i-pill-list", children: [value.map((item, i) => (jsx(TagItem, { item: item, index: i, isEditing: editingIndex === i, isLoading: loadingSet.has(i), tagProps: tagProps, editable: editable, readonly: readonly, renderItem: renderItem, onClose: handleClose, onClick: handleItemClick, onBlur: handleBlur, onKeyDown: handleKeyDown }, i))), canCreate && jsx(CreateTag, { isEditing: editingIndex === -1, isLoading: loadingSet.has(-1), createTagProps: cleanTagProps, tagProps: tagProps, onBlur: handleBlur, onKeyDown: handleKeyDown, onStartCreate: handleStartCreate })] })] }));
5533
+ }
5534
+
5534
5535
  const defaultOk = {
5535
5536
  children: "确定",
5536
5537
  };
@@ -7182,4 +7183,4 @@ const useTheme = (props) => {
7182
7183
  };
7183
7184
  };
7184
7185
 
7185
- export { Affix, Badge, Button, Card, Checkbox, Collapse, ColorPicker, Datagrid, Datepicker as DatePicker, DateRange, Description, Drawer, Dropdown, Editor, Flex, Form, Icon, MemoImage as Image, Input, List$1 as List, Loading, Message, Modal, Pagination, Pill, Popconfirm, Popup, Progress, Radio, Resizable, River, Scroll, Select, Step, Swiper, Tabs, Tag, Text, TimePicker, Tree, Upload, Video, usePreview, useTheme };
7186
+ export { Affix, Badge, Button, Checkbox, Collapse, ColorPicker, Datagrid, Datepicker as DatePicker, DateRange, Description, Drawer, Dropdown, Editor, Flex, Form, Icon, MemoImage as Image, Input, List$1 as List, Loading, Message, Modal, Pagination, Pill, Popconfirm, Popup, Progress, Radio, Resizable, River, Scroll, Select, Step, Swiper, Tabs, Tag, Text, TimePicker, Tree, Upload, Video, usePreview, useTheme };