@ioca/react 1.5.27 → 1.5.29

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, 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, 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';
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';
@@ -1006,6 +1006,224 @@ const Collapse = (props) => {
1006
1006
  };
1007
1007
  Collapse.Item = Item$5;
1008
1008
 
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
+
1009
1227
  function Empty(props) {
1010
1228
  const { className, ...restProps } = props;
1011
1229
  return (jsx(InboxTwotone, { className: classNames("i-empty", className), ...restProps }));
@@ -1037,16 +1255,8 @@ const sumLengths = (parts) => {
1037
1255
  return `calc(${out.join(" + ")})`;
1038
1256
  };
1039
1257
  const toCssWidth = (w) => typeof w === "number" ? `${w}px` : w;
1040
- const buildGridTemplateColumns = (widths, overrideIndex, overrideWidth) => {
1041
- let out = "";
1042
- for (let i = 0; i < widths.length; i++) {
1043
- const w = i === overrideIndex && overrideWidth != null
1044
- ? overrideWidth
1045
- : widths[i];
1046
- out += (i ? " " : "") + toCssWidth(w);
1047
- }
1048
- return out;
1049
- };
1258
+ const buildGridTemplateColumns = (widths, overrideIndex, overrideWidth) => buildCssWidths(widths, overrideIndex, overrideWidth).join(" ");
1259
+ const hasArrayChanged = (a, b) => a.length !== b.length || a.some((v, i) => v !== b[i]);
1050
1260
  const buildCssWidths = (widths, overrideIndex, overrideWidth) => {
1051
1261
  const out = new Array(widths.length);
1052
1262
  for (let i = 0; i < widths.length; i++) {
@@ -1519,21 +1729,8 @@ const Datagrid = (props) => {
1519
1729
  });
1520
1730
  const previewRef = useRef({ index: -1, width: -1, template: "" });
1521
1731
  useEffect(() => {
1522
- const next = columns.map((col, i) => {
1523
- if (col.width != null)
1524
- return col.width;
1525
- return state.widths[i] ?? "min-content";
1526
- });
1527
- let changed = next.length !== state.widths.length;
1528
- if (!changed) {
1529
- for (let i = 0; i < next.length; i++) {
1530
- if (next[i] !== state.widths[i]) {
1531
- changed = true;
1532
- break;
1533
- }
1534
- }
1535
- }
1536
- if (changed)
1732
+ const next = columns.map((col, i) => (col.width != null ? col.width : (state.widths[i] ?? "min-content")));
1733
+ if (hasArrayChanged(next, state.widths))
1537
1734
  state.widths = next;
1538
1735
  }, [columns, state]);
1539
1736
  const styles = useMemo(() => {
@@ -1557,8 +1754,7 @@ const Datagrid = (props) => {
1557
1754
  const el = wrapRef.current;
1558
1755
  if (!el)
1559
1756
  return;
1560
- if (previewRef.current.index === i &&
1561
- previewRef.current.width === w) {
1757
+ if (previewRef.current.index === i && previewRef.current.width === w) {
1562
1758
  return;
1563
1759
  }
1564
1760
  const template = buildGridTemplateColumns(state.widths, i, w);
@@ -1597,9 +1793,7 @@ const Datagrid = (props) => {
1597
1793
  const { sortBy, sortType } = state;
1598
1794
  if (sortBy && !onSort) {
1599
1795
  const sorter = columns.find((col) => col.id === sortBy)?.sorter;
1600
- const sortFn = typeof sorter === "function"
1601
- ? sorter
1602
- : (a, b) => b[sortBy] - a[sortBy];
1796
+ const sortFn = typeof sorter === "function" ? sorter : (a, b) => b[sortBy] - a[sortBy];
1603
1797
  const sorted = [...data].sort(sortFn);
1604
1798
  return sortType === "desc" ? sorted : sorted.reverse();
1605
1799
  }
@@ -1612,66 +1806,36 @@ const Datagrid = (props) => {
1612
1806
  return !!rowHeight && rowHeight > 0;
1613
1807
  }, [virtual]);
1614
1808
  useEffect(() => {
1615
- if (!resizable)
1616
- return;
1617
1809
  if (!container.current)
1618
1810
  return;
1619
- if (!columns.some((col, i) => col.width == null && typeof state.widths[i] !== "number")) {
1811
+ const hasUnmeasured = columns.some((col, i) => col.width == null && typeof state.widths[i] !== "number");
1812
+ if (!hasUnmeasured)
1620
1813
  return;
1621
- }
1622
- let rafId = null;
1623
- let tries = 0;
1624
- const run = () => {
1625
- rafId = null;
1626
- const div = container.current;
1627
- if (!div)
1628
- return;
1814
+ const div = container.current;
1815
+ const rafId = requestAnimationFrame(() => {
1629
1816
  const headerRow = div.querySelector(".i-datagrid-header.i-datagrid-row");
1630
1817
  const bodyRow = div.querySelector(".i-datagrid-row:not(.i-datagrid-header)");
1631
1818
  const headerCells = headerRow ? Array.from(headerRow.children) : [];
1632
1819
  const bodyCells = bodyRow ? Array.from(bodyRow.children) : [];
1633
1820
  const cellCount = Math.max(headerCells.length, bodyCells.length);
1634
- if (cellCount < 1) {
1635
- tries++;
1636
- if (tries < 10)
1637
- rafId = requestAnimationFrame(run);
1821
+ if (cellCount < 1)
1638
1822
  return;
1639
- }
1640
- const measured = new Array(cellCount).fill(null);
1641
- for (let i = 0; i < cellCount; i++) {
1642
- const hw = headerCells[i]
1643
- ?.offsetWidth;
1644
- const bw = bodyCells[i]
1645
- ?.offsetWidth;
1646
- const w = Math.max(hw ?? 0, bw ?? 0);
1647
- measured[i] = w > 0 ? w : null;
1648
- }
1649
1823
  const next = columns.map((col, i) => {
1650
1824
  if (col.width != null)
1651
1825
  return col.width;
1652
- const cur = state.widths[i];
1653
- if (typeof cur === "number")
1654
- return cur;
1655
- return measured[i] ?? cur ?? "min-content";
1826
+ const prev = state.widths[i];
1827
+ if (typeof prev === "number")
1828
+ return prev;
1829
+ const hw = headerCells[i]?.offsetWidth ?? 0;
1830
+ const bw = bodyCells[i]?.offsetWidth ?? 0;
1831
+ const w = Math.max(hw, bw);
1832
+ return w > 0 ? w : "min-content";
1656
1833
  });
1657
- let changed = next.length !== state.widths.length;
1658
- if (!changed) {
1659
- for (let i = 0; i < next.length; i++) {
1660
- if (next[i] !== state.widths[i]) {
1661
- changed = true;
1662
- break;
1663
- }
1664
- }
1665
- }
1666
- if (changed)
1834
+ if (hasArrayChanged(next, state.widths))
1667
1835
  state.widths = next;
1668
- };
1669
- rafId = requestAnimationFrame(run);
1670
- return () => {
1671
- if (rafId != null)
1672
- cancelAnimationFrame(rafId);
1673
- };
1674
- }, [columns, resizable, state, useVirtual]);
1836
+ });
1837
+ return () => cancelAnimationFrame(rafId);
1838
+ }, [columns, state]);
1675
1839
  useEffect(() => {
1676
1840
  if (!loading)
1677
1841
  return;
@@ -1693,16 +1857,14 @@ const Datagrid = (props) => {
1693
1857
  }, [rowKey]);
1694
1858
  return (jsxs("div", { ref: wrapRef, style: {
1695
1859
  maxHeight: height,
1696
- ...(useVirtual
1697
- ? { overflowX: "visible", overflowY: "hidden" }
1698
- : null),
1860
+ ...(useVirtual ? { overflowX: "visible", overflowY: "hidden" } : null),
1699
1861
  ...mergedStyle,
1700
1862
  }, className: classNames("i-datagrid-container", className, {
1701
1863
  "i-datagrid-bordered": border,
1702
1864
  "i-datagrid-striped": striped,
1703
1865
  }), children: [useVirtual && virtual ? (jsx(VirtualDatagrid, { virtual: virtual, columns: columns, rows: rows, header: header, sortBy: state.sortBy, sortType: state.sortType, height: height, loading: loading, resizable: resizable, striped: striped, cellEllipsis: cellEllipsis, empty: empty, wrapRef: wrapRef, containerRef: container, getRowKey: getRowKey, onHeaderClick: handleHeaderClick, onWidthChange: handleWidthChange, onRowClick: onRowClick, onCellClick: onCellClick, onCellDoubleClick: onCellDoubleClick, onScroll: onScroll })) : (jsxs("div", { ref: container, className: classNames("i-datagrid", {
1704
1866
  "i-datagrid-loading": loading,
1705
- }), onWheel: onScroll, children: [header && (jsx(Header, { columns: columns, resizable: resizable, sortType: state.sortType, sortBy: state.sortBy, cellEllipsis: cellEllipsis, onWidthChange: handleWidthChange, onHeaderClick: handleHeaderClick })), rows.map((row, i) => (jsx(Row, { row: i + (header ? 1 : 0), data: row, cellEllipsis: cellEllipsis, columns: columns, onCellClick: onCellClick, onRowClick: onRowClick, onCellDoubleClick: onCellDoubleClick }, getRowKey(row, i) ?? i))), rows.length < 1 && empty] })), loading && renderLoading()] }));
1867
+ }), onWheel: onScroll, children: [header && jsx(Header, { columns: columns, resizable: resizable, sortType: state.sortType, sortBy: state.sortBy, cellEllipsis: cellEllipsis, onWidthChange: handleWidthChange, onHeaderClick: handleHeaderClick }), rows.map((row, i) => (jsx(Row, { row: i + (header ? 1 : 0), data: row, cellEllipsis: cellEllipsis, columns: columns, onCellClick: onCellClick, onRowClick: onRowClick, onCellDoubleClick: onCellDoubleClick }, getRowKey(row, i) ?? i))), rows.length < 1 && empty] })), loading && renderLoading()] }));
1706
1868
  };
1707
1869
 
1708
1870
  const Description = (props) => {
@@ -4776,18 +4938,6 @@ const Pagination = (props) => {
4776
4938
  }), 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 }))] }));
4777
4939
  };
4778
4940
 
4779
- const Tag = (props) => {
4780
- const { dot, dotClass, outline, round, size = "normal", hoverShowClose, className, children, onClose, onClick, ...restProps } = props;
4781
- return (jsxs("span", { className: classNames("i-tag", {
4782
- "i-tag-outline": outline,
4783
- "i-tag-clickable": onClick,
4784
- [`i-tag-${size}`]: size !== "normal",
4785
- round,
4786
- }, className), onClick: onClick, ...restProps, children: [dot && jsx("span", { className: classNames("i-tag-dot", dotClass) }), children, onClose && (jsx(Helpericon, { active: true, className: classNames("i-tag-close", {
4787
- "i-tag-hover-close": hoverShowClose,
4788
- }), onClick: onClose }))] }));
4789
- };
4790
-
4791
4941
  const Options = (props) => {
4792
4942
  const { value: val, options, filter, filterPlaceholder, multiple, empty = jsx(Empty, {}), onSelect, onFilter, } = props;
4793
4943
  return (jsxs("div", { className: classNames("i-select-options", {
@@ -4816,7 +4966,7 @@ const displayValue = (config) => {
4816
4966
  if (typeof opt === "number")
4817
4967
  return jsxs(Tag, { children: ["+", opt] }, i);
4818
4968
  const { label, value } = opt;
4819
- return (jsx(Tag, { hoverShowClose: true, onClose: (e) => {
4969
+ return (jsx(Tag, { onClose: (e) => {
4820
4970
  e?.stopPropagation();
4821
4971
  onSelect?.(value, opt);
4822
4972
  }, children: label }, value));
@@ -4830,14 +4980,20 @@ const Select = (props) => {
4830
4980
  const [filterValue, setFilterValue] = useState("");
4831
4981
  const [selectedValue, setSelectedValue] = useState(value);
4832
4982
  const [active, setActive] = useState(false);
4833
- const formattedOptions = useMemo(() => formatOption(options), [options]);
4983
+ const formattedOptions = useMemo(() => {
4984
+ return formatOption(options).map((opt) => {
4985
+ const label = typeof opt.label === "string" ? opt.label : String(opt.value ?? "");
4986
+ return { ...opt, _label: label.toLowerCase(), _value: String(opt.value ?? "").toLowerCase() };
4987
+ });
4988
+ }, [options]);
4834
4989
  const filterOptions = useMemo(() => {
4835
4990
  const fv = filterValue;
4836
4991
  if (!fv || !filter)
4837
4992
  return formattedOptions;
4993
+ const lowerFv = fv.toLowerCase();
4838
4994
  const filterFn = typeof filter === "function"
4839
4995
  ? filter
4840
- : (opt) => opt.value.includes(fv) || opt.label.includes(fv);
4996
+ : (opt) => opt._value.includes(lowerFv) || opt._label.includes(lowerFv);
4841
4997
  return formattedOptions.filter(filterFn);
4842
4998
  }, [formattedOptions, filter, filterValue]);
4843
4999
  const changeValue = (v) => {
@@ -6346,21 +6502,21 @@ const Tabs = ((props) => {
6346
6502
  Tabs.Item = Item;
6347
6503
 
6348
6504
  const TreeItemHeader = (props) => {
6349
- const { as: Tag = "a", href, selected, children, ...restProps } = props;
6505
+ const { as: Tag = "a", href, selected, children, attrs, ...restProps } = props;
6350
6506
  const className = classNames("i-tree-item-header", {
6351
6507
  "i-tree-item-selected": selected,
6352
6508
  });
6353
6509
  if (typeof Tag === "string") {
6354
- return (jsx(Tag, { href: href, className: className, ...restProps, children: children }));
6510
+ return (jsx(Tag, { href: href, className: className, ...attrs, ...restProps, children: children }));
6355
6511
  }
6356
- return (jsx(Tag, { to: href || "", className: className, ...restProps, children: children }));
6512
+ return (jsx(Tag, { to: href || "", className: className, ...attrs, ...restProps, children: children }));
6357
6513
  };
6358
6514
  function TreeRow(props) {
6359
6515
  const { flatNode, wrapperStyle, virtualMode, selected, checkedSet, partofs = {}, checkable, nodeProps, renderExtra, loadingKeys, onExpand, onItemClick, onItemSelect, onItemCheck, } = props;
6360
6516
  const { node, depth, isExpanded } = flatNode;
6361
- const { key = "", as, href, icon, title, disabled, type } = node;
6517
+ const { key = "", as, href, icon, title, disabled, type, attrs } = node;
6362
6518
  const children = node[nodeProps.children];
6363
- const hasChildren = children instanceof Promise || (Array.isArray(children) && children.length > 0);
6519
+ const hasChildren = children instanceof Promise || typeof children === "function" || (Array.isArray(children) && children.length > 0);
6364
6520
  const loading = loadingKeys?.includes(key);
6365
6521
  if (type === "title") {
6366
6522
  return jsx("div", { style: wrapperStyle, className: "i-tree-group-title", children: title });
@@ -6368,7 +6524,7 @@ function TreeRow(props) {
6368
6524
  if (type && type !== "item") {
6369
6525
  return jsx("div", { style: wrapperStyle, className: `i-tree-type-${type}`, children: title });
6370
6526
  }
6371
- return (jsx("div", { className: !virtualMode ? classNames("i-tree-item", { "i-tree-expand": isExpanded }) : undefined, style: wrapperStyle, children: jsxs(TreeItemHeader, { as: as, href: href, style: { paddingLeft: `${depth * 1.5 + 0.5}em` }, selected: selected === key, onClick: (e) => {
6527
+ return (jsx("div", { className: !virtualMode ? classNames("i-tree-item", { "i-tree-expand": isExpanded }) : undefined, style: wrapperStyle, children: jsxs(TreeItemHeader, { as: as, attrs: attrs, href: href, style: { paddingLeft: `${depth * 1.5 + 0.5}em` }, selected: selected === key, onClick: (e) => {
6372
6528
  if (disabled) {
6373
6529
  e.preventDefault();
6374
6530
  e.stopPropagation();
@@ -6397,7 +6553,7 @@ function TreeList(props) {
6397
6553
  }
6398
6554
 
6399
6555
  function VirtualTree(props) {
6400
- const { flatNodes, onExpand, selected, checkedSet, partofs = {}, checkable, nodeProps, renderExtra, loadingKeys, height, useVirtual, className, style, onItemClick, onItemSelect, onItemCheck, } = props;
6556
+ const { flatNodes, onExpand, selected, checkedSet, partofs = {}, checkable, nodeProps, renderExtra, loadingKeys, height, virtual, className, style, onItemClick, onItemSelect, onItemCheck, } = props;
6401
6557
  const listRef = useRef(null);
6402
6558
  const wrapRef = useRef(null);
6403
6559
  const ro = useResizeObserver();
@@ -6424,7 +6580,7 @@ function VirtualTree(props) {
6424
6580
  return null;
6425
6581
  return (jsx(TreeRow, { flatNode: flatNode, wrapperStyle: style, virtualMode: true, selected: p.selected, checkedSet: p.checkedSet, partofs: p.partofs, checkable: p.checkable, nodeProps: p.nodeProps, renderExtra: p.renderExtra, loadingKeys: p.loadingKeys, onExpand: p.onExpand, onItemClick: p.onItemClick, onItemSelect: p.onItemSelect, onItemCheck: p.onItemCheck }));
6426
6582
  }, []);
6427
- return (jsx("div", { ref: wrapRef, className: classNames("i-tree", className), style: { display: "block", width: "100%", height: "100%", ...style }, children: jsx(List$2, { listRef: listRef, rowCount: flatNodes.length, rowHeight: useVirtual.rowHeight, overscanCount: Math.max(3, useVirtual.threshold ?? 8), rowProps: {}, style: {
6583
+ return (jsx("div", { ref: wrapRef, className: classNames("i-tree", className), style: { display: "block", width: "100%", height: "100%", ...style }, children: jsx(List$2, { listRef: listRef, rowCount: flatNodes.length, rowHeight: virtual.rowHeight, overscanCount: Math.max(3, virtual.threshold ?? 8), rowProps: {}, style: {
6428
6584
  width: "100%",
6429
6585
  height: listHeight,
6430
6586
  overflow: "auto",
@@ -6454,7 +6610,7 @@ function flattenTree(nodes, expandedMap, nodeProps, depth = 0, parentItem, async
6454
6610
  return result;
6455
6611
  }
6456
6612
  const Tree = (props) => {
6457
- const { data = [], ref, selected, checked = [], disabledRelated, nodeProps, height, useVirtual, onItemSelect, onItemCheck, ...restProps } = props;
6613
+ const { data = [], ref, selected, checked = [], disabledRelated, nodeProps, height, virtual, onItemSelect, onItemCheck, ...restProps } = props;
6458
6614
  const [selectedKey, setSelectedKey] = useState(selected);
6459
6615
  const [checkedKeys, setCheckedKeys] = useState(checked);
6460
6616
  const [partofs, setPartofs] = useState({});
@@ -6486,14 +6642,16 @@ const Tree = (props) => {
6486
6642
  if (!item)
6487
6643
  return;
6488
6644
  const rawChildren = item[oNodeProps.children];
6489
- const isAsync = rawChildren instanceof Promise;
6645
+ const isLazy = typeof rawChildren === "function";
6646
+ const isAsync = rawChildren instanceof Promise || isLazy;
6490
6647
  const isExpanded = !!expandedMap[key];
6491
6648
  if (isAsync && !isExpanded) {
6492
6649
  flushSync(() => {
6493
6650
  setLoadingMap((prev) => ({ ...prev, [key]: true }));
6494
6651
  setExpandedMap((prev) => ({ ...prev, [key]: true }));
6495
6652
  });
6496
- rawChildren
6653
+ const promise = isLazy ? rawChildren() : rawChildren;
6654
+ promise
6497
6655
  .then((resolved) => {
6498
6656
  item[oNodeProps.children] = resolved;
6499
6657
  setAsyncChildrenMap((prev) => ({ ...prev, [key]: resolved }));
@@ -6650,8 +6808,8 @@ const Tree = (props) => {
6650
6808
  },
6651
6809
  };
6652
6810
  });
6653
- if (useVirtual) {
6654
- return (jsx(VirtualTree, { flatNodes: flatNodes, onExpand: handleExpand, height: height, useVirtual: useVirtual, selected: selectedKey, checkedSet: checkedSet, partofs: partofs, nodeProps: oNodeProps, loadingKeys: loadingKeys, onItemCheck: handleCheck, onItemSelect: handleSelect, ...restProps }));
6811
+ if (virtual) {
6812
+ return (jsx(VirtualTree, { flatNodes: flatNodes, onExpand: handleExpand, height: height, virtual: virtual, selected: selectedKey, checkedSet: checkedSet, partofs: partofs, nodeProps: oNodeProps, loadingKeys: loadingKeys, onItemCheck: handleCheck, onItemSelect: handleSelect, ...restProps }));
6655
6813
  }
6656
6814
  return (jsx(TreeList, { flatNodes: flatNodes, onExpand: handleExpand, selected: selectedKey, checkedSet: checkedSet, partofs: partofs, nodeProps: oNodeProps, loadingKeys: loadingKeys, onItemCheck: handleCheck, onItemSelect: handleSelect, ...restProps }));
6657
6815
  };
@@ -7024,4 +7182,4 @@ const useTheme = (props) => {
7024
7182
  };
7025
7183
  };
7026
7184
 
7027
- 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, Popconfirm, Popup, Progress, Radio, Resizable, River, Scroll, Select, Step, Swiper, Tabs, Tag, Text, TimePicker, Tree, Upload, Video, usePreview, useTheme };
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 };
@@ -0,0 +1,5 @@
1
+ import Pill from './pill.js';
2
+
3
+
4
+
5
+ export { Pill as default };
@@ -0,0 +1,6 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { IPill } from './type.js';
3
+
4
+ declare function Pill(props: IPill): react_jsx_runtime.JSX.Element;
5
+
6
+ export { Pill as default };
@@ -0,0 +1,27 @@
1
+ import { HTMLAttributes, ReactNode } from 'react';
2
+ import { ITag } from '../tag/type.js';
3
+
4
+ interface IPill extends Omit<HTMLAttributes<HTMLDivElement>, "onChange" | "value"> {
5
+ value?: any[];
6
+ onChange?: (value: any[]) => void;
7
+ tagProps?: Partial<ITag>;
8
+ max?: number;
9
+ icon?: ReactNode;
10
+ editable?: boolean;
11
+ readonly?: boolean;
12
+ label?: ReactNode;
13
+ labelInline?: boolean;
14
+ validator?: (value: any) => boolean | Promise<boolean>;
15
+ format?: (value: any) => any;
16
+ onUpdate?: (newValue: any, oldValue: any, type: "delete" | "create" | "update") => void | Promise<boolean>;
17
+ renderItem?: (context: {
18
+ value: any;
19
+ index: number;
20
+ editing: boolean;
21
+ loading: boolean;
22
+ readonly: boolean;
23
+ remove: () => void;
24
+ }) => ReactNode;
25
+ }
26
+
27
+ export type { IPill };
@@ -6,7 +6,6 @@ interface ITag extends HTMLAttributes<HTMLSpanElement> {
6
6
  outline?: boolean;
7
7
  round?: boolean;
8
8
  size?: "small" | "normal" | "large" | "extreme";
9
- hoverShowClose?: boolean;
10
9
  onClick?: (e: MouseEvent) => void;
11
10
  onClose?: (e: MouseEvent) => void;
12
11
  }
@@ -8,10 +8,11 @@ interface ITreeItem {
8
8
  title: string | ReactNode;
9
9
  icon?: ReactNode;
10
10
  href?: string;
11
- children?: ITreeItem[] | Promise<ITreeItem[]>;
11
+ children?: ITreeItem[] | Promise<ITreeItem[]> | (() => Promise<ITreeItem[]>);
12
12
  expanded?: boolean;
13
13
  disabled?: boolean;
14
14
  checked?: boolean;
15
+ attrs?: Record<string, any>;
15
16
  parent?: ITreeItem;
16
17
  [key: string]: any;
17
18
  }
@@ -34,7 +35,7 @@ interface ITree {
34
35
  disabledRelated?: boolean;
35
36
  round?: boolean;
36
37
  height?: number | string;
37
- useVirtual?: TVirtual;
38
+ virtual?: TVirtual;
38
39
  style?: CSSProperties;
39
40
  className?: string;
40
41
  renderExtra?: (item: ITreeItem) => ReactNode;
@@ -4,6 +4,7 @@ export { default as Button } from './components/button/button.js';
4
4
  export { default as Card } from './components/card/card.js';
5
5
  export { default as Checkbox } from './components/checkbox/checkbox.js';
6
6
  export { default as Collapse } from './components/collapse/collapse.js';
7
+ export { default as Pill } from './components/pill/pill.js';
7
8
  export { default as Datagrid } from './components/datagrid/datagrid.js';
8
9
  export { default as Description } from './components/description/description.js';
9
10
  export { default as Drawer } from './components/drawer/drawer.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ioca/react",
3
- "version": "1.5.27",
3
+ "version": "1.5.29",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite",