@shopgate/engage 7.32.0-beta.2 → 7.32.0-beta.4

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 (85) hide show
  1. package/admin-preview/components/FrontendSettingsPreviewBridge/FrontendSettingsPreviewBridge.d.ts +2 -1
  2. package/admin-preview/components/FrontendSettingsPreviewBridge/FrontendSettingsPreviewBridge.d.ts.map +1 -1
  3. package/admin-preview/components/FrontendSettingsPreviewBridge/FrontendSettingsPreviewBridge.js +23 -1
  4. package/admin-preview/components/FrontendSettingsPreviewBridge/helpers.d.ts +7 -1
  5. package/admin-preview/components/FrontendSettingsPreviewBridge/helpers.d.ts.map +1 -1
  6. package/admin-preview/components/FrontendSettingsPreviewBridge/helpers.js +14 -2
  7. package/admin-preview/components/FrontendSettingsPreviewBridge/types.d.ts +6 -0
  8. package/admin-preview/components/FrontendSettingsPreviewBridge/types.d.ts.map +1 -1
  9. package/admin-preview/constants/index.d.ts +1 -22
  10. package/admin-preview/constants/index.d.ts.map +1 -1
  11. package/admin-preview/constants/index.js +1 -26
  12. package/admin-preview/{detection.d.ts → helpers/_internal/detection.d.ts} +12 -8
  13. package/admin-preview/helpers/_internal/detection.d.ts.map +1 -0
  14. package/admin-preview/helpers/_internal/detection.js +93 -0
  15. package/admin-preview/helpers/index.d.ts +7 -0
  16. package/admin-preview/helpers/index.d.ts.map +1 -1
  17. package/admin-preview/helpers/index.js +10 -2
  18. package/components/Badge/Badge.d.ts +30 -0
  19. package/components/Badge/Badge.d.ts.map +1 -0
  20. package/components/Badge/Badge.js +48 -0
  21. package/components/Badge/index.d.ts +3 -0
  22. package/components/Badge/index.d.ts.map +1 -0
  23. package/components/Badge/index.js +1 -0
  24. package/components/Badge/spec.d.ts +2 -0
  25. package/components/Badge/spec.d.ts.map +1 -0
  26. package/components/Badge/spec.js +75 -0
  27. package/components/View/components/Content/index.d.ts.map +1 -1
  28. package/components/View/components/Content/index.js +4 -1
  29. package/components/index.d.ts +1 -0
  30. package/components/index.d.ts.map +1 -1
  31. package/components/index.js +2 -1
  32. package/core/hooks/useLocalStorage.d.ts +8 -0
  33. package/core/hooks/useLocalStorage.d.ts.map +1 -1
  34. package/core/hooks/useLocalStorage.js +22 -15
  35. package/core/initialization/fetchSettings.d.ts +12 -0
  36. package/core/initialization/fetchSettings.d.ts.map +1 -0
  37. package/core/initialization/fetchSettings.js +134 -0
  38. package/core/initialization/index.d.ts +33 -2
  39. package/core/initialization/index.d.ts.map +1 -1
  40. package/core/initialization/index.js +25 -103
  41. package/package.json +9 -9
  42. package/page/components/ResponsiveWidgetImage/ResponsiveWidgetImage.js +2 -2
  43. package/page/constants/index.js +5 -3
  44. package/product/components/Description/index.js +0 -2
  45. package/product/components/ProductCard/index.js +1 -0
  46. package/product/components/ProductGrid/components/Item/components/ItemDetails/index.js +1 -1
  47. package/product/components/ProductGrid/components/Item/components/ItemImage/index.js +10 -0
  48. package/product/components/ProductGrid/components/Item/components/ItemName/index.js +1 -0
  49. package/product/components/ProductGrid/components/Item/index.js +0 -1
  50. package/product/components/ProductGrid/components/Layout/index.js +17 -3
  51. package/product/components/ProductList/components/Layout/index.js +8 -20
  52. package/styles/helpers/index.d.ts +1 -0
  53. package/styles/helpers/index.js +1 -0
  54. package/styles/helpers/loadThemeCss.d.ts +14 -0
  55. package/styles/helpers/loadThemeCss.d.ts.map +1 -0
  56. package/styles/helpers/loadThemeCss.js +112 -0
  57. package/styles/index.d.ts +1 -1
  58. package/styles/index.d.ts.map +1 -1
  59. package/styles/reset/root.js +1 -1
  60. package/styles/theme/createDefaultThemeOptions/index.d.ts.map +1 -1
  61. package/styles/theme/createDefaultThemeOptions/index.js +12 -1
  62. package/styles/theme/createTheme/createComponents.types.d.ts +22 -0
  63. package/styles/theme/createTheme/createComponents.types.d.ts.map +1 -1
  64. package/styles/theme/createTheme/createComponents.types.js +23 -1
  65. package/styles/theme/createTheme/createCssVarsForColorSchemeThemes.d.ts.map +1 -1
  66. package/styles/theme/createTheme/createCssVarsForColorSchemeThemes.js +4 -1
  67. package/styles/theme/createTheme/index.d.ts +1 -0
  68. package/styles/theme/createTheme/index.d.ts.map +1 -1
  69. package/styles/theme/createTheme/index.js +2 -0
  70. package/styles/theme/createTheme/types.d.ts +2 -2
  71. package/styles/theme/createTheme/types.d.ts.map +1 -1
  72. package/styles/theme/createTheme/types.js +2 -3
  73. package/styles/theme/hooks/useColorScheme.d.ts +4 -3
  74. package/styles/theme/hooks/useColorScheme.d.ts.map +1 -1
  75. package/styles/theme/hooks/useColorScheme.js +4 -3
  76. package/styles/theme/index.d.ts +1 -1
  77. package/styles/theme/index.d.ts.map +1 -1
  78. package/styles/theme/providers/ColorSchemeContext.d.ts +19 -0
  79. package/styles/theme/providers/ColorSchemeContext.d.ts.map +1 -0
  80. package/styles/theme/providers/ColorSchemeContext.js +6 -0
  81. package/styles/theme/providers/ThemeProvider.d.ts +2 -12
  82. package/styles/theme/providers/ThemeProvider.d.ts.map +1 -1
  83. package/styles/theme/providers/ThemeProvider.js +34 -10
  84. package/admin-preview/detection.d.ts.map +0 -1
  85. package/admin-preview/detection.js +0 -166
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Headless bridge that connects the app to the admin frontend settings preview. It listens for
3
- * messages from the parent window and applies the received styling while in preview mode.
3
+ * messages from the parent window and applies the received styling, app settings and color scheme
4
+ * while in preview mode.
4
5
  */
5
6
  declare const FrontendSettingsPreviewBridge: () => null;
6
7
  export default FrontendSettingsPreviewBridge;
@@ -1 +1 @@
1
- {"version":3,"file":"FrontendSettingsPreviewBridge.d.ts","sourceRoot":"","sources":["../../../../admin-preview/components/FrontendSettingsPreviewBridge/FrontendSettingsPreviewBridge.tsx"],"names":[],"mappings":"AAeA;;;GAGG;AACH,QAAA,MAAM,6BAA6B,YAgClC,CAAC;AAEF,eAAe,6BAA6B,CAAC"}
1
+ {"version":3,"file":"FrontendSettingsPreviewBridge.d.ts","sourceRoot":"","sources":["../../../../admin-preview/components/FrontendSettingsPreviewBridge/FrontendSettingsPreviewBridge.tsx"],"names":[],"mappings":"AAmBA;;;;GAIG;AACH,QAAA,MAAM,6BAA6B,YA+ClC,CAAC;AAEF,eAAe,6BAA6B,CAAC"}
@@ -1,17 +1,27 @@
1
+ import "core-js/modules/es.array.includes.js";
1
2
  import { useEffect, useState } from 'react';
2
3
  import { useDispatch } from 'react-redux';
3
4
  import { useIframeMessenger } from '@shopgate/engage/admin-preview/hooks';
4
5
  import { ALLOWED_ADMIN_PREVIEW_ORIGINS } from '@shopgate/engage/admin-preview/constants';
5
6
  import { receiveAppSettings } from '@shopgate/engage/settings/action-creators/appSettings';
7
+ import { logger } from '@shopgate/engage/core/helpers';
8
+ // Imported via its module path, since the "styles" barrel pulls in the ThemeProvider which renders
9
+ // this component, and would therefore create a circular import.
10
+ import useColorScheme from '@shopgate/engage/styles/theme/hooks/useColorScheme';
6
11
  import { getOrCreateStyleTag, removeStyleTag, serializeStyling } from "./helpers";
7
12
 
8
13
  /**
9
14
  * Headless bridge that connects the app to the admin frontend settings preview. It listens for
10
- * messages from the parent window and applies the received styling while in preview mode.
15
+ * messages from the parent window and applies the received styling, app settings and color scheme
16
+ * while in preview mode.
11
17
  */
12
18
  const FrontendSettingsPreviewBridge = () => {
13
19
  const [styling, setStyling] = useState(null);
14
20
  const dispatch = useDispatch();
21
+ const {
22
+ setMode,
23
+ modes
24
+ } = useColorScheme();
15
25
  const {
16
26
  sendToParent
17
27
  } = useIframeMessenger(data => {
@@ -21,6 +31,18 @@ const FrontendSettingsPreviewBridge = () => {
21
31
  dispatch(receiveAppSettings(data.payload.appSettings));
22
32
  }
23
33
  }
34
+ if (data.type === 'setColorScheme') {
35
+ const colorScheme = data.payload?.colorScheme;
36
+
37
+ // setMode rejects unsupported schemes as well, but this message arrives from another origin,
38
+ // so it is worth naming the admin as the source of the bad value. The `colorScheme` guard also
39
+ // narrows away the defensive `undefined`, so `modes.includes` and `setMode` typecheck strictly.
40
+ if (!colorScheme || !modes.includes(colorScheme)) {
41
+ logger.warn(`FrontendSettingsPreviewBridge: received unsupported color scheme "${colorScheme}"`);
42
+ return;
43
+ }
44
+ setMode(colorScheme);
45
+ }
24
46
  }, ALLOWED_ADMIN_PREVIEW_ORIGINS);
