@redneckz/wildless-cms-uni-blocks 0.14.249 → 0.14.251

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 (122) hide show
  1. package/bundle/blocks.schema.json +1 -1
  2. package/bundle/bundle.umd.js +96 -46
  3. package/bundle/bundle.umd.min.js +1 -1
  4. package/bundle/components/Header/HeaderChatBotButton.d.ts +4 -1
  5. package/bundle/components/Header/HeaderSecondaryMenuButton.d.ts +4 -1
  6. package/bundle/components/Header/useChatBot.d.ts +4 -1
  7. package/bundle/hooks/useExternalNS.d.ts +2 -1
  8. package/bundle/hooks/useNavigateHandler.d.ts +1 -0
  9. package/bundle/model/LinkProps.d.ts +2 -0
  10. package/dist/components/Header/HeaderChatBotButton.d.ts +4 -1
  11. package/dist/components/Header/HeaderChatBotButton.js +3 -11
  12. package/dist/components/Header/HeaderChatBotButton.js.map +1 -1
  13. package/dist/components/Header/HeaderSecondaryMenuButton.d.ts +4 -1
  14. package/dist/components/Header/HeaderSecondaryMenuButton.js.map +1 -1
  15. package/dist/components/Header/useChatBot.d.ts +4 -1
  16. package/dist/components/Header/useChatBot.js +24 -3
  17. package/dist/components/Header/useChatBot.js.map +1 -1
  18. package/dist/hooks/useExternalNS.d.ts +2 -1
  19. package/dist/hooks/useExternalNS.js +49 -23
  20. package/dist/hooks/useExternalNS.js.map +1 -1
  21. package/dist/hooks/useNavigateHandler.d.ts +1 -0
  22. package/dist/hooks/useNavigateHandler.js +19 -8
  23. package/dist/hooks/useNavigateHandler.js.map +1 -1
  24. package/dist/model/LinkProps.d.ts +2 -0
  25. package/dist/ui-kit/Select/Select.js +1 -1
  26. package/dist/ui-kit/Select/Select.js.map +1 -1
  27. package/dist/ui-kit/Select/SelectPopup.js +1 -1
  28. package/dist/ui-kit/Select/SelectPopup.js.map +1 -1
  29. package/dist/ui-kit/YandexMap/useYandexMaps.js +1 -1
  30. package/dist/ui-kit/YandexMap/useYandexMaps.js.map +1 -1
  31. package/lib/common.css +1 -1
  32. package/lib/components/Header/HeaderChatBotButton.d.ts +4 -1
  33. package/lib/components/Header/HeaderChatBotButton.js +3 -11
  34. package/lib/components/Header/HeaderChatBotButton.js.map +1 -1
  35. package/lib/components/Header/HeaderSecondaryMenuButton.d.ts +4 -1
  36. package/lib/components/Header/HeaderSecondaryMenuButton.js.map +1 -1
  37. package/lib/components/Header/useChatBot.d.ts +4 -1
  38. package/lib/components/Header/useChatBot.js +25 -4
  39. package/lib/components/Header/useChatBot.js.map +1 -1
  40. package/lib/hooks/useExternalNS.d.ts +2 -1
  41. package/lib/hooks/useExternalNS.js +48 -23
  42. package/lib/hooks/useExternalNS.js.map +1 -1
  43. package/lib/hooks/useNavigateHandler.d.ts +1 -0
  44. package/lib/hooks/useNavigateHandler.js +19 -8
  45. package/lib/hooks/useNavigateHandler.js.map +1 -1
  46. package/lib/model/LinkProps.d.ts +2 -0
  47. package/lib/ui-kit/Select/Select.js +1 -1
  48. package/lib/ui-kit/Select/Select.js.map +1 -1
  49. package/lib/ui-kit/Select/SelectPopup.js +1 -1
  50. package/lib/ui-kit/Select/SelectPopup.js.map +1 -1
  51. package/lib/ui-kit/YandexMap/useYandexMaps.js +1 -1
  52. package/lib/ui-kit/YandexMap/useYandexMaps.js.map +1 -1
  53. package/mobile/bundle/bundle.umd.js +100 -34
  54. package/mobile/bundle/bundle.umd.min.js +1 -1
  55. package/mobile/bundle/components/Header/HeaderChatBotButton.d.ts +4 -1
  56. package/mobile/bundle/components/Header/HeaderSecondaryMenuButton.d.ts +4 -1
  57. package/mobile/bundle/components/Header/useChatBot.d.ts +4 -1
  58. package/mobile/bundle/hooks/useExternalNS.d.ts +2 -1
  59. package/mobile/bundle/hooks/useNavigateHandler.d.ts +1 -0
  60. package/mobile/bundle/model/LinkProps.d.ts +2 -0
  61. package/mobile/dist/components/Header/HeaderChatBotButton.d.ts +4 -1
  62. package/mobile/dist/components/Header/HeaderChatBotButton.js +3 -11
  63. package/mobile/dist/components/Header/HeaderChatBotButton.js.map +1 -1
  64. package/mobile/dist/components/Header/HeaderSecondaryMenuButton.d.ts +4 -1
  65. package/mobile/dist/components/Header/HeaderSecondaryMenuButton.js.map +1 -1
  66. package/mobile/dist/components/Header/useChatBot.d.ts +4 -1
  67. package/mobile/dist/components/Header/useChatBot.js +24 -3
  68. package/mobile/dist/components/Header/useChatBot.js.map +1 -1
  69. package/mobile/dist/hooks/useExternalNS.d.ts +2 -1
  70. package/mobile/dist/hooks/useExternalNS.js +49 -23
  71. package/mobile/dist/hooks/useExternalNS.js.map +1 -1
  72. package/mobile/dist/hooks/useNavigateHandler.d.ts +1 -0
  73. package/mobile/dist/hooks/useNavigateHandler.js +19 -8
  74. package/mobile/dist/hooks/useNavigateHandler.js.map +1 -1
  75. package/mobile/dist/model/LinkProps.d.ts +2 -0
  76. package/mobile/dist/ui-kit/Select/Select.js +1 -1
  77. package/mobile/dist/ui-kit/Select/Select.js.map +1 -1
  78. package/mobile/dist/ui-kit/Select/SelectPopup.js +1 -1
  79. package/mobile/dist/ui-kit/Select/SelectPopup.js.map +1 -1
  80. package/mobile/dist/ui-kit/YandexMap/useYandexMaps.js +1 -1
  81. package/mobile/dist/ui-kit/YandexMap/useYandexMaps.js.map +1 -1
  82. package/mobile/lib/common.css +1 -1
  83. package/mobile/lib/components/Header/HeaderChatBotButton.d.ts +4 -1
  84. package/mobile/lib/components/Header/HeaderChatBotButton.js +3 -11
  85. package/mobile/lib/components/Header/HeaderChatBotButton.js.map +1 -1
  86. package/mobile/lib/components/Header/HeaderSecondaryMenuButton.d.ts +4 -1
  87. package/mobile/lib/components/Header/HeaderSecondaryMenuButton.js.map +1 -1
  88. package/mobile/lib/components/Header/useChatBot.d.ts +4 -1
  89. package/mobile/lib/components/Header/useChatBot.js +25 -4
  90. package/mobile/lib/components/Header/useChatBot.js.map +1 -1
  91. package/mobile/lib/hooks/useExternalNS.d.ts +2 -1
  92. package/mobile/lib/hooks/useExternalNS.js +48 -23
  93. package/mobile/lib/hooks/useExternalNS.js.map +1 -1
  94. package/mobile/lib/hooks/useNavigateHandler.d.ts +1 -0
  95. package/mobile/lib/hooks/useNavigateHandler.js +19 -8
  96. package/mobile/lib/hooks/useNavigateHandler.js.map +1 -1
  97. package/mobile/lib/model/LinkProps.d.ts +2 -0
  98. package/mobile/lib/ui-kit/Select/Select.js +1 -1
  99. package/mobile/lib/ui-kit/Select/Select.js.map +1 -1
  100. package/mobile/lib/ui-kit/Select/SelectPopup.js +1 -1
  101. package/mobile/lib/ui-kit/Select/SelectPopup.js.map +1 -1
  102. package/mobile/lib/ui-kit/YandexMap/useYandexMaps.js +1 -1
  103. package/mobile/lib/ui-kit/YandexMap/useYandexMaps.js.map +1 -1
  104. package/mobile/src/components/Header/HeaderChatBotButton.tsx +15 -30
  105. package/mobile/src/components/Header/HeaderSecondaryMenuButton.tsx +2 -1
  106. package/mobile/src/components/Header/useChatBot.ts +29 -5
  107. package/mobile/src/hooks/useExternalNS.ts +55 -25
  108. package/mobile/src/hooks/useNavigateHandler.ts +34 -18
  109. package/mobile/src/model/LinkProps.ts +2 -0
  110. package/mobile/src/ui-kit/Select/Select.tsx +1 -1
  111. package/mobile/src/ui-kit/Select/SelectPopup.tsx +1 -1
  112. package/mobile/src/ui-kit/YandexMap/useYandexMaps.ts +1 -1
  113. package/package.json +1 -1
  114. package/src/components/Header/HeaderChatBotButton.tsx +15 -30
  115. package/src/components/Header/HeaderSecondaryMenuButton.tsx +2 -1
  116. package/src/components/Header/useChatBot.ts +29 -5
  117. package/src/hooks/useExternalNS.ts +55 -25
  118. package/src/hooks/useNavigateHandler.ts +34 -18
  119. package/src/model/LinkProps.ts +2 -0
  120. package/src/ui-kit/Select/Select.tsx +1 -1
  121. package/src/ui-kit/Select/SelectPopup.tsx +1 -1
  122. package/src/ui-kit/YandexMap/useYandexMaps.ts +1 -1
