@patternfly/chatbot 6.4.0-prerelease.4 → 6.4.0-prerelease.5

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.
@@ -32,6 +32,7 @@ const MessageBarBase = (_a) => {
32
32
  const [message, setMessage] = (0, react_1.useState)(value !== null && value !== void 0 ? value : '');
33
33
  const [isListeningMessage, setIsListeningMessage] = (0, react_1.useState)(false);
34
34
  const [hasSentMessage, setHasSentMessage] = (0, react_1.useState)(false);
35
+ const [isComposing, setIsComposing] = (0, react_1.useState)(false);
35
36
  const inputRef = (0, react_1.useRef)(null);
36
37
  const textareaRef = (_b = innerRef) !== null && _b !== void 0 ? _b : inputRef;
37
38
  const attachButtonRef = (0, react_1.useRef)(null);
@@ -157,18 +158,32 @@ const MessageBarBase = (_a) => {
157
158
  setMessage('');
158
159
  }, [onSendMessage]);
159
160
  const handleKeyDown = (0, react_1.useCallback)((event) => {
160
- if (event.key === 'Enter' && !event.shiftKey) {
161
+ // Japanese and other languages may use IME for character input.
162
+ // In these cases, enter is used to select the final input, so we need to check for composition end instead.
163
+ // See more info at https://www.stum.de/2016/06/24/handling-ime-events-in-javascript/
164
+ // Chrome, Edge, and Firefox seem to work well with just the compose event.
165
+ // Safari is a little bit special. We need to handle 229 as well in this case.
166
+ const nativeEvent = event.nativeEvent;
167
+ const isCompositionKey = nativeEvent.which === 229;
168
+ const isCurrentlyComposing = isComposing || isCompositionKey;
169
+ if (event.key === 'Enter' && !isCurrentlyComposing && !event.shiftKey) {
161
170
  event.preventDefault();
162
171
  if (!isSendButtonDisabled && !hasStopButton) {
163
172
  handleSend(message);
164
173
  }
165
174
  }
166
- if (event.key === 'Enter' && event.shiftKey) {
175
+ if (event.key === 'Enter' && !isCurrentlyComposing && event.shiftKey) {
167
176
  if (textareaRef.current) {
168
177
  handleNewLine(textareaRef.current);
169
178
  }
170
179
  }
171
- }, [isSendButtonDisabled, hasStopButton, handleSend, message]);
180
+ }, [isSendButtonDisabled, hasStopButton, handleSend, message, isComposing]);
181
+ const handleCompositionStart = (0, react_1.useCallback)(() => {
182
+ setIsComposing(true);
183
+ }, []);
184
+ const handleCompositionEnd = (0, react_1.useCallback)(() => {
185
+ setIsComposing(false);
186
+ }, []);
172
187
  const handleAttachMenuToggle = () => {
173
188
  (attachMenuProps === null || attachMenuProps === void 0 ? void 0 : attachMenuProps.setIsAttachMenuOpen) && (attachMenuProps === null || attachMenuProps === void 0 ? void 0 : attachMenuProps.setIsAttachMenuOpen(!(attachMenuProps === null || attachMenuProps === void 0 ? void 0 : attachMenuProps.isAttachMenuOpen)));
174
189
  attachMenuProps === null || attachMenuProps === void 0 ? void 0 : attachMenuProps.onAttachMenuToggleClick();
@@ -184,7 +199,7 @@ const MessageBarBase = (_a) => {
184
199
  }
185
200
  return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [attachMenuProps && ((0, jsx_runtime_1.jsx)(AttachButton_1.AttachButton, Object.assign({ ref: attachButtonRef, onClick: handleAttachMenuToggle, isDisabled: isListeningMessage, tooltipContent: (_d = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.attach) === null || _d === void 0 ? void 0 : _d.tooltipContent, isCompact: isCompact, tooltipProps: (_e = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.attach) === null || _e === void 0 ? void 0 : _e.tooltipProps, allowedFileTypes: allowedFileTypes, minSize: minSize, maxSize: maxSize, maxFiles: maxFiles, isAttachmentDisabled: isAttachmentDisabled, onAttach: onAttach, onAttachRejected: onAttachRejected, validator: validator, dropzoneProps: dropzoneProps }, (_f = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.attach) === null || _f === void 0 ? void 0 : _f.props))), !attachMenuProps && hasAttachButton && ((0, jsx_runtime_1.jsx)(AttachButton_1.AttachButton, Object.assign({ onAttachAccepted: handleAttach, isDisabled: isListeningMessage, tooltipContent: (_g = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.attach) === null || _g === void 0 ? void 0 : _g.tooltipContent, inputTestId: (_h = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.attach) === null || _h === void 0 ? void 0 : _h.inputTestId, isCompact: isCompact, tooltipProps: (_j = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.attach) === null || _j === void 0 ? void 0 : _j.tooltipProps, allowedFileTypes: allowedFileTypes, minSize: minSize, maxSize: maxSize, maxFiles: maxFiles, isAttachmentDisabled: isAttachmentDisabled, onAttach: onAttach, onAttachRejected: onAttachRejected, validator: validator, dropzoneProps: dropzoneProps }, (_k = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.attach) === null || _k === void 0 ? void 0 : _k.props))), hasMicrophoneButton && ((0, jsx_runtime_1.jsx)(MicrophoneButton_1.default, Object.assign({ isListening: isListeningMessage, onIsListeningChange: setIsListeningMessage, onSpeechRecognition: handleSpeechRecognition, tooltipContent: (_l = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.microphone) === null || _l === void 0 ? void 0 : _l.tooltipContent, language: (_m = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.microphone) === null || _m === void 0 ? void 0 : _m.language, isCompact: isCompact, tooltipProps: (_o = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.microphone) === null || _o === void 0 ? void 0 : _o.tooltipProps }, (_p = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.microphone) === null || _p === void 0 ? void 0 : _p.props))), (alwayShowSendButton || message) && ((0, jsx_runtime_1.jsx)(SendButton_1.default, Object.assign({ value: message, onClick: () => handleSend(message), isDisabled: isSendButtonDisabled, tooltipContent: (_q = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.send) === null || _q === void 0 ? void 0 : _q.tooltipContent, isCompact: isCompact, tooltipProps: (_r = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.send) === null || _r === void 0 ? void 0 : _r.tooltipProps }, (_s = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.send) === null || _s === void 0 ? void 0 : _s.props)))] }));
