@antscorp/antsomi-ui 2.0.113 → 2.0.115
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/CHANGELOG.md +92 -63
- package/es/components/atoms/SelectAssociatedTag/SelectAssociatedTag.d.ts +5 -0
- package/es/components/atoms/SelectAssociatedTag/SelectAssociatedTag.js +15 -0
- package/es/components/atoms/SelectAssociatedTag/SelectTag.d.ts +4 -0
- package/es/components/atoms/SelectAssociatedTag/SelectTag.js +26 -0
- package/es/components/atoms/SelectAssociatedTag/constants.d.ts +11 -0
- package/es/components/atoms/SelectAssociatedTag/constants.js +42 -0
- package/es/components/atoms/SelectAssociatedTag/index.d.ts +3 -0
- package/es/components/atoms/SelectAssociatedTag/index.js +2 -0
- package/es/components/atoms/SelectAssociatedTag/styled.d.ts +7 -0
- package/es/components/atoms/SelectAssociatedTag/styled.js +60 -0
- package/es/components/atoms/SelectAssociatedTag/types.d.ts +76 -0
- package/es/components/atoms/SelectAssociatedTag/types.js +1 -0
- package/es/components/atoms/index.d.ts +1 -0
- package/es/components/atoms/index.js +1 -0
- package/es/components/icons/CircleInfoIcon.js +2 -2
- package/es/components/icons/LazyIcon/LazyIcon.d.ts +2 -0
- package/es/components/icons/LazyIcon/LazyIcon.js +2 -0
- package/es/components/molecules/InputSelectAttribute/index.d.ts +25 -0
- package/es/components/molecules/InputSelectAttribute/index.js +124 -0
- package/es/components/molecules/InputSelectAttribute/styled.d.ts +14 -0
- package/es/components/molecules/InputSelectAttribute/styled.js +33 -0
- package/es/components/molecules/SelectV2/styled.d.ts +3 -1
- package/es/components/molecules/SelectV2/styled.js +2 -2
- package/es/components/molecules/TagifyInput/TagifyInput.js +159 -71
- package/es/components/molecules/TagifyInput/constants.d.ts +24 -2
- package/es/components/molecules/TagifyInput/constants.js +25 -2
- package/es/components/molecules/TagifyInput/patternHandlers.d.ts +12 -6
- package/es/components/molecules/TagifyInput/patternHandlers.js +88 -43
- package/es/components/molecules/TagifyInput/types.d.ts +24 -3
- package/es/components/molecules/TagifyInput/utils.d.ts +10 -1
- package/es/components/molecules/TagifyInput/utils.js +82 -4
- package/es/components/molecules/TagifyInput/utils.style.js +81 -96
- package/es/components/molecules/index.d.ts +1 -0
- package/es/components/molecules/index.js +1 -0
- package/es/components/organism/AccountSharing/AccountSharing.js +18 -10
- package/es/components/organism/ActivityTimeline/utils.js +168 -2
- package/es/components/organism/LeftMenu/hooks/usePermission.js +1 -1
- package/es/components/organism/LeftMenu/utils/index.js +1 -1
- package/es/components/organism/TextEditor/TextEditor.js +17 -1
- package/es/components/organism/TextEditor/types.d.ts +6 -2
- package/es/components/organism/TextEditor/ui/Toolbar/FormattingToolbar.js +11 -6
- package/es/utils/cookie.js +9 -0
- package/package.json +5 -5
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
// Libraries
|
|
3
|
+
import { memo, useCallback, useMemo, useState } from 'react';
|
|
4
|
+
import { get, has, keyBy, upperFirst } from 'lodash';
|
|
5
|
+
import { Flex, Form, Select, Tooltip, Typography } from 'antd';
|
|
6
|
+
// Translations
|
|
7
|
+
import { translate, translations } from '@antscorp/antsomi-ui/es/locales';
|
|
8
|
+
// Components
|
|
9
|
+
import Icon from '@antscorp/icons';
|
|
10
|
+
import { ModalV2 } from '../ModalV2';
|
|
11
|
+
import { StyledTag as Tag } from '@antscorp/antsomi-ui/es/components/molecules/SelectV2/styled';
|
|
12
|
+
import { StyledSelect } from './styled';
|
|
13
|
+
import { EmptyData } from '../EmptyData';
|
|
14
|
+
import { Dashboard30Icon, ErrorIcon } from '../../icons';
|
|
15
|
+
// Constants
|
|
16
|
+
import { THEME } from '@antscorp/antsomi-ui/es/constants';
|
|
17
|
+
import { TAG_TYPE } from '../TagifyInput';
|
|
18
|
+
const InputSelectAttribute = (props) => {
|
|
19
|
+
const { value, errorMsg, label, isErrorTag, sourceOptions = [], mapCodeOptions = {}, onChange, } = props;
|
|
20
|
+
const [form] = Form.useForm();
|
|
21
|
+
const sourceValue = Form.useWatch('source', form);
|
|
22
|
+
// States
|
|
23
|
+
const [openModal, setOpenModal] = useState(false);
|
|
24
|
+
const codeOptions = useMemo(() => {
|
|
25
|
+
if (sourceValue) {
|
|
26
|
+
return get(mapCodeOptions, sourceValue, []);
|
|
27
|
+
}
|
|
28
|
+
return [];
|
|
29
|
+
}, [sourceValue, mapCodeOptions]);
|
|
30
|
+
const initCodeTitleField = useMemo(() => {
|
|
31
|
+
if (sourceValue === TAG_TYPE.PROMOTION_CODE) {
|
|
32
|
+
return 'Allocated Code';
|
|
33
|
+
}
|
|
34
|
+
return upperFirst(translate(translations._ITEM_NAME_ATTRIBUTE, 'attribute'));
|
|
35
|
+
}, [sourceValue]);
|
|
36
|
+
const mapCodeBySource = useMemo(() => {
|
|
37
|
+
if (typeof value === 'string')
|
|
38
|
+
return {};
|
|
39
|
+
return keyBy(get(mapCodeOptions, value?.source, []), 'value');
|
|
40
|
+
}, [mapCodeOptions, value]);
|
|
41
|
+
const mapSourceOptions = useMemo(() => {
|
|
42
|
+
if (typeof value === 'string')
|
|
43
|
+
return {};
|
|
44
|
+
return keyBy(sourceOptions, 'value');
|
|
45
|
+
}, [sourceOptions, value]);
|
|
46
|
+
const getCodeDefaultBySource = useCallback((source) => get(mapCodeOptions, [source, '0', 'value'], ''), [mapCodeOptions]);
|
|
47
|
+
const onOpenModal = useCallback(() => {
|
|
48
|
+
if (value && typeof value !== 'string') {
|
|
49
|
+
const isExistCode = has(mapCodeBySource, [value?.code, 'label']);
|
|
50
|
+
const isExistSource = has(mapSourceOptions, [value?.source]);
|
|
51
|
+
form.setFieldsValue({
|
|
52
|
+
source: isExistSource ? value?.source : undefined,
|
|
53
|
+
code: isExistCode ? value?.code : undefined,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
const source = sourceOptions[0]?.value || '';
|
|
58
|
+
const code = getCodeDefaultBySource(source);
|
|
59
|
+
form.setFieldsValue({ source, code });
|
|
60
|
+
}
|
|
61
|
+
setOpenModal(true);
|
|
62
|
+
}, [value, mapCodeBySource, form, sourceOptions, mapSourceOptions, getCodeDefaultBySource]);
|
|
63
|
+
const onDeselect = useCallback(() => {
|
|
64
|
+
onChange({ value: '', valueType: 'input' });
|
|
65
|
+
}, [onChange]);
|
|
66
|
+
const onHideModal = useCallback(() => {
|
|
67
|
+
setOpenModal(false);
|
|
68
|
+
}, []);
|
|
69
|
+
const onAfterClose = useCallback(() => {
|
|
70
|
+
form.resetFields();
|
|
71
|
+
}, [form]);
|
|
72
|
+
const onOk = async () => {
|
|
73
|
+
try {
|
|
74
|
+
// Validate form fields before getting values
|
|
75
|
+
const values = await form.validateFields();
|
|
76
|
+
if (typeof values !== 'string') {
|
|
77
|
+
const newValue = {
|
|
78
|
+
source: values?.source,
|
|
79
|
+
code: values?.code,
|
|
80
|
+
};
|
|
81
|
+
onChange({ value: newValue, valueType: 'select' });
|
|
82
|
+
}
|
|
83
|
+
setOpenModal(false);
|
|
84
|
+
}
|
|
85
|
+
catch (errorInfo) {
|
|
86
|
+
// eslint-disable-next-line no-console
|
|
87
|
+
console.error('Validation Failed:', errorInfo);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
const onChangeInput = useCallback((val) => {
|
|
91
|
+
onChange({ value: val, valueType: 'input' });
|
|
92
|
+
}, [onChange]);
|
|
93
|
+
const onValuesChange = useCallback((changedValues) => {
|
|
94
|
+
// If source changed -> set new code based on new source
|
|
95
|
+
if (changedValues?.source) {
|
|
96
|
+
const newCode = getCodeDefaultBySource(changedValues.source);
|
|
97
|
+
form.setFieldValue('code', newCode);
|
|
98
|
+
}
|
|
99
|
+
}, [form, getCodeDefaultBySource]);
|
|
100
|
+
const renderInput = () => {
|
|
101
|
+
let element = null;
|
|
102
|
+
const isObjValue = value && typeof value !== 'string';
|
|
103
|
+
if (openModal || isObjValue) {
|
|
104
|
+
element = (_jsxs("div", { style: {
|
|
105
|
+
display: 'flex',
|
|
106
|
+
alignItems: 'center',
|
|
107
|
+
justifyContent: 'space-between',
|
|
108
|
+
height: 32,
|
|
109
|
+
padding: '4px 12px 4px 4px',
|
|
110
|
+
borderBottom: `1px solid ${errorMsg ? THEME.token?.colorError : THEME.token?.blue1}`,
|
|
111
|
+
}, children: [_jsx("div", { style: { width: '100%', cursor: 'pointer' }, onClick: onOpenModal, children: isObjValue && (_jsx(Tag, { isError: isErrorTag, children: isErrorTag ? (_jsxs(Flex, { gap: 5, align: "center", children: ["Unknown", _jsx(Tooltip, { title: "The used dynamic content is removed", children: _jsx(ErrorIcon, { size: 16 }) })] })) : (_jsx(Typography.Text, { ellipsis: {
|
|
112
|
+
tooltip: get(mapCodeBySource, [value?.code, 'label'], value?.code),
|
|
113
|
+
}, style: { maxWidth: 150 }, children: get(mapCodeBySource, [value?.code, 'label'], value?.code) })) })) }), _jsx(Icon, { type: "icon-ants-remove", style: { fontSize: 10, color: '#222', cursor: 'pointer' }, onClick: onDeselect })] }));
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
element = (_jsx(StyledSelect, { mode: "multiple", options: [{ value: '', label: 'Or select a field' }], notFoundContent: null, onSelect: onOpenModal, style: { width: '100%', borderTop: 'none', borderLeft: 'none', borderRight: 'none' }, onDeselect: onDeselect, autoClearSearchValue: false, searchValue: typeof value === 'string' ? value : '', onSearch: onChangeInput, status: errorMsg ? 'error' : undefined, placeholder: typeof value === 'string' ? value : translate(translations.inputYourValue.title), "$isPlaceholder": !value, "$isError": !!errorMsg, dropdownStyle: {
|
|
117
|
+
...(openModal ? { display: 'none' } : {}),
|
|
118
|
+
} }));
|
|
119
|
+
}
|
|
120
|
+
return element;
|
|
121
|
+
};
|
|
122
|
+
return (_jsxs(_Fragment, { children: [renderInput(), errorMsg ? (_jsx(Typography.Text, { style: { marginLeft: 8, color: THEME.token?.red8, marginTop: 5 }, children: errorMsg })) : null, _jsx(ModalV2, { title: translate(translations._PREDICT_MODEL_SELECT_ATTRIBUTE, 'Select attribute'), okText: translate(translations._ACT_APPLY, 'Apply'), open: openModal, onOk: onOk, onCancel: onHideModal, afterClose: onAfterClose, destroyOnClose: true, centered: true, children: _jsxs(Form, { colon: false, form: form, onValuesChange: onValuesChange, children: [_jsx(Form.Item, { label: translate(translations._TITL_PERSONALIZATION_TYPE, 'Content Source'), name: "source", required: true, labelCol: { span: 6 }, labelAlign: "left", rules: [{ required: true, message: 'Please select field!' }], children: _jsx(Select, { suffixIcon: _jsx(Icon, { type: "icon-ants-expand-more", style: { fontSize: '20px', color: THEME.token?.colorIcon } }), options: sourceOptions, placeholder: "Please select an item" }) }), _jsx(Form.Item, { label: label || initCodeTitleField, required: true, labelCol: { span: 6 }, labelAlign: "left", name: "code", rules: [{ required: true, message: 'Please select field!' }], children: _jsx(Select, { suffixIcon: _jsx(Icon, { type: "icon-ants-expand-more", style: { fontSize: '20px', color: THEME.token?.colorIcon } }), notFoundContent: _jsx(EmptyData, { size: "small", icon: _jsx(Dashboard30Icon, {}), description: "No personalized content in this journey" }), placeholder: "Please select an item", options: codeOptions }) })] }) })] }));
|
|
123
|
+
};
|
|
124
|
+
export default memo(InputSelectAttribute);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const StyledSelect: import("styled-components").StyledComponent<{
|
|
2
|
+
(props: import("@antscorp/antsomi-ui/es/components/molecules").SelectV2Props): import("react/jsx-runtime").JSX.Element;
|
|
3
|
+
defaultProps: {
|
|
4
|
+
suffixIcon: import("react/jsx-runtime").JSX.Element;
|
|
5
|
+
filterOption: (input: any, option: any) => boolean;
|
|
6
|
+
tagRender: (props: any) => import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
containerStyle: {};
|
|
8
|
+
labelStyle: {};
|
|
9
|
+
clearIcon: import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
};
|
|
11
|
+
}, any, {
|
|
12
|
+
$isPlaceholder?: any;
|
|
13
|
+
$isError?: boolean | undefined;
|
|
14
|
+
}, never>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Libraries
|
|
2
|
+
import styled, { css } from 'styled-components';
|
|
3
|
+
// Components
|
|
4
|
+
import { SelectV2 as Select } from '@antscorp/antsomi-ui/es/components/molecules';
|
|
5
|
+
// Constants
|
|
6
|
+
import { THEME } from '@antscorp/antsomi-ui/es/constants';
|
|
7
|
+
export const StyledSelect = styled(Select) `
|
|
8
|
+
.antsomi-select-selection-overflow .antsomi-select-selection-overflow-item {
|
|
9
|
+
flex: 1;
|
|
10
|
+
|
|
11
|
+
.antsomi-select-selection-search {
|
|
12
|
+
width: fit-content !important;
|
|
13
|
+
flex: 1;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
${({ $isError }) => $isError
|
|
18
|
+
? css `
|
|
19
|
+
.antsomi-select-selector {
|
|
20
|
+
box-shadow: none !important;
|
|
21
|
+
}
|
|
22
|
+
`
|
|
23
|
+
: css ``}
|
|
24
|
+
|
|
25
|
+
.antsomi-select-selection-placeholder {
|
|
26
|
+
${props => !props.$isPlaceholder
|
|
27
|
+
? css `
|
|
28
|
+
color: rgba(0, 0, 0, 0.85);
|
|
29
|
+
font-size: ${THEME.token?.fontSize}px;
|
|
30
|
+
`
|
|
31
|
+
: css ``}
|
|
32
|
+
}
|
|
33
|
+
`;
|
|
@@ -9,7 +9,9 @@ export declare const StyledSelect: import("styled-components").StyledComponent<(
|
|
|
9
9
|
OptGroup: import("rc-select/lib/OptGroup").OptionGroupFC;
|
|
10
10
|
_InternalPanelDoNotUseOrYouWillBeFired: (props: import("antd/es/_util/type").AnyObject) => import("react").JSX.Element;
|
|
11
11
|
}, any, {}, never>;
|
|
12
|
-
export declare const StyledTag: import("styled-components").StyledComponent<import("antd").TagType, any, {
|
|
12
|
+
export declare const StyledTag: import("styled-components").StyledComponent<import("antd").TagType, any, {
|
|
13
|
+
isError?: boolean | undefined;
|
|
14
|
+
}, never>;
|
|
13
15
|
export declare const CloseButton: import("styled-components").StyledComponent<"div", any, {
|
|
14
16
|
borderColor?: any;
|
|
15
17
|
}, never>;
|
|
@@ -105,8 +105,8 @@ export const StyledTag = styled(Tag) `
|
|
|
105
105
|
margin-right: 5px !important;
|
|
106
106
|
padding: 5px 10px !important;
|
|
107
107
|
border-radius: 15px !important;
|
|
108
|
-
border: none !important;
|
|
109
|
-
background-color: ${THEME.token?.blue2} !important;
|
|
108
|
+
border: ${p => (p.isError ? `1px solid ${THEME.token?.colorError}` : 'none')} !important;
|
|
109
|
+
background-color: ${p => (p.isError ? THEME.token?.bw0 : THEME.token?.blue2)} !important;
|
|
110
110
|
cursor: pointer !important;
|
|
111
111
|
|
|
112
112
|
.antsomi-tag-close-icon {
|
|
@@ -17,13 +17,15 @@ 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, selectRange, isTagClickable, findURLInTextNodes, getAttributesString, isAnchorNodeChildOfElement, isShortLinkTagType, isCaretAtEndOfTextNodeWithNextTag, getCurrentSelectionAndCloneRange, handleEnterWithNextTag, handleTextNodeBackspace, } from './utils';
|
|
21
|
-
import { acceptablePatternChecking, detectURLRegex, getCachedRegex, getPersonalizeTagInfo, getShortLinkTagInfo, patternHandlers, } from './patternHandlers';
|
|
20
|
+
import { parseTagStringToTagify, convertInputStringToOriginal, emojiManufacturer, getEmojiTag, isPersonalizeTagType, generateTagContent, unescapeString, hasLineBreak, selectRange, isTagClickable, findURLInTextNodes, getAttributesString, isAnchorNodeChildOfElement, isShortLinkTagType, isCustomTagType, sanitizeTagAttributes, getTagAttributes, applyTagAttributes, getTagContentAttributes, isCaretAtEndOfTextNodeWithNextTag, getCurrentSelectionAndCloneRange, handleEnterWithNextTag, handleTextNodeBackspace, preventUndoRedo, } from './utils';
|
|
21
|
+
import { acceptablePatternChecking, detectURLRegex, getCachedRegex, getCustomTagId, getPersonalizeTagInfo, getShortLinkTagInfo, patternHandlers, } from './patternHandlers';
|
|
22
22
|
// Constants
|
|
23
|
-
import { DETECT_LINK, EMOJI,
|
|
23
|
+
import { DETECT_LINK, EMOJI, PERSONALIZE_PTN, SHORT_LINK, SHORT_LINK_PTN, SHORT_LINK_V2, TAG_TYPE, UNSUBSCRIBE_WHATSAPP, defaultCssVariables, tagifyDefaultProps, TAG_CUSTOM_ATTRIBUTES, } from './constants';
|
|
24
|
+
const { CUSTOM_TAG } = TAG_TYPE;
|
|
25
|
+
const { PREPARING_ST, INVALID_TAG, MESSAGE_TAG, FORCE_SHOW_TOOLTIP, ERROR_TAG, WARNING_TAG } = TAG_CUSTOM_ATTRIBUTES;
|
|
24
26
|
const TagifyInput = forwardRef((props, ref) => {
|
|
25
27
|
// Props
|
|
26
|
-
const { initialValue, escapeHTML, status, readonly, readonlyTag, readonlyText, realtime, disabled, maxLength, maxHeight, minWidth, placeholder, minWidthPlaceholder, isSingleLineText, acceptableTagPattern, mapAttributes, mapErrorAttributes, maxPersonalizeTags, name, children, cssTagifyVariables, onTagClick, onChange, } = props;
|
|
28
|
+
const { initialValue, escapeHTML, status, readonly, readonlyTag, readonlyText, realtime, disabled, maxLength, maxHeight, minWidth, placeholder, minWidthPlaceholder, isSingleLineText, acceptableTagPattern, tagProperties, mapAttributes = {}, mapErrorAttributes = {}, maxPersonalizeTags, name, children, cssTagifyVariables, onTagClick, onTagRemove, onChange, } = props;
|
|
27
29
|
// States
|
|
28
30
|
const [isLineBreak, setIsLineBreak] = useState(hasLineBreak(initialValue));
|
|
29
31
|
const [tooltipRefresher, setTooltipRefresher] = useState(1);
|
|
@@ -33,6 +35,7 @@ const TagifyInput = forwardRef((props, ref) => {
|
|
|
33
35
|
const tagifyWrapperRef = useRef(null);
|
|
34
36
|
const placeholderRef = useRef(null);
|
|
35
37
|
const lastRange = useRef(null);
|
|
38
|
+
const tagLength = tagifyRef?.current?.getTagElms().length;
|
|
36
39
|
// Memoizations
|
|
37
40
|
const cssVariablesMemoized = useMemo(() => _.assign({}, defaultCssVariables, cssTagifyVariables || {}), [cssTagifyVariables]);
|
|
38
41
|
// Only run in the first render
|
|
@@ -454,6 +457,11 @@ const TagifyInput = forwardRef((props, ref) => {
|
|
|
454
457
|
onTagClick(event.detail);
|
|
455
458
|
}
|
|
456
459
|
}, [onTagClick]);
|
|
460
|
+
const onTagifyRemoveTag = useCallback((event) => {
|
|
461
|
+
if (event.detail && onTagRemove) {
|
|
462
|
+
onTagRemove(event.detail);
|
|
463
|
+
}
|
|
464
|
+
}, [onTagRemove]);
|
|
457
465
|
// Used to trigger replace URL detection and line break
|
|
458
466
|
const onTagifyTyping = useCallback((event) => {
|
|
459
467
|
if (event.detail) {
|
|
@@ -523,7 +531,11 @@ const TagifyInput = forwardRef((props, ref) => {
|
|
|
523
531
|
case EMOJI: {
|
|
524
532
|
closeIcon = '';
|
|
525
533
|
const emojiPath = emojiManufacturer(value, collection);
|
|
526
|
-
visibleTagContent = getEmojiTag({
|
|
534
|
+
visibleTagContent = getEmojiTag({
|
|
535
|
+
src: emojiPath,
|
|
536
|
+
emoji: label,
|
|
537
|
+
code: value,
|
|
538
|
+
});
|
|
527
539
|
break;
|
|
528
540
|
}
|
|
529
541
|
case DETECT_LINK: {
|
|
@@ -582,52 +594,29 @@ const TagifyInput = forwardRef((props, ref) => {
|
|
|
582
594
|
while ((match = regex.exec(value)) !== null) {
|
|
583
595
|
const [, personalizeContent] = match;
|
|
584
596
|
const [tagCode] = personalizeContent.split('||');
|
|
585
|
-
const { label: tagLabel,
|
|
586
|
-
const isPromotionCode = type === PROMOTION_CODE;
|
|
597
|
+
const { label: tagLabel, type, status, statusMsg, } = getPersonalizeTagInfo(tagCode, attributes, errorAttributes);
|
|
587
598
|
const tagTextNode = tagifyRef.current?.getTagTextNode(tagElement);
|
|
588
599
|
/*
|
|
589
600
|
* Just only update to the correct text of the tag
|
|
590
601
|
* NOTE: Do not actually affect raw data
|
|
591
602
|
*/
|
|
592
603
|
if (tagTextNode) {
|
|
593
|
-
|
|
594
|
-
//
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
tagElement.removeAttribute(READONLY_TAG);
|
|
599
|
-
tagElement.removeAttribute(MESSAGE_TAG);
|
|
600
|
-
// In case promotion pool has something wrong
|
|
601
|
-
if (isPromotionCode && message) {
|
|
602
|
-
if (isRemoved) {
|
|
603
|
-
tagElement.setAttribute(REMOVED_TAG, 'true');
|
|
604
|
-
tagElement.setAttribute(MESSAGE_TAG, message);
|
|
605
|
-
tagifyRef.current?.getSetTagData(tagElement, {
|
|
606
|
-
...tagData,
|
|
607
|
-
status: 'error',
|
|
608
|
-
statusMsg: message,
|
|
609
|
-
});
|
|
610
|
-
}
|
|
611
|
-
else if (!hasViewPermission) {
|
|
612
|
-
tagElement.setAttribute(NO_VIEW_TAG, 'true');
|
|
613
|
-
tagElement.setAttribute(MESSAGE_TAG, message);
|
|
614
|
-
tagifyRef.current?.getSetTagData(tagElement, {
|
|
615
|
-
...tagData,
|
|
616
|
-
status: 'warning',
|
|
617
|
-
statusMsg: message,
|
|
618
|
-
});
|
|
619
|
-
}
|
|
620
|
-
else if (!isValid) {
|
|
621
|
-
tagElement.setAttribute(READONLY_TAG, 'true');
|
|
622
|
-
tagElement.setAttribute(INVALID_TAG, 'true');
|
|
623
|
-
tagElement.setAttribute(MESSAGE_TAG, message);
|
|
624
|
-
tagifyRef.current?.getSetTagData(tagElement, {
|
|
625
|
-
...tagData,
|
|
626
|
-
status: 'warning',
|
|
627
|
-
statusMsg: message,
|
|
628
|
-
});
|
|
629
|
-
}
|
|
604
|
+
const isCustomTag = isCustomTagType(type);
|
|
605
|
+
// NOTE: Custom tag type [CUSTOM_TAG] does not need to get label from map attribute
|
|
606
|
+
// It's just get from [tagProperties] instead
|
|
607
|
+
if (!isCustomTag) {
|
|
608
|
+
tagTextNode.textContent = tagLabel;
|
|
630
609
|
}
|
|
610
|
+
// Remove all existing attributes
|
|
611
|
+
sanitizeTagAttributes(tagElement);
|
|
612
|
+
// Update new tag attributes
|
|
613
|
+
const tagAttributes = getTagAttributes({
|
|
614
|
+
type: type,
|
|
615
|
+
status,
|
|
616
|
+
statusMsg,
|
|
617
|
+
displayName: tagLabel,
|
|
618
|
+
});
|
|
619
|
+
applyTagAttributes(tagElement, tagAttributes);
|
|
631
620
|
}
|
|
632
621
|
}
|
|
633
622
|
}
|
|
@@ -637,7 +626,7 @@ const TagifyInput = forwardRef((props, ref) => {
|
|
|
637
626
|
if (!isAccepted)
|
|
638
627
|
return;
|
|
639
628
|
const { url, shortener, label } = tagData;
|
|
640
|
-
const { label: tagLabel,
|
|
629
|
+
const { label: tagLabel, type: tagType, status, statusMsg, } = getShortLinkTagInfo({
|
|
641
630
|
type,
|
|
642
631
|
label,
|
|
643
632
|
shortener,
|
|
@@ -652,22 +641,16 @@ const TagifyInput = forwardRef((props, ref) => {
|
|
|
652
641
|
*/
|
|
653
642
|
if (tagTextNode) {
|
|
654
643
|
tagTextNode.textContent = tagLabel;
|
|
655
|
-
//
|
|
656
|
-
tagElement
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
tagifyRef.current?.getSetTagData(tagElement, {
|
|
666
|
-
...tagData,
|
|
667
|
-
status: 'error',
|
|
668
|
-
statusMsg: message,
|
|
669
|
-
});
|
|
670
|
-
}
|
|
644
|
+
// Remove all existing attributes
|
|
645
|
+
sanitizeTagAttributes(tagElement);
|
|
646
|
+
// Update new tag attributes
|
|
647
|
+
const tagAttributes = getTagAttributes({
|
|
648
|
+
type: type,
|
|
649
|
+
status,
|
|
650
|
+
statusMsg,
|
|
651
|
+
displayName: tagLabel,
|
|
652
|
+
});
|
|
653
|
+
applyTagAttributes(tagElement, tagAttributes);
|
|
671
654
|
}
|
|
672
655
|
}
|
|
673
656
|
}
|
|
@@ -675,6 +658,52 @@ const TagifyInput = forwardRef((props, ref) => {
|
|
|
675
658
|
setTooltipRefresher(prev => prev + 1);
|
|
676
659
|
}
|
|
677
660
|
}, [acceptableTagPattern]);
|
|
661
|
+
/*
|
|
662
|
+
* Execute tag properties for each tag
|
|
663
|
+
*/
|
|
664
|
+
const executeTagProperties = useCallback((properties) => {
|
|
665
|
+
if (tagifyRef.current) {
|
|
666
|
+
let isChangeOccurred = false;
|
|
667
|
+
const tagElements = tagifyRef.current.getTagElms();
|
|
668
|
+
const { pattern, name: cachePatternName } = patternHandlers[PERSONALIZE_PTN];
|
|
669
|
+
tagElements.forEach(tagElement => {
|
|
670
|
+
const { __tagifyTagData: tagData } = tagElement;
|
|
671
|
+
if (!tagData || tagData?.type !== CUSTOM_TAG || !tagData?.value)
|
|
672
|
+
return;
|
|
673
|
+
const { type, value } = tagData;
|
|
674
|
+
// Use the cached regex instead of creating a new one each time
|
|
675
|
+
const regex = getCachedRegex(pattern, 'g', cachePatternName);
|
|
676
|
+
let match;
|
|
677
|
+
// Iterate over matches of the current pattern
|
|
678
|
+
// eslint-disable-next-line no-cond-assign
|
|
679
|
+
while ((match = regex.exec(value)) !== null) {
|
|
680
|
+
const [, contentCode] = match;
|
|
681
|
+
const customTagId = getCustomTagId(type, contentCode);
|
|
682
|
+
const property = _.get(properties, customTagId);
|
|
683
|
+
if (!customTagId || !property)
|
|
684
|
+
continue;
|
|
685
|
+
const { displayName } = property;
|
|
686
|
+
// Update tag text (but not raw data)
|
|
687
|
+
tagifyRef.current?.setTagTextNode(tagElement, displayName);
|
|
688
|
+
isChangeOccurred = true;
|
|
689
|
+
// Remove all existing attributes
|
|
690
|
+
sanitizeTagAttributes(tagElement);
|
|
691
|
+
// Update new tag attributes
|
|
692
|
+
const tagAttributes = getTagAttributes(property);
|
|
693
|
+
applyTagAttributes(tagElement, tagAttributes);
|
|
694
|
+
// Update tag content element attributes
|
|
695
|
+
const tagContentEl = tagElement.querySelector('.tagify__tag-content');
|
|
696
|
+
if (tagContentEl) {
|
|
697
|
+
const tagContentAttributes = getTagContentAttributes(property.type);
|
|
698
|
+
applyTagAttributes(tagContentEl, tagContentAttributes);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
if (isChangeOccurred) {
|
|
703
|
+
setTooltipRefresher(prev => prev + 1);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}, []);
|
|
678
707
|
const initializeTagify = useCallback(() => {
|
|
679
708
|
if (inputRef.current && !tagifyRef.current) {
|
|
680
709
|
tagifyRef.current = new Tagify(inputRef.current, {
|
|
@@ -789,6 +818,50 @@ const TagifyInput = forwardRef((props, ref) => {
|
|
|
789
818
|
}
|
|
790
819
|
};
|
|
791
820
|
}, [initializeTagify]);
|
|
821
|
+
useEffect(() => {
|
|
822
|
+
if (tagifyRef.current && tagLength && _.isFunction(onTagRemove)) {
|
|
823
|
+
// Because the remove tag event is not triggered when the tag is removed with Backspace or drag selection tags
|
|
824
|
+
// we need to listen to the input element to detect when a tag is removed
|
|
825
|
+
let mutationQueue = [];
|
|
826
|
+
let processing = false;
|
|
827
|
+
const processMutations = () => {
|
|
828
|
+
processing = true;
|
|
829
|
+
const relevantNodes = mutationQueue.flatMap(mutation => [...mutation.removedNodes].filter(node => {
|
|
830
|
+
const isUpdatedTag = mutation.addedNodes.length > 0;
|
|
831
|
+
return !isUpdatedTag && node.nodeType === Node.ELEMENT_NODE && node.nodeName === 'TAG';
|
|
832
|
+
}));
|
|
833
|
+
relevantNodes.forEach(node => {
|
|
834
|
+
try {
|
|
835
|
+
const tagData = tagifyRef.current?.getSetTagData(node);
|
|
836
|
+
if (tagData && tagData.type === CUSTOM_TAG && !tagData?.__removed) {
|
|
837
|
+
onTagRemove({ data: tagData });
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
catch (error) {
|
|
841
|
+
// biome-ignore lint/suspicious/noConsole: <explanation>
|
|
842
|
+
console.error('Error while removing tag with Backspace', error);
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
mutationQueue = [];
|
|
846
|
+
processing = false;
|
|
847
|
+
};
|
|
848
|
+
const observer = new MutationObserver(mutations => {
|
|
849
|
+
mutationQueue.push(...mutations);
|
|
850
|
+
if (!processing) {
|
|
851
|
+
requestAnimationFrame(processMutations); // Ensures all mutations are processed efficiently
|
|
852
|
+
}
|
|
853
|
+
});
|
|
854
|
+
observer.observe(tagifyRef.current.DOM.input, {
|
|
855
|
+
childList: true, // Only observe direct child additions/removals
|
|
856
|
+
subtree: false, // Prevent deep observation
|
|
857
|
+
attributes: false, // Ignore attribute changes
|
|
858
|
+
characterData: false, // Ignore text content changes
|
|
859
|
+
});
|
|
860
|
+
return () => {
|
|
861
|
+
observer.disconnect();
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
}, [tagLength, onTagRemove]);
|
|
792
865
|
// Settings some tagify attributes
|
|
793
866
|
// Set [readonly, disabled, placeholder]
|
|
794
867
|
useEffect(() => {
|
|
@@ -806,27 +879,28 @@ const TagifyInput = forwardRef((props, ref) => {
|
|
|
806
879
|
}, [disabled, placeholder, readonly]);
|
|
807
880
|
// Set readonly for each tag
|
|
808
881
|
useEffect(() => {
|
|
809
|
-
if (tagifyRef.current && typeof readonlyTag !== 'undefined') {
|
|
882
|
+
if (tagifyRef.current && tagLength && typeof readonlyTag !== 'undefined') {
|
|
810
883
|
const tagElementList = tagifyRef.current.getTagElms();
|
|
811
884
|
tagElementList.forEach((tagElement) => {
|
|
812
885
|
const tagType = _.get(tagElement, '__tagifyTagData.type', '');
|
|
813
886
|
const isPersonalizeTag = isPersonalizeTagType(tagType);
|
|
887
|
+
const isCustomTag = isCustomTagType(tagType);
|
|
814
888
|
// Only support readonly for personalize tag
|
|
815
|
-
if (!isPersonalizeTag)
|
|
889
|
+
if (!isPersonalizeTag && !isCustomTag)
|
|
816
890
|
return;
|
|
817
891
|
// Caution: Don't use readonly attribute -> readonly attribute Tagify library managed
|
|
818
892
|
if (readonlyTag) {
|
|
819
|
-
tagElement.setAttribute(
|
|
893
|
+
tagElement.setAttribute(PREPARING_ST, readonlyTag.toString());
|
|
820
894
|
}
|
|
821
895
|
else {
|
|
822
896
|
// Only remove readonly-tag attribute if the invalid attribute is not existing
|
|
823
897
|
if (!tagElement.hasAttribute(INVALID_TAG)) {
|
|
824
|
-
tagElement.removeAttribute(
|
|
898
|
+
tagElement.removeAttribute(PREPARING_ST);
|
|
825
899
|
}
|
|
826
900
|
}
|
|
827
901
|
});
|
|
828
902
|
}
|
|
829
|
-
}, [readonlyTag]);
|
|
903
|
+
}, [readonlyTag, tagLength]);
|
|
830
904
|
// Set max personalize tags
|
|
831
905
|
useLayoutEffect(() => {
|
|
832
906
|
if (tagifyRef.current && typeof maxPersonalizeTags !== 'undefined') {
|
|
@@ -837,10 +911,13 @@ const TagifyInput = forwardRef((props, ref) => {
|
|
|
837
911
|
* Need to sync label of the tags if any map attribute is changed to make correct label
|
|
838
912
|
* */
|
|
839
913
|
useDeepCompareEffect(() => {
|
|
840
|
-
|
|
841
|
-
makeValidLabelTags(mapAttributes, mapErrorAttributes);
|
|
842
|
-
}
|
|
914
|
+
makeValidLabelTags(mapAttributes, mapErrorAttributes);
|
|
843
915
|
}, [mapAttributes, mapErrorAttributes, makeValidLabelTags]);
|
|
916
|
+
useLayoutEffect(() => {
|
|
917
|
+
if (tagProperties && tagLength) {
|
|
918
|
+
executeTagProperties(tagProperties);
|
|
919
|
+
}
|
|
920
|
+
}, [tagProperties, tagLength, executeTagProperties]);
|
|
844
921
|
// Listen to Tagify events
|
|
845
922
|
useEffect(() => {
|
|
846
923
|
const { current: tagifyInstance } = tagifyRef || {};
|
|
@@ -848,7 +925,11 @@ const TagifyInput = forwardRef((props, ref) => {
|
|
|
848
925
|
tagifyInstance.on('click', onTagItemClick);
|
|
849
926
|
tagifyInstance.on('input', onInputTagifyDebounce);
|
|
850
927
|
tagifyInstance.on('change', onTagifyChangedDebounce);
|
|
928
|
+
tagifyInstance.on('remove', onTagifyRemoveTag);
|
|
851
929
|
tagifyInstance.on('keydown', onKeyDown);
|
|
930
|
+
if (tagifyInstance.DOM.input) {
|
|
931
|
+
tagifyInstance.DOM.input.addEventListener('keydown', preventUndoRedo);
|
|
932
|
+
}
|
|
852
933
|
}
|
|
853
934
|
// Off listen to Tagify events
|
|
854
935
|
return () => {
|
|
@@ -857,6 +938,9 @@ const TagifyInput = forwardRef((props, ref) => {
|
|
|
857
938
|
tagifyInstance.off('input', onInputTagifyDebounce);
|
|
858
939
|
tagifyInstance.off('change', onTagifyChangedDebounce);
|
|
859
940
|
tagifyInstance.off('keydown', onKeyDown);
|
|
941
|
+
if (tagifyInstance.DOM.input) {
|
|
942
|
+
tagifyInstance.DOM.input.removeEventListener('keydown', preventUndoRedo);
|
|
943
|
+
}
|
|
860
944
|
}
|
|
861
945
|
};
|
|
862
946
|
}, [onTagItemClick, onInputTagifyDebounce, onTagifyChangedDebounce, onKeyDown]);
|
|
@@ -880,20 +964,24 @@ const TagifyInput = forwardRef((props, ref) => {
|
|
|
880
964
|
}
|
|
881
965
|
// Convert the string to tagify pattern
|
|
882
966
|
const content = parseTagStringToTagify(textValue, acceptableTagPattern);
|
|
967
|
+
setIsLineBreak(hasLineBreak(textValue));
|
|
883
968
|
tagifyRef.current.loadOriginalValues(content);
|
|
884
969
|
// Need to sync label of the tags if any map attribute is changed to make correct label
|
|
885
|
-
|
|
886
|
-
|
|
970
|
+
makeValidLabelTags(mapAttributes, mapErrorAttributes);
|
|
971
|
+
if (tagProperties) {
|
|
972
|
+
executeTagProperties(tagProperties);
|
|
887
973
|
}
|
|
888
974
|
}
|
|
889
975
|
}
|
|
890
976
|
}, [
|
|
891
977
|
initialValue,
|
|
892
978
|
mapAttributes,
|
|
979
|
+
tagProperties,
|
|
893
980
|
mapErrorAttributes,
|
|
894
981
|
escapeHTML,
|
|
895
982
|
acceptableTagPattern,
|
|
896
983
|
makeValidLabelTags,
|
|
984
|
+
executeTagProperties,
|
|
897
985
|
]);
|
|
898
986
|
useEffect(() => {
|
|
899
987
|
if (tagifyRef.current && initialValue === '') {
|
|
@@ -4,15 +4,32 @@ export declare const DIMENSIONS: {
|
|
|
4
4
|
readonly TAG_H: 24;
|
|
5
5
|
};
|
|
6
6
|
export declare const MIN_H_WRAPPER: 34, TAG_H: 24;
|
|
7
|
+
export declare const TAG_STATUS: {
|
|
8
|
+
readonly REMOVED: "removed";
|
|
9
|
+
readonly ARCHIVED: "archived";
|
|
10
|
+
readonly ACTIVE: "active";
|
|
11
|
+
readonly INACTIVE: "inactive";
|
|
12
|
+
readonly EXPIRED: "expired";
|
|
13
|
+
readonly WARNING: "warning";
|
|
14
|
+
readonly ERROR: "error";
|
|
15
|
+
readonly INVALID: "invalid";
|
|
16
|
+
readonly DO_NOT_VIEW: "do-not-view";
|
|
17
|
+
};
|
|
7
18
|
export declare const TAG_CUSTOM_ATTRIBUTES: {
|
|
19
|
+
readonly PREPARING_ST: "preparing-setting";
|
|
8
20
|
readonly READONLY_TAG: "readonly-tag";
|
|
9
21
|
readonly INVALID_TAG: "invalid-tag";
|
|
10
22
|
readonly REMOVED_TAG: "removed-tag";
|
|
11
23
|
readonly NO_VIEW_TAG: "no-view-tag";
|
|
12
24
|
readonly MESSAGE_TAG: "message-tag";
|
|
13
25
|
readonly FORCE_SHOW_TOOLTIP: "force-show-tooltip";
|
|
26
|
+
readonly ERROR_TAG: "error-tag";
|
|
27
|
+
readonly WARNING_TAG: "warning-tag";
|
|
28
|
+
readonly PRIORITY_COLOR_TYPE: "priority-color-type";
|
|
29
|
+
readonly BG_COLOR_PERSONALIZE_TYPE: "bg-color-personalize-type";
|
|
30
|
+
readonly BG_COLOR_PERSONALIZE_TYPE_V2: "bgColorPersonalizeType";
|
|
14
31
|
};
|
|
15
|
-
export declare const READONLY_TAG: "readonly-tag", INVALID_TAG: "invalid-tag", REMOVED_TAG: "removed-tag", NO_VIEW_TAG: "no-view-tag", MESSAGE_TAG: "message-tag", FORCE_SHOW_TOOLTIP: "force-show-tooltip";
|
|
32
|
+
export declare const PREPARING_ST: "preparing-setting", READONLY_TAG: "readonly-tag", INVALID_TAG: "invalid-tag", REMOVED_TAG: "removed-tag", NO_VIEW_TAG: "no-view-tag", MESSAGE_TAG: "message-tag", FORCE_SHOW_TOOLTIP: "force-show-tooltip", ERROR_TAG: "error-tag", WARNING_TAG: "warning-tag", PRIORITY_COLOR_TYPE: "priority-color-type", BG_COLOR_PERSONALIZE_TYPE: "bg-color-personalize-type", BG_COLOR_PERSONALIZE_TYPE_V2: "bgColorPersonalizeType";
|
|
16
33
|
export declare const defaultCssVariables: {
|
|
17
34
|
'--input-color': string | undefined;
|
|
18
35
|
'--input-font-size': string;
|
|
@@ -59,9 +76,12 @@ export declare const TAG_TYPE: {
|
|
|
59
76
|
readonly SHORT_LINK_V2: "shortlink_v2";
|
|
60
77
|
readonly DETECT_LINK: "detect_link";
|
|
61
78
|
readonly CONTENT_SOURCE_GROUP: "groups";
|
|
79
|
+
readonly ALLOCATED_CODE: "allocated_code";
|
|
80
|
+
readonly CUSTOM_TAG: "custom_tag";
|
|
62
81
|
readonly UNSUBSCRIBE_WHATSAPP: "unsubscribe_whatsapp";
|
|
63
82
|
};
|
|
64
|
-
export declare const CUSTOMER: "customer", VISITOR: "visitor", EVENT: "event", JOURNEY: "story", CAMPAIGN: "campaign", VARIANT: "variant", PROMOTION_CODE: "promotion_code", CUSTOM_FN: "custom", EMOJI: "emoji", DETECT_LINK: "detect_link", SHORT_LINK: "shortlink", SHORT_LINK_V2: "shortlink_v2", OBJECT_WIDGET: "objectWidget", CONTENT_SOURCE_GROUP: "groups", UNSUBSCRIBE_WHATSAPP: "unsubscribe_whatsapp";
|
|
83
|
+
export declare const CUSTOMER: "customer", VISITOR: "visitor", EVENT: "event", JOURNEY: "story", CAMPAIGN: "campaign", VARIANT: "variant", PROMOTION_CODE: "promotion_code", CUSTOM_FN: "custom", EMOJI: "emoji", DETECT_LINK: "detect_link", SHORT_LINK: "shortlink", SHORT_LINK_V2: "shortlink_v2", OBJECT_WIDGET: "objectWidget", CONTENT_SOURCE_GROUP: "groups", ALLOCATED_CODE: "allocated_code", CUSTOM_TAG: "custom_tag", UNSUBSCRIBE_WHATSAPP: "unsubscribe_whatsapp";
|
|
84
|
+
export declare const TAG_TYPE_LIST: readonly ("campaign" | "event" | "custom" | "emoji" | "variant" | "customer" | "visitor" | "groups" | "shortlink" | "objectWidget" | "promotion_code" | "story" | "shortlink_v2" | "detect_link" | "allocated_code" | "custom_tag" | "unsubscribe_whatsapp")[];
|
|
65
85
|
export declare const SHORT_LINK_TYPE: {
|
|
66
86
|
readonly INDIVIDUAL: "shortlink";
|
|
67
87
|
readonly GENERAL: "shortlink_static";
|
|
@@ -91,6 +111,8 @@ export declare const TAG_COLOR: {
|
|
|
91
111
|
readonly custom: "#bbefbe";
|
|
92
112
|
readonly groups: "#ffdd9f";
|
|
93
113
|
readonly emoji: "transparent";
|
|
114
|
+
readonly allocated_code: "#DAA7F8";
|
|
115
|
+
readonly custom_tag: "transparent";
|
|
94
116
|
readonly unsubscribe_whatsapp: "#cafedd";
|
|
95
117
|
};
|
|
96
118
|
export declare const EMOJI_COLLECTIONS: {
|