@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.
Files changed (44) hide show
  1. package/CHANGELOG.md +92 -63
  2. package/es/components/atoms/SelectAssociatedTag/SelectAssociatedTag.d.ts +5 -0
  3. package/es/components/atoms/SelectAssociatedTag/SelectAssociatedTag.js +15 -0
  4. package/es/components/atoms/SelectAssociatedTag/SelectTag.d.ts +4 -0
  5. package/es/components/atoms/SelectAssociatedTag/SelectTag.js +26 -0
  6. package/es/components/atoms/SelectAssociatedTag/constants.d.ts +11 -0
  7. package/es/components/atoms/SelectAssociatedTag/constants.js +42 -0
  8. package/es/components/atoms/SelectAssociatedTag/index.d.ts +3 -0
  9. package/es/components/atoms/SelectAssociatedTag/index.js +2 -0
  10. package/es/components/atoms/SelectAssociatedTag/styled.d.ts +7 -0
  11. package/es/components/atoms/SelectAssociatedTag/styled.js +60 -0
  12. package/es/components/atoms/SelectAssociatedTag/types.d.ts +76 -0
  13. package/es/components/atoms/SelectAssociatedTag/types.js +1 -0
  14. package/es/components/atoms/index.d.ts +1 -0
  15. package/es/components/atoms/index.js +1 -0
  16. package/es/components/icons/CircleInfoIcon.js +2 -2
  17. package/es/components/icons/LazyIcon/LazyIcon.d.ts +2 -0
  18. package/es/components/icons/LazyIcon/LazyIcon.js +2 -0
  19. package/es/components/molecules/InputSelectAttribute/index.d.ts +25 -0
  20. package/es/components/molecules/InputSelectAttribute/index.js +124 -0
  21. package/es/components/molecules/InputSelectAttribute/styled.d.ts +14 -0
  22. package/es/components/molecules/InputSelectAttribute/styled.js +33 -0
  23. package/es/components/molecules/SelectV2/styled.d.ts +3 -1
  24. package/es/components/molecules/SelectV2/styled.js +2 -2
  25. package/es/components/molecules/TagifyInput/TagifyInput.js +159 -71
  26. package/es/components/molecules/TagifyInput/constants.d.ts +24 -2
  27. package/es/components/molecules/TagifyInput/constants.js +25 -2
  28. package/es/components/molecules/TagifyInput/patternHandlers.d.ts +12 -6
  29. package/es/components/molecules/TagifyInput/patternHandlers.js +88 -43
  30. package/es/components/molecules/TagifyInput/types.d.ts +24 -3
  31. package/es/components/molecules/TagifyInput/utils.d.ts +10 -1
  32. package/es/components/molecules/TagifyInput/utils.js +82 -4
  33. package/es/components/molecules/TagifyInput/utils.style.js +81 -96
  34. package/es/components/molecules/index.d.ts +1 -0
  35. package/es/components/molecules/index.js +1 -0
  36. package/es/components/organism/AccountSharing/AccountSharing.js +18 -10
  37. package/es/components/organism/ActivityTimeline/utils.js +168 -2
  38. package/es/components/organism/LeftMenu/hooks/usePermission.js +1 -1
  39. package/es/components/organism/LeftMenu/utils/index.js +1 -1
  40. package/es/components/organism/TextEditor/TextEditor.js +17 -1
  41. package/es/components/organism/TextEditor/types.d.ts +6 -2
  42. package/es/components/organism/TextEditor/ui/Toolbar/FormattingToolbar.js +11 -6
  43. package/es/utils/cookie.js +9 -0
  44. 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, {}, never>;
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, FORCE_SHOW_TOOLTIP, INVALID_TAG, MESSAGE_TAG, NO_VIEW_TAG, PERSONALIZE_PTN, PROMOTION_CODE, READONLY_TAG, REMOVED_TAG, SHORT_LINK, SHORT_LINK_PTN, SHORT_LINK_V2, UNSUBSCRIBE_WHATSAPP, defaultCssVariables, tagifyDefaultProps, } from './constants';
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({ src: emojiPath, emoji: label, code: value });
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, isValid, message, type, isRemoved, hasViewPermission, } = getPersonalizeTagInfo(tagCode, attributes, errorAttributes);
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
- tagTextNode.textContent = tagLabel;
594
- // Clear all previous attributes
595
- tagElement.removeAttribute(REMOVED_TAG);
596
- tagElement.removeAttribute(NO_VIEW_TAG);
597
- tagElement.removeAttribute(INVALID_TAG);
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, isValid, message, type: tagType, } = getShortLinkTagInfo({
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
- // Clear all previous attributes
656
- tagElement.removeAttribute(REMOVED_TAG);
657
- tagElement.removeAttribute(NO_VIEW_TAG);
658
- tagElement.removeAttribute(INVALID_TAG);
659
- tagElement.removeAttribute(READONLY_TAG);
660
- tagElement.removeAttribute(MESSAGE_TAG);
661
- // In case promotion pool has something wrong
662
- if (!isValid && message) {
663
- tagElement.setAttribute(REMOVED_TAG, 'true');
664
- tagElement.setAttribute(MESSAGE_TAG, message);
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(READONLY_TAG, readonlyTag.toString());
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(READONLY_TAG);
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
- if (!_.isEmpty(mapAttributes)) {
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
- if (!_.isEmpty(mapAttributes)) {
886
- makeValidLabelTags(mapAttributes, mapErrorAttributes);
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: {