186
201
  };
187
- const messageBarContents = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: `pf-chatbot__message-bar-input ${isCompact ? 'pf-m-compact' : ''}`, children: (0, jsx_runtime_1.jsx)(react_core_1.TextArea, Object.assign({ className: "pf-chatbot__message-textarea", value: message, onChange: handleChange, "aria-label": isListeningMessage ? listeningText : placeholder, placeholder: isListeningMessage ? listeningText : placeholder, ref: textareaRef, onKeyDown: handleKeyDown }, props)) }), (0, jsx_runtime_1.jsx)("div", { className: "pf-chatbot__message-bar-actions", children: renderButtons() })] }));
202
+ const messageBarContents = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: `pf-chatbot__message-bar-input ${isCompact ? 'pf-m-compact' : ''}`, children: (0, jsx_runtime_1.jsx)(react_core_1.TextArea, Object.assign({ className: "pf-chatbot__message-textarea", value: message, onChange: handleChange, "aria-label": isListeningMessage ? listeningText : placeholder, placeholder: isListeningMessage ? listeningText : placeholder, ref: textareaRef, onKeyDown: handleKeyDown, onCompositionStart: handleCompositionStart, onCompositionEnd: handleCompositionEnd }, props)) }), (0, jsx_runtime_1.jsx)("div", { className: "pf-chatbot__message-bar-actions", children: renderButtons() })] }));
188
203
  if (attachMenuProps) {
189
204
  return ((0, jsx_runtime_1.jsx)(AttachMenu_1.default, Object.assign({ toggle: (toggleRef) => ((0, jsx_runtime_1.jsx)("div", { ref: toggleRef, className: `pf-chatbot__message-bar ${className !== null && className !== void 0 ? className : ''}`, children: messageBarContents })), filteredItems: attachMenuProps === null || attachMenuProps === void 0 ? void 0 : attachMenuProps.attachMenuItems }, (attachMenuProps && { isOpen: attachMenuProps.isAttachMenuOpen }), { onOpenChange: (isAttachMenuOpen) => {
190
205
  var _a;
@@ -26,6 +26,7 @@ export const MessageBarBase = (_a) => {
26
26
  const [message, setMessage] = useState(value !== null && value !== void 0 ? value : '');
27
27
  const [isListeningMessage, setIsListeningMessage] = useState(false);
28
28
  const [hasSentMessage, setHasSentMessage] = useState(false);
29
+ const [isComposing, setIsComposing] = useState(false);
29
30
  const inputRef = useRef(null);
30
31
  const textareaRef = (_b = innerRef) !== null && _b !== void 0 ? _b : inputRef;
31
32
  const attachButtonRef = useRef(null);
@@ -151,18 +152,32 @@ export const MessageBarBase = (_a) => {
151
152
  setMessage('');
152
153
  }, [onSendMessage]);
153
154
  const handleKeyDown = useCallback((event) => {
154
- if (event.key === 'Enter' && !event.shiftKey) {
155
+ // Japanese and other languages may use IME for character input.
156
+ // In these cases, enter is used to select the final input, so we need to check for composition end instead.
157
+ // See more info at https://www.stum.de/2016/06/24/handling-ime-events-in-javascript/
158
+ // Chrome, Edge, and Firefox seem to work well with just the compose event.
159
+ // Safari is a little bit special. We need to handle 229 as well in this case.
160
+ const nativeEvent = event.nativeEvent;
161
+ const isCompositionKey = nativeEvent.which === 229;
162
+ const isCurrentlyComposing = isComposing || isCompositionKey;
163
+ if (event.key === 'Enter' && !isCurrentlyComposing && !event.shiftKey) {
155
164
  event.preventDefault();
156
165
  if (!isSendButtonDisabled && !hasStopButton) {
157
166
  handleSend(message);
158
167
  }
159
168
  }
160
- if (event.key === 'Enter' && event.shiftKey) {
169
+ if (event.key === 'Enter' && !isCurrentlyComposing && event.shiftKey) {
161
170
  if (textareaRef.current) {
162
171
  handleNewLine(textareaRef.current);
163
172
  }
164
173
  }
165
- }, [isSendButtonDisabled, hasStopButton, handleSend, message]);
174
+ }, [isSendButtonDisabled, hasStopButton, handleSend, message, isComposing]);
175
+ const handleCompositionStart = useCallback(() => {
176
+ setIsComposing(true);
177
+ }, []);
178
+ const handleCompositionEnd = useCallback(() => {
179
+ setIsComposing(false);
180
+ }, []);
166
181
  const handleAttachMenuToggle = () => {
167
182
  (attachMenuProps === null || attachMenuProps === void 0 ? void 0 : attachMenuProps.setIsAttachMenuOpen) && (attachMenuProps === null || attachMenuProps === void 0 ? void 0 : attachMenuProps.setIsAttachMenuOpen(!(attachMenuProps === null || attachMenuProps === void 0 ? void 0 : attachMenuProps.isAttachMenuOpen)));
168
183
  attachMenuProps === null || attachMenuProps === void 0 ? void 0 : attachMenuProps.onAttachMenuToggleClick();
@@ -178,7 +193,7 @@ export const MessageBarBase = (_a) => {
178
193
  }
179
194
  return (_jsxs(_Fragment, { children: [attachMenuProps && (_jsx(AttachButton, Object.assign({ ref: attachButtonRef, onClick: handleAttachMenuToggle, isDisabled: isListeningMessage, tooltipContent: (_d = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.attach) === null || _d === void 0 ? void 0 : _d.tooltipContent, isCompact: isCompact, tooltipProps: (_e = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.attach) === null || _e === void 0 ? void 0 : _e.tooltipProps, allowedFileTypes: allowedFileTypes, minSize: minSize, maxSize: maxSize, maxFiles: maxFiles, isAttachmentDisabled: isAttachmentDisabled, onAttach: onAttach, onAttachRejected: onAttachRejected, validator: validator, dropzoneProps: dropzoneProps }, (_f = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.attach) === null || _f === void 0 ? void 0 : _f.props))), !attachMenuProps && hasAttachButton && (_jsx(AttachButton, Object.assign({ onAttachAccepted: handleAttach, isDisabled: isListeningMessage, tooltipContent: (_g = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.attach) === null || _g === void 0 ? void 0 : _g.tooltipContent, inputTestId: (_h = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.attach) === null || _h === void 0 ? void 0 : _h.inputTestId, isCompact: isCompact, tooltipProps: (_j = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.attach) === null || _j === void 0 ? void 0 : _j.tooltipProps, allowedFileTypes: allowedFileTypes, minSize: minSize, maxSize: maxSize, maxFiles: maxFiles, isAttachmentDisabled: isAttachmentDisabled, onAttach: onAttach, onAttachRejected: onAttachRejected, validator: validator, dropzoneProps: dropzoneProps }, (_k = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.attach) === null || _k === void 0 ? void 0 : _k.props))), hasMicrophoneButton && (_jsx(MicrophoneButton, Object.assign({ isListening: isListeningMessage, onIsListeningChange: setIsListeningMessage, onSpeechRecognition: handleSpeechRecognition, tooltipContent: (_l = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.microphone) === null || _l === void 0 ? void 0 : _l.tooltipContent, language: (_m = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.microphone) === null || _m === void 0 ? void 0 : _m.language, isCompact: isCompact, tooltipProps: (_o = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.microphone) === null || _o === void 0 ? void 0 : _o.tooltipProps }, (_p = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.microphone) === null || _p === void 0 ? void 0 : _p.props))), (alwayShowSendButton || message) && (_jsx(SendButton, Object.assign({ value: message, onClick: () => handleSend(message), isDisabled: isSendButtonDisabled, tooltipContent: (_q = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.send) === null || _q === void 0 ? void 0 : _q.tooltipContent, isCompact: isCompact, tooltipProps: (_r = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.send) === null || _r === void 0 ? void 0 : _r.tooltipProps }, (_s = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.send) === null || _s === void 0 ? void 0 : _s.props)))] }));
180
195
  };
181
- const messageBarContents = (_jsxs(_Fragment, { children: [_jsx("div", { className: `pf-chatbot__message-bar-input ${isCompact ? 'pf-m-compact' : ''}`, children: _jsx(TextArea, Object.assign({ className: "pf-chatbot__message-textarea", value: message, onChange: handleChange, "aria-label": isListeningMessage ? listeningText : placeholder, placeholder: isListeningMessage ? listeningText : placeholder, ref: textareaRef, onKeyDown: handleKeyDown }, props)) }), _jsx("div", { className: "pf-chatbot__message-bar-actions", children: renderButtons() })] }));
196
+ const messageBarContents = (_jsxs(_Fragment, { children: [_jsx("div", { className: `pf-chatbot__message-bar-input ${isCompact ? 'pf-m-compact' : ''}`, children: _jsx(TextArea, Object.assign({ className: "pf-chatbot__message-textarea", value: message, onChange: handleChange, "aria-label": isListeningMessage ? listeningText : placeholder, placeholder: isListeningMessage ? listeningText : placeholder, ref: textareaRef, onKeyDown: handleKeyDown, onCompositionStart: handleCompositionStart, onCompositionEnd: handleCompositionEnd }, props)) }), _jsx("div", { className: "pf-chatbot__message-bar-actions", children: renderButtons() })] }));
182
197
  if (attachMenuProps) {
183
198
  return (_jsx(AttachMenu, Object.assign({ toggle: (toggleRef) => (_jsx("div", { ref: toggleRef, className: `pf-chatbot__message-bar ${className !== null && className !== void 0 ? className : ''}`, children: messageBarContents })), filteredItems: attachMenuProps === null || attachMenuProps === void 0 ? void 0 : attachMenuProps.attachMenuItems }, (attachMenuProps && { isOpen: attachMenuProps.isAttachMenuOpen }), { onOpenChange: (isAttachMenuOpen) => {
184
199
  var _a;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patternfly/chatbot",
3
- "version": "6.4.0-prerelease.4",
3
+ "version": "6.4.0-prerelease.5",
4
4
  "description": "This library provides React components based on PatternFly 6 that can be used to build chatbots.",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -141,6 +141,7 @@ export const MessageBarBase: FunctionComponent<MessageBarProps> = ({
141
141
  const [message, setMessage] = useState<string | number>(value ?? '');
142
142
  const [isListeningMessage, setIsListeningMessage] = useState<boolean>(false);
143
143
  const [hasSentMessage, setHasSentMessage] = useState(false);
144
+ const [isComposing, setIsComposing] = useState(false);
144
145
  const inputRef = useRef<HTMLTextAreaElement>(null);
145
146
  const textareaRef = (innerRef as React.RefObject<HTMLTextAreaElement>) ?? inputRef;
146
147
  const attachButtonRef = useRef<HTMLButtonElement>(null);
@@ -285,21 +286,38 @@ export const MessageBarBase: FunctionComponent<MessageBarProps> = ({
285
286
 
286
287
  const handleKeyDown = useCallback(
287
288
  (event: ReactKeyboardEvent) => {
288
- if (event.key === 'Enter' && !event.shiftKey) {
289
+ // Japanese and other languages may use IME for character input.
290
+ // In these cases, enter is used to select the final input, so we need to check for composition end instead.
291
+ // See more info at https://www.stum.de/2016/06/24/handling-ime-events-in-javascript/
292
+ // Chrome, Edge, and Firefox seem to work well with just the compose event.
293
+ // Safari is a little bit special. We need to handle 229 as well in this case.
294
+ const nativeEvent = event.nativeEvent as KeyboardEvent;
295
+ const isCompositionKey = nativeEvent.which === 229;
296
+ const isCurrentlyComposing = isComposing || isCompositionKey;
297
+
298
+ if (event.key === 'Enter' && !isCurrentlyComposing && !event.shiftKey) {
289
299
  event.preventDefault();
290
300
  if (!isSendButtonDisabled && !hasStopButton) {
291
301
  handleSend(message);
292
302
  }
293
303
  }
294
- if (event.key === 'Enter' && event.shiftKey) {
304
+ if (event.key === 'Enter' && !isCurrentlyComposing && event.shiftKey) {
295
305
  if (textareaRef.current) {
296
306
  handleNewLine(textareaRef.current);
297
307
  }
298
308
  }
299
309
  },
300
- [isSendButtonDisabled, hasStopButton, handleSend, message]
310
+ [isSendButtonDisabled, hasStopButton, handleSend, message, isComposing]
301
311
  );
302
312
 
313
+ const handleCompositionStart = useCallback(() => {
314
+ setIsComposing(true);
315
+ }, []);
316
+
317
+ const handleCompositionEnd = useCallback(() => {
318
+ setIsComposing(false);
319
+ }, []);
320
+
303
321
  const handleAttachMenuToggle = () => {
304
322
  attachMenuProps?.setIsAttachMenuOpen && attachMenuProps?.setIsAttachMenuOpen(!attachMenuProps?.isAttachMenuOpen);
305
323
  attachMenuProps?.onAttachMenuToggleClick();
@@ -402,6 +420,8 @@ export const MessageBarBase: FunctionComponent<MessageBarProps> = ({
402
420
  placeholder={isListeningMessage ? listeningText : placeholder}
403
421
  ref={textareaRef}
404
422
  onKeyDown={handleKeyDown}
423
+ onCompositionStart={handleCompositionStart}
424
+ onCompositionEnd={handleCompositionEnd}
405
425
  {...props}
406
426
  />
407
427
  </div>