@ioca/react 1.5.10 → 1.5.11

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
@@ -1,8 +1,8 @@
1
1
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
2
  import classNames from 'classnames';
3
- import { debounce, uid, throttle, title } from 'radash';
3
+ import { debounce, uid, throttle } from 'radash';
4
4
  import { useState, useRef, useEffect, useCallback, useMemo, Children, cloneElement, createElement, isValidElement, memo, Fragment as Fragment$1, useTransition, forwardRef, useLayoutEffect, createContext, useContext, useImperativeHandle } from 'react';
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, InfoOutlined, KeyboardArrowDownRound, ListAltRound, DriveFolderUploadOutlined, PlusSharp } 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, InfoOutlined, KeyboardArrowDownRound, MoveToInboxTwotone, OutboxTwotone, FilePresentOutlined, DriveFolderUploadOutlined, PlusSharp } 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 } from 'react-dom';
@@ -3657,7 +3657,7 @@ function renderFile(props) {
3657
3657
  case TFileType.VIDEO:
3658
3658
  return jsx(Video, { ...props });
3659
3659
  default:
3660
- return (jsxs("div", { className: 'i-preview-unknown', children: [jsx(Icon, { icon: jsx(FeedOutlined, {}), size: '3em' }), jsx("h5", { className: 'mt-4', children: name || suffix || "?" })] }));
3660
+ return (jsxs("div", { className: "i-preview-unknown", children: [jsx(Icon, { icon: jsx(FeedOutlined, {}), size: "3em" }), jsx("h5", { className: "mt-4", children: name || suffix })] }));
3661
3661
  }
3662
3662
  }
3663
3663
 
@@ -5991,155 +5991,220 @@ const Tree = (props) => {
5991
5991
  return (jsx(TreeList, { data: data, selected: selectedKey, checked: checkedKeys, partofs: partofs, nodeProps: oNodeProps, onItemCheck: handleCheck, onItemSelect: handleSelect, ...restProps }));
5992
5992
  };
5993
5993
 
