pager-widget 1.0.11-alpha.9 → 1.0.13

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 (79) hide show
  1. package/dist/index.css +1 -1
  2. package/dist/index.html +40 -0
  3. package/dist/index.js +8 -8
  4. package/dist/react-parcel.9f367d21.css +2970 -0
  5. package/dist/react-parcel.9f367d21.css.map +1 -0
  6. package/dist/react-parcel.js +231527 -0
  7. package/dist/react-parcel.js.map +1 -0
  8. package/dist/server.html +33 -39
  9. package/out/AgentFeedbackButtonWrapper.js +15 -0
  10. package/out/App.js +28 -0
  11. package/out/BotMain.js +91 -0
  12. package/out/ButtonsWrapper.js +29 -0
  13. package/out/ChatPage.js +9 -0
  14. package/out/ChatPageContent.js +131 -0
  15. package/out/ChatPageHeader.js +267 -0
  16. package/out/ChatPageInput.js +29 -0
  17. package/out/ChatWidget.js +17 -0
  18. package/out/Consent.js +66 -0
  19. package/out/CountryDropdown.js +31 -0
  20. package/out/Form.js +238 -0
  21. package/out/HomePage.js +11 -0
  22. package/out/HomePageContent.js +32 -0
  23. package/out/HomePageFooter.js +87 -0
  24. package/out/HomePageHeader.js +66 -0
  25. package/out/InputField.js +80 -0
  26. package/out/Markdown.js +126 -0
  27. package/out/Message.js +197 -0
  28. package/out/MessagesT.js +2 -0
  29. package/out/NewRecentMessagePage.js +525 -0
  30. package/out/NewRecentMessageTab.js +23 -0
  31. package/out/ReceivedMessage.js +211 -0
  32. package/out/RecentMessageDetail.js +178 -0
  33. package/out/RecentMessageList.js +112 -0
  34. package/out/RecentMessageListCard.js +20 -0
  35. package/out/RecentMessageListHeader.js +156 -0
  36. package/out/RecentMessagePage.js +183 -0
  37. package/out/RecentMessageTab.js +35 -0
  38. package/out/WidgetMessage.js +14 -0
  39. package/out/WidgetToggleButton.js +191 -0
  40. package/out/WidgetType.js +2 -0
  41. package/out/assets/icons/pikaicons-react.js +17475 -0
  42. package/out/components/ConsentForm.js +7 -0
  43. package/out/components/CustomTooltip.js +34 -0
  44. package/out/hooks/useChatHistoryExists.js +32 -0
  45. package/out/hooks/useChatHistoryListExist.js +81 -0
  46. package/out/hooks/useChatScroll.js +14 -0
  47. package/out/hooks/useCurrentConversationExists.js +88 -0
  48. package/out/hooks/useInitialMessage.js +33 -0
  49. package/out/hooks/useMessage.js +230 -0
  50. package/out/hooks/useMessageReceiver.js +183 -0
  51. package/out/hooks/usePopupAnimation.js +20 -0
  52. package/out/hooks/useSocket.js +19 -0
  53. package/out/hooks/useSocketContext.js +81 -0
  54. package/out/hooks/useWidgetDimension.js +41 -0
  55. package/out/hooks/useWidgetToggle.js +22 -0
  56. package/out/index.js +31 -0
  57. package/out/markdown/CodeCopy.js +22 -0
  58. package/out/markdown/CodeHighlight.js +58 -0
  59. package/out/markdown/PagerMarkdown.js +36 -0
  60. package/out/markdown/pager_md.js +152 -0
  61. package/out/onscreenNotificationPopup.js +23 -0
  62. package/out/socket.js +11 -0
  63. package/out/utils/checkConversationExit.js +24 -0
  64. package/out/utils/convertTime.js +43 -0
  65. package/out/utils/sendInitialMessage.js +82 -0
  66. package/out/widget.js +21 -0
  67. package/out/widgetConfigStore.js +205 -0
  68. package/out/widgetConfigStore2.js +569 -0
  69. package/out/widgetControlstore.js +23 -0
  70. package/out/widgetStateHandler.js +106 -0
  71. package/out/widgetStore.js +171 -0
  72. package/out/widgetUserStore.js +47 -0
  73. package/package.json +3 -3
  74. package/server.html +33 -39
  75. package/.husky/pre-commit +0 -4
  76. package/docs/issues/001-use-widget-resize-hook.md +0 -51
  77. package/docs/issues/002-embed-snippet-container-resize.md +0 -81
  78. package/docs/prd/0001-iframe-resize-scroll-through.md +0 -333
  79. package/test-iframe-resize.html +0 -74
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setWidgetLocalState = void 0;
4
+ // const LOCAL_KEY = 'DUMMY'
5
+ const setWidgetLocalState = ({ widget_id,
6
+ // conversation_id,
7
+ user_state, events, }) => {
8
+ const conversation_id = localStorage.getItem('CURRENT_CONVERSATION') ?? null;
9
+ console.log('conversation id from current', conversation_id);
10
+ appendEvent({ widget_id, conversation_id, user_state, events });
11
+ };
12
+ exports.setWidgetLocalState = setWidgetLocalState;
13
+ const appendEvent = ({ widget_id, conversation_id, user_state, events, }) => {
14
+ console.log('DEBUGG :: append event called', events);
15
+ const existing = localStorage.getItem(`CONVERSATION::${widget_id}`);
16
+ const _events = Array.isArray(events) ? events : [events];
17
+ const currentWidgetState = {
18
+ widget_id,
19
+ conversation_id,
20
+ user_state,
21
+ events: _events,
22
+ };
23
+ if (!existing) {
24
+ localStorage.setItem(`CONVERSATION::${widget_id}`, JSON.stringify(currentWidgetState));
25
+ return;
26
+ }
27
+ try {
28
+ const parsed = JSON.parse(existing);
29
+ const isSameConversation = parsed.conversation_id === conversation_id;
30
+ const previousEvents = isSameConversation ? parsed.events : [];
31
+ // 🧹 Remove FORM_INPUT_NEEDED if form is now submitted
32
+ // if (user_state === 'FORM_SUBMITTED') {
33
+ // previousEvents = previousEvents.filter(
34
+ // (e) => e.type !== 'FORM_INPUT_NEEDED'
35
+ // )
36
+ // }
37
+ // ✅ Deduplicate events by `event_id` (if present), else fallback to serialized JSON
38
+ const allEvents = [...previousEvents, ..._events];
39
+ const deduplicated = [
40
+ ...new Map(allEvents.map((e) => {
41
+ const key = e.type === 'received' && 'message' in e
42
+ ? e.message?.event_id
43
+ : JSON.stringify(e);
44
+ return [key, e];
45
+ })).values(),
46
+ ];
47
+ const updatedState = {
48
+ widget_id,
49
+ conversation_id,
50
+ user_state,
51
+ events: deduplicated,
52
+ };
53
+ localStorage.setItem(`CONVERSATION::${widget_id}`, JSON.stringify(updatedState));
54
+ }
55
+ catch (error) {
56
+ console.error('Failed to append event to widget state:', error);
57
+ }
58
+ };
59
+ // const appendEvent = ({
60
+ // widget_id,
61
+ // conversation_id,
62
+ // user_state,
63
+ // events,
64
+ // }: appendEventType) => {
65
+ // console.log('DEBUGG :: append event called', events)
66
+ // const existing = localStorage.getItem(LOCAL_KEY)
67
+ // const _events = Array.isArray(events) ? events : [events]
68
+ // const currentWidgetState: LocalEventStore = {
69
+ // widget_id,
70
+ // conversation_id,
71
+ // user_state,
72
+ // events: _events,
73
+ // }
74
+ // if (!existing) {
75
+ // localStorage.setItem(LOCAL_KEY, JSON.stringify(currentWidgetState))
76
+ // return
77
+ // }
78
+ // try {
79
+ // const parsed: LocalEventStore = JSON.parse(existing)
80
+ // const isSameConversation = parsed.conversation_id === conversation_id
81
+ // // const deduplicated = [
82
+ // // ...new Map(
83
+ // // [...parsed.events, ..._events].map((e) => [
84
+ // // e?.message?.event_id ?? JSON.stringify(e),
85
+ // // e,
86
+ // // ])
87
+ // // ).values(),
88
+ // // ]
89
+ // let previousEvents = isSameConversation ? parsed.events : []
90
+ // // 👉 Remove FORM_INPUT_NEEDED if current state is FORM_SUBMITTED
91
+ // if (user_state === 'FORM_SUBMITTED') {
92
+ // previousEvents = previousEvents.filter(
93
+ // (e) => e.type !== 'FORM_INPUT_NEEDED'
94
+ // )
95
+ // }
96
+ // const updatedState: LocalEventStore = {
97
+ // widget_id,
98
+ // conversation_id,
99
+ // user_state,
100
+ // events: [...previousEvents, ..._events],
101
+ // }
102
+ // localStorage.setItem(LOCAL_KEY, JSON.stringify(updatedState))
103
+ // } catch (error) {
104
+ // console.error('Failed to append event to widget state:', error)
105
+ // }
106
+ // }
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useGetCurrentConversationId = exports.useChatExit = exports.useUserDetails = exports.useIsWidgetExpanded = exports.useSelectTab = exports.useSelectedConversationData = exports.useInspectConversation = exports.widgetStore = exports.WidgetExpandType = exports.TabType = void 0;
4
+ const store_1 = require("@xstate/store");
5
+ const react_1 = require("@xstate/store/react");
6
+ const react_2 = require("react");
7
+ // Tab types
8
+ var TabType;
9
+ (function (TabType) {
10
+ TabType["HOME"] = "home";
11
+ TabType["CHAT"] = "chat";
12
+ TabType["RECENT_MESSAGE"] = "recentMessage";
13
+ TabType["RECENT_MESSAGE_LIST"] = "recentMessageList";
14
+ TabType["RECENT_MESSAGE_DETAIL"] = "recentMessageDetail";
15
+ TabType["NEW_RECENT_MESSAGE"] = "newRecentMessage";
16
+ })(TabType || (exports.TabType = TabType = {}));
17
+ var WidgetExpandType;
18
+ (function (WidgetExpandType) {
19
+ WidgetExpandType["DEFAULT"] = "default";
20
+ WidgetExpandType["EXPANDED"] = "expanded";
21
+ WidgetExpandType["FULLSCREEN"] = "fullscreen";
22
+ })(WidgetExpandType || (exports.WidgetExpandType = WidgetExpandType = {}));
23
+ // ---- DEFAULT CONTEXT ----
24
+ const defaultContext = {
25
+ current_conversation_id: null,
26
+ selected_conversation_id: '',
27
+ switchTab: TabType.HOME,
28
+ isExpanded: WidgetExpandType.DEFAULT,
29
+ user_details: {
30
+ SYS_FirstName: '',
31
+ SYS_LastName: '',
32
+ SYS_Location: '',
33
+ SYS_Timezone: '',
34
+ SYS_UserEmail: '',
35
+ },
36
+ isChatExit: false,
37
+ };
38
+ const storedData = localStorage.getItem('widgetContext');
39
+ const initialContext = storedData
40
+ ? { ...defaultContext, ...JSON.parse(storedData) }
41
+ : defaultContext;
42
+ // ---- STORE ----
43
+ exports.widgetStore = (0, store_1.createStore)({
44
+ context: initialContext,
45
+ on: {
46
+ setCurrentConversation: (context, event) => {
47
+ context.current_conversation_id = event.conversation_id;
48
+ },
49
+ selectConversation: (context, event) => {
50
+ context.selected_conversation_id = event.conversation_id;
51
+ persistContext();
52
+ },
53
+ selectTab: (context, event) => {
54
+ context.switchTab = event.tab;
55
+ persistContext();
56
+ },
57
+ expandWidget: (context, event) => {
58
+ context.isExpanded = event.expanded_size;
59
+ persistContext();
60
+ },
61
+ updateUserDetails: (context, event) => {
62
+ context.user_details = {
63
+ ...context.user_details,
64
+ ...event.user_details,
65
+ };
66
+ persistContext();
67
+ },
68
+ handleExitChatState: (context, event) => {
69
+ console.log('exit chat called in store');
70
+ context.isChatExit = event.value;
71
+ },
72
+ },
73
+ });
74
+ // ---- PERSIST ----
75
+ const persistContext = () => {
76
+ const currentContext = exports.widgetStore.getSnapshot().context;
77
+ localStorage.setItem('widgetContext', JSON.stringify(currentContext));
78
+ };
79
+ // ---- DEBUGGING ----
80
+ const useInspectConversation = (debug = false) => {
81
+ (0, react_2.useEffect)(() => {
82
+ if (debug) {
83
+ exports.widgetStore.inspect((inspectionEvent) => {
84
+ console.log('CONVERSATION INSPECTION::', inspectionEvent);
85
+ });
86
+ }
87
+ }, []);
88
+ };
89
+ exports.useInspectConversation = useInspectConversation;
90
+ // ---- SELECTORS ----
91
+ const useSelectedConversationData = () => (0, react_1.useSelector)(exports.widgetStore, (state) => state.context.selected_conversation_id);
92
+ exports.useSelectedConversationData = useSelectedConversationData;
93
+ const useSelectTab = () => (0, react_1.useSelector)(exports.widgetStore, (state) => state.context.switchTab);
94
+ exports.useSelectTab = useSelectTab;
95
+ const useIsWidgetExpanded = () => (0, react_1.useSelector)(exports.widgetStore, (state) => state.context.isExpanded);
96
+ exports.useIsWidgetExpanded = useIsWidgetExpanded;
97
+ const useUserDetails = () => (0, react_1.useSelector)(exports.widgetStore, (state) => state.context.user_details);
98
+ exports.useUserDetails = useUserDetails;
99
+ const useChatExit = () => (0, react_1.useSelector)(exports.widgetStore, (state) => state.context.isChatExit);
100
+ exports.useChatExit = useChatExit;
101
+ const useGetCurrentConversationId = () => (0, react_1.useSelector)(exports.widgetStore, (state) => state.context.current_conversation_id);
102
+ exports.useGetCurrentConversationId = useGetCurrentConversationId;
103
+ // import { createStore } from '@xstate/store'
104
+ // import { useSelector } from '@xstate/store/react'
105
+ // import { useEffect } from 'react'
106
+ // export enum TabType {
107
+ // HOME = 'home',
108
+ // CHAT = 'chat',
109
+ // RECENT_MESSAGE = 'recentMessage',
110
+ // RECENT_MESSAGE_LIST = 'recentMessageList',
111
+ // RECENT_MESSAGE_DETAIL = 'recentMessageDetail',
112
+ // // etc.
113
+ // }
114
+ // type conversationPayload = {
115
+ // conversation_id: string
116
+ // }
117
+ // type selectTabPayloadT = {
118
+ // tab: TabType
119
+ // }
120
+ // const storedData = localStorage.getItem('widgetContext')
121
+ // const initialContext = storedData
122
+ // ? JSON.parse(storedData)
123
+ // : {
124
+ // selected_conversation_id: '',
125
+ // switchTab: TabType.HOME,
126
+ // isExpanded: false,
127
+ // }
128
+ // export const widgetStore = createStore({
129
+ // context: initialContext,
130
+ // on: {
131
+ // selectConversation: (context, event: conversationPayload) => {
132
+ // context.selected_conversation_id = event.conversation_id
133
+ // persistContext()
134
+ // },
135
+ // selectTab: (context, event: selectTabPayloadT) => {
136
+ // context.switchTab = event.tab
137
+ // persistContext()
138
+ // },
139
+ // expandWidget: (context) => {
140
+ // context.isExpanded = !context.isExpanded
141
+ // persistContext()
142
+ // },
143
+ // },
144
+ // })
145
+ // export const useInspectConversation = (debug = false) => {
146
+ // useEffect(() => {
147
+ // if (debug) {
148
+ // widgetStore.inspect((inspectionEvent) => {
149
+ // console.log('CONVERSATION INSPECTION::', inspectionEvent)
150
+ // })
151
+ // }
152
+ // }, [])
153
+ // }
154
+ // const persistContext = () => {
155
+ // const currentContext = widgetStore.getSnapshot().context
156
+ // localStorage.setItem('widgetContext', JSON.stringify(currentContext))
157
+ // }
158
+ // export const useSelectedConversationData = () =>
159
+ // useSelector(widgetStore, (state) => {
160
+ // return state.context.selected_conversation_id
161
+ // })
162
+ // export const useSelectTab = () => {
163
+ // return useSelector(widgetStore, (state) => {
164
+ // return state.context.switchTab
165
+ // })
166
+ // }
167
+ // export const useIsWidgetExpanded = () => {
168
+ // return useSelector(widgetStore, (state) => {
169
+ // return state.context.isExpanded
170
+ // })
171
+ // }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useUserData = exports.userDetailInitializer = exports.widgetUserStore = void 0;
4
+ const store_1 = require("@xstate/store");
5
+ const react_1 = require("react");
6
+ const react_2 = require("@xstate/store/react");
7
+ exports.widgetUserStore = (0, store_1.createStore)({
8
+ context: {
9
+ user_details: {
10
+ SYS_FirstName: 'sample',
11
+ SYS_LastName: 'B',
12
+ SYS_UserEmail: 'example@gmail.com',
13
+ SYS_Location: 'India',
14
+ SYS_Timezone: 'Asia/Kolkata',
15
+ },
16
+ },
17
+ on: {
18
+ setUserDetails: (_, event) => {
19
+ return {
20
+ user_details: {
21
+ SYS_FirstName: event.userDetails?.SYS_FirstName ?? 'sample',
22
+ SYS_LastName: event.userDetails?.SYS_LastName ?? 'B',
23
+ SYS_UserEmail: event.userDetails?.SYS_UserEmail ?? 'example@gmail.com',
24
+ SYS_Location: event.userDetails?.SYS_Location ?? 'India',
25
+ SYS_Timezone: event.userDetails?.SYS_Timezone ?? 'Asia/Kolkata',
26
+ },
27
+ };
28
+ },
29
+ },
30
+ });
31
+ const userDetailInitializer = (userDetails, debug = false) => {
32
+ (0, react_1.useEffect)(() => {
33
+ exports.widgetUserStore.trigger.setUserDetails({ ...userDetails });
34
+ if (debug) {
35
+ exports.widgetUserStore.inspect((inspectionEvent) => {
36
+ console.log('USER INSPECTION::', inspectionEvent);
37
+ });
38
+ }
39
+ }, []);
40
+ };
41
+ exports.userDetailInitializer = userDetailInitializer;
42
+ const useUserData = () => {
43
+ (0, react_2.useSelector)(exports.widgetUserStore, (state) => {
44
+ return state.context.user_details;
45
+ });
46
+ };
47
+ exports.useUserData = useUserData;
package/package.json CHANGED
@@ -8,10 +8,9 @@
8
8
  "lint:fix": "eslint --fix \"src/**/*.{js,jsx,ts,tsx}\"",
