@ioca/react 1.5.2 → 1.5.4
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/cjs/components/editor/controls.js +26 -39
- package/lib/cjs/components/editor/controls.js.map +1 -1
- package/lib/cjs/components/editor/editor.js +204 -41
- package/lib/cjs/components/editor/editor.js.map +1 -1
- package/lib/cjs/components/editor/memtion.js +167 -0
- package/lib/cjs/components/editor/memtion.js.map +1 -0
- package/lib/cjs/components/image/image.js +46 -30
- package/lib/cjs/components/image/image.js.map +1 -1
- package/lib/cjs/components/input/textarea.js +12 -5
- package/lib/cjs/components/input/textarea.js.map +1 -1
- package/lib/cjs/components/modal/hookModal.js +1 -1
- package/lib/cjs/components/modal/hookModal.js.map +1 -1
- package/lib/cjs/components/picker/colors/footer.js +1 -1
- package/lib/cjs/components/picker/colors/footer.js.map +1 -1
- package/lib/cjs/components/popconfirm/popconfirm.js +3 -3
- package/lib/cjs/components/popconfirm/popconfirm.js.map +1 -1
- package/lib/cjs/components/tabs/tabs.js +95 -37
- package/lib/cjs/components/tabs/tabs.js.map +1 -1
- package/lib/cjs/js/hooks.js +60 -40
- package/lib/cjs/js/hooks.js.map +1 -1
- package/lib/css/colors.css +13 -8
- package/lib/css/index.css +1 -1
- package/lib/css/index.css.map +1 -1
- package/lib/css/input.css +12 -6
- package/lib/css/reset.css +2 -5
- package/lib/css/utilities.css +9 -10
- package/lib/es/components/editor/controls.js +27 -36
- package/lib/es/components/editor/controls.js.map +1 -1
- package/lib/es/components/editor/editor.js +205 -42
- package/lib/es/components/editor/editor.js.map +1 -1
- package/lib/es/components/editor/memtion.js +156 -0
- package/lib/es/components/editor/memtion.js.map +1 -0
- package/lib/es/components/image/image.js +47 -31
- package/lib/es/components/image/image.js.map +1 -1
- package/lib/es/components/image/index.js +2 -2
- package/lib/es/components/image/list.js +2 -2
- package/lib/es/components/image/list.js.map +1 -1
- package/lib/es/components/input/textarea.js +12 -5
- package/lib/es/components/input/textarea.js.map +1 -1
- package/lib/es/components/modal/hookModal.js +1 -1
- package/lib/es/components/modal/hookModal.js.map +1 -1
- package/lib/es/components/picker/colors/footer.js +1 -1
- package/lib/es/components/picker/colors/footer.js.map +1 -1
- package/lib/es/components/popconfirm/popconfirm.js +3 -3
- package/lib/es/components/popconfirm/popconfirm.js.map +1 -1
- package/lib/es/components/tabs/tabs.js +95 -37
- package/lib/es/components/tabs/tabs.js.map +1 -1
- package/lib/es/components/upload/renderFile.js +2 -2
- package/lib/es/components/upload/renderFile.js.map +1 -1
- package/lib/es/js/hooks.js +61 -41
- package/lib/es/js/hooks.js.map +1 -1
- package/lib/index.js +598 -195
- package/lib/types/components/editor/type.d.ts +25 -12
- package/lib/types/components/image/image.d.ts +2 -2
- package/lib/types/components/image/index.d.ts +2 -2
- package/lib/types/components/input/type.d.ts +1 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
2
2
|
import classNames from 'classnames';
|
|
3
3
|
import { debounce, uid, throttle, title } from 'radash';
|
|
4
|
-
import { useState, useRef, useEffect, useMemo, Children, cloneElement, createElement, isValidElement, Fragment as Fragment$1, useTransition, forwardRef, useLayoutEffect,
|
|
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
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';
|
|
6
6
|
import { createRoot } from 'react-dom/client';
|
|
7
7
|
import { createPortal } from 'react-dom';
|
|
8
|
-
import xss from 'xss';
|
|
8
|
+
import xss, { escapeAttrValue } from 'xss';
|
|
9
|
+
import { renderToStaticMarkup } from 'react-dom/server';
|
|
9
10
|
import PubSub from 'pubsub-js';
|
|
10
11
|
import { findAll } from 'highlight-words-core';
|
|
11
12
|
import ColorsPanel from '@rc-component/color-picker';
|
|
@@ -234,31 +235,41 @@ const defaultObserver = {
|
|
|
234
235
|
disconnect: undefined,
|
|
235
236
|
};
|
|
236
237
|
function useIntersectionObserver(configs) {
|
|
238
|
+
const wmRef = useRef(new WeakMap());
|
|
239
|
+
const ioRef = useRef(undefined);
|
|
240
|
+
if (typeof window !== "undefined" && !ioRef.current) {
|
|
241
|
+
ioRef.current = new IntersectionObserver((entries) => {
|
|
242
|
+
entries.map((entry) => {
|
|
243
|
+
const callback = wmRef.current.get(entry.target);
|
|
244
|
+
callback?.(entry.target, entry.isIntersecting);
|
|
245
|
+
});
|
|
246
|
+
}, configs);
|
|
247
|
+
}
|
|
248
|
+
const observe = useCallback((target, callback) => {
|
|
249
|
+
if (!target || !ioRef.current || wmRef.current.get(target))
|
|
250
|
+
return;
|
|
251
|
+
wmRef.current.set(target, callback);
|
|
252
|
+
ioRef.current.observe(target);
|
|
253
|
+
}, []);
|
|
254
|
+
const unobserve = useCallback((target) => {
|
|
255
|
+
if (!target || !ioRef.current)
|
|
256
|
+
return;
|
|
257
|
+
ioRef.current.unobserve(target);
|
|
258
|
+
wmRef.current.delete(target);
|
|
259
|
+
}, []);
|
|
260
|
+
const disconnect = useCallback(() => {
|
|
261
|
+
ioRef.current?.disconnect();
|
|
262
|
+
}, []);
|
|
263
|
+
useEffect(() => {
|
|
264
|
+
return () => {
|
|
265
|
+
ioRef.current?.disconnect();
|
|
266
|
+
};
|
|
267
|
+
}, []);
|
|
237
268
|
if (typeof window === "undefined") {
|
|
238
269
|
return {
|
|
239
270
|
...defaultObserver,
|
|
240
271
|
};
|
|
241
272
|
}
|
|
242
|
-
const WM = new WeakMap();
|
|
243
|
-
const IO = new IntersectionObserver((entries) => {
|
|
244
|
-
entries.map((entry) => {
|
|
245
|
-
const callback = WM.get(entry.target);
|
|
246
|
-
callback?.(entry.target, entry.isIntersecting);
|
|
247
|
-
});
|
|
248
|
-
}, configs);
|
|
249
|
-
function observe(target, callback) {
|
|
250
|
-
if (WM.get(target))
|
|
251
|
-
return;
|
|
252
|
-
WM.set(target, callback);
|
|
253
|
-
target && IO.observe(target);
|
|
254
|
-
}
|
|
255
|
-
function unobserve(target) {
|
|
256
|
-
target && IO.unobserve(target);
|
|
257
|
-
WM.delete(target);
|
|
258
|
-
}
|
|
259
|
-
function disconnect() {
|
|
260
|
-
IO.disconnect();
|
|
261
|
-
}
|
|
262
273
|
return {
|
|
263
274
|
observe,
|
|
264
275
|
unobserve,
|
|
@@ -266,31 +277,41 @@ function useIntersectionObserver(configs) {
|
|
|
266
277
|
};
|
|
267
278
|
}
|
|
268
279
|
function useResizeObserver() {
|
|
280
|
+
const wmRef = useRef(new WeakMap());
|
|
281
|
+
const ioRef = useRef(undefined);
|
|
282
|
+
if (typeof window !== "undefined" && !ioRef.current) {
|
|
283
|
+
ioRef.current = new ResizeObserver((entries) => {
|
|
284
|
+
entries.map((entry) => {
|
|
285
|
+
const callback = wmRef.current.get(entry.target);
|
|
286
|
+
callback?.(entry.target);
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
const observe = useCallback((target, callback) => {
|
|
291
|
+
if (!target || !ioRef.current || wmRef.current.get(target))
|
|
292
|
+
return;
|
|
293
|
+
ioRef.current.observe(target);
|
|
294
|
+
wmRef.current.set(target, callback);
|
|
295
|
+
}, []);
|
|
296
|
+
const unobserve = useCallback((target) => {
|
|
297
|
+
if (!target || !ioRef.current)
|
|
298
|
+
return;
|
|
299
|
+
ioRef.current.unobserve(target);
|
|
300
|
+
wmRef.current.delete(target);
|
|
301
|
+
}, []);
|
|
302
|
+
const disconnect = useCallback(() => {
|
|
303
|
+
ioRef.current?.disconnect();
|
|
304
|
+
}, []);
|
|
305
|
+
useEffect(() => {
|
|
306
|
+
return () => {
|
|
307
|
+
ioRef.current?.disconnect();
|
|
308
|
+
};
|
|
309
|
+
}, []);
|
|
269
310
|
if (typeof window === "undefined") {
|
|
270
311
|
return {
|
|
271
312
|
...defaultObserver,
|
|
272
313
|
};
|
|
273
314
|
}
|
|
274
|
-
const WM = new WeakMap();
|
|
275
|
-
const IO = new ResizeObserver((entries) => {
|
|
276
|
-
entries.map((entry) => {
|
|
277
|
-
const callback = WM.get(entry.target);
|
|
278
|
-
callback?.(entry.target);
|
|
279
|
-
});
|
|
280
|
-
});
|
|
281
|
-
function observe(target, callback) {
|
|
282
|
-
if (WM.get(target))
|
|
283
|
-
return;
|
|
284
|
-
target && IO.observe(target);
|
|
285
|
-
WM.set(target, callback);
|
|
286
|
-
}
|
|
287
|
-
function unobserve(target) {
|
|
288
|
-
target && IO.unobserve(target);
|
|
289
|
-
WM.delete(target);
|
|
290
|
-
}
|
|
291
|
-
function disconnect() {
|
|
292
|
-
IO.disconnect();
|
|
293
|
-
}
|
|
294
315
|
return {
|
|
295
316
|
observe,
|
|
296
317
|
unobserve,
|
|
@@ -1823,153 +1844,454 @@ const Dropdown = (props) => {
|
|
|
1823
1844
|
};
|
|
1824
1845
|
Dropdown.Item = Item$3;
|
|
1825
1846
|
|
|
1826
|
-
const { escapeAttrValue } = xss;
|
|
1827
1847
|
const exec = (a, b, c) => {
|
|
1828
1848
|
if (typeof document === "undefined")
|
|
1829
1849
|
return;
|
|
1830
1850
|
return document.execCommand(a, b, c);
|
|
1831
1851
|
};
|
|
1832
1852
|
const xssOptions = {
|
|
1833
|
-
onIgnoreTagAttr
|
|
1834
|
-
if (["
|
|
1853
|
+
onIgnoreTagAttr(tag, name, value) {
|
|
1854
|
+
if (["class", "contenteditable"].includes(name)) {
|
|
1855
|
+
return name + '="' + escapeAttrValue(value) + '"';
|
|
1856
|
+
}
|
|
1857
|
+
if (["data-", "style"].includes(name.substring(0, 5))) {
|
|
1835
1858
|
return name + '="' + escapeAttrValue(value) + '"';
|
|
1836
1859
|
}
|
|
1837
1860
|
},
|
|
1838
1861
|
};
|
|
1862
|
+
const handleMouseDown = (e) => {
|
|
1863
|
+
e.preventDefault();
|
|
1864
|
+
};
|
|
1839
1865
|
const fnMap = {
|
|
1840
1866
|
bold: {
|
|
1841
1867
|
icon: jsx(FormatBoldRound, {}),
|
|
1842
1868
|
onClick: () => exec("bold"),
|
|
1843
|
-
tip: "粗体",
|
|
1844
1869
|
},
|
|
1845
1870
|
italic: {
|
|
1846
1871
|
icon: jsx(FormatItalicRound, {}),
|
|
1847
1872
|
onClick: () => exec("italic"),
|
|
1848
|
-
tip: "斜体",
|
|
1849
1873
|
},
|
|
1850
1874
|
underline: {
|
|
1851
1875
|
icon: jsx(FormatUnderlinedRound, {}),
|
|
1852
1876
|
onClick: () => exec("underline"),
|
|
1853
|
-
tip: "下划线",
|
|
1854
1877
|
},
|
|
1855
1878
|
strike: {
|
|
1856
1879
|
icon: jsx(StrikethroughSRound, {}),
|
|
1857
1880
|
onClick: () => exec("strikeThrough"),
|
|
1858
|
-
tip: "删除线",
|
|
1859
1881
|
},
|
|
1860
1882
|
redo: {
|
|
1861
1883
|
icon: jsx(RedoRound, {}),
|
|
1862
1884
|
onClick: () => exec("redo"),
|
|
1863
|
-
tip: "重做",
|
|
1864
1885
|
},
|
|
1865
1886
|
undo: {
|
|
1866
1887
|
icon: jsx(UndoRound, {}),
|
|
1867
1888
|
onClick: () => exec("undo"),
|
|
1868
|
-
tip: "撤销",
|
|
1869
1889
|
},
|
|
1870
|
-
// color: {
|
|
1871
|
-
// icon: <FormatColorTextRound />,
|
|
1872
|
-
// onClick: () => exec("foreColor", false, ""),
|
|
1873
|
-
// },
|
|
1874
|
-
// backColor: {
|
|
1875
|
-
// icon: <FormatColorFillRound />,
|
|
1876
|
-
// onClick: () => exec("backColor", false, ""),
|
|
1877
|
-
// },
|
|
1878
1890
|
clear: {
|
|
1879
1891
|
icon: jsx(ClearAllRound, {}),
|
|
1880
1892
|
onClick: () => exec("removeFormat"),
|
|
1881
|
-
tip: "清除格式",
|
|
1882
1893
|
},
|
|
1883
1894
|
};
|
|
1884
|
-
const
|
|
1885
|
-
|
|
1886
|
-
|
|
1895
|
+
const defaultKeys = [
|
|
1896
|
+
"undo",
|
|
1897
|
+
"redo",
|
|
1898
|
+
"bold",
|
|
1899
|
+
"italic",
|
|
1900
|
+
"underline",
|
|
1901
|
+
"strike",
|
|
1902
|
+
"clear",
|
|
1903
|
+
];
|
|
1904
|
+
const typedFnMap = fnMap;
|
|
1905
|
+
function getControls(options) {
|
|
1906
|
+
const { controlBtnProps, addtionControls, getSelection } = options;
|
|
1907
|
+
const controls = defaultKeys.map((k) => {
|
|
1908
|
+
const { icon, onClick } = typedFnMap[k];
|
|
1909
|
+
return (jsx(Button, { ...controlBtnProps, onMouseDown: handleMouseDown, onClick: onClick, children: jsx(Icon, { icon: icon }) }, k));
|
|
1910
|
+
});
|
|
1911
|
+
const extControls = (addtionControls ?? []).map((item, index) => (jsx(Button, { ...controlBtnProps, onMouseDown: handleMouseDown, onClick: (e) => item.onClick?.(getSelection(), e), children: item.icon }, `addtion-${index}`)));
|
|
1912
|
+
return [...controls, ...extControls];
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
const MEMTION_TAG_CLASS_NAME = "i-memtion-tag";
|
|
1916
|
+
const getInsertNode = (memtion, option) => memtion?.insert?.(option) ?? option.value;
|
|
1917
|
+
const getInsertText = (memtion, option) => {
|
|
1918
|
+
const nextNode = getInsertNode(memtion, option);
|
|
1919
|
+
if (typeof nextNode === "string" || typeof nextNode === "number") {
|
|
1920
|
+
return `${String(nextNode)} `;
|
|
1921
|
+
}
|
|
1922
|
+
return `${String(option.value)} `;
|
|
1923
|
+
};
|
|
1924
|
+
const getInsertHtml = (memtion, option, sanitizeValue) => {
|
|
1925
|
+
const nextNode = getInsertNode(memtion, option);
|
|
1926
|
+
if (nextNode === null || nextNode === undefined || nextNode === false) {
|
|
1927
|
+
return "";
|
|
1928
|
+
}
|
|
1929
|
+
const content = sanitizeValue(renderToStaticMarkup(jsx(Fragment, { children: nextNode })));
|
|
1930
|
+
return `<span class="${MEMTION_TAG_CLASS_NAME}" contenteditable="false" data-memtion-value="${escapeAttrValue(String(option.value))}">${content}</span>`;
|
|
1931
|
+
};
|
|
1932
|
+
const getSelectionRect = (range) => {
|
|
1933
|
+
if (!range)
|
|
1934
|
+
return null;
|
|
1935
|
+
const rect = range.getBoundingClientRect();
|
|
1936
|
+
if (rect.width || rect.height) {
|
|
1937
|
+
return rect;
|
|
1938
|
+
}
|
|
1939
|
+
return range.getClientRects()[0] ?? rect;
|
|
1940
|
+
};
|
|
1941
|
+
const insertMemtionOption = ({ editor, range, mode, memtion, option, sanitizeValue, }) => {
|
|
1942
|
+
if (!editor || !range)
|
|
1943
|
+
return null;
|
|
1944
|
+
const browserSelection = window.getSelection();
|
|
1945
|
+
if (!browserSelection)
|
|
1946
|
+
return null;
|
|
1947
|
+
const nextRange = range.cloneRange();
|
|
1948
|
+
browserSelection.removeAllRanges();
|
|
1949
|
+
browserSelection.addRange(nextRange);
|
|
1950
|
+
editor.focus();
|
|
1951
|
+
nextRange.deleteContents();
|
|
1952
|
+
if (mode === "plaintext") {
|
|
1953
|
+
const text = document.createTextNode(getInsertText(memtion, option));
|
|
1954
|
+
nextRange.insertNode(text);
|
|
1955
|
+
nextRange.setStartAfter(text);
|
|
1956
|
+
}
|
|
1957
|
+
else {
|
|
1958
|
+
const html = getInsertHtml(memtion, option, sanitizeValue);
|
|
1959
|
+
const fragment = nextRange.createContextualFragment(html);
|
|
1960
|
+
const lastNode = fragment.lastChild;
|
|
1961
|
+
const spacing = document.createTextNode(" ");
|
|
1962
|
+
nextRange.insertNode(fragment);
|
|
1963
|
+
if (lastNode) {
|
|
1964
|
+
nextRange.setStartAfter(lastNode);
|
|
1965
|
+
nextRange.collapse(true);
|
|
1966
|
+
}
|
|
1967
|
+
nextRange.insertNode(spacing);
|
|
1968
|
+
nextRange.setStartAfter(spacing);
|
|
1969
|
+
}
|
|
1970
|
+
nextRange.collapse(true);
|
|
1971
|
+
browserSelection.removeAllRanges();
|
|
1972
|
+
browserSelection.addRange(nextRange);
|
|
1973
|
+
return nextRange;
|
|
1887
1974
|
};
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1975
|
+
const getMemtionText = (triggerRange, selectionRange) => {
|
|
1976
|
+
if (!triggerRange || !selectionRange)
|
|
1977
|
+
return "";
|
|
1978
|
+
const range = triggerRange.cloneRange();
|
|
1979
|
+
range.setEnd(selectionRange.endContainer, selectionRange.endOffset);
|
|
1980
|
+
return range.toString();
|
|
1981
|
+
};
|
|
1982
|
+
const getMemtionReplaceRange = (triggerRange, selectionRange) => {
|
|
1983
|
+
if (!triggerRange || !selectionRange)
|
|
1984
|
+
return null;
|
|
1985
|
+
const range = triggerRange.cloneRange();
|
|
1986
|
+
range.setEnd(selectionRange.endContainer, selectionRange.endOffset);
|
|
1987
|
+
return range;
|
|
1988
|
+
};
|
|
1989
|
+
const filterMemtionOptions = (options, keyword) => {
|
|
1990
|
+
const normalizedKeyword = keyword.trim().toLowerCase();
|
|
1991
|
+
if (!normalizedKeyword) {
|
|
1992
|
+
return options;
|
|
1993
|
+
}
|
|
1994
|
+
return options.filter((option) => String(option.value).toLowerCase().includes(normalizedKeyword));
|
|
1995
|
+
};
|
|
1996
|
+
const isMemtionTag = (node) => node instanceof HTMLElement &&
|
|
1997
|
+
node.classList.contains(MEMTION_TAG_CLASS_NAME);
|
|
1998
|
+
const getAdjacentMemtionTag = (range, direction) => {
|
|
1999
|
+
const { startContainer, startOffset } = range;
|
|
2000
|
+
if (startContainer.nodeType === Node.TEXT_NODE) {
|
|
2001
|
+
const textNode = startContainer;
|
|
2002
|
+
if (direction === "backward" && startOffset === 0) {
|
|
2003
|
+
return isMemtionTag(textNode.previousSibling)
|
|
2004
|
+
? textNode.previousSibling
|
|
2005
|
+
: null;
|
|
2006
|
+
}
|
|
2007
|
+
if (direction === "forward" && startOffset === textNode.data.length) {
|
|
2008
|
+
return isMemtionTag(textNode.nextSibling)
|
|
2009
|
+
? textNode.nextSibling
|
|
2010
|
+
: null;
|
|
2011
|
+
}
|
|
2012
|
+
return null;
|
|
2013
|
+
}
|
|
2014
|
+
const element = startContainer;
|
|
2015
|
+
const targetIndex = direction === "backward" ? startOffset - 1 : startOffset;
|
|
2016
|
+
const targetNode = element.childNodes.item(targetIndex);
|
|
2017
|
+
return isMemtionTag(targetNode) ? targetNode : null;
|
|
2018
|
+
};
|
|
2019
|
+
const removeAdjacentMemtionTag = (editor, key) => {
|
|
2020
|
+
if (!editor)
|
|
2021
|
+
return false;
|
|
2022
|
+
const selection = window.getSelection();
|
|
2023
|
+
if (!selection?.rangeCount || !selection.isCollapsed)
|
|
2024
|
+
return false;
|
|
2025
|
+
const activeRange = selection.getRangeAt(0);
|
|
2026
|
+
const tag = getAdjacentMemtionTag(activeRange, key === "Backspace" ? "backward" : "forward");
|
|
2027
|
+
if (!tag || !tag.parentNode)
|
|
2028
|
+
return false;
|
|
2029
|
+
const parent = tag.parentNode;
|
|
2030
|
+
const nextSibling = tag.nextSibling;
|
|
2031
|
+
const index = Array.prototype.indexOf.call(parent.childNodes, tag);
|
|
2032
|
+
if (nextSibling?.nodeType === Node.TEXT_NODE) {
|
|
2033
|
+
const textNode = nextSibling;
|
|
2034
|
+
if (textNode.data.startsWith(" ")) {
|
|
2035
|
+
textNode.deleteData(0, 1);
|
|
2036
|
+
if (!textNode.data.length) {
|
|
2037
|
+
nextSibling.remove();
|
|
1896
2038
|
}
|
|
1897
|
-
return (jsxs(Button, { ...controlBtnProps, onClick: onClick, children: [jsx(Icon, { icon: icon }), tip && jsx("span", { className: 'i-editor-control-tip', children: tip })] }, k));
|
|
1898
2039
|
}
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
2040
|
+
}
|
|
2041
|
+
tag.remove();
|
|
2042
|
+
const range = document.createRange();
|
|
2043
|
+
range.setStart(parent, Math.min(index, parent.childNodes.length));
|
|
2044
|
+
range.collapse(true);
|
|
2045
|
+
selection.removeAllRanges();
|
|
2046
|
+
selection.addRange(range);
|
|
2047
|
+
editor.focus();
|
|
2048
|
+
return true;
|
|
2049
|
+
};
|
|
2050
|
+
const Memtion = (props) => {
|
|
2051
|
+
const { visible, rect, options, activeIndex, onActiveChange, onSelect } = props;
|
|
2052
|
+
if (!visible || !rect || !options?.length) {
|
|
2053
|
+
return null;
|
|
2054
|
+
}
|
|
2055
|
+
return (jsx(List$1, { className: "i-editor-memtion", type: "option", style: {
|
|
2056
|
+
position: "fixed",
|
|
2057
|
+
top: rect.bottom,
|
|
2058
|
+
left: rect.left,
|
|
2059
|
+
}, 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}`))) }));
|
|
2060
|
+
};
|
|
2061
|
+
var Memtion$1 = memo(Memtion);
|
|
1902
2062
|
|
|
2063
|
+
const controlBtnProps = {
|
|
2064
|
+
square: true,
|
|
2065
|
+
flat: true,
|
|
2066
|
+
size: "small",
|
|
2067
|
+
};
|
|
1903
2068
|
const Editor = (props) => {
|
|
1904
|
-
const { ref, width, height = "10em", placeholder, autosize, border = true,
|
|
2069
|
+
const { ref, value = "", width, height = "10em", placeholder, autosize, border = true, mode = "rich", hideControl, addtionControls, memtion, className, style, onChange, onEnter, onFocus, onBlur, onPaste, onMouseUp, onKeyUp, onKeyDown, ...restProps } = props;
|
|
1905
2070
|
const editorRef = useRef(null);
|
|
1906
|
-
const
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
2071
|
+
const selectionRef = useRef(null);
|
|
2072
|
+
const memtionTriggerRangeRef = useRef(null);
|
|
2073
|
+
const pendingMemtionRef = useRef(false);
|
|
2074
|
+
const [memtionVisible, setMemtionVisible] = useState(false);
|
|
2075
|
+
const [memtionRect, setMemtionRect] = useState(null);
|
|
2076
|
+
const [memtionKeyword, setMemtionKeyword] = useState("");
|
|
2077
|
+
const [memtionActiveIndex, setMemtionActiveIndex] = useState(0);
|
|
2078
|
+
const memtionOptions = useMemo(() => filterMemtionOptions(memtion?.options ?? [], memtionKeyword), [memtion?.options, memtionKeyword]);
|
|
2079
|
+
const sanitizeValue = (nextValue) => {
|
|
2080
|
+
if (mode === "plaintext") {
|
|
2081
|
+
return nextValue === "\n" ? "" : nextValue;
|
|
2082
|
+
}
|
|
2083
|
+
const safeHtml = xss(nextValue, xssOptions);
|
|
2084
|
+
return safeHtml === "<br>" ? "" : safeHtml;
|
|
2085
|
+
};
|
|
2086
|
+
const rememberSelection = () => {
|
|
2087
|
+
if (!editorRef.current)
|
|
2088
|
+
return;
|
|
2089
|
+
const selection = window.getSelection();
|
|
2090
|
+
if (!selection?.rangeCount)
|
|
2091
|
+
return;
|
|
2092
|
+
const range = selection.getRangeAt(0);
|
|
2093
|
+
const container = range.commonAncestorContainer;
|
|
2094
|
+
const parent = container.nodeType === Node.ELEMENT_NODE
|
|
2095
|
+
? container
|
|
2096
|
+
: container.parentElement;
|
|
2097
|
+
if (!parent || !editorRef.current.contains(parent))
|
|
2098
|
+
return;
|
|
2099
|
+
selectionRef.current = range.cloneRange();
|
|
2100
|
+
};
|
|
2101
|
+
const setEditorValue = (nextValue) => {
|
|
2102
|
+
if (!editorRef.current)
|
|
2103
|
+
return;
|
|
2104
|
+
const safeValue = sanitizeValue(nextValue);
|
|
2105
|
+
if (mode === "plaintext") {
|
|
2106
|
+
editorRef.current.textContent = safeValue;
|
|
2107
|
+
return;
|
|
2108
|
+
}
|
|
2109
|
+
editorRef.current.innerHTML = safeValue;
|
|
1910
2110
|
};
|
|
1911
|
-
const
|
|
2111
|
+
const getEditorValue = (sanitize = false) => {
|
|
2112
|
+
if (!editorRef.current)
|
|
2113
|
+
return "";
|
|
2114
|
+
const nextValue = mode === "plaintext"
|
|
2115
|
+
? (editorRef.current.textContent ?? "")
|
|
2116
|
+
: editorRef.current.innerHTML;
|
|
2117
|
+
return sanitize ? sanitizeValue(nextValue) : nextValue;
|
|
2118
|
+
};
|
|
2119
|
+
const hideMemtion = () => {
|
|
2120
|
+
pendingMemtionRef.current = false;
|
|
2121
|
+
memtionTriggerRangeRef.current = null;
|
|
2122
|
+
setMemtionVisible(false);
|
|
2123
|
+
setMemtionRect(null);
|
|
2124
|
+
setMemtionKeyword("");
|
|
2125
|
+
setMemtionActiveIndex(0);
|
|
2126
|
+
};
|
|
2127
|
+
const insertMemtion = (option) => {
|
|
2128
|
+
const replaceRange = getMemtionReplaceRange(memtionTriggerRangeRef.current, selectionRef.current);
|
|
2129
|
+
const range = insertMemtionOption({
|
|
2130
|
+
editor: editorRef.current,
|
|
2131
|
+
range: replaceRange,
|
|
2132
|
+
mode,
|
|
2133
|
+
memtion,
|
|
2134
|
+
option,
|
|
2135
|
+
sanitizeValue,
|
|
2136
|
+
});
|
|
2137
|
+
if (!range || !editorRef.current)
|
|
2138
|
+
return;
|
|
2139
|
+
selectionRef.current = range.cloneRange();
|
|
2140
|
+
hideMemtion();
|
|
2141
|
+
editorRef.current.dispatchEvent(new Event("input", { bubbles: true }));
|
|
2142
|
+
};
|
|
2143
|
+
const handlePaste = (e) => {
|
|
1912
2144
|
onPaste?.(e);
|
|
1913
|
-
if (
|
|
2145
|
+
if (e.defaultPrevented)
|
|
1914
2146
|
return;
|
|
1915
2147
|
e.preventDefault();
|
|
2148
|
+
if (mode === "plaintext") {
|
|
2149
|
+
const text = e.clipboardData.getData("text/plain");
|
|
2150
|
+
exec("insertText", false, text);
|
|
2151
|
+
return;
|
|
2152
|
+
}
|
|
2153
|
+
const html = e.clipboardData.getData("text/html");
|
|
2154
|
+
if (html) {
|
|
2155
|
+
exec("insertHTML", false, sanitizeValue(html));
|
|
2156
|
+
return;
|
|
2157
|
+
}
|
|
1916
2158
|
const text = e.clipboardData.getData("text/plain");
|
|
1917
2159
|
exec("insertText", false, text);
|
|
1918
2160
|
};
|
|
1919
2161
|
const handleKeyDown = (e) => {
|
|
1920
2162
|
onKeyDown?.(e);
|
|
2163
|
+
if (mode === "rich" &&
|
|
2164
|
+
(e.key === "Backspace" || e.key === "Delete") &&
|
|
2165
|
+
removeAdjacentMemtionTag(editorRef.current, e.key)) {
|
|
2166
|
+
e.preventDefault();
|
|
2167
|
+
rememberSelection();
|
|
2168
|
+
editorRef.current?.dispatchEvent(new Event("input", { bubbles: true }));
|
|
2169
|
+
return;
|
|
2170
|
+
}
|
|
2171
|
+
const memtionKey = memtion?.key ?? "@";
|
|
2172
|
+
if (memtionVisible && e.key === " ") {
|
|
2173
|
+
hideMemtion();
|
|
2174
|
+
}
|
|
2175
|
+
if (memtionVisible && memtionOptions.length) {
|
|
2176
|
+
switch (e.key) {
|
|
2177
|
+
case "ArrowDown":
|
|
2178
|
+
e.preventDefault();
|
|
2179
|
+
setMemtionActiveIndex((index) => index + 1 >= memtionOptions.length ? 0 : index + 1);
|
|
2180
|
+
return;
|
|
2181
|
+
case "ArrowUp":
|
|
2182
|
+
e.preventDefault();
|
|
2183
|
+
setMemtionActiveIndex((index) => index - 1 < 0 ? memtionOptions.length - 1 : index - 1);
|
|
2184
|
+
return;
|
|
2185
|
+
case "Enter":
|
|
2186
|
+
e.preventDefault();
|
|
2187
|
+
insertMemtion(memtionOptions[memtionActiveIndex]);
|
|
2188
|
+
return;
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
if (memtion && e.key === memtionKey) {
|
|
2192
|
+
rememberSelection();
|
|
2193
|
+
memtionTriggerRangeRef.current =
|
|
2194
|
+
selectionRef.current?.cloneRange() ?? null;
|
|
2195
|
+
pendingMemtionRef.current = true;
|
|
2196
|
+
}
|
|
1921
2197
|
switch (e.key) {
|
|
1922
2198
|
case "Tab":
|
|
1923
2199
|
e.preventDefault();
|
|
1924
|
-
exec("insertHTML", false, "	");
|
|
2200
|
+
exec(mode === "plaintext" ? "insertText" : "insertHTML", false, mode === "plaintext" ? "\t" : "	");
|
|
1925
2201
|
break;
|
|
1926
2202
|
case "Enter":
|
|
2203
|
+
if (!onEnter)
|
|
2204
|
+
break;
|
|
1927
2205
|
e.preventDefault();
|
|
1928
|
-
|
|
1929
|
-
if (!editorRef.current)
|
|
1930
|
-
return;
|
|
1931
|
-
editorRef.current.scrollBy({
|
|
1932
|
-
top: 20,
|
|
1933
|
-
left: -1e3,
|
|
1934
|
-
});
|
|
1935
|
-
if (!autosize)
|
|
1936
|
-
return;
|
|
1937
|
-
editorRef.current.style.height = `${editorRef.current.scrollHeight}px`;
|
|
2206
|
+
onEnter(e);
|
|
1938
2207
|
break;
|
|
1939
2208
|
}
|
|
1940
2209
|
};
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
}
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
2210
|
+
useEffect(() => {
|
|
2211
|
+
if (!editorRef.current)
|
|
2212
|
+
return;
|
|
2213
|
+
const nextValue = sanitizeValue(value);
|
|
2214
|
+
if (getEditorValue(true) === nextValue)
|
|
2215
|
+
return;
|
|
2216
|
+
setEditorValue(nextValue);
|
|
2217
|
+
if (autosize) {
|
|
2218
|
+
editorRef.current.style.height = `${editorRef.current.scrollHeight}px`;
|
|
2219
|
+
}
|
|
2220
|
+
}, [autosize, mode, value]);
|
|
2221
|
+
useEffect(() => {
|
|
2222
|
+
if (!memtionOptions.length) {
|
|
2223
|
+
setMemtionActiveIndex(0);
|
|
2224
|
+
return;
|
|
2225
|
+
}
|
|
2226
|
+
setMemtionActiveIndex((index) => index >= memtionOptions.length ? 0 : index);
|
|
2227
|
+
}, [memtionOptions]);
|
|
1956
2228
|
const handleInput = (e) => {
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
2229
|
+
const rawValue = getEditorValue();
|
|
2230
|
+
let nextValue = sanitizeValue(rawValue);
|
|
2231
|
+
if (!nextValue && rawValue && editorRef.current) {
|
|
2232
|
+
nextValue = "";
|
|
2233
|
+
setEditorValue(nextValue);
|
|
2234
|
+
}
|
|
2235
|
+
rememberSelection();
|
|
2236
|
+
if (memtion && (pendingMemtionRef.current || memtionVisible)) {
|
|
2237
|
+
const memtionKey = memtion?.key ?? "@";
|
|
2238
|
+
const memtionText = getMemtionText(memtionTriggerRangeRef.current, selectionRef.current);
|
|
2239
|
+
if (!memtionText.startsWith(memtionKey) || /\s/.test(memtionText)) {
|
|
2240
|
+
hideMemtion();
|
|
2241
|
+
}
|
|
2242
|
+
else {
|
|
2243
|
+
const keyword = memtionText.slice(memtionKey.length);
|
|
2244
|
+
pendingMemtionRef.current = false;
|
|
2245
|
+
setMemtionRect(getSelectionRect(selectionRef.current));
|
|
2246
|
+
setMemtionKeyword(keyword);
|
|
2247
|
+
setMemtionActiveIndex(0);
|
|
2248
|
+
setMemtionVisible(true);
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
if (autosize && editorRef.current) {
|
|
2252
|
+
editorRef.current.style.height = `${editorRef.current.scrollHeight}px`;
|
|
1961
2253
|
}
|
|
1962
|
-
|
|
2254
|
+
onChange?.(nextValue, e);
|
|
2255
|
+
};
|
|
2256
|
+
const handleFocus = (e) => {
|
|
2257
|
+
rememberSelection();
|
|
2258
|
+
onFocus?.(e);
|
|
2259
|
+
};
|
|
2260
|
+
const handleBlur = (e) => {
|
|
2261
|
+
hideMemtion();
|
|
2262
|
+
onBlur?.(e);
|
|
1963
2263
|
};
|
|
2264
|
+
const handleMouseUp = (e) => {
|
|
2265
|
+
rememberSelection();
|
|
2266
|
+
onMouseUp?.(e);
|
|
2267
|
+
};
|
|
2268
|
+
const handleKeyUp = (e) => {
|
|
2269
|
+
rememberSelection();
|
|
2270
|
+
onKeyUp?.(e);
|
|
2271
|
+
};
|
|
2272
|
+
const handleRef = (node) => {
|
|
2273
|
+
editorRef.current = node;
|
|
2274
|
+
if (typeof ref === "function") {
|
|
2275
|
+
ref(node);
|
|
2276
|
+
return;
|
|
2277
|
+
}
|
|
2278
|
+
if (ref) {
|
|
2279
|
+
ref.current = node;
|
|
2280
|
+
}
|
|
2281
|
+
};
|
|
2282
|
+
const getSelection = useCallback(() => selectionRef.current?.cloneRange() ?? null, []);
|
|
2283
|
+
const controls = useMemo(() => getControls({
|
|
2284
|
+
controlBtnProps,
|
|
2285
|
+
addtionControls,
|
|
2286
|
+
getSelection,
|
|
2287
|
+
}), [addtionControls, getSelection]);
|
|
1964
2288
|
return (jsxs("div", { className: classNames("i-editor", className, {
|
|
1965
2289
|
"i-editor-borderless": !border,
|
|
1966
2290
|
}), style: {
|
|
1967
2291
|
...style,
|
|
1968
2292
|
[autosize ? "minHeight" : "height"]: height,
|
|
1969
2293
|
width,
|
|
1970
|
-
}, children: [
|
|
1971
|
-
controlBtnProps,
|
|
1972
|
-
}) })), jsx("div", { ref: editorRef, className: 'i-editor-content', "data-placeholder": placeholder, contentEditable: true, onPaste: handlePaste, onInput: handleInput, onKeyDown: handleKeyDown, ...restProps })] }));
|
|
2294
|
+
}, 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 })] }));
|
|
1973
2295
|
};
|
|
1974
2296
|
|
|
1975
2297
|
const Flex = (props) => {
|
|
@@ -2405,7 +2727,7 @@ const HookModal = (props) => {
|
|
|
2405
2727
|
},
|
|
2406
2728
|
close: () => {
|
|
2407
2729
|
state.visible = false;
|
|
2408
|
-
if (mergedProps.closable)
|
|
2730
|
+
if (mergedProps.closable ?? true)
|
|
2409
2731
|
return;
|
|
2410
2732
|
Promise.resolve().then(() => {
|
|
2411
2733
|
state.visible = true;
|
|
@@ -2952,27 +3274,34 @@ function List(props) {
|
|
|
2952
3274
|
if (!files.length)
|
|
2953
3275
|
return "";
|
|
2954
3276
|
return (jsx(Flex, { className: 'i-image-list', gap: gap, columns: columns, wrap: wrap, direction: direction, children: files.map((img, i) => {
|
|
2955
|
-
return (jsx(
|
|
3277
|
+
return (jsx(MemoImage, { src: img.src, usePreview: false, onClick: (e) => handleClick(e, i), ...restProps }, i));
|
|
2956
3278
|
}) }));
|
|
2957
3279
|
}
|
|
2958
3280
|
|
|
3281
|
+
const STATUS_LOADING = "loading";
|
|
3282
|
+
const STATUS_ERROR = "error";
|
|
2959
3283
|
const Image = (props) => {
|
|
2960
3284
|
const { src, thumb, round, size, height, width, ratio, initSize, lazyload, fallback, fit, style, className, cover, coverClass, usePreview: previewable, onLoad, onError, onClick, ...restProps } = props;
|
|
2961
3285
|
const state = useReactive({
|
|
2962
|
-
status:
|
|
3286
|
+
status: STATUS_LOADING,
|
|
2963
3287
|
});
|
|
2964
3288
|
const ref = useRef(null);
|
|
2965
3289
|
const { observe, unobserve } = useIntersectionObserver();
|
|
2966
3290
|
const preview = usePreview();
|
|
2967
|
-
const
|
|
3291
|
+
const setStatus = useCallback((status) => {
|
|
3292
|
+
if (state.status === status)
|
|
3293
|
+
return;
|
|
3294
|
+
state.status = status;
|
|
3295
|
+
}, [state]);
|
|
3296
|
+
const handleError = useCallback((err) => {
|
|
2968
3297
|
onError?.(err);
|
|
2969
|
-
|
|
2970
|
-
};
|
|
2971
|
-
const handleLoad = (e) => {
|
|
3298
|
+
setStatus(STATUS_ERROR);
|
|
3299
|
+
}, [onError, setStatus]);
|
|
3300
|
+
const handleLoad = useCallback((e) => {
|
|
2972
3301
|
onLoad?.(e);
|
|
2973
|
-
|
|
2974
|
-
};
|
|
2975
|
-
const handleClick = (e) => {
|
|
3302
|
+
setStatus(undefined);
|
|
3303
|
+
}, [onLoad, setStatus]);
|
|
3304
|
+
const handleClick = useCallback((e) => {
|
|
2976
3305
|
onClick?.(e);
|
|
2977
3306
|
if (!previewable || !src)
|
|
2978
3307
|
return;
|
|
@@ -2986,44 +3315,53 @@ const Image = (props) => {
|
|
|
2986
3315
|
},
|
|
2987
3316
|
],
|
|
2988
3317
|
});
|
|
2989
|
-
};
|
|
3318
|
+
}, [onClick, preview, previewable, src]);
|
|
2990
3319
|
useEffect(() => {
|
|
2991
3320
|
if (!src || typeof window === "undefined")
|
|
2992
3321
|
return;
|
|
2993
3322
|
const img = ref.current;
|
|
3323
|
+
if (!img)
|
|
3324
|
+
return;
|
|
2994
3325
|
const hasSrcAttr = img?.getAttribute("src");
|
|
2995
3326
|
const canSyncStatus = Boolean(img && (!lazyload || hasSrcAttr));
|
|
2996
|
-
if (canSyncStatus && img
|
|
2997
|
-
|
|
3327
|
+
if (canSyncStatus && img.complete) {
|
|
3328
|
+
setStatus(img.naturalWidth > 0 ? undefined : STATUS_ERROR);
|
|
2998
3329
|
}
|
|
2999
|
-
if (!
|
|
3000
|
-
|
|
3330
|
+
if (!img.complete && observe && lazyload) {
|
|
3331
|
+
setStatus(STATUS_LOADING);
|
|
3001
3332
|
}
|
|
3002
|
-
if (!lazyload || !
|
|
3333
|
+
if (!lazyload || !observe)
|
|
3003
3334
|
return;
|
|
3004
|
-
observe(
|
|
3335
|
+
observe(img, (tar, visible) => {
|
|
3005
3336
|
if (!visible)
|
|
3006
3337
|
return;
|
|
3007
3338
|
tar.setAttribute("src", tar.dataset.src || "");
|
|
3008
3339
|
unobserve(tar);
|
|
3009
3340
|
});
|
|
3010
3341
|
return () => {
|
|
3011
|
-
|
|
3342
|
+
unobserve(img);
|
|
3012
3343
|
};
|
|
3013
|
-
}, [src]);
|
|
3014
|
-
|
|
3015
|
-
const iSize =
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3344
|
+
}, [lazyload, observe, setStatus, src, unobserve]);
|
|
3345
|
+
const imageStatus = state.status;
|
|
3346
|
+
const iSize = imageStatus === STATUS_LOADING ? initSize : undefined;
|
|
3347
|
+
const wrapperStyle = useMemo(() => ({
|
|
3348
|
+
width: width ?? size ?? iSize,
|
|
3349
|
+
height: height ?? size ?? iSize,
|
|
3350
|
+
aspectRatio: ratio,
|
|
3351
|
+
...style,
|
|
3352
|
+
}), [height, iSize, ratio, size, style, width]);
|
|
3353
|
+
const wrapperClassName = useMemo(() => classNames("i-image", className, {
|
|
3354
|
+
rounded: round,
|
|
3355
|
+
[`i-image-${imageStatus}`]: imageStatus,
|
|
3356
|
+
}), [className, imageStatus, round]);
|
|
3357
|
+
const imageStyle = useMemo(() => ({ objectFit: fit }), [fit]);
|
|
3358
|
+
const imageSrcProps = lazyload
|
|
3359
|
+
? { "data-src": thumb ?? src }
|
|
3360
|
+
: { src: thumb ?? src };
|
|
3361
|
+
return (jsx("div", { style: wrapperStyle, className: wrapperClassName, onClick: handleClick, children: imageStatus === STATUS_ERROR ? ((fallback ?? null)) : (jsxs(Fragment, { children: [src && (jsx("img", { ref: ref, style: imageStyle, ...imageSrcProps, ...restProps, onLoad: handleLoad, onError: handleError })), src && imageStatus === STATUS_LOADING && (jsx(Loading, { absolute: true })), cover && (jsx("div", { className: classNames("i-image-cover", coverClass), children: cover }))] })) }));
|
|
3025
3362
|
};
|
|
3026
|
-
|
|
3363
|
+
const MemoImage = memo(Image);
|
|
3364
|
+
MemoImage.List = List;
|
|
3027
3365
|
|
|
3028
3366
|
function InputContainer(props) {
|
|
3029
3367
|
const { as: As = "label", label, className, labelInline, style, children, status, tip, required, } = props;
|
|
@@ -3199,16 +3537,19 @@ const Range = (props) => {
|
|
|
3199
3537
|
};
|
|
3200
3538
|
|
|
3201
3539
|
const Textarea = (props) => {
|
|
3202
|
-
const { ref, label, name, value = "", labelInline, className, status = "normal", message, tip, autoSize, border, width, style, onChange, onEnter, ...restProps } = props;
|
|
3540
|
+
const { ref, label, name, value = "", labelInline, className, status = "normal", message, tip, autoSize, border, width, style, resize, onChange, onEnter, ...restProps } = props;
|
|
3203
3541
|
const [textareaValue, setTextareaValue] = useState(value);
|
|
3204
3542
|
const refTextarea = useRef(null);
|
|
3543
|
+
const syncTextareaHeight = () => {
|
|
3544
|
+
const ta = refTextarea.current;
|
|
3545
|
+
if (!autoSize || !ta)
|
|
3546
|
+
return;
|
|
3547
|
+
ta.style.height = "auto";
|
|
3548
|
+
ta.style.height = `${ta.scrollHeight}px`;
|
|
3549
|
+
};
|
|
3205
3550
|
const handleChange = (e) => {
|
|
3206
3551
|
const v = e.target.value;
|
|
3207
3552
|
setTextareaValue(v);
|
|
3208
|
-
const ta = refTextarea.current;
|
|
3209
|
-
if (autoSize && ta) {
|
|
3210
|
-
ta.style.height = `${ta.scrollHeight}px`;
|
|
3211
|
-
}
|
|
3212
3553
|
onChange?.(v, e);
|
|
3213
3554
|
};
|
|
3214
3555
|
const handleKeydown = (e) => {
|
|
@@ -3220,6 +3561,9 @@ const Textarea = (props) => {
|
|
|
3220
3561
|
useEffect(() => {
|
|
3221
3562
|
setTextareaValue(value);
|
|
3222
3563
|
}, [value]);
|
|
3564
|
+
useEffect(() => {
|
|
3565
|
+
syncTextareaHeight();
|
|
3566
|
+
}, [autoSize, textareaValue]);
|
|
3223
3567
|
useImperativeHandle(ref, () => {
|
|
3224
3568
|
return {
|
|
3225
3569
|
input: refTextarea.current,
|
|
@@ -3230,6 +3574,7 @@ const Textarea = (props) => {
|
|
|
3230
3574
|
name,
|
|
3231
3575
|
value: textareaValue,
|
|
3232
3576
|
className: "i-input i-textarea",
|
|
3577
|
+
style: resize === false ? { resize: "none" } : undefined,
|
|
3233
3578
|
onChange: handleChange,
|
|
3234
3579
|
onKeyDown: handleKeydown,
|
|
3235
3580
|
...restProps,
|
|
@@ -3687,7 +4032,7 @@ function Footer(props) {
|
|
|
3687
4032
|
setInputValue(value);
|
|
3688
4033
|
setColorType(type);
|
|
3689
4034
|
}, [value, type]);
|
|
3690
|
-
return (jsxs("div", { className:
|
|
4035
|
+
return (jsxs("div", { className: "i-colorpicker-footer", children: [jsx(Select, { readOnly: true, hideClear: true, hideArrow: true, style: { width: "5.6em" }, options: ["RGB", "HEX", "HSB"], value: colorType, onChange: handleTypeChange, popupProps: { fitSize: false } }), jsx(Input, { placeholder: "color", value: inputValue, onChange: handleChange }), jsx(Button, { square: true, onClick: onOk, children: jsx(Icon, { icon: jsx(CheckRound, {}) }) })] }));
|
|
3691
4036
|
}
|
|
3692
4037
|
|
|
3693
4038
|
const Handle = (props) => {
|
|
@@ -4029,10 +4374,10 @@ const defaultOk = {
|
|
|
4029
4374
|
};
|
|
4030
4375
|
const defaultCancel = {
|
|
4031
4376
|
children: "取消",
|
|
4032
|
-
|
|
4377
|
+
flat: true,
|
|
4033
4378
|
};
|
|
4034
4379
|
const Popconfirm = (props) => {
|
|
4035
|
-
const { trigger = "click", visible, icon = jsx(Icon, { icon: jsx(InfoOutlined, {}), className:
|
|
4380
|
+
const { trigger = "click", visible, icon = jsx(Icon, { icon: jsx(InfoOutlined, {}), className: "error", size: "1.2em" }), content, okButtonProps, cancelButtonProps, children, align, position = "top", offset = 12, extra, onOk, onClose, ...restProps } = props;
|
|
4036
4381
|
const state = useReactive({
|
|
4037
4382
|
loading: false,
|
|
4038
4383
|
visible,
|
|
@@ -4063,7 +4408,7 @@ const Popconfirm = (props) => {
|
|
|
4063
4408
|
await onClose?.();
|
|
4064
4409
|
state.visible = false;
|
|
4065
4410
|
};
|
|
4066
|
-
const popconfirmContent = (jsxs("div", { className:
|
|
4411
|
+
const popconfirmContent = (jsxs("div", { className: "i-popconfirm", children: [jsxs(Flex, { gap: ".5em", children: [icon, jsx("div", { className: "i-popconfirm-content", children: content })] }), jsxs(Flex, { gap: 12, justify: "flex-end", className: "i-popconfirm-footer", children: [cancelButtonProps !== null && (jsx(Button, { ...cancel, onClick: handleCancel })), extra, okButtonProps !== null && (jsx(Button, { loading: state.loading, ...ok, onClick: handleOk }))] })] }));
|
|
4067
4412
|
return (jsx(Popup, { content: popconfirmContent, ...restProps, trigger: trigger, visible: state.visible, align: align, offset: offset, position: position, onVisibleChange: handleVisibleChange, children: children }));
|
|
4068
4413
|
};
|
|
4069
4414
|
|
|
@@ -4549,7 +4894,7 @@ const Item = (props) => {
|
|
|
4549
4894
|
};
|
|
4550
4895
|
|
|
4551
4896
|
const Tabs = ((props) => {
|
|
4552
|
-
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:
|
|
4897
|
+
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;
|
|
4553
4898
|
const navRefs = useRef([]);
|
|
4554
4899
|
const barRef = useRef(null);
|
|
4555
4900
|
const navsRef = useRef(null);
|
|
@@ -4559,10 +4904,15 @@ const Tabs = ((props) => {
|
|
|
4559
4904
|
const [barStyle, setBarStyle] = useState({});
|
|
4560
4905
|
const [cachedTabs, setCachedTabs] = useState([]);
|
|
4561
4906
|
const [overflow, setOverflow] = useState(false);
|
|
4562
|
-
const [moreTabs, setMoreTabs] = useState([]);
|
|
4563
4907
|
const [tabs, setTabs] = useState([]);
|
|
4564
4908
|
const { observe, unobserve } = useIntersectionObserver();
|
|
4565
4909
|
const size = useSize(navsRef);
|
|
4910
|
+
const tabsRef = useRef(tabs);
|
|
4911
|
+
tabsRef.current = tabs;
|
|
4912
|
+
const activeKeyRef = useRef(activeKey);
|
|
4913
|
+
activeKeyRef.current = activeKey;
|
|
4914
|
+
const prevActiveKeyRef = useRef(prevActiveKey);
|
|
4915
|
+
prevActiveKeyRef.current = prevActiveKey;
|
|
4566
4916
|
useEffect(() => {
|
|
4567
4917
|
contentsRef.current.clear();
|
|
4568
4918
|
if (!items) {
|
|
@@ -4599,10 +4949,11 @@ const Tabs = ((props) => {
|
|
|
4599
4949
|
}));
|
|
4600
4950
|
}, [children, items]);
|
|
4601
4951
|
const add = (tab) => {
|
|
4602
|
-
const
|
|
4603
|
-
const
|
|
4952
|
+
const currentTabs = tabsRef.current;
|
|
4953
|
+
const tkey = String(tab.key ?? currentTabs.length);
|
|
4954
|
+
const i = currentTabs.findIndex((t) => t.key === tkey);
|
|
4604
4955
|
if (i > -1) {
|
|
4605
|
-
open(
|
|
4956
|
+
open(currentTabs[i].key ?? `${i}`);
|
|
4606
4957
|
return;
|
|
4607
4958
|
}
|
|
4608
4959
|
contentsRef.current.set(tkey, tab.content);
|
|
@@ -4611,20 +4962,34 @@ const Tabs = ((props) => {
|
|
|
4611
4962
|
open(tkey);
|
|
4612
4963
|
};
|
|
4613
4964
|
const close = (key) => {
|
|
4614
|
-
const
|
|
4965
|
+
const currentTabs = tabsRef.current;
|
|
4966
|
+
const i = currentTabs.findIndex((t) => t.key === key);
|
|
4615
4967
|
if (i < 0)
|
|
4616
4968
|
return;
|
|
4617
4969
|
contentsRef.current.delete(key);
|
|
4618
|
-
const nextTabs = [...
|
|
4970
|
+
const nextTabs = [...currentTabs];
|
|
4619
4971
|
nextTabs.splice(i, 1);
|
|
4620
4972
|
setTabs(nextTabs);
|
|
4621
|
-
if (
|
|
4973
|
+
if (activeKeyRef.current !== key)
|
|
4622
4974
|
return;
|
|
4623
4975
|
const next = nextTabs[i] || nextTabs[i - 1];
|
|
4624
|
-
|
|
4976
|
+
const prev = prevActiveKeyRef.current;
|
|
4977
|
+
const nextKey = prev && nextTabs.some((t) => t.key === prev) ? prev : next?.key;
|
|
4978
|
+
open(nextKey ?? "");
|
|
4625
4979
|
};
|
|
4626
4980
|
const open = (key) => {
|
|
4627
|
-
|
|
4981
|
+
const nextKey = key || undefined;
|
|
4982
|
+
if (nextKey === undefined) {
|
|
4983
|
+
onTabChange?.(undefined, activeKey);
|
|
4984
|
+
setPrevActiveKey(activeKey);
|
|
4985
|
+
setActiveKey(undefined);
|
|
4986
|
+
setBarStyle({
|
|
4987
|
+
height: 0,
|
|
4988
|
+
width: 0,
|
|
4989
|
+
});
|
|
4990
|
+
return;
|
|
4991
|
+
}
|
|
4992
|
+
if (nextKey === activeKey) {
|
|
4628
4993
|
if (!toggable)
|
|
4629
4994
|
return;
|
|
4630
4995
|
onTabChange?.(undefined, key);
|
|
@@ -4636,38 +5001,75 @@ const Tabs = ((props) => {
|
|
|
4636
5001
|
return;
|
|
4637
5002
|
}
|
|
4638
5003
|
setPrevActiveKey(activeKey);
|
|
4639
|
-
onTabChange?.(
|
|
4640
|
-
setActiveKey(
|
|
5004
|
+
onTabChange?.(nextKey, activeKey);
|
|
5005
|
+
setActiveKey(nextKey);
|
|
5006
|
+
};
|
|
5007
|
+
const handleKeyAction = (e, action) => {
|
|
5008
|
+
if (!["Enter", " "].includes(e.key))
|
|
5009
|
+
return;
|
|
5010
|
+
e.preventDefault();
|
|
5011
|
+
action();
|
|
5012
|
+
};
|
|
5013
|
+
const scrollToTab = (key) => {
|
|
5014
|
+
const index = tabsRef.current.findIndex((tab) => tab.key === key);
|
|
5015
|
+
const nav = navRefs.current[index];
|
|
5016
|
+
nav?.scrollIntoView({
|
|
5017
|
+
behavior: "smooth",
|
|
5018
|
+
block: "nearest",
|
|
5019
|
+
inline: "nearest",
|
|
5020
|
+
});
|
|
5021
|
+
};
|
|
5022
|
+
const handleMoreTabClick = (key) => {
|
|
5023
|
+
open(key);
|
|
5024
|
+
scrollToTab(key);
|
|
4641
5025
|
};
|
|
4642
5026
|
useEffect(() => {
|
|
4643
|
-
if (!size || hideMore || !observe)
|
|
5027
|
+
if (!size || hideMore || !observe || !unobserve)
|
|
4644
5028
|
return;
|
|
4645
5029
|
const { scrollHeight, scrollWidth } = navsRef.current;
|
|
4646
5030
|
const { width, height } = size;
|
|
4647
5031
|
const nextOverflow = scrollHeight > height || scrollWidth > width;
|
|
4648
|
-
setOverflow(nextOverflow);
|
|
4649
|
-
if (!nextOverflow)
|
|
5032
|
+
setOverflow((v) => (v === nextOverflow ? v : nextOverflow));
|
|
5033
|
+
if (!nextOverflow) {
|
|
5034
|
+
setTabs((ts) => {
|
|
5035
|
+
let changed = false;
|
|
5036
|
+
const next = ts.map((t) => {
|
|
5037
|
+
if (t.intersecting === undefined ||
|
|
5038
|
+
t.intersecting === true) {
|
|
5039
|
+
return t;
|
|
5040
|
+
}
|
|
5041
|
+
changed = true;
|
|
5042
|
+
return { ...t, intersecting: true };
|
|
5043
|
+
});
|
|
5044
|
+
return changed ? next : ts;
|
|
5045
|
+
});
|
|
4650
5046
|
return;
|
|
5047
|
+
}
|
|
5048
|
+
const observed = [];
|
|
4651
5049
|
navRefs.current.map((nav, i) => {
|
|
4652
5050
|
if (!nav)
|
|
4653
5051
|
return;
|
|
4654
|
-
|
|
5052
|
+
observed.push(nav);
|
|
5053
|
+
observe(nav, (_tar, visible) => {
|
|
4655
5054
|
setTabs((ts) => {
|
|
4656
5055
|
if (!ts[i])
|
|
4657
5056
|
return ts;
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
return
|
|
5057
|
+
if (ts[i]?.intersecting === visible)
|
|
5058
|
+
return ts;
|
|
5059
|
+
return ts.map((t, idx) => idx === i ? { ...t, intersecting: visible } : t);
|
|
4661
5060
|
});
|
|
4662
5061
|
});
|
|
4663
5062
|
});
|
|
4664
|
-
|
|
5063
|
+
return () => {
|
|
5064
|
+
observed.map((el) => unobserve(el));
|
|
5065
|
+
};
|
|
5066
|
+
}, [size, hideMore, tabs.length, observe, unobserve]);
|
|
4665
5067
|
useEffect(() => {
|
|
4666
5068
|
if (!bar || type === "pane" || activeKey === undefined) {
|
|
4667
5069
|
return;
|
|
4668
5070
|
}
|
|
4669
5071
|
const index = tabs.findIndex((tab) => tab.key === activeKey);
|
|
4670
|
-
setTimeout(() => {
|
|
5072
|
+
const timer = window.setTimeout(() => {
|
|
4671
5073
|
const nav = navRefs.current[index];
|
|
4672
5074
|
if (!nav)
|
|
4673
5075
|
return;
|
|
@@ -4686,19 +5088,15 @@ const Tabs = ((props) => {
|
|
|
4686
5088
|
transform: `translate(${offsetLeft}px, ${offsetTop}px)`,
|
|
4687
5089
|
});
|
|
4688
5090
|
}, 16);
|
|
5091
|
+
return () => {
|
|
5092
|
+
window.clearTimeout(timer);
|
|
5093
|
+
};
|
|
4689
5094
|
}, [activeKey, bar, size, tabs, type, vertical]);
|
|
4690
5095
|
useEffect(() => {
|
|
4691
5096
|
if (active === undefined || activeKey === active)
|
|
4692
5097
|
return;
|
|
4693
5098
|
open(active);
|
|
4694
5099
|
}, [active]);
|
|
4695
|
-
useEffect(() => {
|
|
4696
|
-
if (hideMore || !unobserve)
|
|
4697
|
-
return;
|
|
4698
|
-
return () => {
|
|
4699
|
-
navRefs.current?.map(unobserve);
|
|
4700
|
-
};
|
|
4701
|
-
}, [tabs.length, hideMore, unobserve]);
|
|
4702
5100
|
useEffect(() => {
|
|
4703
5101
|
if (!navsRef.current || vertical)
|
|
4704
5102
|
return;
|
|
@@ -4719,30 +5117,35 @@ const Tabs = ((props) => {
|
|
|
4719
5117
|
return;
|
|
4720
5118
|
navsRef.current.removeEventListener("wheel", handleMouseWheel);
|
|
4721
5119
|
};
|
|
4722
|
-
}, [
|
|
5120
|
+
}, [vertical]);
|
|
4723
5121
|
useImperativeHandle(ref, () => ({
|
|
4724
5122
|
open,
|
|
4725
5123
|
close,
|
|
4726
5124
|
add,
|
|
4727
5125
|
navs: navsRef,
|
|
4728
5126
|
}));
|
|
5127
|
+
const moreTabs = !hideMore && overflow
|
|
5128
|
+
? tabs.filter((tab) => tab.intersecting === false)
|
|
5129
|
+
: [];
|
|
4729
5130
|
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", {
|
|
4730
5131
|
"i-tab-navs-vertical": vertical,
|
|
4731
|
-
}), children: [prepend, jsxs("div", { ref: navsRef, className: classNames("i-tab-navs", `justify-${navsJustify}`), children: [tabs.map((tab, i) => {
|
|
5132
|
+
}), 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) => {
|
|
4732
5133
|
const { title, key = `${i}`, closable } = tab;
|
|
5134
|
+
const isActive = activeKey === key;
|
|
4733
5135
|
return (jsxs("a", { ref: (ref) => (navRefs.current[i] = ref), className: classNames("i-tab-nav", {
|
|
4734
|
-
"i-tab-active":
|
|
4735
|
-
}), onClick: () => open(key), children: [title, closable && (jsx(Helpericon, { as:
|
|
5136
|
+
"i-tab-active": isActive,
|
|
5137
|
+
}), 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) => {
|
|
5138
|
+
e.preventDefault();
|
|
4736
5139
|
e.stopPropagation();
|
|
4737
5140
|
close(key);
|
|
4738
|
-
} }))] }, key));
|
|
4739
|
-
}), 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:
|
|
5141
|
+
}, onKeyDown: (e) => handleKeyAction(e, () => close(key)) }))] }, key));
|
|
5142
|
+
}), 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) => {
|
|
4740
5143
|
const { key = `${i}`, title } = tab;
|
|
4741
5144
|
const isActive = activeKey === key;
|
|
4742
5145
|
return (jsx("a", { className: classNames("i-tab-nav", {
|
|
4743
5146
|
"i-tab-active": isActive,
|
|
4744
|
-
}), onClick: () =>
|
|
4745
|
-
}) }), children: renderMore(moreTabs) })), append] }), jsx("div", { className:
|
|
5147
|
+
}), role: "button", tabIndex: 0, onClick: () => handleMoreTabClick(key), onKeyDown: (e) => handleKeyAction(e, () => handleMoreTabClick(key)), children: title }, key));
|
|
5148
|
+
}) }), children: renderMore(moreTabs) })), append] }), jsx("div", { className: "i-tab-contents", children: tabs.map((tab, i) => {
|
|
4746
5149
|
const key = tab.key ?? `${i}`;
|
|
4747
5150
|
const content = contentsRef.current.get(key);
|
|
4748
5151
|
const isActive = activeKey === key;
|
|
@@ -4750,7 +5153,7 @@ const Tabs = ((props) => {
|
|
|
4750
5153
|
(key !== undefined && cachedTabs.includes(key));
|
|
4751
5154
|
return (show && (jsx("div", { className: classNames("i-tab-content", {
|
|
4752
5155
|
"i-tab-active": isActive,
|
|
4753
|
-
}), children: content }, key)));
|
|
5156
|
+
}), role: "tabpanel", "aria-hidden": !isActive, children: content }, key)));
|
|
4754
5157
|
}) })] }));
|
|
4755
5158
|
});
|
|
4756
5159
|
Tabs.Item = Item;
|
|
@@ -4976,7 +5379,7 @@ const FileListItem = (props) => {
|
|
|
4976
5379
|
let node = jsx(Fragment, {});
|
|
4977
5380
|
switch (type) {
|
|
4978
5381
|
case TFileType.IMAGE:
|
|
4979
|
-
node = (jsx(
|
|
5382
|
+
node = (jsx(MemoImage, { lazyload: true, src: url || src, fit: 'cover', onMouseDown: (e) => e.preventDefault() }));
|
|
4980
5383
|
break;
|
|
4981
5384
|
case TFileType.VIDEO:
|
|
4982
5385
|
node = jsx("video", { src: url || src, preload: 'none' });
|
|
@@ -5241,4 +5644,4 @@ const useTheme = (props) => {
|
|
|
5241
5644
|
};
|
|
5242
5645
|
};
|
|
5243
5646
|
|
|
5244
|
-
export { Affix, Badge, Button, Card, Checkbox, Collapse, ColorPicker, Datagrid, Datepicker as DatePicker, Description, Drawer, Dropdown, Editor, Flex, Form, Icon, Image, Input, List$1 as List, Loading, message as Message, Modal, Pagination, Popconfirm, Popup, Progress, Radio, Resizable, River, Select, Step, Swiper, Tabs, Tag, Text, TimePicker, Tree, Upload, Video, usePreview, useTheme };
|
|
5647
|
+
export { Affix, Badge, Button, Card, Checkbox, Collapse, ColorPicker, Datagrid, Datepicker as DatePicker, Description, Drawer, Dropdown, Editor, Flex, Form, Icon, MemoImage as Image, Input, List$1 as List, Loading, message as Message, Modal, Pagination, Popconfirm, Popup, Progress, Radio, Resizable, River, Select, Step, Swiper, Tabs, Tag, Text, TimePicker, Tree, Upload, Video, usePreview, useTheme };
|