@seamly/web-ui 22.0.0 → 22.0.1

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 (53) hide show
  1. package/build/dist/lib/components.js +130 -100
  2. package/build/dist/lib/components.js.map +1 -1
  3. package/build/dist/lib/components.min.js +1 -1
  4. package/build/dist/lib/components.min.js.LICENSE.txt +2 -2
  5. package/build/dist/lib/components.min.js.map +1 -1
  6. package/build/dist/lib/deprecated-view.css +1 -1
  7. package/build/dist/lib/deprecated-view.js +1 -1
  8. package/build/dist/lib/hooks.js +106 -96
  9. package/build/dist/lib/hooks.js.map +1 -1
  10. package/build/dist/lib/hooks.min.js +1 -1
  11. package/build/dist/lib/hooks.min.js.map +1 -1
  12. package/build/dist/lib/index.debug.js +57 -43
  13. package/build/dist/lib/index.debug.js.map +1 -1
  14. package/build/dist/lib/index.debug.min.js +1 -1
  15. package/build/dist/lib/index.debug.min.js.LICENSE.txt +12 -8
  16. package/build/dist/lib/index.debug.min.js.map +1 -1
  17. package/build/dist/lib/index.js +173 -143
  18. package/build/dist/lib/index.js.map +1 -1
  19. package/build/dist/lib/index.min.js +1 -1
  20. package/build/dist/lib/index.min.js.LICENSE.txt +2 -2
  21. package/build/dist/lib/index.min.js.map +1 -1
  22. package/build/dist/lib/standalone.js +395 -171
  23. package/build/dist/lib/standalone.js.map +1 -1
  24. package/build/dist/lib/standalone.min.js +1 -1
  25. package/build/dist/lib/standalone.min.js.LICENSE.txt +1 -1
  26. package/build/dist/lib/standalone.min.js.map +1 -1
  27. package/build/dist/lib/storage.js +17 -17
  28. package/build/dist/lib/storage.js.map +1 -1
  29. package/build/dist/lib/storage.min.js +1 -1
  30. package/build/dist/lib/storage.min.js.LICENSE.txt +1 -1
  31. package/build/dist/lib/storage.min.js.map +1 -1
  32. package/build/dist/lib/style-guide.js +159 -134
  33. package/build/dist/lib/style-guide.js.map +1 -1
  34. package/build/dist/lib/style-guide.min.js +1 -1
  35. package/build/dist/lib/style-guide.min.js.LICENSE.txt +2 -2
  36. package/build/dist/lib/style-guide.min.js.map +1 -1
  37. package/build/dist/lib/styles-default-implementation.js +1 -1
  38. package/build/dist/lib/styles.css +1 -1
  39. package/build/dist/lib/styles.js +1 -1
  40. package/build/dist/lib/utils.js +167 -146
  41. package/build/dist/lib/utils.js.map +1 -1
  42. package/build/dist/lib/utils.min.js +1 -1
  43. package/build/dist/lib/utils.min.js.LICENSE.txt +1 -1
  44. package/build/dist/lib/utils.min.js.map +1 -1
  45. package/package.json +30 -27
  46. package/src/javascripts/api/conversation-connector.ts +6 -4
  47. package/src/javascripts/domains/store/slice.ts +6 -0
  48. package/src/javascripts/domains/store/store.types.ts +4 -1
  49. package/src/javascripts/ui/components/conversation/conversation.tsx +3 -1
  50. package/src/javascripts/ui/components/core/seamly-event-subscriber.ts +38 -49
  51. package/src/javascripts/ui/components/core/seamly-live-region.js +1 -1
  52. package/src/javascripts/ui/hooks/use-debounce.ts +18 -0
  53. package/src/javascripts/ui/hooks/use-seamly-chat.ts +3 -42
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seamly/web-ui",
3
- "version": "22.0.0",
3
+ "version": "22.0.1",
4
4
  "main": "build/dist/lib/index.js",
5
5
  "types": "build/src/javascripts/index.d.ts",
6
6
  "module": "",
@@ -22,29 +22,29 @@
22
22
  "webpack/*"
23
23
  ],
24
24
  "dependencies": {
25
- "@reduxjs/toolkit": "^1.9.3",
25
+ "@reduxjs/toolkit": "^1.9.5",
26
26
  "@ultraq/icu-message-formatter": "^0.12.0",
27
- "core-js": "^3.30.0",
27
+ "core-js": "^3.30.2",
28
28
  "deep-keys": "^0.5.0",
29
- "focus-trap": "^7.4.0",
29
+ "focus-trap": "^7.4.1",
30
30
  "include-media": "^2.0.0",
31
- "js-cookie": "^3.0.1",
31
+ "js-cookie": "^3.0.5",
32
32
  "minivents": "^2.2.1",
33
33
  "phoenix": "^1.7.2",
34
34
  "react-redux": "^8.0.5"
35
35
  },