9
9
  "prepublishOnly": "npm run build:lib",
10
10
  "format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,css,md}\" --config ./.prettierrc.json",
11
- "prepare": "husky install",
12
11
  "parcel:build": "PARCEL_WORKER_BACKEND=process TAILWIND_MODE=build DEGUG=* parcel build src/index.tsx --no-source-maps",
13
12
  "postcss:build": "postcss src/global.css -o dist/global.css",
14
- "release": "npx bumpp"
13
+ "release": "bumpp"
15
14
  },
16
15
  "devDependencies": {
17
16
  "@eslint/js": "^9.25.1",
@@ -24,6 +23,7 @@
24
23
  "@types/react-syntax-highlighter": "^15.5.13",
25
24
  "@types/socket.io-client": "^1.4.36",
26
25
  "autoprefixer": "^10.4.21",
26
+ "bumpp": "^11.1.0",
27
27
  "eslint": "^9.25.1",
28
28
  "eslint-config-prettier": "^10.1.2",
29
29
  "eslint-plugin-prettier": "^5.2.6",
@@ -89,6 +89,6 @@
89
89
  ],
90
90
  "mode": "all"
91
91
  },
92
- "version": "1.0.11-alpha.9",
92
+ "version": "1.0.13",
93
93
  "name": "pager-widget"
