@capillarytech/creatives-library 9.0.47 → 9.0.48

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.
@@ -179,6 +179,7 @@ export const DLT_HASH_TOKEN_INNER_NAME_REGEX = /^\{#\s*(.*?)\s*#\}$/;
179
179
  export const MUSTACHE_VAR_NAME_CAPTURE_REGEX = /\{\{([^}]+)\}\}/g;
180
180
  /** Global with capture group: inner body inside `{#body#}`. */
181
181
  export const DLT_VAR_BODY_CAPTURE_REGEX = /\{\#([^#]*)\#\}/g;
182
+ export const DLT_HASH_TOKEN_SPLIT_REGEX = /(\{#[^#]*#\})/g;
182
183
 
183
184
  export const GET_TRANSLATION_MAPPED = {
184
185
  'en': 'en-US',
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "9.0.47",
4
+ "version": "9.0.48",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
@@ -24,11 +24,29 @@ import {
24
24
  TIME_FORMAT_PM,
25
25
  } from '../constants';
26
26
  import messages from '../messages';
27
+ import { DLT_HASH_TOKEN_SPLIT_REGEX } from '../../../constants/unified';
27
28
 
28
29
  // Import device mockup images
29
30
  const smsMobileAndroid = require('../../../assets/Android.png');
30
31
  const smsMobileIos = require('../../../assets/iOS.png');
31
32
 
33
+ // Split-with-capture keeps DLT tokens at odd indices so they can be styled distinctly.
34
+ function renderSmsPreviewWithHighlightedTokens(text) {
35
+ if (typeof text !== 'string' || !text) return text;
36
+ return text.split(DLT_HASH_TOKEN_SPLIT_REGEX).map((part, i) => (
37
+ i % 2 === 1
38
+ ? (
39
+ <CapLabel.CapLabelInline
40
+ key={`dlt-tok-${i}`}
41
+ className="sms-preview-dlt-token"
42
+ >
43
+ {part}
44
+ </CapLabel.CapLabelInline>
45
+ )
46
+ : part
47
+ ));
48
+ }
49
+
32
50
  const SmsPreviewContent = ({
33
51
  content,
34
52
  device,
@@ -125,7 +143,9 @@ const SmsPreviewContent = ({
125
143
  <CapRow className="sms-message-container">
126
144
  <CapRow useLegacy className="sms-message-bubble">
127
145
  <CapRow useLegacy className="sms-message-text">
128
- {content || formatMessage(messages.noContent)}
146
+ {content
147
+ ? renderSmsPreviewWithHighlightedTokens(content)
148
+ : formatMessage(messages.noContent)}
129
149
  </CapRow>
130
150
  </CapRow>
131
151
  <CapRow useLegacy className="sms-timestamp">
@@ -2744,3 +2744,8 @@
2744
2744
  }
2745
2745
  }
2746
2746
  }
2747
+
2748
+ .sms-preview-dlt-token {
2749
+ color: $CAP_BLUE01;
2750
+ font-size: 0.8125rem;
2751
+ }
@@ -1350,7 +1350,10 @@ const CommonTestAndPreview = (props) => {
1350
1350
  const language = formDataObj?.language || formDataObj?.templateLanguage || 'en';
1351
1351
  const mediaType = formDataObj?.mediaType || formDataObj?.templateMediaType || MEDIA_TYPE_TEXT;
1352
1352
  const buttonType = formDataObj?.buttonType || BUTTON_TYPE_NONE;
1353
- const varMapped = formDataObj?.varMapped || {};
1353
+ const existingVarMapped = formDataObj?.varMapped || {};
1354
+ const varMapped = Object.keys(existingVarMapped).length > 0
1355
+ ? existingVarMapped
1356
+ : (customValuesObj || {});
1354
1357
  const isPreviewUrl = formDataObj?.isPreviewUrl || false;
1355
1358
 
1356
1359
  // Extract buttons from ctaData or quickReplyData
@@ -601,6 +601,38 @@ describe('SmsPreviewContent', () => {
601
601
  });
602
602
  });
603
603
 
604
+ describe('DLT token highlighting', () => {
605
+ it('wraps `{#…#}` tokens in a highlighted span and leaves plain text as-is', () => {
606
+ const { container } = render(
607
+ <TestWrapper>
608
+ <ComponentToRender {...defaultProps} content="Hi {#alp#}, code {#num#} thanks" />
609
+ </TestWrapper>
610
+ );
611
+ const tokens = container.querySelectorAll('.sms-preview-dlt-token');
612
+ expect(tokens.length).toBe(2);
613
+ expect(tokens[0].textContent).toBe('{#alp#}');
614
+ expect(tokens[1].textContent).toBe('{#num#}');
615
+ });
616
+
617
+ it('renders no token spans when content has no DLT tokens', () => {
618
+ const { container } = render(
619
+ <TestWrapper>
620
+ <ComponentToRender {...defaultProps} content="Plain SMS body" />
621
+ </TestWrapper>
622
+ );
623
+ expect(container.querySelectorAll('.sms-preview-dlt-token').length).toBe(0);
624
+ });
625
+
626
+ it('falls back to noContent placeholder when content is empty', () => {
627
+ render(
628
+ <TestWrapper>
629
+ <ComponentToRender {...defaultProps} content="" />
630
+ </TestWrapper>
631
+ );
632
+ expect(screen.getByText('No content available')).toBeTruthy();
633
+ });
634
+ });
635
+
604
636
  describe('PropTypes Validation', () => {
605
637
  it('should accept all required props without warnings', () => {
606
638
  const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
@@ -1,3 +1,6 @@
1
+ import {
2
+ CAP_BLUE01, CAP_GREEN01, CAP_PURPLE01, CAP_PURPLE03, CAP_PALE_GREY, CAP_G04, CAP_G08,
3
+ } from '@capillarytech/cap-ui-library/styled/variables';
1
4
  import {
2
5
  WHATSAPP,
3
6
  RCS,
@@ -45,6 +48,22 @@ export const SMS_CATEGORY_FILTERS = {
45
48
  SERVICE_IMPLICIT: 'implicit',
46
49
  };
47
50
 
51
+ /** Backend `versions.base.type` display strings for DLT SMS categories. */
52
+ export const SMS_CATEGORY_LABELS = {
53
+ PROMOTIONAL: 'promotional',
54
+ SERVICE_IMPLICIT: 'service implicit',
55
+ SERVICE_EXPLICIT: 'service explicit',
56
+ TRANSACTIONAL: 'transactional',
57
+ };
58
+
59
+ /** Colored-tag styling per DLT SMS category, keyed by lowercased `versions.base.type`. */
60
+ export const SMS_CATEGORY_TAG_STYLES = {
61
+ [SMS_CATEGORY_LABELS.PROMOTIONAL]: { tagColor: CAP_PALE_GREY, tagTextColor: CAP_BLUE01 },
62
+ [SMS_CATEGORY_LABELS.SERVICE_IMPLICIT]: { tagColor: '#E7F6EC', tagTextColor: CAP_GREEN01 },
63
+ [SMS_CATEGORY_LABELS.SERVICE_EXPLICIT]: { tagColor: CAP_PURPLE03, tagTextColor: CAP_PURPLE01 },
64
+ [SMS_CATEGORY_LABELS.TRANSACTIONAL]: { tagColor: CAP_G08, tagTextColor: CAP_G04 },
65
+ };
66
+
48
67
  /** `location` for embedded SMS creatives picker (CreativesContainer / TemplatesV2). */
49
68
  export const EMBEDDED_SMS_CREATIVES_LOCATION = {
50
69
  pathname: '/sms/create',
@@ -263,9 +263,6 @@ function SmsFallbackPortalContent({
263
263
  <CapRadio.Button value={SMS_CATEGORY_FILTERS.PROMOTIONAL}>
264
264
  <CapLabel type="label2"><FormattedMessage {...templatesListMessages.promotional} /></CapLabel>
265
265
  </CapRadio.Button>
266
- <CapRadio.Button value={SMS_CATEGORY_FILTERS.SERVICE_EXPLICIT}>
267
- <CapLabel type="label2"><FormattedMessage {...templatesListMessages.serviceExplicit} /></CapLabel>
268
- </CapRadio.Button>
269
266
  <CapRadio.Button value={SMS_CATEGORY_FILTERS.SERVICE_IMPLICIT}>
270
267
  <CapLabel type="label2"><FormattedMessage {...templatesListMessages.serviceImplicit} /></CapLabel>
271
268
  </CapRadio.Button>
@@ -15,3 +15,5 @@ export const DEFAULT = 'default';
15
15
  export const FULL = 'full';
16
16
  export const ALL = 'all';
17
17
  export const LIBRARY = 'library';
18
+ /** Matches horizontal whitespace only (spaces, tabs) — preserves \r\n so newline-only chunks are still non-empty. */
19
+ export const HORIZONTAL_WHITESPACE_ONLY_REGEX = /[^\S\r\n]/gm;
@@ -0,0 +1,88 @@
1
+ export const DLT_VAR_TYPE_KEYS = [
2
+ 'numeric',
3
+ 'alphanumeric',
4
+ 'url',
5
+ 'urlott',
6
+ 'cbn',
7
+ 'email',
8
+ ];
9
+
10
+ export const DLT_VAR_TYPE_META = {
11
+ numeric: {
12
+ short: '{#num#}',
13
+ long: '{#numeric#}',
14
+ charLimit: 40,
15
+ placeholderKey: 'dltVarPlaceholderNumeric',
16
+ },
17
+ alphanumeric: {
18
+ short: '{#alp#}',
19
+ long: '{#alphanumeric#}',
20
+ charLimit: 40,
21
+ placeholderKey: 'dltVarPlaceholderAlphanumeric',
22
+ },
23
+ url: {
24
+ short: '{#urg#}',
25
+ long: '{#url#}',
26
+ charLimit: 120,
27
+ placeholderKey: 'dltVarPlaceholderUrl',
28
+ },
29
+ urlott: {
30
+ short: '{#uro#}',
31
+ long: '{#urlott#}',
32
+ charLimit: 120,
33
+ placeholderKey: 'dltVarPlaceholderUrlott',
34
+ },
35
+ cbn: {
36
+ short: '{#cbn#}',
37
+ long: '{#cbn#}',
38
+ charLimit: 14,
39
+ placeholderKey: 'dltVarPlaceholderCbn',
40
+ },
41
+ email: {
42
+ short: '{#eml#}',
43
+ long: '{#email#}',
44
+ charLimit: 40,
45
+ placeholderKey: 'dltVarPlaceholderEmail',
46
+ },
47
+ };
48
+
49
+ export const DLT_LEGACY_VAR_TOKEN = '{#var#}';
50
+
51
+ const TOKEN_TO_TYPE = (() => {
52
+ const map = {};
53
+ DLT_VAR_TYPE_KEYS.forEach((key) => {
54
+ const meta = DLT_VAR_TYPE_META[key];
55
+ map[meta.short.toLowerCase()] = key;
56
+ map[meta.long.toLowerCase()] = key;
57
+ });
58
+ map[DLT_LEGACY_VAR_TOKEN] = 'alphanumeric';
59
+ return map;
60
+ })();
61
+
62
+ export const ANY_DLT_HASH_TOKEN_REGEX = /^\{#\s*[^#]*\s*#\}$/;
63
+ export const ANY_DLT_HASH_TOKEN_GLOBAL_REGEX = /\{#\s*[^#]*\s*#\}/g;
64
+ export const ANY_DLT_HASH_TOKEN_PROBE_REGEX = /\{#[^#]*#\}/;
65
+ export const DLT_LEGACY_VAR_REGEX = /\{#\s*var\s*#\}/i;
66
+ export const DLT_HASH_TOKEN_SPLIT_REGEX = /(\{#[^#]*#\})/g;
67
+
68
+ export function isAnyDltVarToken(candidate) {
69
+ return typeof candidate === 'string' && ANY_DLT_HASH_TOKEN_REGEX.test(candidate);
70
+ }
71
+
72
+ function normaliseToken(token) {
73
+ if (typeof token !== 'string') return '';
74
+ return token.replace(/\s+/g, '').toLowerCase();
75
+ }
76
+
77
+ export function getDltVarTypeKey(token) {
78
+ return TOKEN_TO_TYPE[normaliseToken(token)] || null;
79
+ }
80
+
81
+ export function getDltVarTypeMeta(token) {
82
+ const key = getDltVarTypeKey(token);
83
+ return key ? DLT_VAR_TYPE_META[key] : null;
84
+ }
85
+
86
+ export function getDltVarCharLimit(token, fallback = 40) {
87
+ return getDltVarTypeMeta(token)?.charLimit ?? fallback;
88
+ }