36
36
  "devDependencies": {
37
- "@babel/core": "^7.21.4",
37
+ "@babel/core": "^7.21.8",
38
38
  "@babel/plugin-proposal-object-rest-spread": "^7.20.7",
39
39
  "@babel/plugin-syntax-dynamic-import": "^7.8.3",
40
40
  "@babel/plugin-transform-classes": "^7.21.0",
41
- "@babel/plugin-transform-react-jsx": "^7.21.0",
41
+ "@babel/plugin-transform-react-jsx": "^7.21.5",
42
42
  "@babel/plugin-transform-runtime": "^7.21.4",
43
- "@babel/preset-env": "^7.21.4",
43
+ "@babel/preset-env": "^7.21.5",
44
44
  "@babel/preset-react": "^7.18.6",
45
- "@babel/preset-typescript": "^7.21.4",
46
- "@babel/runtime-corejs3": "^7.21.0",
47
- "@playwright/test": "^1.32.2",
45
+ "@babel/preset-typescript": "^7.21.5",
46
+ "@babel/runtime-corejs3": "^7.21.5",
47
+ "@playwright/test": "^1.33.0",
48
48
  "@seamly/doc-site": "^2.0.0",
49
49
  "@seamly/eslint-config": "^2.3.0",
50
50
  "@seamly/prettier-config": "^2.2.0",
@@ -53,17 +53,17 @@
53
53
  "@testing-library/preact": "^3.2.3",
54
54
  "@trivago/prettier-plugin-sort-imports": "^4.1.1",
55
55
  "@types/core-js": "^2.5.5",
56
- "@types/jest": "^29.5.0",
57
- "@types/phoenix": "^1.5.5",
58
- "@typescript-eslint/eslint-plugin": "^5.57.1",
59
- "@typescript-eslint/parser": "^5.57.1",
56
+ "@types/jest": "^29.5.1",
57
+ "@types/phoenix": "^1.5.6",
58
+ "@typescript-eslint/eslint-plugin": "^5.59.5",
59
+ "@typescript-eslint/parser": "^5.59.5",
60
60
  "babel-jest": "^29.5.0",
61
61
  "babel-loader": "^9.1.2",
62
62
  "babel-plugin-istanbul": "^6.1.1",
63
63
  "babel-plugin-react-remove-properties": "^0.3.0",
64
64
  "copy-webpack-plugin": "^11.0.0",
65
65
  "debug": "^4.3.4",
66
- "eslint": "^8.37.0",
66
+ "eslint": "^8.40.0",
67
67
  "eslint-config-prettier": "^8.8.0",
68
68
  "eslint-import-resolver-alias": "^1.1.2",
69
69
  "eslint-import-resolver-typescript": "^3.5.5",
@@ -75,7 +75,7 @@
75
75
  "eslint-plugin-react-hooks": "^4.6.0",
76
76
  "file-loader": "^6.2.0",
77
77
  "fork-ts-checker-webpack-plugin": "^8.0.0",
78
- "glob": "^9.3.4",
78
+ "glob": "^10.2.4",
79
79
  "husky": "^8.0.3",
80
80
  "ignore-loader": "^0.1.2",
81
81
  "isomorphic-unfetch": "^4.0.2",
@@ -85,24 +85,27 @@
85
85
  "mini-css-extract-plugin": "^2.7.5",
86
86
  "nyc": "^15.1.0",
87
87
  "playwright-test-coverage": "^1.2.12",
88
- "postcss": "^8.4.21",
89
- "preact": "^10.13.2",
90
- "preact-render-to-string": "^6.0.2",
91
- "prettier": "^2.8.7",
88
+ "postcss": "^8.4.23",
89
+ "preact": "^10.14.0",
90
+ "preact-render-to-string": "^6.0.3",
91
+ "prettier": "^2.8.8",
92
92
  "raw-loader": "^4.0.2",
93
- "rimraf": "^4.4.1",
93
+ "rimraf": "^5.0.0",
94
94
  "start-server-and-test": "^2.0.0",
95
95
  "style-loader": "^3.3.2",
96
- "stylelint": "^15.4.0",
96
+ "stylelint": "^15.6.1",
97
97
  "ts-loader": "^9.4.2",
98
- "typescript": "^5.0.3",
98
+ "typescript": "^5.0.4",
99
99
  "url-loader": "^4.1.1",
100
- "webpack": "^5.78.0",
100
+ "webpack": "^5.82.1",
101
101
  "webpack-bundle-analyzer": "^4.8.0",
102
- "webpack-cli": "^5.0.1",
103
- "webpack-dev-server": "^4.13.2",
102
+ "webpack-cli": "^5.1.1",
103
+ "webpack-dev-server": "^4.15.0",
104
104
  "webpack-merge": "^5.8.0"
105
105
  },