5994
- const ListContainer = (props) => {
5995
- const { sortable, onSortEnd, itemProps, ...restProps } = props;
5996
- const customProps = {
5997
- className: "i-upload-list",
5998
- onClick: (e) => {
5999
- e.stopPropagation();
6000
- e.preventDefault();
6001
- },
5994
+ const Dropbox = (props) => {
5995
+ const { multiple, accept, disabled, children, onChange, onDropFiles } = props;
5996
+ const [dragging, setDragging] = useState(false);
5997
+ const inputRef = useRef(null);
5998
+ const handleDragOver = (e) => {
5999
+ e.preventDefault();
6000
+ e.stopPropagation();
6001
+ };
6002
+ const handleDragEnter = (e) => {
6003
+ e.preventDefault();
6004
+ e.stopPropagation();
6005
+ if (!disabled)
6006
+ setDragging(true);
6007
+ };
6008
+ const handleDragLeave = (e) => {
6009
+ e.preventDefault();
6010
+ e.stopPropagation();
6011
+ setDragging(false);
6012
+ };
6013
+ const handleDrop = (e) => {
6014
+ e.preventDefault();
6015
+ e.stopPropagation();
6016
+ setDragging(false);
6017
+ if (disabled)
6018
+ return;
6019
+ const files = Array.from(e.dataTransfer.files);
6020
+ if (files.length)
6021
+ onDropFiles(files);
6022
+ };
6023
+ const handleClick = () => {
6024
+ if (!disabled)
6025
+ inputRef.current?.click();
6002
6026
  };
6027
+ return (jsxs("div", { className: classNames("i-upload-dropbox", dragging && "i-upload-dropbox-active"), onDragOver: handleDragOver, onDragEnter: handleDragEnter, onDragLeave: handleDragLeave, onDrop: handleDrop, onClick: handleClick, children: [jsx("input", { ref: inputRef, type: "file", className: "i-input-file-hidden", multiple: multiple, accept: accept, disabled: disabled, onChange: onChange }), typeof children === "function"
6028
+ ? children(dragging)
6029
+ : children || (jsx(Icon, { icon: dragging ? (jsx(MoveToInboxTwotone, {})) : (jsx(OutboxTwotone, {})), size: "2em" }))] }));
6030
+ };
6031
+
6032
+ const ListContainer = memo((props) => {
6033
+ const { sortable, onSortEnd, children, ...restProps } = props;
6003
6034
  if (!sortable) {
6004
- return jsx("div", { ...customProps, ...restProps });
6035
+ return (jsx("div", { className: "i-upload-list", onClick: (e) => {
6036
+ e.stopPropagation();
6037
+ e.preventDefault();
6038
+ }, ...restProps, children: children }));
6005
6039
  }
6006
- return (jsx(SortableContainer, { draggedItemClassName: 'i-upload-item-dragged', onSortEnd: onSortEnd, ...customProps, ...restProps }));
6007
- };
6008
- const FileListItem = (props) => {
6040
+ return (jsx(SortableContainer, { draggedItemClassName: "i-upload-item-dragged", onSortEnd: onSortEnd, className: "i-upload-list", ...restProps, children: children }));
6041
+ });
6042
+ const CloseBtn = memo(({ index, onRemove }) => (jsx(Helpericon, { active: true, className: "i-upload-delete", onClick: (e) => {
6043
+ e.stopPropagation();
6044
+ e.preventDefault();
6045
+ onRemove(index);
6046
+ } })));
6047
+ const FileListItem = memo((props) => {
6009
6048
  const { ref, mode, index, file, renderItem, onRemove, onPreview } = props;
6010
6049
  if (!file)
6011
- return "";
6012
- const { id, name, size, url, src } = file;
6050
+ return null;
6051
+ const { name, size, url, src } = file;
6013
6052
  const type = getFileType(name, file.type);
6053
+ const handleClick = useCallback(() => {
6054
+ onPreview?.(index);
6055
+ }, [onPreview, index]);
6014
6056
  if (renderItem) {
6015
6057
  return renderItem(file, index);
6016
6058
  }
6017
- const CloseBtn = (jsx(Helpericon, { active: true, className: 'i-upload-delete', onClick: (e) => {
6018
- e.stopPropagation();
6019
- e.preventDefault();
6020
- onRemove(index);
6021
- } }));
6059
+ const node = useMemo(() => {
6060
+ switch (type) {
6061
+ case TFileType.IMAGE:
6062
+ return (jsx(MemoImage, { lazyload: true, src: url || src, fit: "cover", onMouseDown: (e) => e.preventDefault() }));
6063
+ case TFileType.VIDEO:
6064
+ return jsx("video", { src: url || src, preload: "none" });
6065
+ default:
6066
+ return (jsxs(Fragment, { children: [jsx(Icon, { icon: jsx(FilePresentOutlined, {}), size: "1.5em" }), jsx("span", { className: "i-upload-file-name", children: name })] }));
6067
+ }
6068
+ }, [type, url, src, name]);
6022
6069
  switch (mode) {
6023
6070
  case "card":
6024
- let node = jsx(Fragment, {});
6025
- switch (type) {
6026
- case TFileType.IMAGE:
6027
- node = (jsx(MemoImage, { lazyload: true, src: url || src, fit: 'cover', onMouseDown: (e) => e.preventDefault() }));
6028
- break;
6029
- case TFileType.VIDEO:
6030
- node = jsx("video", { src: url || src, preload: 'none' });
6031
- break;
6032
- default:
6033
- node = (jsxs(Fragment, { children: [jsx(Icon, { icon: jsx(ListAltRound, {}) }), jsx("span", { className: 'i-upload-file-name', children: title(name) })] }));
6034
- break;
6035
- }
6036
- return (jsxs("div", { ref: ref, title: name, className: 'i-upload-item-card', onClick: () => onPreview?.(index), children: [node, CloseBtn] }));
6071
+ return (jsxs("div", { ref: ref, className: "i-upload-item-card", onClick: handleClick, children: [node, jsx(CloseBtn, { index: index, onRemove: onRemove }), name && (jsx("span", { className: "px-12 py-8 i-upload-tip", children: name }))] }));
6037
6072
  default:
6038
- return (jsxs("div", { ref: ref, className: 'i-upload-item', onClick: () => onPreview?.(index), children: [jsx("span", { children: name }), jsx("i", { className: 'i-upload-size', children: formatBytes(size ?? 0) }), CloseBtn] }, id));
6073
+ return (jsxs("div", { ref: ref, className: "i-upload-item", onClick: handleClick, children: [jsx("span", { children: name }), jsx("i", { className: "i-upload-size", children: formatBytes(size ?? 0) }), jsx(CloseBtn, { index: index, onRemove: onRemove })] }));
6039
6074
  }
6040
- };
6075
+ });
6041
6076
 
6077
+ const normalizeFiles = (files) => (files ?? []).map((item) => {
6078
+ const file = item;
6079
+ if (item instanceof File) {
6080
+ const src = file.src ?? URL.createObjectURL(item);
6081
+ Object.assign(item, {
6082
+ id: file.id ?? uid(7),
6083
+ src,
6084
+ url: file.url ?? src,
6085
+ });
6086
+ return item;
6087
+ }
6088
+ const src = file.src ?? file.name;
6089
+ return {
6090
+ ...file,
6091
+ id: file.id ?? uid(7),
6092
+ src,
6093
+ url: file.url ?? src,
6094
+ };
6095
+ });
6042
6096
  const Upload = (props) => {
6043
- const { ref, label, labelInline, value, files = [], initialFiles, placeholder, status = "normal", message, className, style, children, defaultButtonProps, mode = "default", cardSize = "4em", disabled, sortable, limit = props.multiple ? Infinity : 1, multiple, renderItem, shouldUpload = () => true, uploader, onChange, onFilesChange, onUpload, ...restProps } = props;
6044
- const [fileList, setFileListState] = useState(files);
6045
- const [uploadMessage, setUploadMessage] = useState(message);
6097
+ const { label, labelInline, value, files, placeholder, status = "normal", message, className, style, children, droppable, dropbox, defaultButtonProps, mode = "default", cardSize = "3.2em", disabled, sortable, limit = props.multiple ? Infinity : 1, multiple, renderItem, shouldUpload = () => true, uploader, onChange, onFilesChange, onUpload, onRemove, ...restProps } = props;
6098
+ const [internalFileList, setInternalFileList] = useState([]);
6099
+ const isControlled = useMemo(() => value !== undefined || files !== undefined, [value, files]);
6100
+ const fileList = isControlled
6101
+ ? normalizeFiles(value ?? files ?? [])
6102
+ : internalFileList;
6103
+ const uploadMessage = message;
6046
6104
  const inputRef = useRef(null);
6047
6105
  const preview = usePreview();
6048
- const defBtnProps = Object.assign({
6106
+ const defBtnProps = useMemo(() => ({
6049
6107
  children: (jsxs(Fragment, { children: [jsx(Icon, { icon: jsx(DriveFolderUploadOutlined, {}) }), " \u4E0A\u4F20"] })),
6050
- }, defaultButtonProps);
6108
+ ...defaultButtonProps,
6109
+ }), [defaultButtonProps]);
6051
6110
  const trigger = useMemo(() => {
6052
6111
  if (children)
6053
6112
  return children;
6054
6113
  switch (mode) {
6055
6114
  case "card":
6056
- return (jsx(Button, { className: 'i-upload-card-btn color-5', square: true, flat: true, outline: true, disabled: disabled, children: jsx(Icon, { icon: jsx(PlusSharp, {}) }) }));
6115
+ return (jsx(Button, { className: "i-upload-card-btn color-5", square: true, flat: true, outline: true, disabled: disabled, children: jsx(Icon, { icon: jsx(PlusSharp, {}) }) }));
6057
6116
  default:
6058
6117
  return (jsx(Button, { ...defBtnProps, className: classNames("i-upload-btn", defBtnProps.className), disabled: disabled }));
6059
6118
  }
6060
- }, [mode, children]);
6061
- const handleChange = (e) => {
6062
- const files = Array.from(e.target.files || []);
6119
+ }, [mode, children, disabled, defBtnProps]);
6120
+ const handleUpload = useCallback(async (files) => {
6121
+ if (!uploader)
6122
+ return;
6123
+ const shouldUploadFiles = files.filter(shouldUpload);
6124
+ const result = await Promise.all(shouldUploadFiles.map(uploader));
6125
+ return onUpload?.(result);
6126
+ }, [uploader, shouldUpload, onUpload]);
6127
+ const processFiles = useCallback((inputFiles) => {
6063
6128
  const before = fileList;
6064
6129
  const changed = [];
6065
- files.map((f) => {
6066
- const { id, name, size, type } = f;
6067
- const same = before.find((pf) => {
6068
- const { name: n, size: s, type: t } = pf;
6069
- return n === name && s === size && t === type;
6070
- });
6071
- const src = URL.createObjectURL(f);
6072
- Object.assign(f, {
6130
+ inputFiles.forEach((file) => {
6131
+ const { id, name, size, type } = file;
6132
+ const same = before.some((pf) => pf.name === name &&
6133
+ pf.size === size &&
6134
+ pf.type === type);
6135
+ const src = URL.createObjectURL(file);
6136
+ Object.assign(file, {
6073
6137
  id: id ?? uid(7),
6074
- src: src ?? f.name,
6075
- url: src ?? f.name,
6138
+ src: src ?? file.name,
6139
+ url: src ?? file.name,
6076
6140
  });
6077
- !same && changed.push(f);
6141
+ if (!same)
6142
+ changed.push(file);
6078
6143
  });
6079
6144
  const after = [...before, ...changed];
6080
6145
  const last = after.at(-1);
6081
- const nextFiles = multiple ? after.slice(0, limit) : last ? [last] : [];
6082
- setFileListState(nextFiles);
6083
- setUploadMessage(message);
6146
+ const nextFiles = multiple
6147
+ ? after.slice(0, limit)
6148
+ : last
6149
+ ? [last]
6150
+ : [];
6151
+ return { nextFiles, changed };
6152
+ }, [fileList, multiple, limit]);
6153
+ const applyFiles = useCallback((nextFiles, changed, e) => {
6154
+ if (!isControlled)
6155
+ setInternalFileList(nextFiles);
6084
6156
  onFilesChange?.(nextFiles, changed, e);
6085
6157
  onChange?.(nextFiles, e);
6086
6158
  handleUpload(changed);
6087
- inputRef.current && (inputRef.current.value = "");
6088
- };
6089
- const handleRemove = (i) => {
6090
- const [...files] = fileList;
6159
+ }, [isControlled, onFilesChange, onChange, handleUpload]);
6160
+ const handleChange = useCallback((e) => {
6161
+ const inputFiles = Array.from(e.target.files || []);
6162
+ const { nextFiles, changed } = processFiles(inputFiles);
6163
+ applyFiles(nextFiles, changed, e);
6164
+ if (inputRef.current)
6165
+ inputRef.current.value = "";
6166
+ }, [processFiles, applyFiles]);
6167
+ const handleDropFiles = useCallback((files) => {
6168
+ const { nextFiles, changed } = processFiles(files);
6169
+ applyFiles(nextFiles, changed);
6170
+ }, [processFiles, applyFiles]);
6171
+ const handleRemove = useCallback((i) => {
6172
+ const files = [...fileList];
6091
6173
  const changed = files.splice(i, 1);
6092
6174
  URL.revokeObjectURL(changed[0]?.src || "");
6093
- setFileListState(files);
6175
+ if (!isControlled)
6176
+ setInternalFileList(files);
6094
6177
  onFilesChange?.(files, changed);
6095
6178
  onChange?.(files);
6096
- inputRef.current && (inputRef.current.value = "");
6097
- };
6098
- const handleUpload = async (files) => {
6099
- if (!uploader)
6100
- return;
6101
- const shouldUploadFiles = files.filter(shouldUpload);
6102
- const result = Promise.all(shouldUploadFiles.map(uploader));
6103
- return onUpload?.(result);
6104
- };
6105
- const handlePreview = (i) => {
6106
- preview({ items: fileList, initial: i });
6107
- };
6108
- const setFileList = (files) => {
6109
- if (!files)
6110
- return;
6111
- setFileListState(files.map((f) => {
6112
- const file = f;
6113
- return { ...file, id: file.id ?? uid(7) };
6114
- }));
6115
- };
6116
- const handleSortEnd = (before, after) => {
6117
- const [...files] = fileList;
6179
+ onRemove?.(changed[0]);
6180
+ if (inputRef.current)
6181
+ inputRef.current.value = "";
6182
+ }, [fileList, isControlled, onFilesChange, onChange, onRemove]);
6183
+ const handlePreview = useCallback((i) => {
6184
+ preview({
6185
+ items: fileList,
6186
+ initial: i,
6187
+ });
6188
+ }, [fileList, preview]);
6189
+ const handleSortEnd = useCallback((before, after) => {
6190
+ const files = [...fileList];
6118
6191
  const nextFiles = arrayMove(files, before, after);
6119
- setFileListState(nextFiles);
6192
+ if (!isControlled)
6193
+ setInternalFileList(nextFiles);
6194
+ onFilesChange?.(nextFiles, []);
6120
6195
  onChange?.(nextFiles);
6121
- };
6122
- useEffect(() => {
6123
- setUploadMessage(message);
6124
- }, [status, message]);
6125
- useEffect(() => {
6126
- setFileListState(value?.filter?.((file) => !!file.id) ?? []);
6127
- }, [value]);
6128
- useEffect(() => {
6129
- setFileList(initialFiles);
6130
- }, []);
6131
- useImperativeHandle(ref, () => ({
6132
- getFileList: () => fileList,
6133
- setFileList,
6134
- }), [fileList]);
6135
- return (jsx(InputContainer, { as: 'div', label: label, labelInline: labelInline, className: classNames("i-input-label-file", className), style: style, children: jsxs("div", { className: classNames("i-upload-inner", {
6196
+ }, [fileList, isControlled, onFilesChange, onChange]);
6197
+ return (jsx(InputContainer, { as: "div", label: label, labelInline: labelInline, className: classNames("i-input-label-file", className), style: style, children: jsxs("div", { className: classNames("i-upload-inner", {
6136
6198
  [`i-upload-${mode}`]: mode !== "default",
6137
6199
  }), style: { ["--upload-card-size"]: cardSize }, children: [jsx(ListContainer, { sortable: sortable, onSortEnd: handleSortEnd, children: fileList.map((file, i) => {
6138
- const node = (jsx(FileListItem, { index: i, file: file, mode: mode, renderItem: renderItem, onRemove: handleRemove, onPreview: handlePreview }, i));
6200
+ const f = file;
6201
+ const key = f.id ?? i;
6202
+ const node = (jsx(FileListItem, { index: i, file: f, mode: mode, renderItem: renderItem, onRemove: handleRemove, onPreview: handlePreview }, key));
6139
6203
  if (!sortable)
6140
6204
  return node;
6141
- return jsx(SortableItem, { children: node }, i);
6142
- }) }), uploadMessage && (jsx("span", { className: 'i-upload-message', children: uploadMessage })), fileList.length < limit && (jsxs("label", { children: [jsx("input", { ...restProps, disabled: disabled, ref: inputRef, type: 'file', className: 'i-input-file-hidden', multiple: multiple, onChange: handleChange }), trigger] }))] }) }));
6205
+ return jsx(SortableItem, { children: node }, key);
6206
+ }) }), uploadMessage && (jsx("span", { className: "i-upload-message", children: uploadMessage })), fileList.length < limit &&
6207
+ (droppable ? (jsx(Dropbox, { multiple: multiple, accept: restProps.accept, disabled: disabled, onChange: handleChange, onDropFiles: handleDropFiles, children: dropbox })) : (jsxs("label", { children: [jsx("input", { ...restProps, disabled: disabled, ref: inputRef, type: "file", className: "i-input-file-hidden", multiple: multiple, onChange: handleChange }), trigger] })))] }) }));
6143
6208
  };
