@antscorp/antsomi-ui 1.3.5-beta.831 → 1.3.5-beta.832

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.
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  /* eslint-disable no-param-reassign */
3
3
  // Libraries
4
- import { memo, useCallback, useEffect, useMemo } from 'react';
4
+ import { memo, useCallback, useEffect, useMemo, useState } from 'react';
5
5
  import { useImmer } from 'use-immer';
6
6
  import _ from 'lodash';
7
7
  // Components & Styled
@@ -18,6 +18,7 @@ const { Text } = Typography;
18
18
  const emojiListParsed = JSON.parse(JSON.stringify(ICON_EMOJI_COMMON));
19
19
  const CommonCollection = ({ onEmojiClick }) => {
20
20
  // States
21
+ const [visibleCount, setVisibleCount] = useState(40);
21
22
  const [state, setState] = useImmer({
22
23
  collectionActive: SMILEYS_BODY,
23
24
  txtSearch: '',
@@ -47,10 +48,22 @@ const CommonCollection = ({ onEmojiClick }) => {
47
48
  });
48
49
  }
49
50
  }, [setState]);
51
+ const onScroll = useCallback((e, collectionLength) => {
52
+ const { scrollTop, scrollHeight, clientHeight } = e.target;
53
+ if (scrollHeight - scrollTop <= clientHeight * 1.5) {
54
+ setVisibleCount(prev => {
55
+ const newCount = prev + 30;
56
+ if (newCount >= collectionLength)
57
+ return collectionLength;
58
+ return newCount;
59
+ });
60
+ }
61
+ }, []);
50
62
  const handleChangeSelect = useCallback((newCollection) => {
51
63
  setState(draft => {
52
64
  draft.collectionActive = newCollection;
53
65
  });
66
+ setVisibleCount(prev => prev + 10);
54
67
  }, [setState]);