25
47
  useEffect(() => {
26
48
  sendToParent({
@@ -8,7 +8,13 @@ export declare const PREVIEW_STYLE_TAG_ID = "frontend-settings-preview-overrides
8
8
  */
9
9
  export declare const serializeStyling: (styling: FrontendSettingsStyling) => string;
10
10
  /**
11
- * Returns the existing preview style tag or creates it in the document head.
11
+ * Returns the existing preview style tag or creates it at the preview css insertion point, which
12
+ * the html template declares directly below the theme css one. The preview has to override both
13
+ * the defaults that theme initialization generates and the theme css file, and since all three
14
+ * have the same specificity, only document order decides.
15
+ *
16
+ * Falls back to appending when the meta is absent, which is where the tag used to live before the
17
+ * insertion points existed.
12
18
  *
13
19
  * @returns The style element used for frontend settings preview overrides.
14
20
  */
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../../admin-preview/components/FrontendSettingsPreviewBridge/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAuB,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAE5E,eAAO,MAAM,oBAAoB,wCAAwC,CAAC;AAuB1E;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAAI,SAAS,uBAAuB,WAiBpD,CAAC;AAEd;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,wBAW/B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,cAAc,YAG1B,CAAC"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../../admin-preview/components/FrontendSettingsPreviewBridge/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAuB,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAE5E,eAAO,MAAM,oBAAoB,wCAAwC,CAAC;AAyB1E;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAAI,SAAS,uBAAuB,WAiBpD,CAAC;AAEd;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,wBAkB/B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,cAAc,YAG1B,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import "core-js/modules/es.string.replace.js";
2
2
  export const PREVIEW_STYLE_TAG_ID = 'frontend-settings-preview-overrides';
3
+ const INSERTION_POINT_SELECTOR = 'meta[name="preview-css-insertion-point"]';
3
4
  const UNITLESS_PROPERTIES = new Set(['font-weight', 'line-height', 'opacity', 'z-index', 'flex', 'flex-grow', 'flex-shrink', 'order']);
4
5
  const toKebabCase = property => property.replace(/[A-Z]/g, character => `-${character.toLowerCase()}`);
5
6
  const stringifyValue = (property, value) => {
@@ -25,7 +26,13 @@ export const serializeStyling = styling => Object.entries(styling).map(([selecto
25
26
  }).filter(Boolean).join('\n');
26
27
 
27
28
  /**
28
- * Returns the existing preview style tag or creates it in the document head.
29
+ * Returns the existing preview style tag or creates it at the preview css insertion point, which
30
+ * the html template declares directly below the theme css one. The preview has to override both
31
+ * the defaults that theme initialization generates and the theme css file, and since all three
32
+ * have the same specificity, only document order decides.
33
+ *
34
+ * Falls back to appending when the meta is absent, which is where the tag used to live before the
35
+ * insertion points existed.
29
36
  *
30
37
  * @returns The style element used for frontend settings preview overrides.
31
38
  */
@@ -35,7 +42,12 @@ export const getOrCreateStyleTag = () => {
35
42
  styleTag = document.createElement('style');
36
43
  styleTag.setAttribute('id', PREVIEW_STYLE_TAG_ID);
37
44
  styleTag.setAttribute('type', 'text/css');
38
- document.head.appendChild(styleTag);
45
+ const insertionPoint = document.querySelector(INSERTION_POINT_SELECTOR);
46
+ if (insertionPoint) {
47
+ insertionPoint.parentNode?.insertBefore(styleTag, insertionPoint.nextSibling);
48
+ } else {
49
+ document.head.appendChild(styleTag);
50
+ }
39
51
  }
40
52
  return styleTag;
41
53
  };
@@ -1,4 +1,5 @@
1
1
  import type { AppSettings } from '@shopgate/engage/settings/types/appSettings';
2
+ import type { ColorSchemeName } from '@shopgate/engage/styles/theme/createTheme';
2
3
  export type CSSDeclarationValue = string | number;
3
4
  export type FrontendSettingsStyling = Record<string, Record<string, CSSDeclarationValue>>;
4
5
  export type FrontendSettingsPreviewBridgeMessage = {
@@ -7,6 +8,11 @@ export type FrontendSettingsPreviewBridgeMessage = {
7
8
  styling?: FrontendSettingsStyling;
8
9
  appSettings?: AppSettings;
9
10
  };
11
+ } | {
12
+ type: 'setColorScheme';
13
+ payload: {
14
+ colorScheme: ColorSchemeName;
15
+ };
10
16
  } | {
11
17
  type: 'frontendSettingsPreviewReady';
12
18
  payload?: never;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../admin-preview/components/FrontendSettingsPreviewBridge/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6CAA6C,CAAC;AAE/E,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,MAAM,CAAC;AAClD,MAAM,MAAM,uBAAuB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAC;AAE1F,MAAM,MAAM,oCAAoC,GAC5C;IACA,IAAI,EAAE,yBAAyB,CAAC;IAChC,OAAO,CAAC,EAAE;QACR,OAAO,CAAC,EAAE,uBAAuB,CAAC;QAClC,WAAW,CAAC,EAAE,WAAW,CAAC;KAC3B,CAAC;CACH,GACC;IACA,IAAI,EAAE,8BAA8B,CAAC;IACrC,OAAO,CAAC,EAAE,KAAK,CAAC;CACjB,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../admin-preview/components/FrontendSettingsPreviewBridge/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6CAA6C,CAAC;AAC/E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2CAA2C,CAAC;AAEjF,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,MAAM,CAAC;AAClD,MAAM,MAAM,uBAAuB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAC;AAE1F,MAAM,MAAM,oCAAoC,GAC5C;IACA,IAAI,EAAE,yBAAyB,CAAC;IAChC,OAAO,CAAC,EAAE;QACR,OAAO,CAAC,EAAE,uBAAuB,CAAC;QAClC,WAAW,CAAC,EAAE,WAAW,CAAC;KAC3B,CAAC;CACH,GACC;IACA,IAAI,EAAE,gBAAgB,CAAC;IACvB,OAAO,EAAE;QACP,WAAW,EAAE,eAAe,CAAC;KAC9B,CAAC;CACH,GACC;IACA,IAAI,EAAE,8BAA8B,CAAC;IACrC,OAAO,CAAC,EAAE,KAAK,CAAC;CACjB,CAAC"}
@@ -1,25 +1,4 @@
1
- export { PAGE_PREVIEW_PATTERN } from '../detection';
2
- /**
3
- * Whether the app is currently in page admin preview mode.
4
- *
5
- * Snapshot taken when this module is first evaluated. Prefer the isPageAdminPreviewActive()
6
- * helper, which re-evaluates on every call.
7
- */
8
- export declare const IS_PAGE_ADMIN_PREVIEW_ACTIVE: boolean;
9
- /**
10
- * Whether the app is currently in frontend settings admin preview mode.
11
- *
12
- * Snapshot taken when this module is first evaluated. Prefer the
13
- * isFrontendSettingsAdminPreviewActive() helper, which re-evaluates on every call.
14
- */
15
- export declare const IS_FRONTEND_SETTINGS_ADMIN_PREVIEW_ACTIVE: boolean;
16
- /**
17
- * Whether the app is currently in admin preview mode (pages or frontend settings).
18
- *
19
- * Snapshot taken when this module is first evaluated. Prefer the isAdminPreviewActive() helper,
20
- * which re-evaluates on every call.
21
- */
22
- export declare const IS_ADMIN_PREVIEW_ACTIVE: boolean;
1
+ export declare const PAGE_PREVIEW_PATTERN = "/shopgate-internal-page-preview";
23
2
  /**
24
3
  * List of allowed origins for cms page preview iFrame communication.
25
4
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../admin-preview/constants/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEpD;;;;;GAKG;AACH,eAAO,MAAM,4BAA4B,SAA2B,CAAC;AAErE;;;;;GAKG;AACH,eAAO,MAAM,yCAAyC,SAAuC,CAAC;AAE9F;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,SAAuB,CAAC;AAE5D;;GAEG;AACH,eAAO,MAAM,6BAA6B,UAMzC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../admin-preview/constants/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,oBAAoB,oCAAoC,CAAC;AAEtE;;GAEG;AACH,eAAO,MAAM,6BAA6B,UAMzC,CAAC"}
@@ -1,29 +1,4 @@
1
- import { detectAdminPreview, detectFrontendSettingsAdminPreview, detectPageAdminPreview } from "../detection";
2
- export { PAGE_PREVIEW_PATTERN } from "../detection";
3
-
4
- /**
5
- * Whether the app is currently in page admin preview mode.
6
- *
7
- * Snapshot taken when this module is first evaluated. Prefer the isPageAdminPreviewActive()
8
- * helper, which re-evaluates on every call.
9
- */
10
- export const IS_PAGE_ADMIN_PREVIEW_ACTIVE = detectPageAdminPreview();
11
-
12
- /**
13
- * Whether the app is currently in frontend settings admin preview mode.
14
- *
15
- * Snapshot taken when this module is first evaluated. Prefer the
16
- * isFrontendSettingsAdminPreviewActive() helper, which re-evaluates on every call.
17
- */
18
- export const IS_FRONTEND_SETTINGS_ADMIN_PREVIEW_ACTIVE = detectFrontendSettingsAdminPreview();
19
-
20
- /**
21
- * Whether the app is currently in admin preview mode (pages or frontend settings).
22
- *
23
- * Snapshot taken when this module is first evaluated. Prefer the isAdminPreviewActive() helper,
24
- * which re-evaluates on every call.
25
- */
26
- export const IS_ADMIN_PREVIEW_ACTIVE = detectAdminPreview();
1
+ export const PAGE_PREVIEW_PATTERN = '/shopgate-internal-page-preview';
27
2
 
28
3
  /**
29
4
  * List of allowed origins for cms page preview iFrame communication.
@@ -1,17 +1,19 @@
1
- export declare const PAGE_PREVIEW_PATTERN = "/shopgate-internal-page-preview";
1
+ /**
2
+ * Internal to the admin-preview package - do not import this module from anywhere else. Consume
3
+ * the isAdminPreviewActive / isPageAdminPreviewActive / isFrontendSettingsAdminPreviewActive
4
+ * helpers from @shopgate/engage/admin-preview/helpers instead.
5
+ */
2
6
  export declare const FRONTEND_SETTINGS_PREVIEW_PARAM = "frontendSettingsPreview=true";
3
- export declare const SESSION_STORAGE_KEY_PAGE_PREVIEW = "sgPageAdminPreview";
4
- export declare const SESSION_STORAGE_KEY_FRONTEND_SETTINGS_PREVIEW = "sgFrontendSettingsAdminPreview";
5
7
  /**
6
- * Detects whether the app is in page admin preview mode. Once detected, the result is remembered
7
- * for the rest of the session, because the url is not guaranteed to keep the preview marker.
8
+ * Detects whether the app is in page admin preview mode. Once detected, the result is latched for
9
+ * the lifetime of the document, because the url is not guaranteed to keep the preview marker.
8
10
  * @returns True if the app is in page admin preview mode, false otherwise.
9
11
  */
10
12
  export declare const detectPageAdminPreview: () => boolean;
11
13
  /**
12
14
  * Detects whether the app is in frontend settings admin preview mode. Once detected, the result is
13
- * remembered for the rest of the session, because the url is not guaranteed to keep the query
14
- * param.
15
+ * latched for the lifetime of the document, because the app keeps navigating while the preview is
16
+ * open and the query param only exists on the url it started on.
15
17
  * @returns True if the app is in frontend settings admin preview mode, false otherwise.
16
18
  */
17
19
  export declare const detectFrontendSettingsAdminPreview: () => boolean;
@@ -26,6 +28,8 @@ export declare const detectAdminPreview: () => boolean;
26
28
  *
27
29
  * window.SGAdminPreviewDebug()
28
30
  *
31
+ * Compare initialHref with currentHref to see whether the url changed since the app booted, and
32
+ * check latched to see whether a result came from the latch rather than the current url.
29
33
  * @returns A snapshot of everything detection depends on.
30
34
  */
31
35
  export declare const getAdminPreviewDebugInfo: () => {
@@ -36,7 +40,7 @@ export declare const getAdminPreviewDebugInfo: () => {
36
40
  urlChangedSinceBoot: boolean;
37
41
  isInIframe: boolean;
38
42
  referrer: string;
39
- stickyFlags: {
43
+ latched: {
40
44
  page: boolean;
41
45
  frontendSettings: boolean;
42
46
  };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detection.d.ts","sourceRoot":"","sources":["../../../../admin-preview/helpers/_internal/detection.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,eAAO,MAAM,+BAA+B,iCAAiC,CAAC;AAsB9E;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,QAAO,OAQzC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,kCAAkC,QAAO,OAWrD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,QAAO,OAC4B,CAAC;AAEnE;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;CAiBnC,CAAC"}
@@ -0,0 +1,93 @@
1
+ import "core-js/modules/es.array.includes.js";
2
+ /**
3
+ * Internal to the admin-preview package - do not import this module from anywhere else. Consume
4
+ * the isAdminPreviewActive / isPageAdminPreviewActive / isFrontendSettingsAdminPreviewActive
5
+ * helpers from @shopgate/engage/admin-preview/helpers instead.
6
+ */
7
+
8
+ import { PAGE_PREVIEW_PATTERN } from "../../constants";
9
+ export const FRONTEND_SETTINGS_PREVIEW_PARAM = 'frontendSettingsPreview=true';
10
+
11
+ /**
12
+ * The url as it was when this module was first evaluated. Captured here because detection can run
13
+ * again later, when the url may already have been rewritten.
14
+ */
15
+ const INITIAL_HREF = window.location.href;
16
+
17
+ /**
18
+ * Preview mode is latched in memory rather than persisted. Session storage is scoped per origin
19
+ * per tab, not per iframe - the admin shows the preview in same-origin iframes on several screens
20
+ * within one tab, so a persisted flag from one screen would leak into the next. Module state is
21
+ * per document, so each iframe gets its own.
22
+ *
23
+ * The latches only ever go false -> true. Detection must not fall back to re-reading the url,
24
+ * because the frontend settings preview navigates the app normally: once the admin opens another
25
+ * page, the query param is gone from the url even though preview mode is still on.
26
+ */
27
+ let pageAdminPreviewLatched = false;
28
+ let frontendSettingsAdminPreviewLatched = false;
29
+
30
+ /**
31
+ * Detects whether the app is in page admin preview mode. Once detected, the result is latched for
32
+ * the lifetime of the document, because the url is not guaranteed to keep the preview marker.
33
+ * @returns True if the app is in page admin preview mode, false otherwise.
34
+ */
35
+ export const detectPageAdminPreview = () => {
36
+ if (pageAdminPreviewLatched) {
37
+ return true;
38
+ }
39
+ pageAdminPreviewLatched = window.location.pathname.startsWith(PAGE_PREVIEW_PATTERN);
40
+ return pageAdminPreviewLatched;
41
+ };
42
+
43
+ /**
44
+ * Detects whether the app is in frontend settings admin preview mode. Once detected, the result is
45
+ * latched for the lifetime of the document, because the app keeps navigating while the preview is
46
+ * open and the query param only exists on the url it started on.
47
+ * @returns True if the app is in frontend settings admin preview mode, false otherwise.
48
+ */
49
+ export const detectFrontendSettingsAdminPreview = () => {
50
+ if (frontendSettingsAdminPreviewLatched) {
51
+ return true;
52
+ }
53
+ frontendSettingsAdminPreviewLatched = window.location.search.includes(FRONTEND_SETTINGS_PREVIEW_PARAM);
54
+ return frontendSettingsAdminPreviewLatched;
55
+ };
56
+
57
+ /**
58
+ * Detects whether the app is in admin preview mode (pages or frontend settings).
59
+ * @returns True if the app is in admin preview mode, false otherwise.
60
+ */
61
+ export const detectAdminPreview = () => detectPageAdminPreview() || detectFrontendSettingsAdminPreview();
62
+
63
+ /**
64
+ * Returns the current detection state. Exposed on window as SGAdminPreviewDebug so the values can
65
+ * be inspected on a deployed build, from the console of the preview iframe:
66
+ *
67
+ * window.SGAdminPreviewDebug()
68
+ *
69
+ * Compare initialHref with currentHref to see whether the url changed since the app booted, and
70
+ * check latched to see whether a result came from the latch rather than the current url.
71
+ * @returns A snapshot of everything detection depends on.
72
+ */
73
+ export const getAdminPreviewDebugInfo = () => ({
74
+ initialHref: INITIAL_HREF,
75
+ currentHref: window.location.href,
76
+ pathname: window.location.pathname,
77
+ search: window.location.search,
78
+ urlChangedSinceBoot: INITIAL_HREF !== window.location.href,
79
+ isInIframe: window.parent !== window,
80
+ referrer: document.referrer,
81
+ latched: {
82
+ page: pageAdminPreviewLatched,
83
+ frontendSettings: frontendSettingsAdminPreviewLatched
84
+ },
85
+ detected: {
86
+ page: detectPageAdminPreview(),
87
+ frontendSettings: detectFrontendSettingsAdminPreview(),
88
+ any: detectAdminPreview()
89
+ }
90
+ });
91
+
92
+ // @ts-expect-error - debug-only global, intentionally not on the Window type.
93
+ window.SGAdminPreviewDebug = getAdminPreviewDebugInfo;
@@ -13,4 +13,11 @@ export declare const isPageAdminPreviewActive: () => boolean;
13
13
  * @returns True if the app is in frontend settings admin preview mode, false otherwise.
14
14
  */
15
15
  export declare const isFrontendSettingsAdminPreviewActive: () => boolean;
16
+ /**
17
+ * Checks if navigation is blocked. Page admin preview renders a single page in an iframe, so
18
+ * leaving that page would only ever show the admin something it did not ask to preview.
19
+ * Frontend settings preview is not affected - it previews styling on the regular app.
20
+ * @returns True if the app must not navigate, false otherwise.
21
+ */
22
+ export declare const isNavigationBlocked: () => boolean;
16
23
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../admin-preview/helpers/index.ts"],"names":[],"mappings":"AAMA;;;GAGG;AACH,eAAO,MAAM,oBAAoB,QAAO,OAClB,CAAC;AAEvB;;;GAGG;AACH,eAAO,MAAM,wBAAwB,QAAO,OAClB,CAAC;AAE3B;;;GAGG;AACH,eAAO,MAAM,oCAAoC,QAAO,OAClB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../admin-preview/helpers/index.ts"],"names":[],"mappings":"AAMA;;;GAGG;AACH,eAAO,MAAM,oBAAoB,QAAO,OAClB,CAAC;AAEvB;;;GAGG;AACH,eAAO,MAAM,wBAAwB,QAAO,OAClB,CAAC;AAE3B;;;GAGG;AACH,eAAO,MAAM,oCAAoC,QAAO,OAClB,CAAC;AAEvC;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,QAAO,OACX,CAAC"}
@@ -1,4 +1,4 @@
1
- import { detectAdminPreview, detectFrontendSettingsAdminPreview, detectPageAdminPreview } from "../detection";
1
+ import { detectAdminPreview, detectFrontendSettingsAdminPreview, detectPageAdminPreview } from "./_internal/detection";
2
2
 
3
3
  /**
4
4
  * Checks if the app is currently in admin preview mode (pages or frontend settings).
@@ -16,4 +16,12 @@ export const isPageAdminPreviewActive = () => detectPageAdminPreview();
16
16
  * Checks if the app is currently in frontend settings admin preview mode.
17
17
  * @returns True if the app is in frontend settings admin preview mode, false otherwise.
18
18
  */
19
- export const isFrontendSettingsAdminPreviewActive = () => detectFrontendSettingsAdminPreview();
19
+ export const isFrontendSettingsAdminPreviewActive = () => detectFrontendSettingsAdminPreview();
20
+
21
+ /**
22
+ * Checks if navigation is blocked. Page admin preview renders a single page in an iframe, so
23
+ * leaving that page would only ever show the admin something it did not ask to preview.
24
+ * Frontend settings preview is not affected - it previews styling on the regular app.
25
+ * @returns True if the app must not navigate, false otherwise.
26
+ */
27
+ export const isNavigationBlocked = () => isPageAdminPreviewActive();
@@ -0,0 +1,30 @@
1
+ export interface BadgeProps {
2
+ /**
3
+ * The number to display. The badge renders nothing when this is 0.
4
+ */
5
+ count: number;
6
+ /**
7
+ * Counts above this are displayed as `{max}+`.
8
+ */
9
+ max?: number;
10
+ /**
11
+ * Additional CSS classes. Callers own the badge's positioning.
12
+ */
13
+ className?: string;
14
+ /**
15
+ * Inline style overrides.
16
+ */
17
+ style?: React.CSSProperties;
18
+ /**
19
+ * Renders an empty bubble when false, to indicate state without a number.
20
+ */
21
+ showCount?: boolean;
22
+ [key: `data-${string}`]: string;
23
+ }
24
+ /**
25
+ * A counter bubble used to display item counts on icons, e.g. within the tab bar or app bar.
26
+ * @returns The rendered component.
27
+ */
28
+ declare const Badge: ({ count, max, className, style, showCount, ...restProps }: BadgeProps) => import("react").JSX.Element | null;
29
+ export default Badge;
30
+ //# sourceMappingURL=Badge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Badge.d.ts","sourceRoot":"","sources":["../../../components/Badge/Badge.tsx"],"names":[],"mappings":"AAkBA,MAAM,WAAW,UAAU;IACzB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,CAAC,GAAG,EAAE,QAAQ,MAAM,EAAE,GAAG,MAAM,CAAC;CACjC;AAED;;;GAGG;AACH,QAAA,MAAM,KAAK,GAAI,2DAOZ,UAAU,uCAqBZ,CAAC;AAEF,eAAe,KAAK,CAAC"}
@@ -0,0 +1,48 @@
1
+ import { makeStyles } from '@shopgate/engage/styles';
2
+ import { Typography } from "../Typography";
3
+ import { jsx as _jsx } from "react/jsx-runtime";
4
+ const useStyles = makeStyles()(theme => ({
5
+ root: {
6
+ display: 'flex',
7
+ alignItems: 'center',
8
+ justifyContent: 'center',
9
+ background: theme.components.badge.background,
10
+ color: theme.contrastColor(theme.components.badge.background),
11
+ borderRadius: theme.spacing(1),
12
+ height: theme.spacing(2),
13
+ minWidth: theme.spacing(2),
14
+ paddingLeft: theme.spacing(0.5),
15
+ paddingRight: theme.spacing(0.5)
16
+ }
17
+ }));
18
+ /**
19
+ * A counter bubble used to display item counts on icons, e.g. within the tab bar or app bar.
20
+ * @returns The rendered component.
21
+ */
22
+ const Badge = ({
23
+ count,
24
+ max,
25
+ className,
26
+ style,
27
+ showCount = true,
28
+ ...restProps
29
+ }) => {
30
+ const {
31
+ classes,
32
+ cx
33
+ } = useStyles();
34
+ if (!count) {
35
+ return null;
36
+ }
37
+ const label = max !== undefined && count > max ? `${max}+` : `${count}`;
38
+ return /*#__PURE__*/_jsx(Typography, {
39
+ variant: "caption",
40
+ component: "div",
41
+ fontWeight: "bold",
42
+ style: style,
43
+ className: cx(classes.root, className, 'theme__badge'),
44
+ ...restProps,
45
+ children: showCount ? label : ''
46
+ });
47
+ };
48
+ export default Badge;
@@ -0,0 +1,3 @@
1
+ export { default } from './Badge';
2
+ export type { BadgeProps } from './Badge';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../components/Badge/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC"}
@@ -0,0 +1 @@
1
+ export { default } from "./Badge";
@@ -0,0 +1,2 @@
1
+ import '@testing-library/jest-dom';
2
+ //# sourceMappingURL=spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec.d.ts","sourceRoot":"","sources":["../../../components/Badge/spec.tsx"],"names":[],"mappings":"AAGA,OAAO,2BAA2B,CAAC"}
@@ -0,0 +1,75 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ // Loaded at runtime by utils/unit-tests/envSetup.js; imported here for the matcher types.
4
+ import '@testing-library/jest-dom';
5
+ import { ThemeProvider, createTheme } from '@shopgate/engage/styles';
6
+ import Badge from "./Badge";
7
+
8
+ // ThemeProvider resolves the active breakpoint via matchMedia, which jsdom doesn't implement.
9
+ import { jsx as _jsx } from "react/jsx-runtime";
10
+ Object.defineProperty(window, 'matchMedia', {
11
+ writable: true,
12
+ value: query => ({
13
+ matches: false,
14
+ media: query,
15
+ addEventListener: jest.fn(),
16
+ removeEventListener: jest.fn()
17
+ })
18
+ });
19
+ const theme = createTheme();
20
+
21
+ /**
22
+ * Renders the subject within a theme context.
23
+ * @param ui The element to render.
24
+ * @returns The render result.
25
+ */
26
+ const renderWithTheme = ui => render(/*#__PURE__*/_jsx(ThemeProvider, {
27
+ theme: theme,
28
+ children: ui
29
+ }));
30
+ describe('<Badge />', () => {
31
+ it('should render the count', () => {
32
+ renderWithTheme(/*#__PURE__*/_jsx(Badge, {
33
+ count: 3
34
+ }));
35
+ expect(screen.getByText('3')).toBeInTheDocument();
36
+ });
37
+ it('should render nothing when the count is zero', () => {
38
+ renderWithTheme(/*#__PURE__*/_jsx(Badge, {
39
+ count: 0
40
+ }));
41
+ expect(screen.queryByText('0')).not.toBeInTheDocument();
42
+ });
43
+ it('should cap the count at max', () => {
44
+ renderWithTheme(/*#__PURE__*/_jsx(Badge, {
45
+ count: 6,
46
+ max: 5
47
+ }));
48
+ expect(screen.getByText('5+')).toBeInTheDocument();
49
+ expect(screen.queryByText('6')).not.toBeInTheDocument();
50
+ });
51
+ it('should render the exact count when it equals max', () => {
52
+ renderWithTheme(/*#__PURE__*/_jsx(Badge, {
53
+ count: 5,
54
+ max: 5
55
+ }));
56
+ expect(screen.getByText('5')).toBeInTheDocument();
57
+ });
58
+ it('should render an empty bubble when showCount is false', () => {
59
+ renderWithTheme(/*#__PURE__*/_jsx(Badge, {
60
+ count: 7,
61
+ showCount: false
62
+ }));
63
+ expect(screen.queryByText('7')).not.toBeInTheDocument();
64
+ expect(document.querySelector('.theme__badge')).toBeInTheDocument();
65
+ });
66
+ it('should apply the shared badge class so themes can target it', () => {
67
+ renderWithTheme(/*#__PURE__*/_jsx(Badge, {
68
+ count: 1,
69
+ className: "custom-position"
70
+ }));
71
+ const badge = document.querySelector('.theme__badge');
72
+ expect(badge).toBeInTheDocument();
73
+ expect(badge).toHaveClass('custom-position');
74
+ });
75
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../components/View/components/Content/index.jsx"],"names":[],"mappings":"AAiQe,yDAUd;;kBApQM,OAAO"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../components/View/components/Content/index.jsx"],"names":[],"mappings":"AAoQe,yDAUd;;kBAvQM,OAAO"}
@@ -38,7 +38,10 @@ const useStyles = makeStyles()({
38
38
  WebkitOverflowScrolling: 'touch',
39
39
  ...(isAdminPreviewActive() && {
40
40
  scrollbarWidth: 'thin',
41
- backgroundColor: 'var(--page-background-color)'
41
+ backgroundColor: 'var(--page-background-color)',
42
+ // Fill the positioned ancestor exactly. In Safari `100vw` (from containerBase)
43
+ // includes the vertical scrollbar width, causing a spurious horizontal scrollbar.
44
+ width: '100%'
42
45
  })
43
46
  },
44
47
  containerWindow: {
@@ -183,4 +183,5 @@ export { default as Switch } from './Switch';
183
183
  export { default as SnackBarContainer } from './SnackBarContainer';
184
184
  export { default as ConnectedReactPortal } from './ConnectedReactPortal';
185
185
  export { Typography } from './Typography';
186
+ export { default as Badge } from './Badge';
186
187
  //# sourceMappingURL=index.d.ts.map