6144
6209
 
6145
6210
  const stores = new Map();
@@ -1,11 +1,9 @@
1
- import { InputHTMLAttributes, RefObject, ReactNode, ChangeEvent } from 'react';
1
+ import { InputHTMLAttributes, ReactNode, ChangeEvent } from 'react';
2
2
  import { BaseInput } from '../../type/index.js';
3
3
  import { IButton } from '../button/type.js';
4
4
 
5
5
  interface IUpload extends Omit<BaseInput, "ref">, Omit<InputHTMLAttributes<HTMLInputElement>, "value" | "onChange"> {
6
- ref?: RefObject<RefUpload | null>;
7
6
  files?: IFile[];
8
- initialFiles?: IFile[] | File[];
9
7
  accept?: string;
10
8
  multiple?: boolean;
11
9
  directory?: boolean;
@@ -13,6 +11,7 @@ interface IUpload extends Omit<BaseInput, "ref">, Omit<InputHTMLAttributes<HTMLI
13
11
  sortable?: boolean;
14
12
  mode?: "default" | "card";
15
13
  droppable?: boolean;
14
+ dropbox?: (dragging?: boolean) => ReactNode;
16
15
  cardSize?: string;
17
16
  defaultButtonProps?: IButton;
18
17
  shouldUpload?: (file: IFile) => boolean;
@@ -29,9 +28,5 @@ interface IFile extends File {
29
28
  url?: string;
30
29
  [key: string]: any;
31
30
  }
32
- interface RefUpload {
33
- getFileList: () => IFile[];
34
- setFileList: (files?: IFile[] | File[]) => void;
35
- }
36
31
 
37
- export type { IFile, IUpload, RefUpload };
32
+ export type { IFile, IUpload };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ioca/react",
3
- "version": "1.5.10",
3
+ "version": "1.5.11",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite",