@ioca/react 1.5.5 → 1.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/lib/cjs/components/editor/controls.js +12 -34
  2. package/lib/cjs/components/editor/controls.js.map +1 -1
  3. package/lib/cjs/components/editor/editor.js +34 -27
  4. package/lib/cjs/components/editor/editor.js.map +1 -1
  5. package/lib/cjs/components/editor/memtion.js +93 -4
  6. package/lib/cjs/components/editor/memtion.js.map +1 -1
  7. package/lib/cjs/components/picker/colors/index.js +6 -5
  8. package/lib/cjs/components/picker/colors/index.js.map +1 -1
  9. package/lib/cjs/components/select/select.js +4 -4
  10. package/lib/cjs/components/select/select.js.map +1 -1
  11. package/lib/cjs/components/tabs/contents.js +28 -0
  12. package/lib/cjs/components/tabs/contents.js.map +1 -0
  13. package/lib/cjs/components/tabs/helper.js +68 -0
  14. package/lib/cjs/components/tabs/helper.js.map +1 -0
  15. package/lib/cjs/components/tabs/navs.js +38 -0
  16. package/lib/cjs/components/tabs/navs.js.map +1 -0
  17. package/lib/cjs/components/tabs/tabs.js +62 -84
  18. package/lib/cjs/components/tabs/tabs.js.map +1 -1
  19. package/lib/css/index.css +1 -1
  20. package/lib/css/index.css.map +1 -1
  21. package/lib/css/reset.css +2 -2
  22. package/lib/es/components/editor/controls.js +13 -35
  23. package/lib/es/components/editor/controls.js.map +1 -1
  24. package/lib/es/components/editor/editor.js +35 -28
  25. package/lib/es/components/editor/editor.js.map +1 -1
  26. package/lib/es/components/editor/memtion.js +93 -5
  27. package/lib/es/components/editor/memtion.js.map +1 -1
  28. package/lib/es/components/picker/colors/index.js +6 -5
  29. package/lib/es/components/picker/colors/index.js.map +1 -1
  30. package/lib/es/components/select/select.js +4 -4
  31. package/lib/es/components/select/select.js.map +1 -1
  32. package/lib/es/components/tabs/contents.js +20 -0
  33. package/lib/es/components/tabs/contents.js.map +1 -0
  34. package/lib/es/components/tabs/helper.js +63 -0
  35. package/lib/es/components/tabs/helper.js.map +1 -0
  36. package/lib/es/components/tabs/navs.js +30 -0
  37. package/lib/es/components/tabs/navs.js.map +1 -0
  38. package/lib/es/components/tabs/tabs.js +64 -86
  39. package/lib/es/components/tabs/tabs.js.map +1 -1
  40. package/lib/index.js +297 -154
  41. package/lib/types/components/editor/type.d.ts +1 -1
  42. package/lib/types/components/picker/type.d.ts +3 -1
  43. package/lib/types/components/tabs/type.d.ts +4 -4
  44. package/package.json +1 -1
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, throttle, title } from 'radash';
4
4
  import { useState, useRef, useCallback, useEffect, useMemo, Children, cloneElement, createElement, isValidElement, Fragment as Fragment$1, useTransition, forwardRef, useLayoutEffect, memo, createContext, useContext, useImperativeHandle } from 'react';
5
- import { SkipPreviousRound, CloseRound, MinusRound, PlusRound, InboxTwotone, ClearAllRound, UndoRound, RedoRound, StrikethroughSRound, FormatUnderlinedRound, FormatItalicRound, FormatBoldRound, 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, ListAltRound, DriveFolderUploadOutlined, PlusSharp } from '@ricons/material';
6
6
  import { createRoot } from 'react-dom/client';
7
7
  import { createPortal } from 'react-dom';
8
8
  import xss from 'xss';
@@ -1867,57 +1867,67 @@ const xssOptions = {
1867
1867
  const handleMouseDown = (e) => {
1868
1868
  e.preventDefault();
1869
1869
  };
1870
- const fnMap = {
1871
- bold: {
1872
- icon: jsx(FormatBoldRound, {}),
1873
- onClick: () => exec("bold"),
1874
- },
1875
- italic: {
1876
- icon: jsx(FormatItalicRound, {}),
1877
- onClick: () => exec("italic"),
1878
- },
1879
- underline: {
1870
+ const defaultControls = [
1871
+ { key: "undo", icon: jsx(UndoRound, {}), onClick: () => exec("undo") },
1872
+ { key: "redo", icon: jsx(RedoRound, {}), onClick: () => exec("redo") },
1873
+ { key: "bold", icon: jsx(FormatBoldRound, {}), onClick: () => exec("bold") },
1874
+ { key: "italic", icon: jsx(FormatItalicRound, {}), onClick: () => exec("italic") },
1875
+ {
1876
+ key: "underline",
1880
1877
  icon: jsx(FormatUnderlinedRound, {}),
1881
1878
  onClick: () => exec("underline"),
1882
1879
  },
1883
- strike: {
1880
+ {
1881
+ key: "strike",
1884
1882
  icon: jsx(StrikethroughSRound, {}),
1885
1883
  onClick: () => exec("strikeThrough"),
1886
1884
  },
1887
- redo: {
1888
- icon: jsx(RedoRound, {}),
1889
- onClick: () => exec("redo"),
1890
- },
1891
- undo: {
1892
- icon: jsx(UndoRound, {}),
1893
- onClick: () => exec("undo"),
1894
- },
1895
- clear: {
1885
+ {
1886
+ key: "clear",
1896
1887
  icon: jsx(ClearAllRound, {}),
1897
1888
  onClick: () => exec("removeFormat"),
1898
1889
  },
1899
- };
1900
- const defaultKeys = [
1901
- "undo",
1902
- "redo",
1903
- "bold",
1904
- "italic",
1905
- "underline",
1906
- "strike",
1907
- "clear",
1908
1890
  ];
1909
- const typedFnMap = fnMap;
1910
1891
  function getControls(options) {
1911
1892
  const { controlBtnProps, addtionControls, getSelection } = options;
1912
- const controls = defaultKeys.map((k) => {
1913
- const { icon, onClick } = typedFnMap[k];
1914
- return (jsx(Button, { ...controlBtnProps, onMouseDown: handleMouseDown, onClick: onClick, children: jsx(Icon, { icon: icon }) }, k));
1915
- });
1893
+ const controls = defaultControls.map(({ key, icon, onClick }) => (jsx(Button, { ...controlBtnProps, onMouseDown: handleMouseDown, onClick: onClick, children: jsx(Icon, { icon: icon }) }, key)));
1916
1894
  const extControls = (addtionControls ?? []).map((item, index) => (jsx(Button, { ...controlBtnProps, onMouseDown: handleMouseDown, onClick: (e) => item.onClick?.(getSelection(), e), children: item.icon }, `addtion-${index}`)));
1917
1895
  return [...controls, ...extControls];
1918
1896
  }
1919
1897
 
1920
1898
  const MEMTION_TAG_CLASS_NAME = "i-memtion-tag";
1899
+ const blockTags = new Set([
1900
+ "ADDRESS",
1901
+ "ARTICLE",
1902
+ "ASIDE",
1903
+ "BLOCKQUOTE",
1904
+ "DIV",
1905
+ "DL",
1906
+ "FIELDSET",
1907
+ "FIGCAPTION",
1908
+ "FIGURE",
1909
+ "FOOTER",
1910
+ "FORM",
1911
+ "H1",
1912
+ "H2",
1913
+ "H3",
1914
+ "H4",
1915
+ "H5",
1916
+ "H6",
1917
+ "HEADER",
1918
+ "LI",
1919
+ "MAIN",
1920
+ "NAV",
1921
+ "OL",
1922
+ "P",
1923
+ "PRE",
1924
+ "SECTION",
1925
+ "TABLE",
1926
+ "TD",
1927
+ "TH",
1928
+ "TR",
1929
+ "UL",
1930
+ ]);
1921
1931
  const escapeHtmlAttr = (value) => value
1922
1932
  .replaceAll("&", "&")
1923
1933
  .replaceAll('"', """)
@@ -1968,14 +1978,11 @@ const insertMemtionOption = ({ editor, range, mode, memtion, option, sanitizeVal
1968
1978
  const html = getInsertHtml(memtion, option, sanitizeValue);
1969
1979
  const fragment = nextRange.createContextualFragment(html);
1970
1980
  const lastNode = fragment.lastChild;
1971
- const spacing = document.createTextNode(" ");
1972
1981
  nextRange.insertNode(fragment);
1973
1982
  if (lastNode) {
1974
1983
  nextRange.setStartAfter(lastNode);
1975
1984
  nextRange.collapse(true);
1976
1985
  }
1977
- nextRange.insertNode(spacing);
1978
- nextRange.setStartAfter(spacing);
1979
1986
  }
1980
1987
  nextRange.collapse(true);
1981
1988
  browserSelection.removeAllRanges();
@@ -2005,6 +2012,60 @@ const filterMemtionOptions = (options, keyword) => {
2005
2012
  };
2006
2013
  const isMemtionTag = (node) => node instanceof HTMLElement &&
2007
2014
  node.classList.contains(MEMTION_TAG_CLASS_NAME);
2015
+ const appendBreak = (container) => {
2016
+ if (!container.lastChild || container.lastChild.nodeName === "BR") {
2017
+ return;
2018
+ }
2019
+ container.appendChild(document.createElement("br"));
2020
+ };
2021
+ const appendPlaintextOnMemtionNode = (container, node) => {
2022
+ if (!node)
2023
+ return;
2024
+ if (node.nodeType === Node.TEXT_NODE) {
2025
+ container.appendChild(document.createTextNode((node.textContent ?? "").replaceAll("\r", "")));
2026
+ return;
2027
+ }
2028
+ if (!(node instanceof HTMLElement)) {
2029
+ return;
2030
+ }
2031
+ if (isMemtionTag(node)) {
2032
+ const tag = document.createElement("span");
2033
+ const memtionValue = node.getAttribute("data-memtion-value");
2034
+ tag.className = MEMTION_TAG_CLASS_NAME;
2035
+ tag.setAttribute("contenteditable", "false");
2036
+ if (memtionValue !== null) {
2037
+ tag.setAttribute("data-memtion-value", memtionValue);
2038
+ }
2039
+ tag.innerHTML = node.innerHTML;
2040
+ container.appendChild(tag);
2041
+ return;
2042
+ }
2043
+ if (node.tagName === "BR") {
2044
+ appendBreak(container);
2045
+ return;
2046
+ }
2047
+ Array.from(node.childNodes).forEach((child) => {
2048
+ appendPlaintextOnMemtionNode(container, child);
2049
+ });
2050
+ if (blockTags.has(node.tagName)) {
2051
+ appendBreak(container);
2052
+ }
2053
+ };
2054
+ const sanitizePlaintextOnMemtionHtml = (value) => {
2055
+ if (typeof document === "undefined") {
2056
+ return value;
2057
+ }
2058
+ const source = document.createElement("div");
2059
+ const result = document.createElement("div");
2060
+ source.innerHTML = value;
2061
+ Array.from(source.childNodes).forEach((child) => {
2062
+ appendPlaintextOnMemtionNode(result, child);
2063
+ });
2064
+ while (result.lastChild?.nodeName === "BR") {
2065
+ result.lastChild.remove();
2066
+ }
2067
+ return result.innerHTML;
2068
+ };
2008
2069
  const getAdjacentMemtionTag = (range, direction) => {
2009
2070
  const { startContainer, startOffset } = range;
2010
2071
  if (startContainer.nodeType === Node.TEXT_NODE) {
@@ -2062,11 +2123,15 @@ const Memtion = (props) => {
2062
2123
  if (!visible || !rect || !options?.length) {
2063
2124
  return null;
2064
2125
  }
2065
- return (jsx(List$1, { className: "i-editor-memtion", type: "option", style: {
2126
+ const content = (jsx(List$1, { className: "i-editor-memtion", type: "option", style: {
2066
2127
  position: "fixed",
2067
2128
  top: rect.bottom,
2068
2129
  left: rect.left,
2069
2130
  }, children: options.map((option, i) => (jsx(List$1.Item, { type: "option", active: i === activeIndex, onMouseDown: (e) => e.preventDefault(), onMouseEnter: () => onActiveChange?.(i), onClick: () => onSelect?.(option), children: option.label }, `${option.value}-${i}`))) }));
2131
+ if (typeof document === "undefined") {
2132
+ return content;
2133
+ }
2134
+ return createPortal(content, document.body);
2070
2135
  };
2071
2136
  var Memtion$1 = memo(Memtion);
2072
2137
 
@@ -2081,18 +2146,28 @@ const Editor = (props) => {
2081
2146
  const selectionRef = useRef(null);
2082
2147
  const memtionTriggerRangeRef = useRef(null);
2083
2148
  const pendingMemtionRef = useRef(false);
2149
+ const isPlaintextMode = mode === "plaintext";
2150
+ const isRichMode = mode === "rich";
2151
+ const isPlaintextOnMemtionMode = mode === "plaintextOnMemtion";
2084
2152
  const [memtionVisible, setMemtionVisible] = useState(false);
2085
2153
  const [memtionRect, setMemtionRect] = useState(null);
2086
2154
  const [memtionKeyword, setMemtionKeyword] = useState("");
2087
2155
  const [memtionActiveIndex, setMemtionActiveIndex] = useState(0);
2088
2156
  const memtionOptions = useMemo(() => filterMemtionOptions(memtion?.options ?? [], memtionKeyword), [memtion?.options, memtionKeyword]);
2089
2157
  const sanitizeValue = (nextValue) => {
2090
- if (mode === "plaintext") {
2158
+ if (isPlaintextMode) {
2091
2159
  return nextValue === "\n" ? "" : nextValue;
2092
2160
  }
2093
- const safeHtml = xss(nextValue, xssOptions);
2161
+ const safeHtml = isPlaintextOnMemtionMode
2162
+ ? sanitizePlaintextOnMemtionHtml(xss(nextValue, xssOptions))
2163
+ : xss(nextValue, xssOptions);
2094
2164
  return safeHtml === "<br>" ? "" : safeHtml;
2095
2165
  };
2166
+ const syncHeight = () => {
2167
+ if (autosize && editorRef.current) {
2168
+ editorRef.current.style.height = `${editorRef.current.scrollHeight}px`;
2169
+ }
2170
+ };
2096
2171
  const rememberSelection = () => {
2097
2172
  if (!editorRef.current)
2098
2173
  return;
@@ -2112,18 +2187,18 @@ const Editor = (props) => {
2112
2187
  if (!editorRef.current)
2113
2188
  return;
2114
2189
  const safeValue = sanitizeValue(nextValue);
2115
- if (mode === "plaintext") {
2190
+ if (isPlaintextMode) {
2116
2191
  editorRef.current.textContent = safeValue;
2117
2192
  return;
2118
2193
  }
2119
2194
  editorRef.current.innerHTML = safeValue;
2120
2195
  };
2121
2196
  const getEditorValue = (sanitize = false) => {
2122
- if (!editorRef.current)
2123
- return "";
2124
- const nextValue = mode === "plaintext"
2125
- ? (editorRef.current.textContent ?? "")
2126
- : editorRef.current.innerHTML;
2197
+ const nextValue = !editorRef.current
2198
+ ? ""
2199
+ : isPlaintextMode
2200
+ ? (editorRef.current.textContent ?? "")
2201
+ : editorRef.current.innerHTML;
2127
2202
  return sanitize ? sanitizeValue(nextValue) : nextValue;
2128
2203
  };
2129
2204
  const hideMemtion = () => {
@@ -2155,22 +2230,18 @@ const Editor = (props) => {
2155
2230
  if (e.defaultPrevented)
2156
2231
  return;
2157
2232
  e.preventDefault();
2158
- if (mode === "plaintext") {
2159
- const text = e.clipboardData.getData("text/plain");
2160
- exec("insertText", false, text);
2161
- return;
2162
- }
2163
2233
  const html = e.clipboardData.getData("text/html");
2164
- if (html) {
2165
- exec("insertHTML", false, sanitizeValue(html));
2166
- return;
2167
- }
2168
2234
  const text = e.clipboardData.getData("text/plain");
2169
- exec("insertText", false, text);
2235
+ const pasteValue = isPlaintextMode
2236
+ ? text
2237
+ : html
2238
+ ? sanitizeValue(html)
2239
+ : text;
2240
+ exec(isPlaintextMode ? "insertText" : "insertHTML", false, pasteValue);
2170
2241
  };
2171
2242
  const handleKeyDown = (e) => {
2172
2243
  onKeyDown?.(e);
2173
- if (mode === "rich" &&
2244
+ if (!isPlaintextMode &&
2174
2245
  (e.key === "Backspace" || e.key === "Delete") &&
2175
2246
  removeAdjacentMemtionTag(editorRef.current, e.key)) {
2176
2247
  e.preventDefault();
@@ -2207,7 +2278,7 @@ const Editor = (props) => {
2207
2278
  switch (e.key) {
2208
2279
  case "Tab":
2209
2280
  e.preventDefault();
2210
- exec(mode === "plaintext" ? "insertText" : "insertHTML", false, mode === "plaintext" ? "\t" : "&#09;");
2281
+ exec(isRichMode ? "insertHTML" : "insertText", false, isRichMode ? "&#09;" : "\t");
2211
2282
  break;
2212
2283
  case "Enter":
2213
2284
  if (!onEnter)
@@ -2224,9 +2295,7 @@ const Editor = (props) => {
2224
2295
  if (getEditorValue(true) === nextValue)
2225
2296
  return;
2226
2297
  setEditorValue(nextValue);
2227
- if (autosize) {
2228
- editorRef.current.style.height = `${editorRef.current.scrollHeight}px`;
2229
- }
2298
+ syncHeight();
2230
2299
  }, [autosize, mode, value]);
2231
2300
  useEffect(() => {
2232
2301
  if (!memtionOptions.length) {
@@ -2238,6 +2307,11 @@ const Editor = (props) => {
2238
2307
  const handleInput = (e) => {
2239
2308
  const rawValue = getEditorValue();
2240
2309
  let nextValue = sanitizeValue(rawValue);
2310
+ if (isPlaintextOnMemtionMode &&
2311
+ rawValue !== nextValue &&
2312
+ editorRef.current) {
2313
+ setEditorValue(nextValue);
2314
+ }
2241
2315
  if (!nextValue && rawValue && editorRef.current) {
2242
2316
  nextValue = "";
2243
2317
  setEditorValue(nextValue);
@@ -2258,9 +2332,7 @@ const Editor = (props) => {
2258
2332
  setMemtionVisible(true);
2259
2333
  }
2260
2334
  }
2261
- if (autosize && editorRef.current) {
2262
- editorRef.current.style.height = `${editorRef.current.scrollHeight}px`;
2263
- }
2335
+ syncHeight();
2264
2336
  onChange?.(nextValue, e);
2265
2337
  };
2266
2338
  const handleFocus = (e) => {
@@ -2301,7 +2373,7 @@ const Editor = (props) => {
2301
2373
  ...style,
2302
2374
  [autosize ? "minHeight" : "height"]: height,
2303
2375
  width,
2304
- }, children: [!hideControl && (jsx("div", { className: "i-editor-controls", children: controls })), memtion && (jsx(Memtion$1, { visible: memtionVisible, rect: memtionRect, options: memtionOptions, activeIndex: memtionActiveIndex, onActiveChange: setMemtionActiveIndex, onSelect: insertMemtion })), jsx("div", { ref: handleRef, className: "i-editor-content", "data-placeholder": placeholder, contentEditable: mode === "plaintext" ? "plaintext-only" : true, onFocus: handleFocus, onBlur: handleBlur, onMouseUp: handleMouseUp, onPaste: handlePaste, onInput: handleInput, onKeyUp: handleKeyUp, onKeyDown: handleKeyDown, ...restProps })] }));
2376
+ }, children: [!hideControl && (jsx("div", { className: "i-editor-controls", children: controls })), memtion && (jsx(Memtion$1, { visible: memtionVisible, rect: memtionRect, options: memtionOptions, activeIndex: memtionActiveIndex, onActiveChange: setMemtionActiveIndex, onSelect: insertMemtion })), jsx("div", { ref: handleRef, className: "i-editor-content", "data-placeholder": placeholder, contentEditable: isPlaintextMode ? "plaintext-only" : true, onFocus: handleFocus, onBlur: handleBlur, onMouseUp: handleMouseUp, onPaste: handlePaste, onInput: handleInput, onKeyUp: handleKeyUp, onKeyDown: handleKeyDown, ...restProps })] }));
2305
2377
  };
2306
2378
 
2307
2379
  const Flex = (props) => {
@@ -3939,7 +4011,7 @@ const displayValue = (config) => {
3939
4011
  };
3940
4012
 
3941
4013
  const Select = (props) => {
3942
- const { ref, type = "text", name, label, value = "", placeholder, options = [], multiple, prepend, append, labelInline, style, className, message, status = "normal", hideClear, hideArrow, maxDisplay, border, filter, tip, filterPlaceholder = "...", popupProps, onSelect, onChange, ...restProps } = props;
4014
+ 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;
3943
4015
  const [filterValue, setFilterValue] = useState("");
3944
4016
  const [selectedValue, setSelectedValue] = useState(value);
3945
4017
  const [active, setActive] = useState(false);
@@ -4006,11 +4078,11 @@ const Select = (props) => {
4006
4078
  const text = message ?? tip;
4007
4079
  return (jsxs("label", { className: classNames("i-input-label", className, {
4008
4080
  "i-input-inline": labelInline,
4009
- }), style: style, children: [label && jsx("span", { className: 'i-input-label-text', children: label }), jsx(Popup, { position: 'bottom', arrow: false, fitSize: true, offset: 0, ...popupProps, visible: active, trigger: 'none', onVisibleChange: handleVisibleChange, content: jsx(Options, { options: filterOptions, value: selectedValue, multiple: multiple, filter: !!filter, filterPlaceholder: filterPlaceholder, onSelect: handleSelect, onFilter: handleFilterChange }), children: jsxs("div", { className: classNames("i-input-item", {
4081
+ }), style: style, children: [label && (jsxs("span", { className: "i-input-label-text", children: [required && jsx("span", { className: "error", children: "*" }), label] })), jsx(Popup, { position: "bottom", arrow: false, fitSize: true, offset: 0, ...popupProps, visible: active, trigger: "none", onVisibleChange: handleVisibleChange, content: jsx(Options, { options: filterOptions, value: selectedValue, multiple: multiple, filter: !!filter, filterPlaceholder: filterPlaceholder, onSelect: handleSelect, onFilter: handleFilterChange }), children: jsxs("div", { className: classNames("i-input-item", {
4010
4082
  [`i-input-${status}`]: status !== "normal",
4011
4083
  "i-input-borderless": !border,
4012
4084
  "i-input-focus": active,
4013
- }), onClick: () => setActive(true), children: [prepend, jsx("input", { ref: ref, type: 'hidden', value: selectedValue, ...restProps }), multiple ? (hasValue ? (jsx("div", { className: classNames("i-input i-select", {
4085
+ }), onClick: () => setActive(true), children: [prepend, jsx("input", { ref: ref, type: "hidden", value: selectedValue, ...restProps }), multiple ? (hasValue ? (jsx("div", { className: classNames("i-input i-select", {
4014
4086
  "i-select-multiple": multiple,
4015
4087
  }), children: displayValue({
4016
4088
  options: formattedOptions,
@@ -4018,7 +4090,7 @@ const Select = (props) => {
4018
4090
  multiple,
4019
4091
  maxDisplay,
4020
4092
  onSelect: handleSelect,
4021
- }) })) : (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 })] }));
4093
+ }) })) : (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 })] }));
4022
4094
  };
4023
4095
 
4024
4096
  const ColorMethods = {
@@ -4051,7 +4123,7 @@ const Handle = (props) => {
4051
4123
  };
4052
4124
 
4053
4125
  function ColorPicker(props) {
4054
- const { value, type = "HEX", disabledAlpha, children, usePanel, handle = "both", placeholder = "Colors", popupProps, onChange, } = props;
4126
+ const { value, type = "HEX", disabledAlpha, children, usePanel, handle = "both", placeholder = "Colors", popupProps, onChange, label, required, ...restProps } = props;
4055
4127
  const [colorType, setColorType] = useState(type);
4056
4128
  const [colorValue, setColorValue] = useState(value);
4057
4129
  const [syncValue, setSyncValue] = useState(value);
@@ -4093,11 +4165,11 @@ function ColorPicker(props) {
4093
4165
  }
4094
4166
  }, [popupProps?.visible]);
4095
4167
  if (usePanel) {
4096
- return jsx(ColorsPanel, { ...props });
4168
+ return (jsx(InputContainer, { label: label, required: required, children: jsx(ColorsPanel, { ...restProps, value: value, onChange: onChange }) }));
4097
4169
  }
4098
- return (jsx(Popup, { trigger: 'click', touchable: true, position: 'bottom', ...popupProps, visible: visible, content: jsx(ColorsPanel, { value: syncValue, disabledAlpha: disabledAlpha, panelRender: (panel) => {
4099
- return (jsxs(Fragment, { children: [panel, jsx(Footer, { value: colorValue, type: colorType, onTypeChange: handleTypeChange, onChange: handleValueChange, onOk: handleOk })] }));
4100
- }, onChange: handleChange, onChangeComplete: handleComplete }), onVisibleChange: handleVisibleChange, children: children ?? (jsx(Handle, { color: value, handle: handle, placeholder: placeholder })) }));
4170
+ return (jsx(InputContainer, { label: label, required: required, children: jsx(Popup, { trigger: "click", touchable: true, position: "bottom", ...popupProps, visible: visible, content: jsx(ColorsPanel, { ...restProps, value: syncValue, disabledAlpha: disabledAlpha, panelRender: (panel) => {
4171
+ return (jsxs(Fragment, { children: [panel, jsx(Footer, { value: colorValue, type: colorType, onTypeChange: handleTypeChange, onChange: handleValueChange, onOk: handleOk })] }));
4172
+ }, onChange: handleChange, onChangeComplete: handleComplete }), onVisibleChange: handleVisibleChange, children: children ?? (jsx(Handle, { color: value, handle: handle, placeholder: placeholder })) }) }));
4101
4173
  }
4102
4174
 
4103
4175
  const Dates = (props) => {
@@ -4899,14 +4971,104 @@ const Swiper = ((props) => {
4899
4971
  });
4900
4972
  Swiper.Item = Item$1;
4901
4973
 
4974
+ const TabsContents = (props) => {
4975
+ const { tabs, activeKey, cachedTabKeySet, getContent } = props;
4976
+ return (jsx("div", { className: "i-tab-contents", children: tabs.map((tab, i) => {
4977
+ const key = tab.key ?? `${i}`;
4978
+ const content = getContent(key);
4979
+ const isActive = activeKey === key;
4980
+ const show = isActive || cachedTabKeySet.has(key);
4981
+ return (show && (jsx("div", { className: classNames("i-tab-content", {
4982
+ "i-tab-active": isActive,
4983
+ }), role: "tabpanel", "aria-hidden": !isActive, children: content }, key)));
4984
+ }) }));
4985
+ };
4986
+ var TabsContents$1 = memo(TabsContents);
4987
+
4988
+ const emptyBarStyle = {
4989
+ height: 0,
4990
+ width: 0,
4991
+ };
4992
+ const defaultRenderMore = () => (jsx(Button, { flat: true, square: true, size: "small", children: jsx(Icon, { icon: jsx(MoreHorizRound, {}) }) }));
4993
+ const isSameTabs = (prev, next) => prev.length === next.length &&
4994
+ prev.every((tab, index) => {
4995
+ const target = next[index];
4996
+ return (tab.key === target.key &&
4997
+ tab.title === target.title &&
4998
+ tab.keepDOM === target.keepDOM &&
4999
+ tab.closable === target.closable &&
5000
+ tab.intersecting === target.intersecting);
5001
+ });
5002
+ const getParsedTabs = (items, children) => {
5003
+ const contents = new Map();
5004
+ if (!items) {
5005
+ const tabs = Children.map(children, (node, i) => {
5006
+ const { key, props: nodeProps } = node;
5007
+ const { title, children: tabChildren, content, keepDOM, closable, } = nodeProps;
5008
+ const tabKey = String(key ?? i);
5009
+ contents.set(tabKey, tabChildren ?? content);
5010
+ return {
5011
+ key: tabKey,
5012
+ title,
5013
+ keepDOM,
5014
+ closable,
5015
+ };
5016
+ }) ?? [];
5017
+ return {
5018
+ tabs,
5019
+ contents,
5020
+ keys: new Set(tabs.map((tab) => String(tab.key))),
5021
+ };
5022
+ }
5023
+ const tabs = items.map((item, i) => {
5024
+ if (["string", "number"].includes(typeof item)) {
5025
+ const key = String(item);
5026
+ return { key, title: item };
5027
+ }
5028
+ const key = String(item.key ?? i);
5029
+ contents.set(key, item.content);
5030
+ const { content, ...rest } = item;
5031
+ return {
5032
+ ...rest,
5033
+ key,
5034
+ };
5035
+ });
5036
+ return {
5037
+ tabs,
5038
+ contents,
5039
+ keys: new Set(tabs.map((tab) => String(tab.key))),
5040
+ };
5041
+ };
5042
+
4902
5043
  const Item = (props) => {
4903
5044
  return jsx(Fragment, {});
4904
5045
  };
4905
5046
 
5047
+ const TabsNavs = (props) => {
5048
+ const { tabs, moreTabs, activeKey, vertical, overflow, hideMore, navsJustify = "start", bar, barClass, barStyle, navsRef, renderMore, setNavRef, onOpen, onClose, onMoreTabClick, onKeyAction, } = props;
5049
+ return (jsxs(Fragment, { children: [jsxs("div", { ref: navsRef, className: classNames("i-tab-navs", `justify-${navsJustify}`), role: "tablist", "aria-orientation": vertical ? "vertical" : "horizontal", children: [tabs.map((tab, i) => {
5050
+ const { title, key = `${i}`, closable } = tab;
5051
+ const isActive = activeKey === key;
5052
+ return (jsxs("a", { ref: (node) => setNavRef(i, node), className: classNames("i-tab-nav", {
5053
+ "i-tab-active": isActive,
5054
+ }), role: "tab", tabIndex: isActive ? 0 : -1, "aria-selected": isActive, onClick: () => onOpen(key), onKeyDown: (e) => onKeyAction(e, () => onOpen(key)), children: [title, closable && (jsx(Helpericon, { as: "i", active: true, className: "i-tab-nav-close", role: "button", tabIndex: 0, "aria-label": "\u5173\u95ED\u6807\u7B7E\u9875", onClick: (e) => {
5055
+ e.preventDefault();
5056
+ e.stopPropagation();
5057
+ onClose(key);
5058
+ }, onKeyDown: (e) => onKeyAction(e, () => onClose(key)) }))] }, key));
5059
+ }), bar && (jsx("span", { className: classNames("i-tab-navs-bar", barClass), style: barStyle }))] }), !hideMore && overflow && moreTabs.length > 0 && (jsx(Popup, { arrow: false, position: vertical ? "right" : "bottom", align: "end", touchable: true, hideDelay: 500, content: jsx("div", { className: "i-tabs-morelist pd-4", children: moreTabs.map((tab, i) => {
5060
+ const { key = `${i}`, title } = tab;
5061
+ const isActive = activeKey === key;
5062
+ return (jsx("a", { className: classNames("i-tab-nav", {
5063
+ "i-tab-active": isActive,
5064
+ }), role: "button", tabIndex: 0, onClick: () => onMoreTabClick(key), onKeyDown: (e) => onKeyAction(e, () => onMoreTabClick(key)), children: title }, key));
5065
+ }) }), children: renderMore(moreTabs) }))] }));
5066
+ };
5067
+ var TabsNavs$1 = memo(TabsNavs);
5068
+
4906
5069
  const Tabs = ((props) => {
4907
- const { ref, active, tabs: items, type = "default", prepend, append, children, className, vertical, toggable, navsJustify = "start", bar = true, hideMore, barClass, renderMore = () => (jsx(Button, { flat: true, square: true, size: "small", children: jsx(Icon, { icon: jsx(MoreHorizRound, {}) }) })), onTabChange, ...rest } = props;
5070
+ const { ref, active, tabs: items, type = "default", prepend, append, children, className, vertical, toggable, navsJustify = "start", navsClass, bar = true, hideMore, barClass, renderMore = defaultRenderMore, onTabChange, ...rest } = props;
4908
5071
  const navRefs = useRef([]);
4909
- const barRef = useRef(null);
4910
5072
  const navsRef = useRef(null);
4911
5073
  const contentsRef = useRef(new Map());
4912
5074
  const [activeKey, setActiveKey] = useState(active);
@@ -4923,42 +5085,36 @@ const Tabs = ((props) => {
4923
5085
  activeKeyRef.current = activeKey;
4924
5086
  const prevActiveKeyRef = useRef(prevActiveKey);
4925
5087
  prevActiveKeyRef.current = prevActiveKey;
5088
+ const sourceKeysRef = useRef(new Set());
5089
+ const hiddenSourceKeysRef = useRef(new Set());
5090
+ const parsedTabs = useMemo(() => getParsedTabs(items, children), [children, items]);
4926
5091
  useEffect(() => {
4927
- contentsRef.current.clear();
4928
- if (!items) {
4929
- if (!children) {
4930
- setTabs([]);
4931
- return;
4932
- }
4933
- setTabs(Children.map(children, (node, i) => {
4934
- const { key, props: nodeProps } = node;
4935
- const { title, children: tabChildren, content, keepDOM, closable, } = nodeProps;
4936
- const tabKey = String(key ?? i);
4937
- contentsRef.current.set(tabKey, tabChildren ?? content);
4938
- return {
4939
- key: tabKey,
4940
- title,
4941
- keepDOM,
4942
- closable,
4943
- };
4944
- }) ?? []);
4945
- return;
4946
- }
4947
- setTabs(items.map((item, i) => {
4948
- if (["string", "number"].includes(typeof item)) {
4949
- const key = String(item);
4950
- return { key, title: item };
5092
+ const prevContents = new Map(contentsRef.current);
5093
+ const nextContents = new Map(parsedTabs.contents);
5094
+ const sourceTabs = parsedTabs.tabs;
5095
+ const sourceKeys = parsedTabs.keys;
5096
+ hiddenSourceKeysRef.current.forEach((key) => {
5097
+ if (!sourceKeys.has(key)) {
5098
+ hiddenSourceKeysRef.current.delete(key);
4951
5099
  }
4952
- const key = String(item.key ?? i);
4953
- contentsRef.current.set(key, item.content);
4954
- const { content, ...rest } = item;
4955
- return {
4956
- ...rest,
4957
- key,
4958
- };
4959
- }));
4960
- }, [children, items]);
4961
- const add = (tab) => {
5100
+ });
5101
+ const dynamicTabs = tabsRef.current.filter((tab) => {
5102
+ const key = String(tab.key);
5103
+ return !sourceKeysRef.current.has(key) && !sourceKeys.has(key);
5104
+ });
5105
+ dynamicTabs.forEach((tab) => {
5106
+ const key = String(tab.key);
5107
+ nextContents.set(key, prevContents.get(key));
5108
+ });
5109
+ sourceKeysRef.current = sourceKeys;
5110
+ contentsRef.current = nextContents;
5111
+ const nextTabs = [
5112
+ ...sourceTabs.filter((tab) => !hiddenSourceKeysRef.current.has(String(tab.key))),
5113
+ ...dynamicTabs,
5114
+ ];
5115
+ setTabs((currentTabs) => isSameTabs(currentTabs, nextTabs) ? currentTabs : nextTabs);
5116
+ }, [parsedTabs]);
5117
+ const add = (tab, position) => {
4962
5118
  const currentTabs = tabsRef.current;
4963
5119
  const tkey = String(tab.key ?? currentTabs.length);
4964
5120
  const i = currentTabs.findIndex((t) => t.key === tkey);
@@ -4968,7 +5124,14 @@ const Tabs = ((props) => {
4968
5124
  }
4969
5125
  contentsRef.current.set(tkey, tab.content);
4970
5126
  const { content, ...rest } = tab;
4971
- setTabs((ts) => [...ts, { ...rest, key: tkey }]);
5127
+ setTabs((ts) => {
5128
+ const nextTabs = [...ts];
5129
+ const index = position === undefined
5130
+ ? nextTabs.length
5131
+ : Math.max(0, Math.min(position, nextTabs.length));
5132
+ nextTabs.splice(index, 0, { ...rest, key: tkey });
5133
+ return nextTabs;
5134
+ });
4972
5135
  open(tkey);
4973
5136
  };
4974
5137
  const close = (key) => {
@@ -4976,7 +5139,12 @@ const Tabs = ((props) => {
4976
5139
  const i = currentTabs.findIndex((t) => t.key === key);
4977
5140
  if (i < 0)
4978
5141
  return;
4979
- contentsRef.current.delete(key);
5142
+ if (sourceKeysRef.current.has(key)) {
5143
+ hiddenSourceKeysRef.current.add(key);
5144
+ }
5145
+ else {
5146
+ contentsRef.current.delete(key);
5147
+ }
4980
5148
  const nextTabs = [...currentTabs];
4981
5149
  nextTabs.splice(i, 1);
4982
5150
  setTabs(nextTabs);
@@ -4993,10 +5161,7 @@ const Tabs = ((props) => {
4993
5161
  onTabChange?.(undefined, activeKey);
4994
5162
  setPrevActiveKey(activeKey);
4995
5163
  setActiveKey(undefined);
4996
- setBarStyle({
4997
- height: 0,
4998
- width: 0,
4999
- });
5164
+ setBarStyle(emptyBarStyle);
5000
5165
  return;
5001
5166
  }
5002
5167
  if (nextKey === activeKey) {
@@ -5004,10 +5169,7 @@ const Tabs = ((props) => {
5004
5169
  return;
5005
5170
  onTabChange?.(undefined, key);
5006
5171
  setActiveKey(undefined);
5007
- setBarStyle({
5008
- height: 0,
5009
- width: 0,
5010
- });
5172
+ setBarStyle(emptyBarStyle);
5011
5173
  return;
5012
5174
  }
5013
5175
  setPrevActiveKey(activeKey);
@@ -5033,6 +5195,10 @@ const Tabs = ((props) => {
5033
5195
  open(key);
5034
5196
  scrollToTab(key);
5035
5197
  };
5198
+ const setNavRef = (index, node) => {
5199
+ navRefs.current[index] = node;
5200
+ };
5201
+ const getContent = (key) => contentsRef.current.get(key);
5036
5202
  useEffect(() => {
5037
5203
  if (!size || hideMore || !observe || !unobserve)
5038
5204
  return;
@@ -5056,7 +5222,7 @@ const Tabs = ((props) => {
5056
5222
  return;
5057
5223
  }
5058
5224
  const observed = [];
5059
- navRefs.current.map((nav, i) => {
5225
+ navRefs.current.forEach((nav, i) => {
5060
5226
  if (!nav)
5061
5227
  return;
5062
5228
  observed.push(nav);
@@ -5071,7 +5237,7 @@ const Tabs = ((props) => {
5071
5237
  });
5072
5238
  });
5073
5239
  return () => {
5074
- observed.map((el) => unobserve(el));
5240
+ observed.forEach((el) => unobserve(el));
5075
5241
  };
5076
5242
  }, [size, hideMore, tabs.length, observe, unobserve]);
5077
5243
  useEffect(() => {
@@ -5093,7 +5259,7 @@ const Tabs = ((props) => {
5093
5259
  const { offsetHeight, offsetLeft, offsetTop, offsetWidth } = nav;
5094
5260
  const isLine = type === "line";
5095
5261
  setBarStyle({
5096
- height: !vertical && isLine ? ".25em" : offsetHeight,
5262
+ height: !vertical && isLine ? ".25em" : offsetHeight * 0.5,
5097
5263
  width: vertical && isLine ? ".25em" : offsetWidth,
5098
5264
  transform: `translate(${offsetLeft}px, ${offsetTop}px)`,
5099
5265
  });
@@ -5105,7 +5271,8 @@ const Tabs = ((props) => {
5105
5271
  useEffect(() => {
5106
5272
  if (active === undefined || activeKey === active)
5107
5273
  return;
5108
- open(active);
5274
+ setPrevActiveKey(activeKey);
5275
+ setActiveKey(active);
5109
5276
  }, [active]);
5110
5277
  useEffect(() => {
5111
5278
  if (!navsRef.current || vertical)
@@ -5134,37 +5301,13 @@ const Tabs = ((props) => {
5134
5301
  add,
5135
5302
  navs: navsRef,
5136
5303
  }));
5137
- const moreTabs = !hideMore && overflow
5304
+ const cachedTabKeySet = useMemo(() => new Set(cachedTabs), [cachedTabs]);
5305
+ const moreTabs = useMemo(() => !hideMore && overflow
5138
5306
  ? tabs.filter((tab) => tab.intersecting === false)
5139
- : [];
5140
- return (jsxs("div", { className: classNames("i-tabs", { flex: vertical, [`i-tabs-${type}`]: type !== "default" }, className), ...rest, children: [jsxs("div", { className: classNames("i-tab-navs-container", {
5307
+ : [], [hideMore, overflow, tabs]);
5308
+ return (jsxs("div", { className: classNames("i-tabs", { flex: vertical, [`i-tabs-${type}`]: type !== "default" }, className), ...rest, children: [jsxs("div", { className: classNames("i-tab-navs-container", navsClass, {
5141
5309
  "i-tab-navs-vertical": vertical,
5142
- }), children: [prepend, jsxs("div", { ref: navsRef, className: classNames("i-tab-navs", `justify-${navsJustify}`), role: "tablist", "aria-orientation": vertical ? "vertical" : "horizontal", children: [tabs.map((tab, i) => {
5143
- const { title, key = `${i}`, closable } = tab;
5144
- const isActive = activeKey === key;
5145
- return (jsxs("a", { ref: (ref) => (navRefs.current[i] = ref), className: classNames("i-tab-nav", {
5146
- "i-tab-active": isActive,
5147
- }), role: "tab", tabIndex: isActive ? 0 : -1, "aria-selected": isActive, onClick: () => open(key), onKeyDown: (e) => handleKeyAction(e, () => open(key)), children: [title, closable && (jsx(Helpericon, { as: "i", active: true, className: "i-tab-nav-close", role: "button", tabIndex: 0, "aria-label": "\u5173\u95ED\u6807\u7B7E\u9875", onClick: (e) => {
5148
- e.preventDefault();
5149
- e.stopPropagation();
5150
- close(key);
5151
- }, onKeyDown: (e) => handleKeyAction(e, () => close(key)) }))] }, key));
5152
- }), bar && (jsx("span", { ref: barRef, className: classNames("i-tab-navs-bar", barClass), style: barStyle }))] }), !hideMore && overflow && moreTabs.length > 0 && (jsx(Popup, { arrow: false, position: vertical ? "right" : "bottom", align: "end", touchable: true, hideDelay: 500, content: jsx("div", { className: "i-tabs-morelist pd-4", children: moreTabs.map((tab, i) => {
5153
- const { key = `${i}`, title } = tab;
5154
- const isActive = activeKey === key;
5155
- return (jsx("a", { className: classNames("i-tab-nav", {
5156
- "i-tab-active": isActive,
5157
- }), role: "button", tabIndex: 0, onClick: () => handleMoreTabClick(key), onKeyDown: (e) => handleKeyAction(e, () => handleMoreTabClick(key)), children: title }, key));
5158
- }) }), children: renderMore(moreTabs) })), append] }), jsx("div", { className: "i-tab-contents", children: tabs.map((tab, i) => {
5159
- const key = tab.key ?? `${i}`;
5160
- const content = contentsRef.current.get(key);
5161
- const isActive = activeKey === key;
5162
- const show = isActive ||
5163
- (key !== undefined && cachedTabs.includes(key));
5164
- return (show && (jsx("div", { className: classNames("i-tab-content", {
5165
- "i-tab-active": isActive,
5166
- }), role: "tabpanel", "aria-hidden": !isActive, children: content }, key)));
5167
- }) })] }));
5310
+ }), children: [prepend, jsx(TabsNavs$1, { tabs: tabs, moreTabs: moreTabs, activeKey: activeKey, vertical: vertical, overflow: overflow, hideMore: hideMore, navsJustify: navsJustify, bar: bar, barClass: barClass, barStyle: barStyle, navsRef: navsRef, renderMore: renderMore, setNavRef: setNavRef, onOpen: open, onClose: close, onMoreTabClick: handleMoreTabClick, onKeyAction: handleKeyAction }), append] }), jsx(TabsContents$1, { tabs: tabs, activeKey: activeKey, cachedTabKeySet: cachedTabKeySet, getContent: getContent })] }));
5168
5311
  });
5169
5312
  Tabs.Item = Item;
5170
5313