@@ -1,7 +1,6 @@
1
1
  import { JSX } from '@redneckz/uni-jsx';
2
- import { useBool } from '@redneckz/uni-jsx/lib/hooks/useBool';
3
2
  import { type IconVersion } from '../../model/IconVersion';
4
- import { style } from '../../utils/style';
3
+ import { type PreventableEvent } from '../../ui-kit/PreventableEvent';
5
4
  import { HeaderSecondaryMenuButton } from './HeaderSecondaryMenuButton';
6
5
  import { useChatBot } from './useChatBot';
7
6
 
@@ -9,36 +8,22 @@ interface HeaderChatBotButtonProps {
9
8
  className?: string;
10
9
  iconVersion?: IconVersion;
11
10
  chat?: string;
12
- onClick?: () => void;
11
+ onClick?: (ev: PreventableEvent & { target: EventTarget | null }) => void;
13
12
  }
14
13
 
15
- const CHAT_BUTTON_EXTERNAL_NAME = 'text-page__btn-chat';
14
+ export const HeaderChatBotButton = JSX<HeaderChatBotButtonProps>(
15
+ ({ chat = 'personal', iconVersion, className }) => {
16
+ const load = useChatBot(chat);
16
17
 
17
- export const HeaderChatBotButton = JSX<HeaderChatBotButtonProps>((props) => {
18
- const [loadRequested, { setTrue: load }] = useBool();
19
-
20
- return loadRequested ? (
21
- <HeaderChatBotButtonWithLoader {...props} />
22
- ) : (
23
- renderChatBotButton({ ...props, onClick: load })
24
- );
25
- });
26
-
27
- const HeaderChatBotButtonWithLoader = JSX<HeaderChatBotButtonProps>(
28
- ({ chat = 'personal', ...rest }) => {
29
- useChatBot(chat);
30
-
31
- return renderChatBotButton(rest);
18
+ return (
19
+ <HeaderSecondaryMenuButton
20
+ className={className}
21
+ image={{ icon: 'HeadphonesIcon', iconVersion }}
22
+ ariaLabel="Чат"
23
+ onClick={load}
24
+ buttonSize="large"
25
+ isGrayBg={true}
26
+ />
27
+ );
32
28
  },
33
29
  );
34
-
35
- const renderChatBotButton = ({ className, iconVersion, onClick }: HeaderChatBotButtonProps) => (
36
- <HeaderSecondaryMenuButton
37
- className={style(CHAT_BUTTON_EXTERNAL_NAME, className)}
38
- image={{ icon: 'HeadphonesIcon', iconVersion }}
39
- ariaLabel="Чат"
40
- onClick={onClick}
41
- buttonSize="large"
42
- isGrayBg={true}
43
- />
44
- );
@@ -1,6 +1,7 @@
1
1
  import { JSX } from '@redneckz/uni-jsx';
2
2
  import { Img } from '../../ui-kit/Img/Img';
3
3
  import { type ImageContent } from '../../ui-kit/Img/ImgProps';
4
+ import { type PreventableEvent } from '../../ui-kit/PreventableEvent';
4
5
  import { style } from '../../utils/style';
5
6
 
6
7
  export interface HeaderSecondaryMenuButtonProps extends ImageContent {
@@ -10,7 +11,7 @@ export interface HeaderSecondaryMenuButtonProps extends ImageContent {
10
11
  isGrayBg?: boolean;
11
12
  disabled?: boolean;
12
13
  ariaLabel?: string;
13
- onClick?: () => void;
14
+ onClick?: (ev: PreventableEvent & { target: EventTarget | null }) => void;
14
15
  }
15
16
 
16
17
  const BUTTON_SIZE_STYLE = {
@@ -1,15 +1,39 @@
1
1
  import { useEffect } from '@redneckz/uni-jsx/lib/hooks';
2
- import { useExternalNS } from '../../hooks/useExternalNS';
2
+ import { initializeExternalNS } from '../../hooks/useExternalNS';
3
+ import { type PreventableEvent } from '../../ui-kit/PreventableEvent';
3
4
 
4
- const CHATBOT_NAMESPACE = 'chatbot';
5
+ const CHAT_NAMESPACE = 'chatbot';
6
+ const CHAT_BUTTON_EXTERNAL_NAME = 'text-page__btn-chat';
7
+ const CHAT_FRAME_ID = 'webchat-js-container:webchat_rshb';
8
+ const CHAT_STORAGE_NAME = 'webchat-keep-open';
5
9
 
6
10
  const CHATBOT_URL: Record<string, string> = {
7
11
  personal: '//chat.mes.rshb.ru/assets/js/webchat_rshb',
8
12
  business: '', //TODO: add url business chat
9
13
  };
10
14
 
11
- export const useChatBot = (chat: string) => {
12
- useEffect(() => globalThis.localStorage?.setItem('webchat-keep-open', String(Date.now())), []);
15
+ export const useChatBot = (chat?: string) => {
16
+ useEffect(() => globalThis.localStorage?.setItem(CHAT_STORAGE_NAME, String(Date.now())), []);
17
+ const chatUrl = CHATBOT_URL[chat ?? ''];
13
18
 
14
- return useExternalNS(CHATBOT_NAMESPACE, CHATBOT_URL[chat]);
19
+ return (ev: PreventableEvent & { target: EventTarget | null }) => {
20
+ if (!chatUrl) {
21
+ return;
22
+ }
23
+
24
+ if (chat !== 'personal') {
25
+ initializeExternalNS(CHAT_NAMESPACE, chatUrl);
26
+ } else {
27
+ const target = ev.target as HTMLElement;
28
+
29
+ if (!target?.classList.contains(CHAT_BUTTON_EXTERNAL_NAME)) {
30
+ target?.classList.add(CHAT_BUTTON_EXTERNAL_NAME);
31
+ }
32
+
33
+ const chatFrame = document.getElementById(CHAT_FRAME_ID);
34
+ if (!chatFrame) {
35
+ initializeExternalNS(CHAT_NAMESPACE, chatUrl);
36
+ }
37
+ }
38
+ };
15
39
  };
@@ -1,44 +1,74 @@
1
- import { useCallback, useEffect, useState } from '@redneckz/uni-jsx/lib/hooks';
1
+ import { useEffect, useState } from '@redneckz/uni-jsx/lib/hooks';
2
2
 
3
3
  export const getNS = <NS>(_: string): NS | undefined => (globalThis as any)[_];
4
4
 
5
- export function useExternalNS<NS>(namespaceName: string, url: string): NS | undefined {
6
- const [externalNS, setExternalNS] = useState<NS>();
5
+ export const initializeExternalNS = <NS>(namespaceName: string, url: string) => {
6
+ const script = document.getElementById(url);
7
7
 
8
- const setNS = useCallback(() => setExternalNS(getNS<NS>(namespaceName)), [namespaceName]);
8
+ if (script) {
9
+ const ns = getNS<NS>(namespaceName);
9
10
 
10
- useEffect(() => {
11
- const script = document.getElementById(url);
12
-
13
- if (script) {
14
- if (getNS<NS>(namespaceName)) {
15
- setNS();
16
- } else {
17
- script.addEventListener('load', setNS);
18
-
19
- return () => {
20
- script.removeEventListener('load', setNS);
21
- };
22
- }
11
+ if (ns) {
12
+ return Promise.resolve(ns);
23
13
  } else {
14
+ return new Promise<NS | undefined>((resolve) => {
15
+ script.addEventListener('load', () => {
16
+ resolve(getNS<NS>(namespaceName));
17
+ });
18
+ });
19
+ }
20
+ } else {
21
+ return new Promise<NS | undefined>((resolve, reject) => {
24
22
  const newScript = document.createElement('script');
25
23
 
26
24
  newScript.src = url;
27
25
  newScript.async = true;
28
26
  newScript.id = url;
29
- newScript.addEventListener('load', setNS);
27
+ newScript.addEventListener('load', () => {
28
+ resolve(getNS<NS>(namespaceName));
29
+ });
30
+ newScript.addEventListener('error', (error) => {
31
+ reject(error);
32
+ });
30
33
 
31
34
  document.head.appendChild(newScript);
35
+ });
36
+ }
37
+ };
32
38
 
33
- return () => {
34
- document.head.removeChild(newScript);
39
+ export function useExternalNS<NS>(
40
+ namespaceName: string,
41
+ url: string,
42
+ unmountNS = true,
43
+ ): NS | undefined {
44
+ const [externalNS, setExternalNS] = useState<NS | undefined>(undefined);
35
45
 
36
- setExternalNS(undefined);
37
- };
38
- }
46
+ useEffect(() => {
47
+ let isMounted = true;
48
+
49
+ initializeExternalNS<NS>(namespaceName, url)
50
+ .then((ns) => {
51
+ if (isMounted) {
52
+ setExternalNS(ns);
53
+ }
54
+ })
55
+ .catch((error) => {
56
+ console.error(`Failed to initialize external namespace: ${error}`);
57
+ });
58
+
59
+ return () => {
60
+ isMounted = false;
39
61
 
40
- return undefined;
41
- }, [namespaceName, url]);
62
+ if (unmountNS) {
63
+ const script = document.getElementById(url);
64
+ if (script) {
65
+ document.head.removeChild(script);
66
+ }
67
+
68
+ setExternalNS(undefined);
69
+ }
70
+ };
71
+ }, [namespaceName, url, unmountNS]);
42
72
 
43
73
  return externalNS;
44
74
  }
@@ -1,5 +1,6 @@
1
1
  import { useEffect } from '@redneckz/uni-jsx/lib/hooks';
2
2
  import { useEventListener } from '@redneckz/uni-jsx/lib/hooks/useEventListener';
3
+ import { useChatBot } from '../components/Header/useChatBot';
3
4
  import { type Target } from '../model/LinkProps';
4
5
  import { projectSettings } from '../ProjectSettings';
5
6
  import { type PreventableEvent } from '../ui-kit/PreventableEvent';
@@ -10,35 +11,45 @@ import { isURL } from '../utils/url';
10
11
  import { handlerDecorator } from './handlerDecorator';
11
12
 
12
13
  export type NavigateElement = Element | null;
14
+ export type MethodsMap = Record<string, (params: any) => any>;
13
15
 
14
16
  export const useNavigateHandler = handlerDecorator(() => {
15
17
  useEffect(() => {
16
18
  getSessionQuery(); //? Call first time for save query in sessionStorage
17
19
  }, []);
18
20
 
19
- useEventListener(globalThis, 'click', navigationHandler);
20
- });
21
+ const loadBot = useChatBot(projectSettings.CHAT_BOT);
21
22
 
22
- const navigationHandler = (ev: PreventableEvent & { target: EventTarget | null }) => {
23
- const element = getAnchorElement(ev.target as NavigateElement);
23
+ const reachGoalYM = (targetName?: string) => {
24
+ const [ymID] = projectSettings.YANDEX_METRIKA_ID?.split(',').reverse() ?? [];
24
25
 
25
- const [ymID] = projectSettings._.YANDEX_METRIKA_ID?.split(',').reverse() ?? [];
26
- const targetName = element?.dataset?.ymtarget;
26
+ if ([ymID, targetName, globalThis.ym].every(Boolean)) {
27
+ globalThis.ym(ymID, 'reachGoal', targetName);
28
+ }
29
+ };
27
30
 
28
- if ([ymID, targetName, globalThis.ym].every(Boolean)) {
29
- globalThis.ym(ymID, 'reachGoal', targetName);
30
- }
31
+ useEventListener(globalThis, 'click', navigationHandler({ loadBot, reachGoalYM }));
32
+ });
31
33
 
32
- if (isURL(element?.href) && !element?.href?.startsWith(globalThis.location?.origin)) {
33
- ev.preventDefault();
34
- ev.stopPropagation();
34
+ const navigationHandler =
35
+ (methods: MethodsMap) => (ev: PreventableEvent & { target: EventTarget | null }) => {
36
+ const element = getAnchorElement(ev.target as NavigateElement);
35
37
 
36
- const url = adjustSessionQuery(element?.href) || element?.href;
37
- if (url) {
38
- goTransition(element?.target as Target)?.(url);
38
+ for (const key in element?.dataset) {
39
+ getScenario(key)?.(element as HTMLElement, methods);
39
40
  }
40
- }
41
- };
41
+
42
+ if (isURL(element?.href) && !element?.href?.startsWith(globalThis.location?.origin)) {
43
+ ev.preventDefault();
44
+ ev.stopPropagation();
45
+
46
+ const url = adjustSessionQuery(element?.href) || element?.href;
47
+ if (url) {
48
+ const transition = getTransition(element?.target as Target);
49
+ transition?.(url);
50
+ }
51
+ }
52
+ };
42
53
 
43
54
  const getAnchorElement = (target: NavigateElement) => {
44
55
  let _target = target;
@@ -49,7 +60,7 @@ const getAnchorElement = (target: NavigateElement) => {
49
60
  return _target as HTMLAnchorElement | null;
50
61
  };
51
62
 
52
- const goTransition = tableFunc<Target, (url: string) => void>([
63
+ const getTransition = tableFunc<Target, (url: string) => void>([
53
64
  [(_) => _ === '_blank', (url) => globalThis.open(url, '_blank')],
54
65
  [(_) => _ === '_parent', (url) => globalThis.parent && (globalThis.parent.location.href = url)],
55
66
  [
@@ -60,3 +71,8 @@ const goTransition = tableFunc<Target, (url: string) => void>([
60
71
  ],
61
72
  [() => true, (url) => globalThis.location && (globalThis.location.href = url)],
62
73
  ]);
74
+
75
+ const getScenario = tableFunc<string, (target: HTMLElement | null, methods: MethodsMap) => void>([
76
+ [(_) => _ === 'ymtarget', (target, methods) => methods?.reachGoalYM(target?.dataset?.ymtarget)],
77
+ [(_) => _ === 'chatbot', (target, methods) => methods?.loadBot({ target })],
78
+ ]);
@@ -14,6 +14,8 @@ export type Target = '' | '_self' | '_blank' | '_parent' | '_top';
14
14
  export type DataAttributes = {
15
15
  /** @title Имя цели для Яндекс Метрики */
16
16
  ymTarget?: string;
17
+ /** @title Открыть чат бот */
18
+ chatbot?: boolean;
17
19
  };
18
20
 
19
21
  export interface LinkCommonProps {
@@ -35,7 +35,7 @@ export const Select = JSX<SelectProps>(
35
35
  isBorder = true,
36
36
  placeholder = '',
37
37
  isSearch = false,
38
- iconVersion,
38
+ iconVersion = 'black',
39
39
  onChange,
40
40
  disabled,
41
41
  }) => {
@@ -19,7 +19,7 @@ export const SelectPopup = JSX<SelectPopupProps>(
19
19
  ({ popupRef, options, value, query, onChange, iconVersion }) =>
20
20
  options?.length ? (
21
21
  <div
22
- className="bg-white text-l max-h-64 overflow-y-auto overflow-x-hidden rounded-md"
22
+ className="bg-white text-l max-h-64 overflow-y-auto overflow-x-hidden rounded-md shadow-2xl"
23
23
  role="list"
24
24
  ref={popupRef}
25
25
  >
@@ -9,5 +9,5 @@ export const useYandexMaps = () => {
9
9
  projectSettings.YANDEX_MAP_API_KEY || ''
10
10
  }&lang=ru_RU`;
11
11
 
12
- return useExternalNS<typeof ymaps>(YMAPS_NAMESPACE, url);
12
+ return useExternalNS<typeof ymaps>(YMAPS_NAMESPACE, url, false);
13
13
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redneckz/wildless-cms-uni-blocks",
3
- "version": "0.14.249",
3
+ "version": "0.14.251",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "author": "ЦК",
@@ -1,7 +1,6 @@
1
1
  import { JSX } from '@redneckz/uni-jsx';
2
- import { useBool } from '@redneckz/uni-jsx/lib/hooks/useBool';
3
2
  import { type IconVersion } from '../../model/IconVersion';
4
- import { style } from '../../utils/style';
3
+ import { type PreventableEvent } from '../../ui-kit/PreventableEvent';
5
4
  import { HeaderSecondaryMenuButton } from './HeaderSecondaryMenuButton';
6
5
  import { useChatBot } from './useChatBot';
7
6
 
@@ -9,36 +8,22 @@ interface HeaderChatBotButtonProps {
9
8
  className?: string;
10
9
  iconVersion?: IconVersion;
11
10
  chat?: string;
12
- onClick?: () => void;
11
+ onClick?: (ev: PreventableEvent & { target: EventTarget | null }) => void;
13
12
  }
14
13
 
15
- const CHAT_BUTTON_EXTERNAL_NAME = 'text-page__btn-chat';
14
+ export const HeaderChatBotButton = JSX<HeaderChatBotButtonProps>(
15
+ ({ chat = 'personal', iconVersion, className }) => {
16
+ const load = useChatBot(chat);
16
17
 
17
- export const HeaderChatBotButton = JSX<HeaderChatBotButtonProps>((props) => {
18
- const [loadRequested, { setTrue: load }] = useBool();
19
-
20
- return loadRequested ? (
21
- <HeaderChatBotButtonWithLoader {...props} />
22
- ) : (
23
- renderChatBotButton({ ...props, onClick: load })
24
- );
25
- });
26
-
27
- const HeaderChatBotButtonWithLoader = JSX<HeaderChatBotButtonProps>(
28
- ({ chat = 'personal', ...rest }) => {
29
- useChatBot(chat);
30
-
31
- return renderChatBotButton(rest);
18
+ return (
19
+ <HeaderSecondaryMenuButton
20
+ className={className}
21
+ image={{ icon: 'HeadphonesIcon', iconVersion }}
22
+ ariaLabel="Чат"
23
+ onClick={load}
24
+ buttonSize="large"
25
+ isGrayBg={true}
26
+ />
27
+ );
32
28
  },
33
29
  );
34
-
35
- const renderChatBotButton = ({ className, iconVersion, onClick }: HeaderChatBotButtonProps) => (
36
- <HeaderSecondaryMenuButton
37
- className={style(CHAT_BUTTON_EXTERNAL_NAME, className)}
38
- image={{ icon: 'HeadphonesIcon', iconVersion }}
39
- ariaLabel="Чат"
40
- onClick={onClick}
41
- buttonSize="large"
42
- isGrayBg={true}
43
- />
44
- );
@@ -1,6 +1,7 @@
1
1
  import { JSX } from '@redneckz/uni-jsx';
2
2
  import { Img } from '../../ui-kit/Img/Img';
3
3
  import { type ImageContent } from '../../ui-kit/Img/ImgProps';
4
+ import { type PreventableEvent } from '../../ui-kit/PreventableEvent';
4
5
  import { style } from '../../utils/style';
5
6
 
6
7
  export interface HeaderSecondaryMenuButtonProps extends ImageContent {
@@ -10,7 +11,7 @@ export interface HeaderSecondaryMenuButtonProps extends ImageContent {
10
11
  isGrayBg?: boolean;
11
12
  disabled?: boolean;
12
13
  ariaLabel?: string;
13
- onClick?: () => void;
14
+ onClick?: (ev: PreventableEvent & { target: EventTarget | null }) => void;
14
15
  }
15
16
 
16
17
  const BUTTON_SIZE_STYLE = {
@@ -1,15 +1,39 @@
1
1
  import { useEffect } from '@redneckz/uni-jsx/lib/hooks';
2
- import { useExternalNS } from '../../hooks/useExternalNS';
2
+ import { initializeExternalNS } from '../../hooks/useExternalNS';
3
+ import { type PreventableEvent } from '../../ui-kit/PreventableEvent';
3
4
 
4
- const CHATBOT_NAMESPACE = 'chatbot';
5
+ const CHAT_NAMESPACE = 'chatbot';
6
+ const CHAT_BUTTON_EXTERNAL_NAME = 'text-page__btn-chat';
7
+ const CHAT_FRAME_ID = 'webchat-js-container:webchat_rshb';
8
+ const CHAT_STORAGE_NAME = 'webchat-keep-open';
5
9
 
6
10
  const CHATBOT_URL: Record<string, string> = {
7
11
  personal: '//chat.mes.rshb.ru/assets/js/webchat_rshb',
8
12
  business: '', //TODO: add url business chat
9
13
  };
10
14
 
11
- export const useChatBot = (chat: string) => {
12
- useEffect(() => globalThis.localStorage?.setItem('webchat-keep-open', String(Date.now())), []);
15
+ export const useChatBot = (chat?: string) => {
16
+ useEffect(() => globalThis.localStorage?.setItem(CHAT_STORAGE_NAME, String(Date.now())), []);
17
+ const chatUrl = CHATBOT_URL[chat ?? ''];
13
18
 
14
- return useExternalNS(CHATBOT_NAMESPACE, CHATBOT_URL[chat]);
19
+ return (ev: PreventableEvent & { target: EventTarget | null }) => {
20
+ if (!chatUrl) {
21
+ return;
22
+ }
23
+
24
+ if (chat !== 'personal') {
25
+ initializeExternalNS(CHAT_NAMESPACE, chatUrl);
26
+ } else {
27
+ const target = ev.target as HTMLElement;
28
+
29
+ if (!target?.classList.contains(CHAT_BUTTON_EXTERNAL_NAME)) {
30
+ target?.classList.add(CHAT_BUTTON_EXTERNAL_NAME);
31
+ }
32
+
33
+ const chatFrame = document.getElementById(CHAT_FRAME_ID);
34
+ if (!chatFrame) {
35
+ initializeExternalNS(CHAT_NAMESPACE, chatUrl);
36
+ }
37
+ }
38
+ };
15
39
  };
@@ -1,44 +1,74 @@
1
- import { useCallback, useEffect, useState } from '@redneckz/uni-jsx/lib/hooks';
1
+ import { useEffect, useState } from '@redneckz/uni-jsx/lib/hooks';
2
2
 
3
3
  export const getNS = <NS>(_: string): NS | undefined => (globalThis as any)[_];
4
4
 
5
- export function useExternalNS<NS>(namespaceName: string, url: string): NS | undefined {
6
- const [externalNS, setExternalNS] = useState<NS>();
5
+ export const initializeExternalNS = <NS>(namespaceName: string, url: string) => {
6
+ const script = document.getElementById(url);
7
7
 
8
- const setNS = useCallback(() => setExternalNS(getNS<NS>(namespaceName)), [namespaceName]);
8
+ if (script) {
9
+ const ns = getNS<NS>(namespaceName);
9
10
 
10
- useEffect(() => {
11
- const script = document.getElementById(url);
12
-
13
- if (script) {
14
- if (getNS<NS>(namespaceName)) {
15
- setNS();
16
- } else {
17
- script.addEventListener('load', setNS);
18
-
19
- return () => {
20
- script.removeEventListener('load', setNS);
21
- };
22
- }
11
+ if (ns) {
12
+ return Promise.resolve(ns);
23
13
  } else {
14
+ return new Promise<NS | undefined>((resolve) => {
15
+ script.addEventListener('load', () => {
16
+ resolve(getNS<NS>(namespaceName));
17
+ });
18
+ });
19
+ }
20
+ } else {
21
+ return new Promise<NS | undefined>((resolve, reject) => {
24
22
  const newScript = document.createElement('script');
25
23
 
26
24
  newScript.src = url;
27
25
  newScript.async = true;
28
26
  newScript.id = url;
29
- newScript.addEventListener('load', setNS);
27
+ newScript.addEventListener('load', () => {
28
+ resolve(getNS<NS>(namespaceName));
29
+ });
30
+ newScript.addEventListener('error', (error) => {
31
+ reject(error);
32
+ });
30
33
 
31
34
  document.head.appendChild(newScript);
35
+ });
36
+ }
37
+ };
32
38
 
33
- return () => {
34
- document.head.removeChild(newScript);
39
+ export function useExternalNS<NS>(
40
+ namespaceName: string,
41
+ url: string,
42
+ unmountNS = true,
43
+ ): NS | undefined {
44
+ const [externalNS, setExternalNS] = useState<NS | undefined>(undefined);
35
45
 
36
- setExternalNS(undefined);
37
- };
38
- }
46
+ useEffect(() => {
47
+ let isMounted = true;
48
+
49
+ initializeExternalNS<NS>(namespaceName, url)
50
+ .then((ns) => {
51
+ if (isMounted) {
52
+ setExternalNS(ns);
53
+ }
54
+ })
55
+ .catch((error) => {
56
+ console.error(`Failed to initialize external namespace: ${error}`);
57
+ });
58
+
59
+ return () => {
60
+ isMounted = false;
39
61
 
40
- return undefined;
41
- }, [namespaceName, url]);
62
+ if (unmountNS) {
63
+ const script = document.getElementById(url);
64
+ if (script) {
65
+ document.head.removeChild(script);
66
+ }
67
+
68
+ setExternalNS(undefined);
69
+ }
70
+ };
71
+ }, [namespaceName, url, unmountNS]);
42
72
 
43
73
  return externalNS;
44
74
  }