@ioca/react 1.5.3 → 1.5.5
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 +31 -40
- 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 +171 -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 +32 -37
- 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 +160 -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 +608 -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
8
|
import xss 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,464 @@ 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
|
};
|
|
1852
|
+
const escapeHtmlAttr$1 = (value) => value
|
|
1853
|
+
.replaceAll("&", "&")
|
|
1854
|
+
.replaceAll('"', """)
|
|
1855
|
+
.replaceAll("<", "<")
|
|
1856
|
+
.replaceAll(">", ">");
|
|
1832
1857
|
const xssOptions = {
|
|
1833
|
-
onIgnoreTagAttr
|
|
1834
|
-
if (["
|
|
1835
|
-
return name + '="' +
|
|
1858
|
+
onIgnoreTagAttr(tag, name, value) {
|
|
1859
|
+
if (["class", "contenteditable"].includes(name)) {
|
|
1860
|
+
return name + '="' + escapeHtmlAttr$1(value) + '"';
|
|
1861
|
+
}
|
|
1862
|
+
if (["data-", "style"].includes(name.substring(0, 5))) {
|
|
1863
|
+
return name + '="' + escapeHtmlAttr$1(value) + '"';
|
|
1836
1864
|
}
|
|
1837
1865
|
},
|
|
1838
1866
|
};
|
|
1867
|
+
const handleMouseDown = (e) => {
|
|
1868
|
+
e.preventDefault();
|
|
1869
|
+
};
|
|
1839
1870
|
const fnMap = {
|
|
1840
1871
|
bold: {
|
|
1841
1872
|
icon: jsx(FormatBoldRound, {}),
|
|
1842
1873
|
onClick: () => exec("bold"),
|
|
1843
|
-
tip: "粗体",
|
|
1844
1874
|
},
|
|
1845
1875
|
italic: {
|
|
1846
1876
|
icon: jsx(FormatItalicRound, {}),
|
|
1847
1877
|
onClick: () => exec("italic"),
|
|
1848
|
-
tip: "斜体",
|
|
1849
1878
|
},
|
|
1850
1879
|
underline: {
|
|
1851
1880
|
icon: jsx(FormatUnderlinedRound, {}),
|
|
1852
1881
|
onClick: () => exec("underline"),
|
|
1853
|
-
tip: "下划线",
|
|
1854
1882
|
},
|
|
1855
1883
|
strike: {
|
|
1856
1884
|
icon: jsx(StrikethroughSRound, {}),
|
|
1857
1885
|
onClick: () => exec("strikeThrough"),
|
|
1858
|
-
tip: "删除线",
|
|
1859
1886
|
},
|
|
1860
1887
|
redo: {
|
|
1861
1888
|
icon: jsx(RedoRound, {}),
|
|
1862
1889
|
onClick: () => exec("redo"),
|
|
1863
|
-
tip: "重做",
|
|
1864
1890
|
},
|
|
1865
1891
|
undo: {
|
|
1866
1892
|
icon: jsx(UndoRound, {}),
|
|
1867
1893
|
onClick: () => exec("undo"),
|
|
1868
|
-
tip: "撤销",
|
|
1869
1894
|
},
|
|
1870
|
-
// color: {
|
|
1871
|
-
// icon: <FormatColorTextRound />,
|
|
1872
|
-
// onClick: () => exec("foreColor", false, ""),
|
|
1873
|
-
// },
|
|
1874
|
-
// backColor: {
|
|
1875
|
-
// icon: <FormatColorFillRound />,
|
|
1876
|
-
// onClick: () => exec("backColor", false, ""),
|
|
1877
|
-
// },
|
|
1878
1895
|
clear: {
|
|
1879
1896
|
icon: jsx(ClearAllRound, {}),
|
|
1880
1897
|
onClick: () => exec("removeFormat"),
|
|
1881
|
-
tip: "清除格式",
|
|
1882
1898
|
},
|
|
1883
1899
|
};
|
|
1884
|
-
const
|
|
1885
|
-
|
|
1886
|
-
|
|
1900
|
+
const defaultKeys = [
|
|
1901
|
+
"undo",
|
|
1902
|
+
"redo",
|
|
1903
|
+
"bold",
|
|
1904
|
+
"italic",
|
|
1905
|
+
"underline",
|
|
1906
|
+
"strike",
|
|
1907
|
+
"clear",
|
|
1908
|
+
];
|
|
1909
|
+
const typedFnMap = fnMap;
|
|
1910
|
+
function getControls(options) {
|
|
1911
|
+
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
|
+
});
|
|
1916
|
+
const extControls = (addtionControls ?? []).map((item, index) => (jsx(Button, { ...controlBtnProps, onMouseDown: handleMouseDown, onClick: (e) => item.onClick?.(getSelection(), e), children: item.icon }, `addtion-${index}`)));
|
|
1917
|
+
return [...controls, ...extControls];
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
const MEMTION_TAG_CLASS_NAME = "i-memtion-tag";
|
|
1921
|
+
const escapeHtmlAttr = (value) => value
|
|
1922
|
+
.replaceAll("&", "&")
|
|
1923
|
+
.replaceAll('"', """)
|
|
1924
|
+
.replaceAll("<", "<")
|
|
1925
|
+
.replaceAll(">", ">");
|
|
1926
|
+
const getInsertNode = (memtion, option) => memtion?.insert?.(option) ?? option.value;
|
|
1927
|
+
const getInsertText = (memtion, option) => {
|
|
1928
|
+
const nextNode = getInsertNode(memtion, option);
|
|
1929
|
+
if (typeof nextNode === "string" || typeof nextNode === "number") {
|
|
1930
|
+
return `${String(nextNode)} `;
|
|
1931
|
+
}
|
|
1932
|
+
return `${String(option.value)} `;
|
|
1887
1933
|
};
|
|
1888
|
-
|
|
1889
|
-
const
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1934
|
+
const getInsertHtml = (memtion, option, sanitizeValue) => {
|
|
1935
|
+
const nextNode = getInsertNode(memtion, option);
|
|
1936
|
+
if (nextNode === null || nextNode === undefined || nextNode === false) {
|
|
1937
|
+
return "";
|
|
1938
|
+
}
|
|
1939
|
+
const content = sanitizeValue(renderToStaticMarkup(jsx(Fragment, { children: nextNode })));
|
|
1940
|
+
return `<span class="${MEMTION_TAG_CLASS_NAME}" contenteditable="false" data-memtion-value="${escapeHtmlAttr(String(option.value))}">${content}</span>`;
|
|
1941
|
+
};
|
|
1942
|
+
const getSelectionRect = (range) => {
|
|
1943
|
+
if (!range)
|
|
1944
|
+
return null;
|
|
1945
|
+
const rect = range.getBoundingClientRect();
|
|
1946
|
+
if (rect.width || rect.height) {
|
|
1947
|
+
return rect;
|
|
1948
|
+
}
|
|
1949
|
+
return range.getClientRects()[0] ?? rect;
|
|
1950
|
+
};
|
|
1951
|
+
const insertMemtionOption = ({ editor, range, mode, memtion, option, sanitizeValue, }) => {
|
|
1952
|
+
if (!editor || !range)
|
|
1953
|
+
return null;
|
|
1954
|
+
const browserSelection = window.getSelection();
|
|
1955
|
+
if (!browserSelection)
|
|
1956
|
+
return null;
|
|
1957
|
+
const nextRange = range.cloneRange();
|
|
1958
|
+
browserSelection.removeAllRanges();
|
|
1959
|
+
browserSelection.addRange(nextRange);
|
|
1960
|
+
editor.focus();
|
|
1961
|
+
nextRange.deleteContents();
|
|
1962
|
+
if (mode === "plaintext") {
|
|
1963
|
+
const text = document.createTextNode(getInsertText(memtion, option));
|
|
1964
|
+
nextRange.insertNode(text);
|
|
1965
|
+
nextRange.setStartAfter(text);
|
|
1966
|
+
}
|
|
1967
|
+
else {
|
|
1968
|
+
const html = getInsertHtml(memtion, option, sanitizeValue);
|
|
1969
|
+
const fragment = nextRange.createContextualFragment(html);
|
|
1970
|
+
const lastNode = fragment.lastChild;
|
|
1971
|
+
const spacing = document.createTextNode(" ");
|
|
1972
|
+
nextRange.insertNode(fragment);
|
|
1973
|
+
if (lastNode) {
|
|
1974
|
+
nextRange.setStartAfter(lastNode);
|
|
1975
|
+
nextRange.collapse(true);
|
|
1976
|
+
}
|
|
1977
|
+
nextRange.insertNode(spacing);
|
|
1978
|
+
nextRange.setStartAfter(spacing);
|
|
1979
|
+
}
|
|
1980
|
+
nextRange.collapse(true);
|
|
1981
|
+
browserSelection.removeAllRanges();
|
|
1982
|
+
browserSelection.addRange(nextRange);
|
|
1983
|
+
return nextRange;
|
|
1984
|
+
};
|
|
1985
|
+
const getMemtionText = (triggerRange, selectionRange) => {
|
|
1986
|
+
if (!triggerRange || !selectionRange)
|
|
1987
|
+
return "";
|
|
1988
|
+
const range = triggerRange.cloneRange();
|
|
1989
|
+
range.setEnd(selectionRange.endContainer, selectionRange.endOffset);
|
|
1990
|
+
return range.toString();
|
|
1991
|
+
};
|
|
1992
|
+
const getMemtionReplaceRange = (triggerRange, selectionRange) => {
|
|
1993
|
+
if (!triggerRange || !selectionRange)
|
|
1994
|
+
return null;
|
|
1995
|
+
const range = triggerRange.cloneRange();
|
|
1996
|
+
range.setEnd(selectionRange.endContainer, selectionRange.endOffset);
|
|
1997
|
+
return range;
|
|
1998
|
+
};
|
|
1999
|
+
const filterMemtionOptions = (options, keyword) => {
|
|
2000
|
+
const normalizedKeyword = keyword.trim().toLowerCase();
|
|
2001
|
+
if (!normalizedKeyword) {
|
|
2002
|
+
return options;
|
|
2003
|
+
}
|
|
2004
|
+
return options.filter((option) => String(option.value).toLowerCase().includes(normalizedKeyword));
|
|
2005
|
+
};
|
|
2006
|
+
const isMemtionTag = (node) => node instanceof HTMLElement &&
|
|
2007
|
+
node.classList.contains(MEMTION_TAG_CLASS_NAME);
|
|
2008
|
+
const getAdjacentMemtionTag = (range, direction) => {
|
|
2009
|
+
const { startContainer, startOffset } = range;
|
|
2010
|
+
if (startContainer.nodeType === Node.TEXT_NODE) {
|
|
2011
|
+
const textNode = startContainer;
|
|
2012
|
+
if (direction === "backward" && startOffset === 0) {
|
|
2013
|
+
return isMemtionTag(textNode.previousSibling)
|
|
2014
|
+
? textNode.previousSibling
|
|
2015
|
+
: null;
|
|
2016
|
+
}
|
|
2017
|
+
if (direction === "forward" && startOffset === textNode.data.length) {
|
|
2018
|
+
return isMemtionTag(textNode.nextSibling)
|
|
2019
|
+
? textNode.nextSibling
|
|
2020
|
+
: null;
|
|
2021
|
+
}
|
|
2022
|
+
return null;
|
|
2023
|
+
}
|
|
2024
|
+
const element = startContainer;
|
|
2025
|
+
const targetIndex = direction === "backward" ? startOffset - 1 : startOffset;
|
|
2026
|
+
const targetNode = element.childNodes.item(targetIndex);
|
|
2027
|
+
return isMemtionTag(targetNode) ? targetNode : null;
|
|
2028
|
+
};
|
|
2029
|
+
const removeAdjacentMemtionTag = (editor, key) => {
|
|
2030
|
+
if (!editor)
|
|
2031
|
+
return false;
|
|
2032
|
+
const selection = window.getSelection();
|
|
2033
|
+
if (!selection?.rangeCount || !selection.isCollapsed)
|
|
2034
|
+
return false;
|
|
2035
|
+
const activeRange = selection.getRangeAt(0);
|
|
2036
|
+
const tag = getAdjacentMemtionTag(activeRange, key === "Backspace" ? "backward" : "forward");
|
|
2037
|
+
if (!tag || !tag.parentNode)
|
|
2038
|
+
return false;
|
|
2039
|
+
const parent = tag.parentNode;
|
|
2040
|
+
const nextSibling = tag.nextSibling;
|
|
2041
|
+
const index = Array.prototype.indexOf.call(parent.childNodes, tag);
|
|
2042
|
+
if (nextSibling?.nodeType === Node.TEXT_NODE) {
|
|
2043
|
+
const textNode = nextSibling;
|
|
2044
|
+
if (textNode.data.startsWith(" ")) {
|
|
2045
|
+
textNode.deleteData(0, 1);
|
|
2046
|
+
if (!textNode.data.length) {
|
|
2047
|
+
nextSibling.remove();
|
|
1896
2048
|
}
|
|
1897
|
-
return (jsxs(Button, { ...controlBtnProps, onClick: onClick, children: [jsx(Icon, { icon: icon }), tip && jsx("span", { className: 'i-editor-control-tip', children: tip })] }, k));
|
|
1898
2049
|
}
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
2050
|
+
}
|
|
2051
|
+
tag.remove();
|
|
2052
|
+
const range = document.createRange();
|
|
2053
|
+
range.setStart(parent, Math.min(index, parent.childNodes.length));
|
|
2054
|
+
range.collapse(true);
|
|
2055
|
+
selection.removeAllRanges();
|
|
2056
|
+
selection.addRange(range);
|
|
2057
|
+
editor.focus();
|
|
2058
|
+
return true;
|
|
2059
|
+
};
|
|
2060
|
+
const Memtion = (props) => {
|
|
2061
|
+
const { visible, rect, options, activeIndex, onActiveChange, onSelect } = props;
|
|
2062
|
+
if (!visible || !rect || !options?.length) {
|
|
2063
|
+
return null;
|
|
2064
|
+
}
|
|
2065
|
+
return (jsx(List$1, { className: "i-editor-memtion", type: "option", style: {
|
|
2066
|
+
position: "fixed",
|
|
2067
|
+
top: rect.bottom,
|
|
2068
|
+
left: rect.left,
|
|
2069
|
+
}, 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}`))) }));
|
|
2070
|
+
};
|
|
2071
|
+
var Memtion$1 = memo(Memtion);
|
|
1902
2072
|
|
|
2073
|
+
const controlBtnProps = {
|
|
2074
|
+
square: true,
|
|
2075
|
+
flat: true,
|
|
2076
|
+
size: "small",
|
|
2077
|
+
};
|
|
1903
2078
|
const Editor = (props) => {
|
|
1904
|
-
const { ref, width, height = "10em", placeholder, autosize, border = true,
|
|
2079
|
+
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
2080
|
const editorRef = useRef(null);
|
|
1906
|
-
const
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
2081
|
+
const selectionRef = useRef(null);
|
|
2082
|
+
const memtionTriggerRangeRef = useRef(null);
|
|
2083
|
+
const pendingMemtionRef = useRef(false);
|
|
2084
|
+
const [memtionVisible, setMemtionVisible] = useState(false);
|
|
2085
|
+
const [memtionRect, setMemtionRect] = useState(null);
|
|
2086
|
+
const [memtionKeyword, setMemtionKeyword] = useState("");
|
|
2087
|
+
const [memtionActiveIndex, setMemtionActiveIndex] = useState(0);
|
|
2088
|
+
const memtionOptions = useMemo(() => filterMemtionOptions(memtion?.options ?? [], memtionKeyword), [memtion?.options, memtionKeyword]);
|
|
2089
|
+
const sanitizeValue = (nextValue) => {
|
|
2090
|
+
if (mode === "plaintext") {
|
|
2091
|
+
return nextValue === "\n" ? "" : nextValue;
|
|
2092
|
+
}
|
|
2093
|
+
const safeHtml = xss(nextValue, xssOptions);
|
|
2094
|
+
return safeHtml === "<br>" ? "" : safeHtml;
|
|
2095
|
+
};
|
|
2096
|
+
const rememberSelection = () => {
|
|
2097
|
+
if (!editorRef.current)
|
|
2098
|
+
return;
|
|
2099
|
+
const selection = window.getSelection();
|
|
2100
|
+
if (!selection?.rangeCount)
|
|
2101
|
+
return;
|
|
2102
|
+
const range = selection.getRangeAt(0);
|
|
2103
|
+
const container = range.commonAncestorContainer;
|
|
2104
|
+
const parent = container.nodeType === Node.ELEMENT_NODE
|
|
2105
|
+
? container
|
|
2106
|
+
: container.parentElement;
|
|
2107
|
+
if (!parent || !editorRef.current.contains(parent))
|
|
2108
|
+
return;
|
|
2109
|
+
selectionRef.current = range.cloneRange();
|
|
2110
|
+
};
|
|
2111
|
+
const setEditorValue = (nextValue) => {
|
|
2112
|
+
if (!editorRef.current)
|
|
2113
|
+
return;
|
|
2114
|
+
const safeValue = sanitizeValue(nextValue);
|
|
2115
|
+
if (mode === "plaintext") {
|
|
2116
|
+
editorRef.current.textContent = safeValue;
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
2119
|
+
editorRef.current.innerHTML = safeValue;
|
|
1910
2120
|
};
|
|
1911
|
-
const
|
|
2121
|
+
const getEditorValue = (sanitize = false) => {
|
|
2122
|
+
if (!editorRef.current)
|
|
2123
|
+
return "";
|
|
2124
|
+
const nextValue = mode === "plaintext"
|
|
2125
|
+
? (editorRef.current.textContent ?? "")
|
|
2126
|
+
: editorRef.current.innerHTML;
|
|
2127
|
+
return sanitize ? sanitizeValue(nextValue) : nextValue;
|
|
2128
|
+
};
|
|
2129
|
+
const hideMemtion = () => {
|
|
2130
|
+
pendingMemtionRef.current = false;
|
|
2131
|
+
memtionTriggerRangeRef.current = null;
|
|
2132
|
+
setMemtionVisible(false);
|
|
2133
|
+
setMemtionRect(null);
|
|
2134
|
+
setMemtionKeyword("");
|
|
2135
|
+
setMemtionActiveIndex(0);
|
|
2136
|
+
};
|
|
2137
|
+
const insertMemtion = (option) => {
|
|
2138
|
+
const replaceRange = getMemtionReplaceRange(memtionTriggerRangeRef.current, selectionRef.current);
|
|
2139
|
+
const range = insertMemtionOption({
|
|
2140
|
+
editor: editorRef.current,
|
|
2141
|
+
range: replaceRange,
|
|
2142
|
+
mode,
|
|
2143
|
+
memtion,
|
|
2144
|
+
option,
|
|
2145
|
+
sanitizeValue,
|
|
2146
|
+
});
|
|
2147
|
+
if (!range || !editorRef.current)
|
|
2148
|
+
return;
|
|
2149
|
+
selectionRef.current = range.cloneRange();
|
|
2150
|
+
hideMemtion();
|
|
2151
|
+
editorRef.current.dispatchEvent(new Event("input", { bubbles: true }));
|
|
2152
|
+
};
|
|
2153
|
+
const handlePaste = (e) => {
|
|
1912
2154
|
onPaste?.(e);
|
|
1913
|
-
if (
|
|
2155
|
+
if (e.defaultPrevented)
|
|
1914
2156
|
return;
|
|
1915
2157
|
e.preventDefault();
|
|
2158
|
+
if (mode === "plaintext") {
|
|
2159
|
+
const text = e.clipboardData.getData("text/plain");
|
|
2160
|
+
exec("insertText", false, text);
|
|
2161
|
+
return;
|
|
2162
|
+
}
|
|
2163
|
+
const html = e.clipboardData.getData("text/html");
|
|
2164
|
+
if (html) {
|
|
2165
|
+
exec("insertHTML", false, sanitizeValue(html));
|
|
2166
|
+
return;
|
|
2167
|
+
}
|
|
1916
2168
|
const text = e.clipboardData.getData("text/plain");
|
|
1917
2169
|
exec("insertText", false, text);
|
|
1918
2170
|
};
|
|
1919
2171
|
const handleKeyDown = (e) => {
|
|
1920
2172
|
onKeyDown?.(e);
|
|
2173
|
+
if (mode === "rich" &&
|
|
2174
|
+
(e.key === "Backspace" || e.key === "Delete") &&
|
|
2175
|
+
removeAdjacentMemtionTag(editorRef.current, e.key)) {
|
|
2176
|
+
e.preventDefault();
|
|
2177
|
+
rememberSelection();
|
|
2178
|
+
editorRef.current?.dispatchEvent(new Event("input", { bubbles: true }));
|
|
2179
|
+
return;
|
|
2180
|
+
}
|
|
2181
|
+
const memtionKey = memtion?.key ?? "@";
|
|
2182
|
+
if (memtionVisible && e.key === " ") {
|
|
2183
|
+
hideMemtion();
|
|
2184
|
+
}
|
|
2185
|
+
if (memtionVisible && memtionOptions.length) {
|
|
2186
|
+
switch (e.key) {
|
|
2187
|
+
case "ArrowDown":
|
|
2188
|
+
e.preventDefault();
|
|
2189
|
+
setMemtionActiveIndex((index) => index + 1 >= memtionOptions.length ? 0 : index + 1);
|
|
2190
|
+
return;
|
|
2191
|
+
case "ArrowUp":
|
|
2192
|
+
e.preventDefault();
|
|
2193
|
+
setMemtionActiveIndex((index) => index - 1 < 0 ? memtionOptions.length - 1 : index - 1);
|
|
2194
|
+
return;
|
|
2195
|
+
case "Enter":
|
|
2196
|
+
e.preventDefault();
|
|
2197
|
+
insertMemtion(memtionOptions[memtionActiveIndex]);
|
|
2198
|
+
return;
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
if (memtion && e.key === memtionKey) {
|
|
2202
|
+
rememberSelection();
|
|
2203
|
+
memtionTriggerRangeRef.current =
|
|
2204
|
+
selectionRef.current?.cloneRange() ?? null;
|
|
2205
|
+
pendingMemtionRef.current = true;
|
|
2206
|
+
}
|
|
1921
2207
|
switch (e.key) {
|
|
1922
2208
|
case "Tab":
|
|
1923
2209
|
e.preventDefault();
|
|
1924
|
-
exec("insertHTML", false, "	");
|
|
2210
|
+
exec(mode === "plaintext" ? "insertText" : "insertHTML", false, mode === "plaintext" ? "\t" : "	");
|
|
1925
2211
|
break;
|
|
1926
2212
|
case "Enter":
|
|
2213
|
+
if (!onEnter)
|
|
2214
|
+
break;
|
|
1927
2215
|
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`;
|
|
2216
|
+
onEnter(e);
|
|
1938
2217
|
break;
|
|
1939
2218
|
}
|
|
1940
2219
|
};
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
}
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
2220
|
+
useEffect(() => {
|
|
2221
|
+
if (!editorRef.current)
|
|
2222
|
+
return;
|
|
2223
|
+
const nextValue = sanitizeValue(value);
|
|
2224
|
+
if (getEditorValue(true) === nextValue)
|
|
2225
|
+
return;
|
|
2226
|
+
setEditorValue(nextValue);
|
|
2227
|
+
if (autosize) {
|
|
2228
|
+
editorRef.current.style.height = `${editorRef.current.scrollHeight}px`;
|
|
2229
|
+
}
|
|
2230
|
+
}, [autosize, mode, value]);
|
|
2231
|
+
useEffect(() => {
|
|
2232
|
+
if (!memtionOptions.length) {
|
|
2233
|
+
setMemtionActiveIndex(0);
|
|
2234
|
+
return;
|
|
2235
|
+
}
|
|
2236
|
+
setMemtionActiveIndex((index) => index >= memtionOptions.length ? 0 : index);
|
|
2237
|
+
}, [memtionOptions]);
|
|
1956
2238
|
const handleInput = (e) => {
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
2239
|
+
const rawValue = getEditorValue();
|
|
2240
|
+
let nextValue = sanitizeValue(rawValue);
|
|
2241
|
+
if (!nextValue && rawValue && editorRef.current) {
|
|
2242
|
+
nextValue = "";
|
|
2243
|
+
setEditorValue(nextValue);
|
|
2244
|
+
}
|
|
2245
|
+
rememberSelection();
|
|
2246
|
+
if (memtion && (pendingMemtionRef.current || memtionVisible)) {
|
|
2247
|
+
const memtionKey = memtion?.key ?? "@";
|
|
2248
|
+
const memtionText = getMemtionText(memtionTriggerRangeRef.current, selectionRef.current);
|
|
2249
|
+
if (!memtionText.startsWith(memtionKey) || /\s/.test(memtionText)) {
|
|
2250
|
+
hideMemtion();
|
|
2251
|
+
}
|
|
2252
|
+
else {
|
|
2253
|
+
const keyword = memtionText.slice(memtionKey.length);
|
|
2254
|
+
pendingMemtionRef.current = false;
|
|
2255
|
+
setMemtionRect(getSelectionRect(selectionRef.current));
|
|
2256
|
+
setMemtionKeyword(keyword);
|
|
2257
|
+
setMemtionActiveIndex(0);
|
|
2258
|
+
setMemtionVisible(true);
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
if (autosize && editorRef.current) {
|
|
2262
|
+
editorRef.current.style.height = `${editorRef.current.scrollHeight}px`;
|
|
1961
2263
|
}
|
|
1962
|
-
|
|
2264
|
+
onChange?.(nextValue, e);
|
|
2265
|
+
};
|
|
2266
|
+
const handleFocus = (e) => {
|
|
2267
|
+
rememberSelection();
|
|
2268
|
+
onFocus?.(e);
|
|
2269
|
+
};
|
|
2270
|
+
const handleBlur = (e) => {
|
|
2271
|
+
hideMemtion();
|
|
2272
|
+
onBlur?.(e);
|
|
1963
2273
|
};
|
|
2274
|
+
const handleMouseUp = (e) => {
|
|
2275
|
+
rememberSelection();
|
|
2276
|
+
onMouseUp?.(e);
|
|
2277
|
+
};
|
|
2278
|
+
const handleKeyUp = (e) => {
|
|
2279
|
+
rememberSelection();
|
|
2280
|
+
onKeyUp?.(e);
|
|
2281
|
+
};
|
|
2282
|
+
const handleRef = (node) => {
|
|
2283
|
+
editorRef.current = node;
|
|
2284
|
+
if (typeof ref === "function") {
|
|
2285
|
+
ref(node);
|
|
2286
|
+
return;
|
|
2287
|
+
}
|
|
2288
|
+
if (ref) {
|
|
2289
|
+
ref.current = node;
|
|
2290
|
+
}
|
|
2291
|
+
};
|
|
2292
|
+
const getSelection = useCallback(() => selectionRef.current?.cloneRange() ?? null, []);
|
|
2293
|
+
const controls = useMemo(() => getControls({
|
|
2294
|
+
controlBtnProps,
|
|
2295
|
+
addtionControls,
|
|
2296
|
+
getSelection,
|
|
2297
|
+
}), [addtionControls, getSelection]);
|
|
1964
2298
|
return (jsxs("div", { className: classNames("i-editor", className, {
|
|
1965
2299
|
"i-editor-borderless": !border,
|
|
1966
2300
|
}), style: {
|
|
1967
2301
|
...style,
|
|
1968
2302
|
[autosize ? "minHeight" : "height"]: height,
|
|
1969
2303
|
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 })] }));
|
|
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 })] }));
|
|
1973
2305
|
};
|
|
1974
2306
|
|
|
1975
2307
|
const Flex = (props) => {
|
|
@@ -2405,7 +2737,7 @@ const HookModal = (props) => {
|
|
|
2405
2737
|
},
|
|
2406
2738
|
close: () => {
|
|
2407
2739
|
state.visible = false;
|
|
2408
|
-
if (mergedProps.closable)
|
|
2740
|
+
if (mergedProps.closable ?? true)
|
|
2409
2741
|
return;
|
|
2410
2742
|
Promise.resolve().then(() => {
|
|
2411
2743
|
state.visible = true;
|
|
@@ -2952,27 +3284,34 @@ function List(props) {
|
|
|
2952
3284
|
if (!files.length)
|
|
2953
3285
|
return "";
|
|
2954
3286
|
return (jsx(Flex, { className: 'i-image-list', gap: gap, columns: columns, wrap: wrap, direction: direction, children: files.map((img, i) => {
|
|
2955
|
-
return (jsx(
|
|
3287
|
+
return (jsx(MemoImage, { src: img.src, usePreview: false, onClick: (e) => handleClick(e, i), ...restProps }, i));
|
|
2956
3288
|
}) }));
|
|
2957
3289
|
}
|
|
2958
3290
|
|
|
3291
|
+
const STATUS_LOADING = "loading";
|
|
3292
|
+
const STATUS_ERROR = "error";
|
|
2959
3293
|
const Image = (props) => {
|
|
2960
3294
|
const { src, thumb, round, size, height, width, ratio, initSize, lazyload, fallback, fit, style, className, cover, coverClass, usePreview: previewable, onLoad, onError, onClick, ...restProps } = props;
|
|
2961
3295
|
const state = useReactive({
|
|
2962
|
-
status:
|
|
3296
|
+
status: STATUS_LOADING,
|
|
2963
3297
|
});
|
|
2964
3298
|
const ref = useRef(null);
|
|
2965
3299
|
const { observe, unobserve } = useIntersectionObserver();
|
|
2966
3300
|
const preview = usePreview();
|
|
2967
|
-
const
|
|
3301
|
+
const setStatus = useCallback((status) => {
|
|
3302
|
+
if (state.status === status)
|
|
3303
|
+
return;
|
|
3304
|
+
state.status = status;
|
|
3305
|
+
}, [state]);
|
|
3306
|
+
const handleError = useCallback((err) => {
|
|
2968
3307
|
onError?.(err);
|
|
2969
|
-
|
|
2970
|
-
};
|
|
2971
|
-
const handleLoad = (e) => {
|
|
3308
|
+
setStatus(STATUS_ERROR);
|
|
3309
|
+
}, [onError, setStatus]);
|
|
3310
|
+
const handleLoad = useCallback((e) => {
|
|
2972
3311
|
onLoad?.(e);
|
|
2973
|
-
|
|
2974
|
-
};
|
|
2975
|
-
const handleClick = (e) => {
|
|
3312
|
+
setStatus(undefined);
|
|
3313
|
+
}, [onLoad, setStatus]);
|
|
3314
|
+
const handleClick = useCallback((e) => {
|
|
2976
3315
|
onClick?.(e);
|
|
2977
3316
|
if (!previewable || !src)
|
|
2978
3317
|
return;
|
|
@@ -2986,44 +3325,53 @@ const Image = (props) => {
|
|
|
2986
3325
|
},
|
|
2987
3326
|
],
|
|
2988
3327
|
});
|
|
2989
|
-
};
|
|
3328
|
+
}, [onClick, preview, previewable, src]);
|
|
2990
3329
|
useEffect(() => {
|
|
2991
3330
|
if (!src || typeof window === "undefined")
|
|
2992
3331
|
return;
|
|
2993
3332
|
const img = ref.current;
|
|
3333
|
+
if (!img)
|
|
3334
|
+
return;
|
|
2994
3335
|
const hasSrcAttr = img?.getAttribute("src");
|
|
2995
3336
|
const canSyncStatus = Boolean(img && (!lazyload || hasSrcAttr));
|
|
2996
|
-
if (canSyncStatus && img
|
|
2997
|
-
|
|
3337
|
+
if (canSyncStatus && img.complete) {
|
|
3338
|
+
setStatus(img.naturalWidth > 0 ? undefined : STATUS_ERROR);
|
|
2998
3339
|
}
|
|
2999
|
-
if (!
|
|
3000
|
-
|
|
3340
|
+
if (!img.complete && observe && lazyload) {
|
|
3341
|
+
setStatus(STATUS_LOADING);
|
|
3001
3342
|
}
|
|
3002
|
-
if (!lazyload || !
|
|
3343
|
+
if (!lazyload || !observe)
|
|
3003
3344
|
return;
|
|
3004
|
-
observe(
|
|
3345
|
+
observe(img, (tar, visible) => {
|
|
3005
3346
|
if (!visible)
|
|
3006
3347
|
return;
|
|
3007
3348
|
tar.setAttribute("src", tar.dataset.src || "");
|
|
3008
3349
|
unobserve(tar);
|
|
3009
3350
|
});
|
|
3010
3351
|
return () => {
|
|
3011
|
-
|
|
3352
|
+
unobserve(img);
|
|
3012
3353
|
};
|
|
3013
|
-
}, [src]);
|
|
3014
|
-
|
|
3015
|
-
const iSize =
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3354
|
+
}, [lazyload, observe, setStatus, src, unobserve]);
|
|
3355
|
+
const imageStatus = state.status;
|
|
3356
|
+
const iSize = imageStatus === STATUS_LOADING ? initSize : undefined;
|
|
3357
|
+
const wrapperStyle = useMemo(() => ({
|
|
3358
|
+
width: width ?? size ?? iSize,
|
|
3359
|
+
height: height ?? size ?? iSize,
|
|
3360
|
+
aspectRatio: ratio,
|
|
3361
|
+
...style,
|
|
3362
|
+
}), [height, iSize, ratio, size, style, width]);
|
|
3363
|
+
const wrapperClassName = useMemo(() => classNames("i-image", className, {
|
|
3364
|
+
rounded: round,
|
|
3365
|
+
[`i-image-${imageStatus}`]: imageStatus,
|
|
3366
|
+
}), [className, imageStatus, round]);
|
|
3367
|
+
const imageStyle = useMemo(() => ({ objectFit: fit }), [fit]);
|
|
3368
|
+
const imageSrcProps = lazyload
|
|
3369
|
+
? { "data-src": thumb ?? src }
|
|
3370
|
+
: { src: thumb ?? src };
|
|
3371
|
+
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
3372
|
};
|
|
3026
|
-
|
|
3373
|
+
const MemoImage = memo(Image);
|
|
3374
|
+
MemoImage.List = List;
|
|
3027
3375
|
|
|
3028
3376
|
function InputContainer(props) {
|
|
3029
3377
|
const { as: As = "label", label, className, labelInline, style, children, status, tip, required, } = props;
|
|
@@ -3199,16 +3547,19 @@ const Range = (props) => {
|
|
|
3199
3547
|
};
|
|
3200
3548
|
|
|
3201
3549
|
const Textarea = (props) => {
|
|
3202
|
-
const { ref, label, name, value = "", labelInline, className, status = "normal", message, tip, autoSize, border, width, style, onChange, onEnter, ...restProps } = props;
|
|
3550
|
+
const { ref, label, name, value = "", labelInline, className, status = "normal", message, tip, autoSize, border, width, style, resize, onChange, onEnter, ...restProps } = props;
|
|
3203
3551
|
const [textareaValue, setTextareaValue] = useState(value);
|
|
3204
3552
|
const refTextarea = useRef(null);
|
|
3553
|
+
const syncTextareaHeight = () => {
|
|
3554
|
+
const ta = refTextarea.current;
|
|
3555
|
+
if (!autoSize || !ta)
|
|
3556
|
+
return;
|
|
3557
|
+
ta.style.height = "auto";
|
|
3558
|
+
ta.style.height = `${ta.scrollHeight}px`;
|
|
3559
|
+
};
|
|
3205
3560
|
const handleChange = (e) => {
|
|
3206
3561
|
const v = e.target.value;
|
|
3207
3562
|
setTextareaValue(v);
|
|
3208
|
-
const ta = refTextarea.current;
|
|
3209
|
-
if (autoSize && ta) {
|
|
3210
|
-
ta.style.height = `${ta.scrollHeight}px`;
|
|
3211
|
-
}
|
|
3212
3563
|
onChange?.(v, e);
|
|
3213
3564
|
};
|
|
3214
3565
|
const handleKeydown = (e) => {
|
|
@@ -3220,6 +3571,9 @@ const Textarea = (props) => {
|
|
|
3220
3571
|
useEffect(() => {
|
|
3221
3572
|
setTextareaValue(value);
|
|
3222
3573
|
}, [value]);
|
|
3574
|
+
useEffect(() => {
|
|
3575
|
+
syncTextareaHeight();
|
|
3576
|
+
}, [autoSize, textareaValue]);
|
|
3223
3577
|
useImperativeHandle(ref, () => {
|
|
3224
3578
|
return {
|
|
3225
3579
|
input: refTextarea.current,
|
|
@@ -3230,6 +3584,7 @@ const Textarea = (props) => {
|
|
|
3230
3584
|
name,
|
|
3231
3585
|
value: textareaValue,
|
|
3232
3586
|
className: "i-input i-textarea",
|
|
3587
|
+
style: resize === false ? { resize: "none" } : undefined,
|
|
3233
3588
|
onChange: handleChange,
|
|
3234
3589
|
onKeyDown: handleKeydown,
|
|
3235
3590
|
...restProps,
|
|
@@ -3687,7 +4042,7 @@ function Footer(props) {
|
|
|
3687
4042
|
setInputValue(value);
|
|
3688
4043
|
setColorType(type);
|
|
3689
4044
|
}, [value, type]);
|
|
3690
|
-
return (jsxs("div", { className:
|
|
4045
|
+
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
4046
|
}
|
|
3692
4047
|
|
|
3693
4048
|
const Handle = (props) => {
|
|
@@ -4029,10 +4384,10 @@ const defaultOk = {
|
|
|
4029
4384
|
};
|
|
4030
4385
|
const defaultCancel = {
|
|
4031
4386
|
children: "取消",
|
|
4032
|
-
|
|
4387
|
+
flat: true,
|
|
4033
4388
|
};
|
|
4034
4389
|
const Popconfirm = (props) => {
|
|
4035
|
-
const { trigger = "click", visible, icon = jsx(Icon, { icon: jsx(InfoOutlined, {}), className:
|
|
4390
|
+
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
4391
|
const state = useReactive({
|
|
4037
4392
|
loading: false,
|
|
4038
4393
|
visible,
|
|
@@ -4063,7 +4418,7 @@ const Popconfirm = (props) => {
|
|
|
4063
4418
|
await onClose?.();
|
|
4064
4419
|
state.visible = false;
|
|
4065
4420
|
};
|
|
4066
|
-
const popconfirmContent = (jsxs("div", { className:
|
|
4421
|
+
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
4422
|
return (jsx(Popup, { content: popconfirmContent, ...restProps, trigger: trigger, visible: state.visible, align: align, offset: offset, position: position, onVisibleChange: handleVisibleChange, children: children }));
|
|
4068
4423
|
};
|
|
4069
4424
|
|
|
@@ -4549,7 +4904,7 @@ const Item = (props) => {
|
|
|
4549
4904
|
};
|
|
4550
4905
|
|
|
4551
4906
|
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:
|
|
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;
|
|
4553
4908
|
const navRefs = useRef([]);
|
|
4554
4909
|
const barRef = useRef(null);
|
|
4555
4910
|
const navsRef = useRef(null);
|
|
@@ -4559,10 +4914,15 @@ const Tabs = ((props) => {
|
|
|
4559
4914
|
const [barStyle, setBarStyle] = useState({});
|
|
4560
4915
|
const [cachedTabs, setCachedTabs] = useState([]);
|
|
4561
4916
|
const [overflow, setOverflow] = useState(false);
|
|
4562
|
-
const [moreTabs, setMoreTabs] = useState([]);
|
|
4563
4917
|
const [tabs, setTabs] = useState([]);
|
|
4564
4918
|
const { observe, unobserve } = useIntersectionObserver();
|
|
4565
4919
|
const size = useSize(navsRef);
|
|
4920
|
+
const tabsRef = useRef(tabs);
|
|
4921
|
+
tabsRef.current = tabs;
|
|
4922
|
+
const activeKeyRef = useRef(activeKey);
|
|
4923
|
+
activeKeyRef.current = activeKey;
|
|
4924
|
+
const prevActiveKeyRef = useRef(prevActiveKey);
|
|
4925
|
+
prevActiveKeyRef.current = prevActiveKey;
|
|
4566
4926
|
useEffect(() => {
|
|
4567
4927
|
contentsRef.current.clear();
|
|
4568
4928
|
if (!items) {
|
|
@@ -4599,10 +4959,11 @@ const Tabs = ((props) => {
|
|
|
4599
4959
|
}));
|
|
4600
4960
|
}, [children, items]);
|
|
4601
4961
|
const add = (tab) => {
|
|
4602
|
-
const
|
|
4603
|
-
const
|
|
4962
|
+
const currentTabs = tabsRef.current;
|
|
4963
|
+
const tkey = String(tab.key ?? currentTabs.length);
|
|
4964
|
+
const i = currentTabs.findIndex((t) => t.key === tkey);
|
|
4604
4965
|
if (i > -1) {
|
|
4605
|
-
open(
|
|
4966
|
+
open(currentTabs[i].key ?? `${i}`);
|
|
4606
4967
|
return;
|
|
4607
4968
|
}
|
|
4608
4969
|
contentsRef.current.set(tkey, tab.content);
|
|
@@ -4611,20 +4972,34 @@ const Tabs = ((props) => {
|
|
|
4611
4972
|
open(tkey);
|
|
4612
4973
|
};
|
|
4613
4974
|
const close = (key) => {
|
|
4614
|
-
const
|
|
4975
|
+
const currentTabs = tabsRef.current;
|
|
4976
|
+
const i = currentTabs.findIndex((t) => t.key === key);
|
|
4615
4977
|
if (i < 0)
|
|
4616
4978
|
return;
|
|
4617
4979
|
contentsRef.current.delete(key);
|
|
4618
|
-
const nextTabs = [...
|
|
4980
|
+
const nextTabs = [...currentTabs];
|
|
4619
4981
|
nextTabs.splice(i, 1);
|
|
4620
4982
|
setTabs(nextTabs);
|
|
4621
|
-
if (
|
|
4983
|
+
if (activeKeyRef.current !== key)
|
|
4622
4984
|
return;
|
|
4623
4985
|
const next = nextTabs[i] || nextTabs[i - 1];
|
|
4624
|
-
|
|
4986
|
+
const prev = prevActiveKeyRef.current;
|
|
4987
|
+
const nextKey = prev && nextTabs.some((t) => t.key === prev) ? prev : next?.key;
|
|
4988
|
+
open(nextKey ?? "");
|
|
4625
4989
|
};
|
|
4626
4990
|
const open = (key) => {
|
|
4627
|
-
|
|
4991
|
+
const nextKey = key || undefined;
|
|
4992
|
+
if (nextKey === undefined) {
|
|
4993
|
+
onTabChange?.(undefined, activeKey);
|
|
4994
|
+
setPrevActiveKey(activeKey);
|
|
4995
|
+
setActiveKey(undefined);
|
|
4996
|
+
setBarStyle({
|
|
4997
|
+
height: 0,
|
|
4998
|
+
width: 0,
|
|
4999
|
+
});
|
|
5000
|
+
return;
|
|
5001
|
+
}
|
|
5002
|
+
if (nextKey === activeKey) {
|
|
4628
5003
|
if (!toggable)
|
|
4629
5004
|
return;
|
|
4630
5005
|
onTabChange?.(undefined, key);
|
|
@@ -4636,38 +5011,75 @@ const Tabs = ((props) => {
|
|
|
4636
5011
|
return;
|
|
4637
5012
|
}
|
|
4638
5013
|
setPrevActiveKey(activeKey);
|
|
4639
|
-
onTabChange?.(
|
|
4640
|
-
setActiveKey(
|
|
5014
|
+
onTabChange?.(nextKey, activeKey);
|
|
5015
|
+
setActiveKey(nextKey);
|
|
5016
|
+
};
|
|
5017
|
+
const handleKeyAction = (e, action) => {
|
|
5018
|
+
if (!["Enter", " "].includes(e.key))
|
|
5019
|
+
return;
|
|
5020
|
+
e.preventDefault();
|
|
5021
|
+
action();
|
|
5022
|
+
};
|
|
5023
|
+
const scrollToTab = (key) => {
|
|
5024
|
+
const index = tabsRef.current.findIndex((tab) => tab.key === key);
|
|
5025
|
+
const nav = navRefs.current[index];
|
|
5026
|
+
nav?.scrollIntoView({
|
|
5027
|
+
behavior: "smooth",
|
|
5028
|
+
block: "nearest",
|
|
5029
|
+
inline: "nearest",
|
|
5030
|
+
});
|
|
5031
|
+
};
|
|
5032
|
+
const handleMoreTabClick = (key) => {
|
|
5033
|
+
open(key);
|
|
5034
|
+
scrollToTab(key);
|
|
4641
5035
|
};
|
|
4642
5036
|
useEffect(() => {
|
|
4643
|
-
if (!size || hideMore || !observe)
|
|
5037
|
+
if (!size || hideMore || !observe || !unobserve)
|
|
4644
5038
|
return;
|
|
4645
5039
|
const { scrollHeight, scrollWidth } = navsRef.current;
|
|
4646
5040
|
const { width, height } = size;
|
|
4647
5041
|
const nextOverflow = scrollHeight > height || scrollWidth > width;
|
|
4648
|
-
setOverflow(nextOverflow);
|
|
4649
|
-
if (!nextOverflow)
|
|
5042
|
+
setOverflow((v) => (v === nextOverflow ? v : nextOverflow));
|
|
5043
|
+
if (!nextOverflow) {
|
|
5044
|
+
setTabs((ts) => {
|
|
5045
|
+
let changed = false;
|
|
5046
|
+
const next = ts.map((t) => {
|
|
5047
|
+
if (t.intersecting === undefined ||
|
|
5048
|
+
t.intersecting === true) {
|
|
5049
|
+
return t;
|
|
5050
|
+
}
|
|
5051
|
+
changed = true;
|
|
5052
|
+
return { ...t, intersecting: true };
|
|
5053
|
+
});
|
|
5054
|
+
return changed ? next : ts;
|
|
5055
|
+
});
|
|
4650
5056
|
return;
|
|
5057
|
+
}
|
|
5058
|
+
const observed = [];
|
|
4651
5059
|
navRefs.current.map((nav, i) => {
|
|
4652
5060
|
if (!nav)
|
|
4653
5061
|
return;
|
|
4654
|
-
|
|
5062
|
+
observed.push(nav);
|
|
5063
|
+
observe(nav, (_tar, visible) => {
|
|
4655
5064
|
setTabs((ts) => {
|
|
4656
5065
|
if (!ts[i])
|
|
4657
5066
|
return ts;
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
return
|
|
5067
|
+
if (ts[i]?.intersecting === visible)
|
|
5068
|
+
return ts;
|
|
5069
|
+
return ts.map((t, idx) => idx === i ? { ...t, intersecting: visible } : t);
|
|
4661
5070
|
});
|
|
4662
5071
|
});
|
|
4663
5072
|
});
|
|
4664
|
-
|
|
5073
|
+
return () => {
|
|
5074
|
+
observed.map((el) => unobserve(el));
|
|
5075
|
+
};
|
|
5076
|
+
}, [size, hideMore, tabs.length, observe, unobserve]);
|
|
4665
5077
|
useEffect(() => {
|
|
4666
5078
|
if (!bar || type === "pane" || activeKey === undefined) {
|
|
4667
5079
|
return;
|
|
4668
5080
|
}
|
|
4669
5081
|
const index = tabs.findIndex((tab) => tab.key === activeKey);
|
|
4670
|
-
setTimeout(() => {
|
|
5082
|
+
const timer = window.setTimeout(() => {
|
|
4671
5083
|
const nav = navRefs.current[index];
|
|
4672
5084
|
if (!nav)
|
|
4673
5085
|
return;
|
|
@@ -4686,19 +5098,15 @@ const Tabs = ((props) => {
|
|
|
4686
5098
|
transform: `translate(${offsetLeft}px, ${offsetTop}px)`,
|
|
4687
5099
|
});
|
|
4688
5100
|
}, 16);
|
|
5101
|
+
return () => {
|
|
5102
|
+
window.clearTimeout(timer);
|
|
5103
|
+
};
|
|
4689
5104
|
}, [activeKey, bar, size, tabs, type, vertical]);
|
|
4690
5105
|
useEffect(() => {
|
|
4691
5106
|
if (active === undefined || activeKey === active)
|
|
4692
5107
|
return;
|
|
4693
5108
|
open(active);
|
|
4694
5109
|
}, [active]);
|
|
4695
|
-
useEffect(() => {
|
|
4696
|
-
if (hideMore || !unobserve)
|
|
4697
|
-
return;
|
|
4698
|
-
return () => {
|
|
4699
|
-
navRefs.current?.map(unobserve);
|
|
4700
|
-
};
|
|
4701
|
-
}, [tabs.length, hideMore, unobserve]);
|
|
4702
5110
|
useEffect(() => {
|
|
4703
5111
|
if (!navsRef.current || vertical)
|
|
4704
5112
|
return;
|
|
@@ -4719,30 +5127,35 @@ const Tabs = ((props) => {
|
|
|
4719
5127
|
return;
|
|
4720
5128
|
navsRef.current.removeEventListener("wheel", handleMouseWheel);
|
|
4721
5129
|
};
|
|
4722
|
-
}, [
|
|
5130
|
+
}, [vertical]);
|
|
4723
5131
|
useImperativeHandle(ref, () => ({
|
|
4724
5132
|
open,
|
|
4725
5133
|
close,
|
|
4726
5134
|
add,
|
|
4727
5135
|
navs: navsRef,
|
|
4728
5136
|
}));
|
|
5137
|
+
const moreTabs = !hideMore && overflow
|
|
5138
|
+
? tabs.filter((tab) => tab.intersecting === false)
|
|
5139
|
+
: [];
|
|
4729
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", {
|
|
4730
5141
|
"i-tab-navs-vertical": vertical,
|
|
4731
|
-
}), children: [prepend, jsxs("div", { ref: navsRef, className: classNames("i-tab-navs", `justify-${navsJustify}`), children: [tabs.map((tab, i) => {
|
|
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) => {
|
|
4732
5143
|
const { title, key = `${i}`, closable } = tab;
|
|
5144
|
+
const isActive = activeKey === key;
|
|
4733
5145
|
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:
|
|
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();
|
|
4736
5149
|
e.stopPropagation();
|
|
4737
5150
|
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:
|
|
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) => {
|
|
4740
5153
|
const { key = `${i}`, title } = tab;
|
|
4741
5154
|
const isActive = activeKey === key;
|
|
4742
5155
|
return (jsx("a", { className: classNames("i-tab-nav", {
|
|
4743
5156
|
"i-tab-active": isActive,
|
|
4744
|
-
}), onClick: () =>
|
|
4745
|
-
}) }), children: renderMore(moreTabs) })), append] }), jsx("div", { className:
|
|
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) => {
|
|
4746
5159
|
const key = tab.key ?? `${i}`;
|
|
4747
5160
|
const content = contentsRef.current.get(key);
|
|
4748
5161
|
const isActive = activeKey === key;
|
|
@@ -4750,7 +5163,7 @@ const Tabs = ((props) => {
|
|
|
4750
5163
|
(key !== undefined && cachedTabs.includes(key));
|
|
4751
5164
|
return (show && (jsx("div", { className: classNames("i-tab-content", {
|
|
4752
5165
|
"i-tab-active": isActive,
|
|
4753
|
-
}), children: content }, key)));
|
|
5166
|
+
}), role: "tabpanel", "aria-hidden": !isActive, children: content }, key)));
|
|
4754
5167
|
}) })] }));
|
|
4755
5168
|
});
|
|
4756
5169
|
Tabs.Item = Item;
|
|
@@ -4976,7 +5389,7 @@ const FileListItem = (props) => {
|
|
|
4976
5389
|
let node = jsx(Fragment, {});
|
|
4977
5390
|
switch (type) {
|
|
4978
5391
|
case TFileType.IMAGE:
|
|
4979
|
-
node = (jsx(
|
|
5392
|
+
node = (jsx(MemoImage, { lazyload: true, src: url || src, fit: 'cover', onMouseDown: (e) => e.preventDefault() }));
|
|
4980
5393
|
break;
|
|
4981
5394
|
case TFileType.VIDEO:
|
|
4982
5395
|
node = jsx("video", { src: url || src, preload: 'none' });
|
|
@@ -5241,4 +5654,4 @@ const useTheme = (props) => {
|
|
|
5241
5654
|
};
|
|
5242
5655
|
};
|
|
5243
5656
|
|
|
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 };
|
|
5657
|
+
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 };
|