94
94
  }
package/server.html CHANGED
@@ -3,45 +3,39 @@
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Pager Widget</title>
7
- <style>
8
- body { margin: 0; overflow: hidden; }
9
- </style>
6
+
7
+ <title>React with Tailwind</title>
8
+ <!-- <script type="module" src="./index.tsx"></script> -->
9
+ <script type="module" src="./index.js"></script>
10
+ <link rel="stylesheet" href="./index.css">
10
11
  </head>
11
12
  <body>
12
- <div id="pager-widget-container"
13
- style="position:fixed;bottom:0;right:0;z-index:9999;min-height:60px;min-width:60px">
14
- <iframe id="pager-widget-iframe"
15
- src="https://dev.pagergpt.ai/wgt-hook/embed/ae320291-260f-48fc-a069-68ab73858664?v=1.0.10&SYS_FirstName=John&SYS_UserEmail=john@example.com"
16
- style="position:absolute;width:100%!important;height:100%!important;border:none!important;bottom:0!important;right:0!important;background:transparent!important"
17
- title="Pager Widget">
18
- </iframe>
19
- </div>
20
- <script>
21
- (function() {
22
- var container = document.getElementById('pager-widget-container');
23
- var iframe = document.getElementById('pager-widget-iframe');
24
-
25
- window.addEventListener('message', function(event) {
26
- if (event.data && event.data.type === 'widgetResize') {
27
- container.style.width = event.data.width + 'px';
28
- container.style.height = event.data.height + 'px';
29
- }
30
- });
31
-
32
- iframe.addEventListener('load', function() {
33
- iframe.contentWindow.postMessage({
34
- type: 'userDetails',
35
- data: {
36
- SYS_FirstName: "{{firstName}}",
37
- SYS_LastName: "{{lastName}}",
38
- SYS_UserEmail: "{{email}}",
39
- SYS_Location: "{{location}}",
40
- SYS_Timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
41
- }
42
- }, 'https://dev.pagergpt.ai');
43
- });
44
- })();
45
- </script>
46
- </body>
13
+ <div id="pager-widget-f08cbac9-472f-40ca-a1ef-4cdbf88cddae"></div>
14
+ <!-- This is where React will mount -->
15
+ <script>
16
+ setTimeout(() => {
17
+ window.WAWidget.init({
18
+ id: 'pager-widget-f08cbac9-472f-40ca-a1ef-4cdbf88cddae',
19
+ endpoint: 'https://dev.pagergpt.ai/wgt-hook',
20
+ // widget_id: 'b6a8b7a6-10e8-41fa-ab89-b4c9413c20a8',
21
+ widget_id: 'f08cbac9-472f-40ca-a1ef-4cdbf88cddae',
22
+ openByDefault: true,
23
+ widget_size: 'small',
24
+ userDetails: {
25
+ SYS_FirstName: 'sample',
26
+ SYS_LastName: 'user',
27
+ SYS_UserEmail: 'email',
28
+ SYS_Location: 'india',
29
+ SYS_Timezone: 'Asia/Kolkata',
30
+ },
31
+ style: {
32
+ logo_shape: 'circle',
33
+ logo_size: {
34
+ size: 1,
35
+ },
36
+ },
37
+ })
38
+ }, 1000)
39
+ </script>
40
+ </body>
47
41
  </html>