106
+ "resolutions": {
107
+ "string-width": "^4.2.3"
108
+ },
106
109
  "peerDependencies": {
107
110
  "preact": "^10.13.2"
108
111
  },
@@ -147,10 +147,12 @@ export default class ConversationConnector {
147
147
  }
148
148
 
149
149
  disconnect() {
150
- this.channel?.leave()
151
- this.socket?.remove(this.channel)
152
- this.socket?.disconnect()
153
- this.#connectionListeners = []
150
+ this.channel?.leave().receive('ok', () => {
151
+ log('[CHANNEL][LEAVE] OK')
152
+ this.socket?.remove(this.channel)
153
+ this.socket?.disconnect()
154
+ this.#connectionListeners = []
155
+ })
154
156
  }
155
157
 
156
158
  #listenTo(...types: string[]) {
@@ -41,6 +41,8 @@ export const mergeHistory = (
41
41
  ) => {
42
42
  const newStateEvents = stateEvents.filter(
43
43
  (stateEvent) =>
44
+ stateEvent.type === 'message' &&
45
+ !stateEvent.payload.optimisticallyInjected &&
44
46
  // Deduplicate the event streams, giving events in historyEvents
45
47
  // precedence so the server is able to push changes to events.
46
48
  !historyEvents.some(
@@ -638,6 +640,9 @@ export const storeSlice = createSlice({
638
640
  extraReducers: (builder) => {
639
641
  builder
640
642
  .addCase(resetApp.pending, () => initialStoreState)
643
+ .addCase(initializeApp.pending, (state) => {
644
+ state.isLoading = true
645
+ })
641
646
  .addCase(initializeConfig.fulfilled, (state, { payload }) => {
642
647
  state.headerTitles.subTitle = payload.agentParticipant?.name
643
648
 
@@ -647,6 +652,7 @@ export const storeSlice = createSlice({
647
652
  })
648
653
  .addCase(initializeApp.fulfilled, (state, { payload }) => {
649
654
  if (!payload.initialState) return
655
+ state.isLoading = false
650
656
  state.initialState = payload.initialState
651
657
  })
652
658
  .addMatcher(
@@ -48,7 +48,10 @@ export type MessageUpload = components['schemas']['MessageMessage'] & {
48
48
 
49
49
  export interface MessageEvent {
50
50
  type: 'message'
51
- payload: MessageMessage & DefaultEventProps
51
+ payload: MessageMessage &
52
+ DefaultEventProps & {
53
+ optimisticallyInjected?: boolean
54
+ }
52
55
  }
53
56
 
54
57
  export type MessageParticipant = components['schemas']['MessageParticipant'] & {
@@ -5,6 +5,7 @@ import {
5
5
  useSkiplinkTargetFocusing,
6
6
  } from 'ui/hooks/seamly-hooks'
7
7
  import { useEvents } from 'ui/hooks/seamly-state-hooks'
8
+ import useDebounce from 'ui/hooks/use-debounce'
8
9
  import { useI18n } from 'domains/i18n/hooks'
9
10
  import { useVisibility } from 'domains/visibility/hooks'
10
11
  import { className } from 'lib/css'
@@ -49,6 +50,7 @@ const Events = () => {
49
50
  const Conversation = () => {
50
51
  const { t } = useI18n()
51
52
  const isLoading = useSeamlyIsLoading()
53
+ const debouncedIsLoading = useDebounce(isLoading, isLoading ? 0 : 20)
52
54
  const { isOpen } = useVisibility()
53
55
  const skiplinkTargetId = useSkiplink()
54
56
  const focusSkiplinkTarget = useSkiplinkTargetFocusing()
@@ -77,7 +79,7 @@ const Conversation = () => {
77
79
  <ComponentFilter>
78
80
  <Events />
79
81
  </ComponentFilter>
80
- {isLoading && <Loader />}
82
+ {debouncedIsLoading ? <Loader /> : null}
81
83
  <AbortTransactionButton />
82
84
  </ol>
83
85
  </div>
@@ -28,7 +28,11 @@ import {
28
28
  setServiceDataItem,
29
29
  setServiceEntryMetadata,
30
30
  } from 'domains/store/slice'
31
- import type { AckEvent, ChannelEvent } from 'domains/store/store.types'
31
+ import type {
32
+ AckEvent,
33
+ ChannelEvent,
34
+ ParticipantEvent,
35
+ } from 'domains/store/store.types'
32
36
  import { setTranslationProposalPrompt } from 'domains/translations/slice'
33
37
 
34
38
  const EMITTABLE_MESSAGE_TYPES = ['text', 'choice_prompt', 'image', 'video']
@@ -67,16 +71,6 @@ const SeamlyEventSubscriber = () => {
67
71
  }
68
72
  }, [api, api.connectionInfo, api.conversation.socket, dispatch])
69
73
 
70
- useEffect(() => {
71
- if (api.connectionInfo && api.conversation.channel) {
72
- const { channel } = api.conversation
73
- return () => {
74
- channel?.leave()
75
- }
76
- }
77
- return () => undefined
78
- }, [api, api.connectionInfo, api.conversation])
79
-
80
74
  useEffect(() => {
81
75
  api.conversation.onConnection(({ currentState }) => {
82
76
  if (currentState === 'join_channel_erred') {
@@ -93,51 +87,46 @@ const SeamlyEventSubscriber = () => {
93
87
  )
94
88
  }
95
89
  })
96
- }, [api.conversation, dispatch])
90
+ }, [api, api.connectionInfo, api.conversation.channel, dispatch])
97
91
 
98
92
  useEffect(() => {
99
93
  if (api.connectionInfo) {
100
- api.conversation.onConnection(({ connected }) => {
101
- if (!connected) return false
102
-
103
- const { channel } = api.conversation
94
+ const updateParticipant = (event: ParticipantEvent) => {
95
+ if (event.type !== 'participant') {
96
+ return
97
+ }
98
+ const { payload } = event
104
99
 
105
- const updateParticipant = (event) => {
106
- const { payload } = event
100
+ if (!payload || !payload.participant) {
101
+ return
102
+ }
107
103
 
108
- if (!payload || !payload.participant) {
109
- return
110
- }
104
+ const { fromClient, participant } = payload
111
105
 
112
- const { fromClient, participant } = payload
106
+ if (
107
+ !fromClient &&
108
+ typeof participant !== 'string' &&
109
+ participant?.name
110
+ ) {
111
+ dispatch(setHeaderSubTitle(participant.name))
112
+ }
113
113
 
114
- if (
115
- !fromClient &&
116
- typeof participant !== 'string' &&
117
- participant?.name
118
- ) {
119
- dispatch(setHeaderSubTitle(participant.name))
120
- }
114
+ dispatch(
115
+ setParticipant({
116
+ participant,
117
+ fromClient,
118
+ }),
119
+ )
121
120
 
122
- dispatch(
123
- setParticipant({
124
- participant,
125
- fromClient,
126
- }),
127
- )
128
-
129
- if (typeof participant !== 'string' && participant.introduction) {
130
- dispatch(
131
- addEvent({
132
- ...event,
133
- payload: {
134
- ...payload,
135
- type: 'participant',
136
- },
137
- }),
138
- )
139
- }
121
+ if (typeof participant !== 'string' && participant.introduction) {
122
+ dispatch(addEvent(event))
140
123
  }
124
+ }
125
+
126
+ api.conversation.onConnection(({ connected }) => {
127
+ if (!connected) return false
128
+
129
+ const { channel } = api.conversation
141
130
 
142
131
  channel.onMessage = (type, payload) => {
143
132
  const event = { type, payload }
@@ -189,7 +178,7 @@ const SeamlyEventSubscriber = () => {
189
178
  }
190
179
  break
191
180
  case 'participant':
192
- updateParticipant(event)
181
+ updateParticipant(event as ParticipantEvent)
193
182
  break
194
183
  case 'service_data':
195
184
  if (payload.persist) {
@@ -268,7 +257,7 @@ const SeamlyEventSubscriber = () => {
268
257
  return payload
269
258
  }
270
259
 
271
- return true
260
+ return false
272
261
  })
273
262
  }
274
263
  }, [
@@ -31,7 +31,7 @@ const SeamlyLiveRegion = ({ children }) => {
31
31
  timeOutHandler = setTimeout(() => {
32
32
  messageSetter({})
33
33
  clearTimeout(timeOutHandler)
34
- }, 500) // To get it to work in VoiceOver in MacOS
34
+ }, 600) // To get it to work in VoiceOver in MacOS
35
35
  }
36
36
 
37
37
  return () => {
@@ -0,0 +1,18 @@
1
+ import { useEffect, useRef, useState } from 'preact/hooks'
2
+
3
+ const useDebounce = <T extends unknown>(value: T, delay = 20): T => {
4
+ const [debouncedValue, setDebouncedValue] = useState<T>(value)
5
+ const timeoutRef = useRef<ReturnType<typeof setTimeout>>(null)
6
+
7
+ useEffect(() => {
8
+ timeoutRef.current = setTimeout(() => setDebouncedValue(value), delay)
9
+
10
+ return () => {
11
+ clearTimeout(timeoutRef.current)
12
+ }
13
+ }, [value, delay])
14
+
15
+ return debouncedValue
16
+ }
17
+
18
+ export default useDebounce
@@ -1,25 +1,19 @@
1
1
  import { useCallback, useEffect, useRef } from 'preact/hooks'
2
- import { useDispatch, useSelector } from 'react-redux'
2
+ import { useSelector } from 'react-redux'
3
3
  import useSeamlyCommands from 'ui/hooks/use-seamly-commands'
4
4
  import { useI18n } from 'domains/i18n/hooks'
5
- import { setIsLoading } from 'domains/store/slice'
6
5
  import { useVisibility } from 'domains/visibility/hooks'
7
6
  import { selectShowInlineView } from '../../domains/visibility/selectors'
8
7
  import { useLiveRegion } from './live-region-hooks'
9
- import { useSeamlyHasConversation } from './seamly-api-hooks'
10
- import { useEvents, useSeamlyLayoutMode } from './seamly-state-hooks'
8
+ import { useSeamlyLayoutMode } from './seamly-state-hooks'
11
9
  import useSessionExpiredCommand from './use-session-expired-command'
12
10
 
13
11
  const useSeamlyChat = () => {
14
- const events = useEvents()
15
12
  const { t } = useI18n()
16
13
  const { isInline, isWindow, isApp } = useSeamlyLayoutMode()
17
14
  const { isOpen, isVisible } = useVisibility()
18
15
  const showInlineView = useSelector(selectShowInlineView)
19
- const dispatch = useDispatch()
20
- const spinnerTimeout = useRef(null)
21
16
  const { start, connect, apiConfigReady, apiConnected } = useSeamlyCommands()
22
- const hasConversation = useSeamlyHasConversation()
23
17
  const connectCalled = useRef(false)
24
18
  const { sendAssertive } = useLiveRegion()
25
19
 
@@ -48,30 +42,6 @@ const useSeamlyChat = () => {
48
42
  }
49
43
  }, [isOpen, isVisible, sendAssertive, t])
50
44
 
51
- useEffect(() => {
52
- // This delays the start of the loading inidicator we set when we initialise
53
- // the application. This is done to only avoid BSOD on initial load if DCX is slow.
54
- spinnerTimeout.current = setTimeout(() => {
55
- dispatch(setIsLoading(true))
56
- }, 500)
57
-
58
- return () => {
59
- clearTimeout(spinnerTimeout.current)
60
- }
61
- }, [dispatch])
62
-
63
- useEffect(() => {
64
- if (events.length) {
65
- spinnerTimeout.current = setTimeout(() => {
66
- dispatch(setIsLoading(false))
67
- }, 5000)
68
- }
69
-
70
- return () => {
71
- clearTimeout(spinnerTimeout.current)
72
- }
73
- }, [events, dispatch])
74
-
75
45
  useEffect(() => {
76
46
  // This is needed to reset the ref to allow connect and start to happen again.
77
47
  // Mostly due to Interrupt situations and a reset being called.
@@ -107,23 +77,14 @@ const useSeamlyChat = () => {
107
77
  return
108
78
  }
109
79
 
110
- if (hasConversation() && isOpen) {
111
- // We deactivate the extra startup loading spinner when a conversation is available
112
- // We also stop setting the loading indicator in the first place to avoid a flash.
113
- clearTimeout(spinnerTimeout.current)
114
- dispatch(setIsLoading(false))
115
- }
116
-
117
80
  connectAndStart()
118
81
  }, [
119
82
  apiConfigReady,
120
83
  connectAndStart,
121
- dispatch,
122
- hasConversation,
123
84
  isInline,
124
85
  isOpen,
125
- isWindow,
126
86
  isVisible,
87
+ isWindow,
127
88
  showInlineView,
128
89
  ])
129
90
  }