55
68
  useEffect(() => () => {
56
69
  setState(() => ({
@@ -72,13 +85,14 @@ const CommonCollection = ({ onEmojiClick }) => {
72
85
  (_.isArray(emojiCollection) && emojiCollection.length === 0)) {
73
86
  return (_jsx(EmptyEmoji, { image: _jsx(Button, { type: "text", shape: "round", icon: _jsx(EmojiSmileIcon, { color: globalToken?.bw5, style: { width: 30, height: 30 } }), style: { width: 60, height: 60, background: globalToken?.bw2 } }), imageStyle: { height: 60, marginBottom: 15 }, description: _jsx("span", { style: { color: globalToken?.bw8 }, children: "No emoji matches your keyword" }) }));
74
87
  }
75
- const content = emojiCollection.map((item) => {
88
+ const list = emojiCollection.slice(0, Math.min(visibleCount, emojiCollection.length));
89
+ const content = list.map((item) => {
76
90
  const { emoji, slug = '', unicode_version: unicodeVersion = '' } = item;
77
91
  const key = `${slug}-${unicodeVersion}`;
78
92
  return (_jsx(Emoji, { type: "text", onClick: () => onEmojiClick(emoji), children: _jsx("span", { style: { fontSize: '30px' }, children: emoji }) }, key));
79
93
  });
80
- return (_jsx(Scrollbars, { autoHeight: true, autoHeightMax: 150, children: _jsx(Flex, { wrap: "wrap", children: content }) }));
81
- }, [emojiCollection, onEmojiClick]);
94
+ return (_jsx(Scrollbars, { autoHeight: true, autoHeightMax: 150, onScroll: e => onScroll(e, emojiCollection.length), children: _jsx(Flex, { wrap: "wrap", children: content }) }));
95
+ }, [emojiCollection, visibleCount, onScroll, onEmojiClick]);
82
96
  return (_jsxs(WrapperCollection, { children: [_jsxs(Flex, { gap: 20, justify: "space-between", align: "flex-end", children: [_jsx(Input, { placeholder: "Search...", suffix: _jsx(SearchIcon, { color: globalToken?.bw8 }), styles: { affixWrapper: { minWidth: 280 } }, onChange: handleChangeInput }), _jsx(Select, { placeholder: "Select collection", value: state.collectionActive, style: { width: 200 }, options: collectionOptions, optionRender: renderOption, onChange: handleChangeSelect })] }), _jsx(EmojiList, { children: renderCommonCollection() })] }));
83
97
  };
84
98
  CommonCollection.defaultProps = {
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  /* eslint-disable no-param-reassign */
3
3
  // Libraries
4
- import { memo, useCallback, useEffect, useMemo } from 'react';
4
+ import { memo, useCallback, useEffect, useMemo, useState } from 'react';
5
5
  import _ from 'lodash';
6
6
  import { useImmer } from 'use-immer';
7
7
  // Components & Styled
@@ -14,6 +14,7 @@ import { LINE_MESSAGE_EMOJIS, TAB_LINE_MESSAGE_EMOJIS } from './constants';
14
14
  import { globalToken } from '@antscorp/antsomi-ui/es/constants';
15
15
  const LineCollection = ({ onEmojiClick }) => {
16
16
  // States
17
+ const [visibleCount, setVisibleCount] = useState(40);
17
18
  const [state, setState] = useImmer({
18
19
  collectionActive: TAB_LINE_MESSAGE_EMOJIS[0]?.key || '',
19
20
  });
@@ -23,7 +24,19 @@ const LineCollection = ({ onEmojiClick }) => {
23
24
  setState(draft => {
24
25
  draft.collectionActive = newCollection;
25
26
  });
27
+ setVisibleCount(prev => prev + 10);
26
28
  }, [setState]);
29
+ const onScroll = useCallback((e, collectionLength) => {
30
+ const { scrollTop, scrollHeight, clientHeight } = e.target;
31
+ if (scrollHeight - scrollTop <= clientHeight * 1.5) {
32
+ setVisibleCount(prev => {
33
+ const newCount = prev + 30;
34
+ if (newCount >= collectionLength)
35
+ return collectionLength;
36
+ return newCount;
37
+ });
38
+ }
39
+ }, []);
27
40
  useEffect(() => () => {
28
41
  setState(draft => {
29
42
  draft.collectionActive = TAB_LINE_MESSAGE_EMOJIS[0]?.key || '';
@@ -40,13 +53,14 @@ const LineCollection = ({ onEmojiClick }) => {
40
53
  if (_.isArray(emojiList) && !emojiList.length) {
41
54
  return (_jsx(EmptyEmoji, { image: _jsx(Button, { type: "text", shape: "round", icon: _jsx(EmojiSmileIcon, { color: globalToken?.bw5, style: { width: 30, height: 30 } }), style: { width: 60, height: 60, background: globalToken?.bw2 } }), imageStyle: { height: 60, marginBottom: 15 }, description: _jsx("span", { style: { color: globalToken?.bw8 }, children: "No emoji matches your keyword" }) }));
42
55
  }
43
- const content = emojiList.map((emojiId) => {
56
+ const list = emojiList.slice(0, Math.min(visibleCount, emojiList.length));
57
+ const content = list.map((emojiId) => {
44
58
  const src = getLinkLineURLImage(state.collectionActive, emojiId);
45
59
  const code = `$((${PREFIX_PATTERN_LINE_MESSAGE}:${state.collectionActive}:${emojiId}))`;
46
60
  return (_jsx(Emoji, { type: "text", icon: _jsx(EmojiImage, { src: src, alt: emojiId }), onClick: () => onEmojiClick(code) }, emojiId));
47
61
  });
48
- return (_jsx(Scrollbars, { autoHeight: true, autoHeightMax: 150, children: _jsx(Flex, { wrap: "wrap", children: content }) }));
49
- }, [emojiList, state.collectionActive, onEmojiClick]);
62
+ return (_jsx(Scrollbars, { autoHeight: true, autoHeightMax: 150, onScroll: e => onScroll(e, emojiList.length), children: _jsx(Flex, { wrap: "wrap", children: content }) }));
63
+ }, [emojiList, visibleCount, state.collectionActive, onEmojiClick, onScroll]);
50
64
  return (_jsx(WrapperCollection, { children: _jsxs(Flex, { vertical: true, children: [renderLineCategory(), _jsx(EmojiList, { children: renderLineEmoji() })] }) }));
51
65
  };
52
66
  LineCollection.defaultProps = {
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  /* eslint-disable no-param-reassign */
3
3
  // Libraries
4
- import { memo, useCallback, useEffect, useMemo } from 'react';
4
+ import { memo, useCallback, useEffect, useMemo, useState } from 'react';
5
5
  import { useImmer } from 'use-immer';
6
6
  import _ from 'lodash';
7
7
  // Hooks
@@ -16,6 +16,7 @@ import { globalToken } from '@antscorp/antsomi-ui/es/constants';
16
16
  const listEmoji = Object.values(iconsViber);
17
17
  const ViberCollection = ({ onEmojiClick }) => {
18
18
  // States
19
+ const [visibleCount, setVisibleCount] = useState(40);
19
20
  const [state, setState] = useImmer({
20
21
  txtSearch: '',
21
22
  });
@@ -36,6 +37,17 @@ const ViberCollection = ({ onEmojiClick }) => {
36
37
  });
37
38
  }
38
39
  }, [setState]);
40
+ const onScroll = useCallback((e, collectionLength) => {
41
+ const { scrollTop, scrollHeight, clientHeight } = e.target;
42
+ if (scrollHeight - scrollTop <= clientHeight * 1.5) {
43
+ setVisibleCount(prev => {
44
+ const newCount = prev + 30;
45
+ if (newCount >= collectionLength)
46
+ return collectionLength;
47
+ return newCount;
48
+ });
49
+ }
50
+ }, []);
39
51
  useEffect(() => () => {
40
52
  setState(draft => {
41
53
  draft.txtSearch = '';
@@ -45,14 +57,15 @@ const ViberCollection = ({ onEmojiClick }) => {
45
57
  if (_.isArray(listEmojiMemoized) && !listEmojiMemoized.length) {
46
58
  return (_jsx(EmptyEmoji, { image: _jsx(Button, { type: "text", shape: "round", icon: _jsx(EmojiSmileIcon, { color: globalToken?.bw5, style: { width: 30, height: 30 } }), style: { width: 60, height: 60, background: globalToken?.bw2 } }), imageStyle: { height: 60, marginBottom: 15 }, description: _jsx("span", { style: { color: globalToken?.bw8 }, children: "No emoji matches your keyword" }) }));
47
59
  }
48
- const content = listEmojiMemoized.map(emojiFileName => {
60
+ const list = listEmojiMemoized.slice(0, Math.min(visibleCount, listEmojiMemoized.length));
61
+ const content = list.map(emojiFileName => {
49
62
  const src = getImageSourceViberEmoji(emojiFileName);
50
63
  const [emoji] = emojiFileName.split('.');
51
64
  const code = `(${emoji})`;
52
65
  return (_jsx(Emoji, { type: "text", icon: _jsx(EmojiImage, { src: src, alt: emojiFileName }), onClick: () => onEmojiClick(code) }, emojiFileName));
53
66
  });
54
- return (_jsx(Scrollbars, { autoHeight: true, autoHeightMax: 150, children: _jsx(Flex, { wrap: "wrap", children: content }) }));
55
- }, [listEmojiMemoized, onEmojiClick]);
67
+ return (_jsx(Scrollbars, { autoHeight: true, autoHeightMax: 150, onScroll: e => onScroll(e, listEmojiMemoized.length), children: _jsx(Flex, { wrap: "wrap", children: content }) }));
68
+ }, [listEmojiMemoized, visibleCount, onEmojiClick, onScroll]);
56
69
  return (_jsx(WrapperCollection, { children: _jsxs(Flex, { vertical: true, children: [_jsx(Input, { placeholder: "Search...", suffix: _jsx(SearchIcon, { color: globalToken?.bw8 }), onChange: handleChangeInput }), _jsx(EmojiList, { children: renderViberCollection() })] }) }));
57
70
  };
58
71
  ViberCollection.defaultProps = {
@@ -66,7 +66,7 @@ const EmojiPopover = ({ disabled, collections, isForceHide, children, onEmojiCli
66
66
  children: renderCollection(collection.key),
67
67
  }));
68
68
  }, [collections, renderCollection]);
69
- return (_jsx(EmojiPopoverStyled, { content: _jsx(EmojiTabs, { activeKey: state.collectionActive, onTabClick: handleClickCollection, items: emojiCollections }), placement: "topLeft", trigger: "click", style: { padding: 15 }, arrow: false, rootClassName: "antsomi-emoji-popover", open: state.isOpen, onOpenChange: handleOpenChange, children: children || (_jsx(Button, { type: "link", icon: _jsx(EmojiSmileIcon, {}), disabled: disabled, onClick: () => handleOpenChange(true) })) }));
69
+ return (_jsx(EmojiPopoverStyled, { content: _jsx(EmojiTabs, { activeKey: state.collectionActive, onTabClick: handleClickCollection, items: emojiCollections }), placement: "topLeft", trigger: "click", fresh: false, style: { padding: 15 }, arrow: false, rootClassName: "antsomi-emoji-popover", open: state.isOpen, onOpenChange: handleOpenChange, children: children || (_jsx(Button, { type: "link", icon: _jsx(EmojiSmileIcon, {}), disabled: disabled, onClick: () => handleOpenChange(true) })) }));
70
70
  };
71
71
  EmojiPopover.defaultProps = {
72
72
  disabled: false,
@@ -17,7 +17,7 @@ import '@yaireo/tagify/dist/tagify.css';
17
17
  // Styled
18
18
  import { TagTextArea, TagifyWrapper, WrapperPlaceHolder } from './styled';
19
19
  // Utils
20
- import { parseTagStringToTagify, convertInputStringToOriginal, emojiManufacturer, getEmojiTag, isPersonalizeTagType, generateTagContent, unescapeString, hasLineBreak, selectTextRange, isTagClickable, findURLInTextNodes, } from './utils';
20
+ import { parseTagStringToTagify, convertInputStringToOriginal, emojiManufacturer, getEmojiTag, isPersonalizeTagType, generateTagContent, unescapeString, hasLineBreak, selectRange, isTagClickable, findURLInTextNodes, } from './utils';
21
21
  import { acceptablePatternChecking, detectURLRegex, getCachedRegex, getPersonalizeTagInfo, patternHandlers, } from './patternHandlers';
22
22
  // Constants
23
23
  import { DETECT_LINK, EMOJI, INVALID_TAG, PERSONALIZE_PTN, PROMOTION_CODE, READONLY_TAG, SHORT_LINK, SHORT_LINK_PTN, defaultCssVariables, tagifyDefaultProps, } from './constants';
@@ -120,22 +120,34 @@ const TagifyInput = forwardRef((props, ref) => {
120
120
  // If the last range is lost, need to select the last text node of the input
121
121
  const { input: inputElement } = tagifyRef.current.DOM;
122
122
  if (inputElement) {
123
- // Get all child nodes that are of type TEXT_NODE
124
- const textNodes = Array.from(inputElement.childNodes).filter((node) => node.nodeType === Node.TEXT_NODE);
125
- // In case have text nodes
126
- // -> need to place the caret at the end of the last text
127
- if (textNodes.length) {
128
- const lastTextNode = textNodes[textNodes.length - 1];
129
- if (lastTextNode?.textContent) {
130
- const textNodeLength = lastTextNode.textContent.length;
123
+ // Get all child nodes that are of type TEXT_NODE or ELEMENT_NODE with nodeName TAG
124
+ const nodeList = Array.from(inputElement.childNodes).filter((node) => {
125
+ if (node.nodeType === Node.ELEMENT_NODE) {
126
+ return node.nodeName === 'TAG';
127
+ }
128
+ return node.nodeType === Node.TEXT_NODE;
129
+ });
130
+ if (nodeList.length && selection) {
131
+ const lastNode = nodeList[nodeList.length - 1];
132
+ // In case have the last text nodes
133
+ // -> need to place the caret at the end of the last text
134
+ if (lastNode.nodeType === Node.TEXT_NODE && lastNode?.textContent) {
135
+ const textNodeLength = lastNode.textContent.length;
131
136
  // Place the caret at the end of the last text
132
- selectTextRange(lastTextNode, textNodeLength, textNodeLength);
137
+ selectRange(lastNode, textNodeLength, textNodeLength);
138
+ onSyncSelectionStateTagify();
139
+ }
140
+ else if (lastNode.nodeType === Node.ELEMENT_NODE && lastNode?.nodeName === 'TAG') {
141
+ // In case no text nodes but have an element node
142
+ const lastNodeExceptBrLength = inputElement.childNodes.length - 1; // Exclude the last <br>
143
+ // Place the caret at the end of the last element node
144
+ selectRange(inputElement, lastNodeExceptBrLength, lastNodeExceptBrLength);
133
145
  onSyncSelectionStateTagify();
134
146
  }
135
147
  }
136
148
  else if (DOM?.scope?.classList?.contains(empty)) {
137
149
  // In case no text nodes and the input is empty
138
- selectTextRange(inputElement, 0, 0);
150
+ selectRange(inputElement, 0, 0);
139
151
  onSyncSelectionStateTagify();
140
152
  }
141
153
  }
@@ -82,31 +82,34 @@ export declare const isValidTagType: (str: string) => boolean;
82
82
  */
83
83
  export declare const hasLineBreak: (str: string) => boolean;
84
84
  /**
85
- * Selects a range of text within a given text node.
85
+ * Selects a range of text within a given DOM node.
86
86
  *
87
- * @param {Node} textNode - The text node containing the text to be selected.
88
- * @param {number} start - The starting index of the selection within the text node.
89
- * @param {number} end - The ending index of the selection within the text node.
90
- *
91
- * @throws {Error} If there's an issue creating the range or applying the selection.
87
+ * @param {Node} node - The DOM node containing the text to be selected.
88
+ * @param {number} start - The starting index position of the selection.
89
+ * @param {number} end - The ending index position of the selection.
92
90
  *
93
91
  * @example
94
92
  * const paragraph = document.querySelector('p');
95
- * const textNode = paragraph.firstChild;
96
- * selectTextRange(textNode, 5, 10);
93
+ * if (paragraph.firstChild) {
94
+ * selectRange(paragraph.firstChild, 5, 10);
95
+ * }
97
96
  *
98
97
  * @description
99
- * This function creates a new range from the given start and end positions within
100
- * the provided text node. It then applies this range to the current window selection,
101
- * effectively selecting the specified text. If any errors occur during this process,
102
- * they are caught and logged to the console along with the input parameters.
103
- *
104
- * Note: This function will only visually select the text. It does not copy the text
105
- * or perform any actions on it. The selection may not be visible if the containing
106
- * element is not focusable or if the browser prevents programmatic selection for
107
- * security reasons.
98
+ * Creates a new DOM Range from the given start and end positions within the provided node
99
+ * and applies it to the current window selection. This visually selects the specified
100
+ * portion of text in the browser.
101
+ *
102
+ * Important considerations:
103
+ * - The node should be a text node or a node that can contain text content
104
+ * - The start and end indices must be valid positions within the node's text content
105
+ * - The selection may not be visible if the containing element is not focusable
106
+ * - Some browsers may prevent programmatic selection for security reasons
107
+ * - Any errors during selection are caught and logged to the console
108
+ *
109
+ * Note that this function only creates a visual selection - it does not copy the text
110
+ * or modify it in any way.
108
111
  */
109
- export declare function selectTextRange(textNode: Node, start: number, end: number): void;
112
+ export declare function selectRange(node: Node, start: number, end: number): void;
110
113
  /**
111
114
  * Determines whether a tag is clickable based on its properties and the Tagify instance settings.
112
115
  *
@@ -458,35 +458,38 @@ export const isValidTagType = (str) => Object.values(TAG_TYPE).includes(str);
458
458
  */
459
459
  export const hasLineBreak = (str) => str.includes('\n');
460
460
  /**
461
- * Selects a range of text within a given text node.
461
+ * Selects a range of text within a given DOM node.
462
462
  *
463
- * @param {Node} textNode - The text node containing the text to be selected.
464
- * @param {number} start - The starting index of the selection within the text node.
465
- * @param {number} end - The ending index of the selection within the text node.
466
- *
467
- * @throws {Error} If there's an issue creating the range or applying the selection.
463
+ * @param {Node} node - The DOM node containing the text to be selected.
464
+ * @param {number} start - The starting index position of the selection.
465
+ * @param {number} end - The ending index position of the selection.
468
466
  *
469
467
  * @example
470
468
  * const paragraph = document.querySelector('p');
471
- * const textNode = paragraph.firstChild;
472
- * selectTextRange(textNode, 5, 10);
469
+ * if (paragraph.firstChild) {
470
+ * selectRange(paragraph.firstChild, 5, 10);
471
+ * }
473
472
  *
474
473
  * @description
475
- * This function creates a new range from the given start and end positions within
476
- * the provided text node. It then applies this range to the current window selection,
477
- * effectively selecting the specified text. If any errors occur during this process,
478
- * they are caught and logged to the console along with the input parameters.
479
- *
480
- * Note: This function will only visually select the text. It does not copy the text
481
- * or perform any actions on it. The selection may not be visible if the containing
482
- * element is not focusable or if the browser prevents programmatic selection for
483
- * security reasons.
474
+ * Creates a new DOM Range from the given start and end positions within the provided node
475
+ * and applies it to the current window selection. This visually selects the specified
476
+ * portion of text in the browser.
477
+ *
478
+ * Important considerations:
479
+ * - The node should be a text node or a node that can contain text content
480
+ * - The start and end indices must be valid positions within the node's text content
481
+ * - The selection may not be visible if the containing element is not focusable
482
+ * - Some browsers may prevent programmatic selection for security reasons
483
+ * - Any errors during selection are caught and logged to the console
484
+ *
485
+ * Note that this function only creates a visual selection - it does not copy the text
486
+ * or modify it in any way.
484
487
  */
485
- export function selectTextRange(textNode, start, end) {
488
+ export function selectRange(node, start, end) {
486
489
  try {
487
490
  const range = document.createRange();
488
- range.setStart(textNode, start);
489
- range.setEnd(textNode, end);
491
+ range.setStart(node, start);
492
+ range.setEnd(node, end);
490
493
  const selection = window.getSelection();
491
494
  if (selection) {
492
495
  selection.removeAllRanges();
@@ -495,7 +498,7 @@ export function selectTextRange(textNode, start, end) {
495
498
  }
496
499
  catch (error) {
497
500
  // eslint-disable-next-line no-console
498
- console.log('Error selecting text range', error, { textNode, start, end });
501
+ console.log('Error selecting range', error, { node, start, end });
499
502
  }
500
503
  }
501
504
  /**
@@ -1288,7 +1288,7 @@
1288
1288
  "_TITL_TOTAL_TESTING_INPUT": "Total testing input",
1289
1289
  "_TITL_TRACKED_TIME": "Tracked time",
1290
1290
  "_TITL_TREAT_AS_CONVERSION_OBECJT": "Treat as Conversion Object",
1291
- "_TOOLTIP__TITL_TREAT_AS_CONVERSION_OBECJT": "Coi như đối tượng chuyển đổi",
1291
+ "_TOOLTIP__TITL_TREAT_AS_CONVERSION_OBECJT": "Loading...",
1292
1292
  "_TITL_TRIGGER_EVENT": "{{trigger_event_name}}, where:",
1293
1293
  "_TITL_TRIGGER_TIME": "Trigger time",
1294
1294
  "_TITL_UNTITLED_MEASURE": "Untitled Measure",