package/.husky/pre-commit DELETED
@@ -1,4 +0,0 @@
1
- #!/bin/sh
2
- . "$(dirname "$0")/_/husky.sh"
3
-
4
- npx lint-staged
@@ -1,51 +0,0 @@
1
- ## What to build
2
-
3
- Create a `useWidgetResize` hook that sends the widget's rendered dimensions to the parent page via `postMessage` whenever the widget's DOM size changes.
4
-
5
- The hook uses **ResizeObserver** (not state-based triggers) to detect ALL size changes:
6
- - Toggle open/close
7
- - Expand (DEFAULT → EXPANDED → FULLSCREEN)
8
- - Window resize
9
- - Content growth / layout shifts
10
-
11
- The hook measures the rendered `.pager-widget-root` element and sends the actual pixel dimensions to `window.parent` via `postMessage`.
12
-
13
- ### Message shape
14
-
15
- ```json
16
- {
17
- "type": "widgetResize",
18
- "width": 448,
19
- "height": 637
20
- }
21
- ```
22
-
23
- Note: no `state` field — dimensions are the source of truth. The parent just resizes to match.
24
-
25
- ### Edge cases
26
-
27
- - **Not in iframe**: skip sending if `window.parent === window`
28
- - **Zero dimensions**: skip if measured width or height is 0 (DOM not yet ready)
29
- - **Initial render**: fire once on mount after first requestAnimationFrame
30
- - **Flood protection**: throttle via `requestAnimationFrame` guard (one message per frame)
31
-
32
- ### Integration point
33
-
34
- The hook is called in `widget.tsx` where the toggle state and rendering live.
35
-
36
- ## Acceptance criteria
37
-
38
- - [x] `src/hooks/useWidgetResize.ts` exists
39
- - [x] Hook measures the rendered widget dimensions via `getBoundingClientRect()`
40
- - [x] Hook sends `postMessage({ type: 'widgetResize', width, height }, '*')` on size change
41
- - [x] Hook skips when not in an iframe
42
- - [x] Hook skips when dimensions are zero
43
- - [x] Hook is integrated into `widget.tsx`
44
- - [ ] Hook uses **ResizeObserver** instead of state-based `setTimeout(50ms)` approach
45
- - [ ] Hook fires on ALL size changes (toggle, expand, window resize — not just toggle state transitions)
46
- - [ ] Hook throttles messages with `requestAnimationFrame`
47
- - [ ] Unit tests pass for all edge cases (not in iframe, zero dimensions, observer cleanup)
48
-
49
- ## Blocked by
50
-
51
- None — can start immediately.
@@ -1,81 +0,0 @@
1
- ## What to build
2
-
3
- Update `server.html` (the backend container page served at `/embed/{widget_id}`) to use HubSpot's container pattern instead of the current full-viewport iframe.
4
-
5
- ### Current (broken)
6
-
7
- The current `server.html` has a full-viewport iframe that blocks all pointer events on the host page:
8
-
9
- ```html
10
- <iframe style="border:none;position:fixed;bottom:0;right:0;width:100%;height:100%;z-index:9999;">
11
- </iframe>
12
- ```
13
-
14
- ### Target (container pattern)
15
-
16
- The updated page has a container div that starts at launcher size and is dynamically resized:
17
-
18
- ```html
19
- <div id="pager-widget-container"
20
- style="position:fixed;bottom:0;right:0;z-index:9999;min-height:60px;min-width:60px">
21
- <iframe src="https://dev.pagergpt.ai/wgt-hook/embed/{widget_id}?v={version}"
22
- style="position:absolute;width:100%!important;height:100%!important;border:none!important;bottom:0!important;right:0!important;background:transparent!important"
23
- title="Pager Widget">
24
- </iframe>
25
- </div>
26
- <script>
27
- (function() {
28
- var container = document.getElementById('pager-widget-container');
29
-
30
- window.addEventListener('message', function(event) {
31
- if (event.data && event.data.type === 'widgetResize') {
32
- container.style.width = event.data.width + 'px';
33
- container.style.height = event.data.height + 'px';
34
- }
35
- });
36
- })();
37
- </script>
38
- ```
39
-
40
- ### Behavior
41
-
42
- | Widget state | Container size | Page interaction |
43
- |---|---|---|
44
- | Collapsed (launcher only) | ~60×60px | Fully interactive — scroll, click all page elements |
45
- | Open (chat panel, DEFAULT) | ~400×58-70vh (dynamic) | Only the chat panel area captures events |
46
- | Open (chat panel, EXPANDED) | ~50vw × 58-70vh | Wider panel, still only panel area blocks events |
47
- | Open (chat panel, FULLSCREEN) | ~95vw × 58-70vh | Nearly full-width, still only panel area blocks events |
48
-
49
- ### CSS breakdown
50
-
51
- | Property | Value | Purpose |
52
- |---|---|---|
53
- | `position: fixed` | bottom; right | Anchors widget to viewport corner |
54
- | `z-index: 9999` | high value | Stays above page content |
55
- | `min-height: 60px` | launcher button height | Prevents collapse to zero before first message |
56
- | `min-width: 60px` | launcher button width | Prevents collapse to zero before first message |
57
- | `width` / `height` | set dynamically | Updated by message listener |
58
-
59
- The inner iframe uses `position:absolute;width:100%;height:100%` to fill the container.
60
-
61
- ### Security
62
-
63
- The `postMessage` listener does not check `event.origin` since customers have different domains. The widget side uses `targetOrigin: '*'`. Origin validation can be added later via a known-allowlist of customer origins.
64
-
65
- ### File to update
66
-
67
- - `server.html` — the backend template served at `/embed/{widget_id}`
68
-
69
- ## Acceptance criteria
70
-
71
- - [ ] `server.html` container div starts at `min-height:60px; min-width:60px` before first resize message
72
- - [ ] Page scrolling works when widget is collapsed (no full-viewport iframe)
73
- - [ ] Page scrolling works outside the widget's chat panel when open
74
- - [ ] Container resizes correctly when widget opens and collapses
75
- - [ ] Container resizes correctly when widget expands (DEFAULT → EXPANDED → FULLSCREEN)
76
- - [ ] UserDetails postMessage to inner iframe still works on load
77
- - [ ] Failed resize (JS error) leaves container at 60×60px (safe failure)
78
-
79
- ## Blocked by
80
-
81
- - [ ] Slice 1: `useWidgetResize` hook must be updated to ResizeObserver (the container page has nothing to listen to without the hook sending messages)