@hh.ru/magritte-ui-chat-input 1.2.2 → 1.2.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 (66) hide show
  1. package/index.css +117 -117
  2. package/index.d.ts +10 -10
  3. package/index.js +1 -1
  4. package/index.mock.d.ts +9 -9
  5. package/index.mock.js +37 -36
  6. package/index.mock.js.map +1 -1
  7. package/internal/ChatInputContext.d.ts +1 -1
  8. package/internal/ChatInputContext.js.map +1 -1
  9. package/internal/ChatInputSlotButton.js +13 -13
  10. package/internal/ChatInputSlotButton.js.map +1 -1
  11. package/internal/FocusTarget.js +3 -10
  12. package/internal/FocusTarget.js.map +1 -1
  13. package/internal/ModeTransition.js +41 -11
  14. package/internal/ModeTransition.js.map +1 -1
  15. package/internal/TextInput.d.ts +1 -1
  16. package/internal/TextInput.js +146 -126
  17. package/internal/TextInput.js.map +1 -1
  18. package/internal/VoiceRecord.d.ts +1 -1
  19. package/internal/VoiceRecord.js +66 -24
  20. package/internal/VoiceRecord.js.map +1 -1
  21. package/internal/VoiceRecordIndicator.js +22 -9
  22. package/internal/VoiceRecordIndicator.js.map +1 -1
  23. package/internal/flattenFragments.js +9 -13
  24. package/internal/flattenFragments.js.map +1 -1
  25. package/internal/focusGroup.js +18 -28
  26. package/internal/focusGroup.js.map +1 -1
  27. package/internal/useHasSound.js +49 -64
  28. package/internal/useHasSound.js.map +1 -1
  29. package/internal/useObjectUrl.js +15 -21
  30. package/internal/useObjectUrl.js.map +1 -1
  31. package/internal/useRovingFocusGroup.js +102 -137
  32. package/internal/useRovingFocusGroup.js.map +1 -1
  33. package/internal/useSendOnEnter.js +44 -46
  34. package/internal/useSendOnEnter.js.map +1 -1
  35. package/internal/useVoiceRecordSession.d.ts +1 -1
  36. package/internal/useVoiceRecordSession.js +185 -219
  37. package/internal/useVoiceRecordSession.js.map +1 -1
  38. package/internal/voiceSlotRegistry.d.ts +1 -1
  39. package/internal/voiceSlotRegistry.js +3 -3
  40. package/internal/voiceSlotRegistry.js.map +1 -1
  41. package/package.json +17 -16
  42. package/public/ChatInput.d.ts +6 -6
  43. package/public/ChatInput.js +129 -73
  44. package/public/ChatInput.js.map +1 -1
  45. package/public/ChatInputChips.js +4 -4
  46. package/public/ChatInputChips.js.map +1 -1
  47. package/public/ChatInputFile.d.ts +1 -1
  48. package/public/ChatInputFile.js +107 -47
  49. package/public/ChatInputFile.js.map +1 -1
  50. package/public/ChatInputFiles.js +10 -3
  51. package/public/ChatInputFiles.js.map +1 -1
  52. package/public/ChatInputPreview.js +14 -9
  53. package/public/ChatInputPreview.js.map +1 -1
  54. package/public/ChatInputSendButton.d.ts +1 -1
  55. package/public/ChatInputSendButton.js +34 -23
  56. package/public/ChatInputSendButton.js.map +1 -1
  57. package/public/ChatInputVoiceButton.d.ts +1 -1
  58. package/public/ChatInputVoiceButton.js +87 -91
  59. package/public/ChatInputVoiceButton.js.map +1 -1
  60. package/public/getChatInputFileType.d.ts +1 -1
  61. package/public/getChatInputFileType.js +7 -10
  62. package/public/getChatInputFileType.js.map +1 -1
  63. package/styles-DofLkp5v.js +5 -0
  64. package/styles-DofLkp5v.js.map +1 -0
  65. package/styles-CHMi0EYL.js +0 -5
  66. package/styles-CHMi0EYL.js.map +0 -1
package/index.mock.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.mock.js","sources":["../mock-component/lib/setupMockComponent.js","src/index.mock.ts"],"sourcesContent":["import { jsxs, jsx } from 'react/jsx-runtime';\nimport { forwardRef } from 'react';\n\nfunction enhance(mockComponent, staticProps) {\n // удаляем внутренний свойства реакта, чтобы не перезаписать ими mockComponent\n delete staticProps.type;\n delete staticProps.$$typeof;\n delete staticProps.render;\n for (const prop in staticProps) {\n mockComponent[prop] = staticProps[prop];\n }\n}\nconst MockComponent = ({ style, ...props }) => (\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-ignore\n// eslint-disable-next-line react/jsx-no-undef\njsx(\"magritte-component-mock\", { \"style-data\": style, ...props }));\nconst MockComponentRenderProp = ({ style, ...props }) => (\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-ignore\n// eslint-disable-next-line react/jsx-no-undef\njsx(\"magritte-component-mock-render-prop\", { \"style-data\": style, ...props }));\n/**\n * Возвращает mock React компонента\n */\nconst mockComponent = (componentName, staticProps = {}, { withChildren, innerDependence, WrapperComponent } = {}) => {\n const mockWrapper = forwardRef(({ children, ...props }, ref) => {\n const mock = (\n // eslint-disable-next-line react/jsx-no-undef\n jsxs(MockComponent, { ...props, _component: componentName, children: [innerDependence ? innerDependence(props, ref) : null, withChildren ? children : null] }));\n return WrapperComponent ? jsx(WrapperComponent, { ...props, children: mock }) : mock;\n });\n staticProps && enhance(mockWrapper, staticProps);\n mockWrapper.displayName = 'magritte-ref';\n return mockWrapper;\n};\n/**\n * Возвращает mock React компонента, который рендерит переданный в него пропс render\n *\n * @param {String} componentName наименование компонента\n * @param {Object} [staticProps] объект со свойствами, которыми необходимо усилить mock\n * @param {Object} [options] параметры мока\n * @param {boolean} [options.withChildren] флаг отвечает за рендеринг children компонента\n * @param {React.Component} [options.innerDependence] Реакт компонент, который рендерится внутри мока.\n * Необходим, когда mock компоненту требуется иметь\n * дополнительные элементы\n */\nconst mockComponentRenderProp = (componentName, staticProps, { withChildren, innerDependence, renderArguments } = { renderArguments: [] }) => {\n const mockWrapper = ({ render, children, ...props }) => (jsxs(MockComponentRenderProp, { ...props, _component: componentName, children: [innerDependence ? innerDependence(props) : null, withChildren ? children : null, render?.(...(renderArguments || []))] }));\n staticProps && enhance(mockWrapper, staticProps);\n mockWrapper.displayName = 'magritte-component-mock-render-prop';\n return mockWrapper;\n};\n\nexport { mockComponent, mockComponentRenderProp };\n//# sourceMappingURL=setupMockComponent.js.map\n","import { mockComponent } from '@hh.ru/magritte-ui-mock-component';\n\nexport { type ChatInputProps } from '@hh.ru/magritte-ui-chat-input/public/ChatInput';\nexport { type ChatInputVoiceRecordConfig } from '@hh.ru/magritte-ui-chat-input/types';\nexport { type ChatInputChipsProps } from '@hh.ru/magritte-ui-chat-input/public/ChatInputChips';\nexport {\n type ChatInputFileProps,\n type ChatInputFileState,\n type ChatInputFileType,\n} from '@hh.ru/magritte-ui-chat-input/public/ChatInputFile';\nexport { getChatInputFileType } from '@hh.ru/magritte-ui-chat-input/public/getChatInputFileType';\nexport { type ChatInputFilesProps } from '@hh.ru/magritte-ui-chat-input/public/ChatInputFiles';\nexport { type ChatInputPreviewProps } from '@hh.ru/magritte-ui-chat-input/public/ChatInputPreview';\nexport { type ChatInputSendButtonProps } from '@hh.ru/magritte-ui-chat-input/public/ChatInputSendButton';\nexport { type ChatInputVoiceButtonProps } from '@hh.ru/magritte-ui-chat-input/public/ChatInputVoiceButton';\n\nconst staticProps = {\n Chips: mockComponent('Chips', undefined, {\n withChildren: true,\n }),\n File: mockComponent('File'),\n Files: mockComponent('Files', undefined, {\n withChildren: true,\n }),\n Preview: mockComponent('Preview'),\n SendButton: mockComponent('SendButton'),\n VoiceButton: mockComponent('VoiceButton'),\n};\n\nexport const ChatInput = mockComponent('ChatInput', staticProps, {\n withChildren: true,\n});\n"],"names":[],"mappings":";;;;AAGA,SAAS,OAAO,CAAC,aAAa,EAAE,WAAW,EAAE;AAC7C;AACA,IAAI,OAAO,WAAW,CAAC,IAAI,CAAC;AAC5B,IAAI,OAAO,WAAW,CAAC,QAAQ,CAAC;AAChC,IAAI,OAAO,WAAW,CAAC,MAAM,CAAC;AAC9B,IAAI,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE;AACpC,QAAQ,aAAa,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;AAChD,KAAK;AACL,CAAC;AACD,MAAM,aAAa,GAAG,CAAC,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE;AAC1C;AACA;AACA;AACA,GAAG,CAAC,yBAAyB,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC;AAMnE;AACA;AACA;AACA,MAAM,aAAa,GAAG,CAAC,aAAa,EAAE,WAAW,GAAG,EAAE,EAAE,EAAE,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,GAAG,EAAE,KAAK;AACrH,IAAI,MAAM,WAAW,GAAG,UAAU,CAAC,CAAC,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG,KAAK;AACpE,QAAQ,MAAM,IAAI;AAClB;AACA,QAAQ,IAAI,CAAC,aAAa,EAAE,EAAE,GAAG,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,eAAe,GAAG,eAAe,CAAC,KAAK,EAAE,GAAG,CAAC,GAAG,IAAI,EAAE,YAAY,GAAG,QAAQ,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;AACxK,QAAQ,OAAO,gBAAgB,GAAG,GAAG,CAAC,gBAAgB,EAAE,EAAE,GAAG,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;AAC7F,KAAK,CAAC,CAAC;AACP,IAAI,WAAW,IAAI,OAAO,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;AACrD,IAAI,WAAW,CAAC,WAAW,GAAG,cAAc,CAAC;AAC7C,IAAI,OAAO,WAAW,CAAC;AACvB,CAAC;;ACnBD,MAAM,WAAW,GAAG;AAChB,IAAA,KAAK,EAAE,aAAa,CAAC,OAAO,EAAE,SAAS,EAAE;AACrC,QAAA,YAAY,EAAE,IAAI;KACrB,CAAC;AACF,IAAA,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC;AAC3B,IAAA,KAAK,EAAE,aAAa,CAAC,OAAO,EAAE,SAAS,EAAE;AACrC,QAAA,YAAY,EAAE,IAAI;KACrB,CAAC;AACF,IAAA,OAAO,EAAE,aAAa,CAAC,SAAS,CAAC;AACjC,IAAA,UAAU,EAAE,aAAa,CAAC,YAAY,CAAC;AACvC,IAAA,WAAW,EAAE,aAAa,CAAC,aAAa,CAAC;CAC5C,CAAC;MAEW,SAAS,GAAG,aAAa,CAAC,WAAW,EAAE,WAAW,EAAE;AAC7D,IAAA,YAAY,EAAE,IAAI;AACrB,CAAA;;;;"}
1
+ {"version":3,"file":"index.mock.js","sources":["../mock-component/lib/setupMockComponent.js","src/index.mock.ts"],"sourcesContent":["import { jsxs, jsx } from 'react/jsx-runtime';\nimport { forwardRef } from 'react';\n\nfunction enhance(mockComponent2, staticProps) {\n delete staticProps.type;\n delete staticProps.$$typeof;\n delete staticProps.render;\n for (const prop in staticProps) {\n mockComponent2[prop] = staticProps[prop];\n }\n}\nconst MockComponent = ({ style, ...props }) => (\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n // eslint-disable-next-line react/jsx-no-undef\n /* @__PURE__ */ jsx(\"magritte-component-mock\", { \"style-data\": style, ...props })\n);\nconst MockComponentRenderProp = ({ style, ...props }) => (\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n // eslint-disable-next-line react/jsx-no-undef\n /* @__PURE__ */ jsx(\"magritte-component-mock-render-prop\", { \"style-data\": style, ...props })\n);\nconst mockComponent = (componentName, staticProps = {}, { withChildren, innerDependence, WrapperComponent } = {}) => {\n const mockWrapper = forwardRef(({ children, ...props }, ref) => {\n const mock = (\n // eslint-disable-next-line react/jsx-no-undef\n /* @__PURE__ */ jsxs(MockComponent, { ...props, _component: componentName, children: [\n innerDependence ? innerDependence(props, ref) : null,\n withChildren ? children : null\n ] })\n );\n return WrapperComponent ? /* @__PURE__ */ jsx(WrapperComponent, { ...props, children: mock }) : mock;\n });\n staticProps && enhance(mockWrapper, staticProps);\n mockWrapper.displayName = \"magritte-ref\";\n return mockWrapper;\n};\nconst mockComponentRenderProp = (componentName, staticProps, { withChildren, innerDependence, renderArguments } = { renderArguments: [] }) => {\n const mockWrapper = ({\n render,\n children,\n ...props\n }) => /* @__PURE__ */ jsxs(MockComponentRenderProp, { ...props, _component: componentName, children: [\n innerDependence ? innerDependence(props) : null,\n withChildren ? children : null,\n render?.(...renderArguments || [])\n ] });\n staticProps && enhance(mockWrapper, staticProps);\n mockWrapper.displayName = \"magritte-component-mock-render-prop\";\n return mockWrapper;\n};\n\nexport { mockComponent, mockComponentRenderProp };\n//# sourceMappingURL=setupMockComponent.js.map\n","import { mockComponent } from '@hh.ru/magritte-ui-mock-component';\n\nexport { type ChatInputProps } from '@hh.ru/magritte-ui-chat-input/public/ChatInput';\nexport { type ChatInputVoiceRecordConfig } from '@hh.ru/magritte-ui-chat-input/types';\nexport { type ChatInputChipsProps } from '@hh.ru/magritte-ui-chat-input/public/ChatInputChips';\nexport {\n type ChatInputFileProps,\n type ChatInputFileState,\n type ChatInputFileType,\n} from '@hh.ru/magritte-ui-chat-input/public/ChatInputFile';\nexport { getChatInputFileType } from '@hh.ru/magritte-ui-chat-input/public/getChatInputFileType';\nexport { type ChatInputFilesProps } from '@hh.ru/magritte-ui-chat-input/public/ChatInputFiles';\nexport { type ChatInputPreviewProps } from '@hh.ru/magritte-ui-chat-input/public/ChatInputPreview';\nexport { type ChatInputSendButtonProps } from '@hh.ru/magritte-ui-chat-input/public/ChatInputSendButton';\nexport { type ChatInputVoiceButtonProps } from '@hh.ru/magritte-ui-chat-input/public/ChatInputVoiceButton';\n\nconst staticProps = {\n Chips: mockComponent('Chips', undefined, {\n withChildren: true,\n }),\n File: mockComponent('File'),\n Files: mockComponent('Files', undefined, {\n withChildren: true,\n }),\n Preview: mockComponent('Preview'),\n SendButton: mockComponent('SendButton'),\n VoiceButton: mockComponent('VoiceButton'),\n};\n\nexport const ChatInput = mockComponent('ChatInput', staticProps, {\n withChildren: true,\n});\n"],"names":[],"mappings":";;;;AAGA,SAAS,OAAO,CAAC,cAAc,EAAE,WAAW,EAAE;AAC9C,EAAE,OAAO,WAAW,CAAC,IAAI,CAAC;AAC1B,EAAE,OAAO,WAAW,CAAC,QAAQ,CAAC;AAC9B,EAAE,OAAO,WAAW,CAAC,MAAM,CAAC;AAC5B,EAAE,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE;AAClC,IAAI,cAAc,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;AAC7C,GAAG;AACH,CAAC;AACD,MAAM,aAAa,GAAG,CAAC,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE;AAC1C;AACA;AACA;AACA,kBAAkB,GAAG,CAAC,yBAAyB,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,CAAC;AACnF,CAAC,CAAC;AAOF,MAAM,aAAa,GAAG,CAAC,aAAa,EAAE,WAAW,GAAG,EAAE,EAAE,EAAE,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,GAAG,EAAE,KAAK;AACrH,EAAE,MAAM,WAAW,GAAG,UAAU,CAAC,CAAC,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG,KAAK;AAClE,IAAI,MAAM,IAAI;AACd;AACA,sBAAsB,IAAI,CAAC,aAAa,EAAE,EAAE,GAAG,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,QAAQ,EAAE;AAC3F,QAAQ,eAAe,GAAG,eAAe,CAAC,KAAK,EAAE,GAAG,CAAC,GAAG,IAAI;AAC5D,QAAQ,YAAY,GAAG,QAAQ,GAAG,IAAI;AACtC,OAAO,EAAE,CAAC;AACV,KAAK,CAAC;AACN,IAAI,OAAO,gBAAgB,mBAAmB,GAAG,CAAC,gBAAgB,EAAE,EAAE,GAAG,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;AACzG,GAAG,CAAC,CAAC;AACL,EAAE,WAAW,IAAI,OAAO,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;AACnD,EAAE,WAAW,CAAC,WAAW,GAAG,cAAc,CAAC;AAC3C,EAAE,OAAO,WAAW,CAAC;AACrB,CAAC;;ACrBD,MAAM,WAAc,GAAA;AAAA,EAChB,KAAA,EAAO,aAAc,CAAA,OAAA,EAAS,KAAW,CAAA,EAAA;AAAA,IACrC,YAAc,EAAA,IAAA;AAAA,GACjB,CAAA;AAAA,EACD,IAAA,EAAM,cAAc,MAAM,CAAA;AAAA,EAC1B,KAAA,EAAO,aAAc,CAAA,OAAA,EAAS,KAAW,CAAA,EAAA;AAAA,IACrC,YAAc,EAAA,IAAA;AAAA,GACjB,CAAA;AAAA,EACD,OAAA,EAAS,cAAc,SAAS,CAAA;AAAA,EAChC,UAAA,EAAY,cAAc,YAAY,CAAA;AAAA,EACtC,WAAA,EAAa,cAAc,aAAa,CAAA;AAC5C,CAAA,CAAA;AAEa,MAAA,SAAA,GAAY,aAAc,CAAA,WAAA,EAAa,WAAa,EAAA;AAAA,EAC7D,YAAc,EAAA,IAAA;AAClB,CAAC;;;;"}
@@ -1,4 +1,4 @@
1
- import { type VoiceRecordStatus } from '@hh.ru/magritte-ui-chat-input/types';
1
+ import { type VoiceRecordStatus } from '../types';
2
2
  interface ChatInputContextValue {
3
3
  /** Хендлер отправки; SendButton вызывает его по клику */
4
4
  onSend?: VoidFunction;
@@ -1 +1 @@
1
- {"version":3,"file":"ChatInputContext.js","sources":["src/internal/ChatInputContext.ts"],"sourcesContent":["import { createContext, useContext } from 'react';\n\nimport { type VoiceRecordStatus } from '@hh.ru/magritte-ui-chat-input/types';\n\ninterface ChatInputContextValue {\n /** Хендлер отправки; SendButton вызывает его по клику */\n onSend?: VoidFunction;\n /** Старт записи голоса; VoiceButton вызывает его по клику. Есть внутри ChatInput\n * (снаружи контекст пустой, кнопка неактивна) */\n startVoiceRecord?: VoidFunction;\n /** Статус сессии записи — VoiceButton показывает по нему ожидание доступа к микрофону */\n voiceRecordStatus?: VoiceRecordStatus;\n}\n\nconst ChatInputContext = createContext<ChatInputContextValue>({});\n\nexport const ChatInputContextProvider = ChatInputContext.Provider;\n\nexport const useChatInputContext = (): ChatInputContextValue => useContext(ChatInputContext);\n"],"names":[],"mappings":";;AAcA,MAAM,gBAAgB,GAAG,aAAa,CAAwB,EAAE,CAAC,CAAC;AAErD,MAAA,wBAAwB,GAAG,gBAAgB,CAAC,SAAS;AAErD,MAAA,mBAAmB,GAAG,MAA6B,UAAU,CAAC,gBAAgB;;;;"}
1
+ {"version":3,"file":"ChatInputContext.js","sources":["src/internal/ChatInputContext.ts"],"sourcesContent":["import { createContext, useContext } from 'react';\n\nimport { type VoiceRecordStatus } from '@hh.ru/magritte-ui-chat-input/types';\n\ninterface ChatInputContextValue {\n /** Хендлер отправки; SendButton вызывает его по клику */\n onSend?: VoidFunction;\n /** Старт записи голоса; VoiceButton вызывает его по клику. Есть внутри ChatInput\n * (снаружи контекст пустой, кнопка неактивна) */\n startVoiceRecord?: VoidFunction;\n /** Статус сессии записи — VoiceButton показывает по нему ожидание доступа к микрофону */\n voiceRecordStatus?: VoiceRecordStatus;\n}\n\nconst ChatInputContext = createContext<ChatInputContextValue>({});\n\nexport const ChatInputContextProvider = ChatInputContext.Provider;\n\nexport const useChatInputContext = (): ChatInputContextValue => useContext(ChatInputContext);\n"],"names":[],"mappings":";;AAcA,MAAM,gBAAA,GAAmB,aAAqC,CAAA,EAAE,CAAA,CAAA;AAEzD,MAAM,2BAA2B,gBAAiB,CAAA,SAAA;AAE5C,MAAA,mBAAA,GAAsB,MAA6B,UAAA,CAAW,gBAAgB;;;;"}
@@ -5,21 +5,21 @@ import { useDisabled } from '@hh.ru/magritte-common-use-disabled';
5
5
  import { IconDefaultPropsContext } from '@hh.ru/magritte-ui-icon';
6
6
 
7
7
  const ChatInputSlotButton = ({ icon, iconProps, onClick, disabled, children }) => {
8
- const handledEventRef = useRef(null);
9
- const isDisabled = useDisabled() || !!disabled;
10
- if (Children.toArray(children).length === 0) {
11
- return icon;
8
+ const handledEventRef = useRef(null);
9
+ const isDisabled = useDisabled() || !!disabled;
10
+ if (Children.toArray(children).length === 0) {
11
+ return icon;
12
+ }
13
+ const handleClickOnce = (event) => {
14
+ if (handledEventRef.current === event.nativeEvent || isDisabled) {
15
+ return;
12
16
  }
13
- const handleClickOnce = (event) => {
14
- if (handledEventRef.current === event.nativeEvent || isDisabled) {
15
- return;
16
- }
17
- handledEventRef.current = event.nativeEvent;
18
- onClick?.(event);
19
- };
20
- return (jsx(IconDefaultPropsContext, { props: { ...iconProps, onClick: handleClickOnce }, children: jsx("div", { style: { display: 'contents' }, onClick: handleClickOnce, children: children }) }));
17
+ handledEventRef.current = event.nativeEvent;
18
+ onClick?.(event);
19
+ };
20
+ return /* @__PURE__ */ jsx(IconDefaultPropsContext, { props: { ...iconProps, onClick: handleClickOnce }, children: /* @__PURE__ */ jsx("div", { style: { display: "contents" }, onClick: handleClickOnce, children }) });
21
21
  };
22
- ChatInputSlotButton.displayName = 'ChatInputSlotButton';
22
+ ChatInputSlotButton.displayName = "ChatInputSlotButton";
23
23
 
24
24
  export { ChatInputSlotButton };
25
25
  //# sourceMappingURL=ChatInputSlotButton.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChatInputSlotButton.js","sources":["src/internal/ChatInputSlotButton.tsx"],"sourcesContent":["import {\n Children,\n type FC,\n type MouseEvent as ReactMouseEvent,\n type MouseEventHandler,\n type ReactElement,\n type ReactNode,\n useRef,\n} from 'react';\n\nimport { useDisabled } from '@hh.ru/magritte-common-use-disabled';\nimport { IconDefaultPropsContext } from '@hh.ru/magritte-ui-icon';\nimport { type IconProps } from '@hh.ru/magritte-ui-icon/types';\n\ninterface ChatInputSlotButtonProps {\n /** Встроенная иконка со всеми пропсами — рендерится, когда children не переданы */\n icon: ReactElement;\n /** Те же пропсы иконки — уходят в IconDefaultPropsContext, чтобы children-иконка получила\n * то же оформление/aria и (за счёт onClick) стала интерактивной кнопкой */\n iconProps: Partial<IconProps>;\n /** Обработчик клика (тот же, что в `iconProps.onClick`) — им ловим всплывший клик от\n * произвольных (не-иконочных) children */\n onClick?: MouseEventHandler<HTMLButtonElement>;\n /** Индивидуальная блокировка кнопки (в дополнение к общему `disabled` из `DisabledContext`) —\n * гасит клик по произвольным children так же, как контекстный disabled */\n disabled?: boolean;\n /** Кастомный контент вместо встроенной иконки */\n children?: ReactNode;\n}\n\nexport const ChatInputSlotButton: FC<ChatInputSlotButtonProps> = ({ icon, iconProps, onClick, disabled, children }) => {\n const handledEventRef = useRef<Event | null>(null);\n const isDisabled = useDisabled() || !!disabled;\n\n if (Children.toArray(children).length === 0) {\n return icon;\n }\n\n const handleClickOnce = (event: ReactMouseEvent<HTMLElement>) => {\n if (handledEventRef.current === event.nativeEvent || isDisabled) {\n return;\n }\n handledEventRef.current = event.nativeEvent;\n onClick?.(event as ReactMouseEvent<HTMLButtonElement>);\n };\n\n return (\n <IconDefaultPropsContext props={{ ...iconProps, onClick: handleClickOnce }}>\n <div style={{ display: 'contents' }} onClick={handleClickOnce}>\n {children}\n </div>\n </IconDefaultPropsContext>\n );\n};\n\nChatInputSlotButton.displayName = 'ChatInputSlotButton';\n"],"names":["_jsx"],"mappings":";;;;;AA8Ba,MAAA,mBAAmB,GAAiC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAI;AAClH,IAAA,MAAM,eAAe,GAAG,MAAM,CAAe,IAAI,CAAC,CAAC;IACnD,MAAM,UAAU,GAAG,WAAW,EAAE,IAAI,CAAC,CAAC,QAAQ,CAAC;IAE/C,IAAI,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE;AACzC,QAAA,OAAO,IAAI,CAAC;KACf;AAED,IAAA,MAAM,eAAe,GAAG,CAAC,KAAmC,KAAI;QAC5D,IAAI,eAAe,CAAC,OAAO,KAAK,KAAK,CAAC,WAAW,IAAI,UAAU,EAAE;YAC7D,OAAO;SACV;AACD,QAAA,eAAe,CAAC,OAAO,GAAG,KAAK,CAAC,WAAW,CAAC;AAC5C,QAAA,OAAO,GAAG,KAA2C,CAAC,CAAC;AAC3D,KAAC,CAAC;AAEF,IAAA,QACIA,GAAA,CAAC,uBAAuB,EAAA,EAAC,KAAK,EAAE,EAAE,GAAG,SAAS,EAAE,OAAO,EAAE,eAAe,EAAE,EACtE,QAAA,EAAAA,GAAA,CAAA,KAAA,EAAA,EAAK,KAAK,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,OAAO,EAAE,eAAe,EACxD,QAAA,EAAA,QAAQ,EACP,CAAA,EAAA,CACgB,EAC5B;AACN,EAAE;AAEF,mBAAmB,CAAC,WAAW,GAAG,qBAAqB;;;;"}
1
+ {"version":3,"file":"ChatInputSlotButton.js","sources":["src/internal/ChatInputSlotButton.tsx"],"sourcesContent":["import {\n Children,\n type FC,\n type MouseEvent as ReactMouseEvent,\n type MouseEventHandler,\n type ReactElement,\n type ReactNode,\n useRef,\n} from 'react';\n\nimport { useDisabled } from '@hh.ru/magritte-common-use-disabled';\nimport { IconDefaultPropsContext } from '@hh.ru/magritte-ui-icon';\nimport { type IconProps } from '@hh.ru/magritte-ui-icon/types';\n\ninterface ChatInputSlotButtonProps {\n /** Встроенная иконка со всеми пропсами — рендерится, когда children не переданы */\n icon: ReactElement;\n /** Те же пропсы иконки — уходят в IconDefaultPropsContext, чтобы children-иконка получила\n * то же оформление/aria и (за счёт onClick) стала интерактивной кнопкой */\n iconProps: Partial<IconProps>;\n /** Обработчик клика (тот же, что в `iconProps.onClick`) — им ловим всплывший клик от\n * произвольных (не-иконочных) children */\n onClick?: MouseEventHandler<HTMLButtonElement>;\n /** Индивидуальная блокировка кнопки (в дополнение к общему `disabled` из `DisabledContext`) —\n * гасит клик по произвольным children так же, как контекстный disabled */\n disabled?: boolean;\n /** Кастомный контент вместо встроенной иконки */\n children?: ReactNode;\n}\n\nexport const ChatInputSlotButton: FC<ChatInputSlotButtonProps> = ({ icon, iconProps, onClick, disabled, children }) => {\n const handledEventRef = useRef<Event | null>(null);\n const isDisabled = useDisabled() || !!disabled;\n\n if (Children.toArray(children).length === 0) {\n return icon;\n }\n\n const handleClickOnce = (event: ReactMouseEvent<HTMLElement>) => {\n if (handledEventRef.current === event.nativeEvent || isDisabled) {\n return;\n }\n handledEventRef.current = event.nativeEvent;\n onClick?.(event as ReactMouseEvent<HTMLButtonElement>);\n };\n\n return (\n <IconDefaultPropsContext props={{ ...iconProps, onClick: handleClickOnce }}>\n <div style={{ display: 'contents' }} onClick={handleClickOnce}>\n {children}\n </div>\n </IconDefaultPropsContext>\n );\n};\n\nChatInputSlotButton.displayName = 'ChatInputSlotButton';\n"],"names":[],"mappings":";;;;;AA8Ba,MAAA,mBAAA,GAAoD,CAAC,EAAE,IAAA,EAAM,WAAW,OAAS,EAAA,QAAA,EAAU,UAAe,KAAA;AACnH,EAAM,MAAA,eAAA,GAAkB,OAAqB,IAAI,CAAA,CAAA;AACjD,EAAA,MAAM,UAAa,GAAA,WAAA,EAAiB,IAAA,CAAC,CAAC,QAAA,CAAA;AAEtC,EAAA,IAAI,QAAS,CAAA,OAAA,CAAQ,QAAQ,CAAA,CAAE,WAAW,CAAG,EAAA;AACzC,IAAO,OAAA,IAAA,CAAA;AAAA,GACX;AAEA,EAAM,MAAA,eAAA,GAAkB,CAAC,KAAwC,KAAA;AAC7D,IAAA,IAAI,eAAgB,CAAA,OAAA,KAAY,KAAM,CAAA,WAAA,IAAe,UAAY,EAAA;AAC7D,MAAA,OAAA;AAAA,KACJ;AACA,IAAA,eAAA,CAAgB,UAAU,KAAM,CAAA,WAAA,CAAA;AAChC,IAAA,OAAA,GAAU,KAA2C,CAAA,CAAA;AAAA,GACzD,CAAA;AAEA,EAAA,2BACK,uBAAwB,EAAA,EAAA,KAAA,EAAO,EAAE,GAAG,SAAA,EAAW,SAAS,eAAgB,EAAA,EACrE,8BAAC,KAAI,EAAA,EAAA,KAAA,EAAO,EAAE,OAAS,EAAA,UAAA,IAAc,OAAS,EAAA,eAAA,EACzC,UACL,CACJ,EAAA,CAAA,CAAA;AAER,EAAA;AAEA,mBAAA,CAAoB,WAAc,GAAA,qBAAA;;;;"}
@@ -2,16 +2,9 @@ import './../index.css';
2
2
  import { jsx } from 'react/jsx-runtime';
3
3
  import { focusPriorityProps } from './focusGroup.js';
4
4
 
5
- // стиль обёртки-маркера: не создаёт бокс, дети остаются flex-элементами родительского слота
6
- const TARGET_STYLE = { display: 'contents' };
7
- /**
8
- * Обёртка-маркер вокруг наших кнопок для roving-группы (useRovingFocusGroup). `display:contents` —
9
- * не создаёт бокс, так что раскладка слота/блока записи не меняется, а менеджер находит кнопку
10
- * внутри по data-атрибуту. Маркер вешаем на обёртку, а не на саму иконку: кастомный data-* на
11
- * иконке уезжает на внутренний скрытый <svg> (не на фокусируемый <button>) и не проходит по типам.
12
- */
13
- const FocusTarget = ({ priority, children }) => (jsx("span", { style: TARGET_STYLE, ...focusPriorityProps(priority), children: children }));
14
- FocusTarget.displayName = 'ChatInputFocusTarget';
5
+ const TARGET_STYLE = { display: "contents" };
6
+ const FocusTarget = ({ priority, children }) => /* @__PURE__ */ jsx("span", { style: TARGET_STYLE, ...focusPriorityProps(priority), children });
7
+ FocusTarget.displayName = "ChatInputFocusTarget";
15
8
 
16
9
  export { FocusTarget };
17
10
  //# sourceMappingURL=FocusTarget.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"FocusTarget.js","sources":["src/internal/FocusTarget.tsx"],"sourcesContent":["import { type FC, type ReactNode } from 'react';\n\nimport { focusPriorityProps } from '@hh.ru/magritte-ui-chat-input/internal/focusGroup';\n\n// стиль обёртки-маркера: не создаёт бокс, дети остаются flex-элементами родительского слота\nconst TARGET_STYLE = { display: 'contents' } as const;\n\ninterface FocusTargetProps {\n /** Приоритет обхода стрелками в roving-группе (меньше — раньше), см. FOCUS_PRIORITY */\n priority: number;\n children: ReactNode;\n}\n\n/**\n * Обёртка-маркер вокруг наших кнопок для roving-группы (useRovingFocusGroup). `display:contents` —\n * не создаёт бокс, так что раскладка слота/блока записи не меняется, а менеджер находит кнопку\n * внутри по data-атрибуту. Маркер вешаем на обёртку, а не на саму иконку: кастомный data-* на\n * иконке уезжает на внутренний скрытый <svg> (не на фокусируемый <button>) и не проходит по типам.\n */\nexport const FocusTarget: FC<FocusTargetProps> = ({ priority, children }) => (\n <span style={TARGET_STYLE} {...focusPriorityProps(priority)}>\n {children}\n </span>\n);\n\nFocusTarget.displayName = 'ChatInputFocusTarget';\n"],"names":["_jsx"],"mappings":";;;AAIA;AACA,MAAM,YAAY,GAAG,EAAE,OAAO,EAAE,UAAU,EAAW,CAAC;AAQtD;;;;;AAKG;AACU,MAAA,WAAW,GAAyB,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,MACpEA,GAAA,CAAA,MAAA,EAAA,EAAM,KAAK,EAAE,YAAY,EAAA,GAAM,kBAAkB,CAAC,QAAQ,CAAC,EACtD,QAAA,EAAA,QAAQ,EACN,CAAA,EACT;AAEF,WAAW,CAAC,WAAW,GAAG,sBAAsB;;;;"}
1
+ {"version":3,"file":"FocusTarget.js","sources":["src/internal/FocusTarget.tsx"],"sourcesContent":["import { type FC, type ReactNode } from 'react';\n\nimport { focusPriorityProps } from '@hh.ru/magritte-ui-chat-input/internal/focusGroup';\n\n// стиль обёртки-маркера: не создаёт бокс, дети остаются flex-элементами родительского слота\nconst TARGET_STYLE = { display: 'contents' } as const;\n\ninterface FocusTargetProps {\n /** Приоритет обхода стрелками в roving-группе (меньше — раньше), см. FOCUS_PRIORITY */\n priority: number;\n children: ReactNode;\n}\n\n/**\n * Обёртка-маркер вокруг наших кнопок для roving-группы (useRovingFocusGroup). `display:contents` —\n * не создаёт бокс, так что раскладка слота/блока записи не меняется, а менеджер находит кнопку\n * внутри по data-атрибуту. Маркер вешаем на обёртку, а не на саму иконку: кастомный data-* на\n * иконке уезжает на внутренний скрытый <svg> (не на фокусируемый <button>) и не проходит по типам.\n */\nexport const FocusTarget: FC<FocusTargetProps> = ({ priority, children }) => (\n <span style={TARGET_STYLE} {...focusPriorityProps(priority)}>\n {children}\n </span>\n);\n\nFocusTarget.displayName = 'ChatInputFocusTarget';\n"],"names":[],"mappings":";;;AAKA,MAAM,YAAA,GAAe,EAAE,OAAA,EAAS,UAAW,EAAA,CAAA;AAcpC,MAAM,WAAoC,GAAA,CAAC,EAAE,QAAA,EAAU,UAC1D,qBAAA,GAAA,CAAC,MAAK,EAAA,EAAA,KAAA,EAAO,YAAe,EAAA,GAAG,kBAAmB,CAAA,QAAQ,GACrD,QACL,EAAA,EAAA;AAGJ,WAAA,CAAY,WAAc,GAAA,sBAAA;;;;"}
@@ -2,23 +2,53 @@ import './../index.css';
2
2
  import { jsxs, jsx } from 'react/jsx-runtime';
3
3
  import { useReducedMotion, motion } from 'motion/react';
4
4
  import CoreTokens from '@hh.ru/magritte-design-tokens/core-tokens';
5
- import { s as styles } from '../styles-CHMi0EYL.js';
5
+ import { s as styles } from '../styles-DofLkp5v.js';
6
6
 
7
- const EASE_BASE = CoreTokens['magritte-semantic-animation-ease-base'];
8
- const MODE_TRANSITION_DURATION_MS = CoreTokens['magritte-semantic-animation-time-s'].duration;
7
+ const EASE_BASE = CoreTokens["magritte-semantic-animation-ease-base"];
8
+ const MODE_TRANSITION_DURATION_MS = CoreTokens["magritte-semantic-animation-time-s"].duration;
9
9
  const INSTANT_TRANSITION = { duration: 0 };
10
10
  const MODE_CONTENT_TRANSITION = {
11
- duration: MODE_TRANSITION_DURATION_MS / 1000,
12
- ease: EASE_BASE,
11
+ duration: MODE_TRANSITION_DURATION_MS / 1e3,
12
+ ease: EASE_BASE
13
13
  };
14
- const VISIBLE_LAYER = { opacity: 1, visibility: 'visible' };
15
- const HIDDEN_LAYER = { opacity: 0, transitionEnd: { visibility: 'hidden' } };
14
+ const VISIBLE_LAYER = { opacity: 1, visibility: "visible" };
15
+ const HIDDEN_LAYER = { opacity: 0, transitionEnd: { visibility: "hidden" } };
16
16
  const ModeTransition = ({ mode, text, voice }) => {
17
- const transition = useReducedMotion() ? INSTANT_TRANSITION : MODE_CONTENT_TRANSITION;
18
- const isVoice = mode === 'voice';
19
- return (jsxs(motion.div, { className: styles.modeContainer, animate: { height: isVoice ? 40 : 'auto' }, transition: transition, children: [jsx(motion.div, { initial: false, animate: isVoice ? HIDDEN_LAYER : VISIBLE_LAYER, transition: transition, "aria-hidden": isVoice, children: text }), jsx(motion.div, { className: styles.voiceOverlay, initial: false, animate: isVoice ? VISIBLE_LAYER : HIDDEN_LAYER, transition: transition, "aria-hidden": !isVoice, children: voice })] }));
17
+ const transition = useReducedMotion() ? INSTANT_TRANSITION : MODE_CONTENT_TRANSITION;
18
+ const isVoice = mode === "voice";
19
+ return /* @__PURE__ */ jsxs(
20
+ motion.div,
21
+ {
22
+ className: styles.modeContainer,
23
+ animate: { height: isVoice ? 40 : "auto" },
24
+ transition,
25
+ children: [
26
+ /* @__PURE__ */ jsx(
27
+ motion.div,
28
+ {
29
+ initial: false,
30
+ animate: isVoice ? HIDDEN_LAYER : VISIBLE_LAYER,
31
+ transition,
32
+ "aria-hidden": isVoice,
33
+ children: text
34
+ }
35
+ ),
36
+ /* @__PURE__ */ jsx(
37
+ motion.div,
38
+ {
39
+ className: styles.voiceOverlay,
40
+ initial: false,
41
+ animate: isVoice ? VISIBLE_LAYER : HIDDEN_LAYER,
42
+ transition,
43
+ "aria-hidden": !isVoice,
44
+ children: voice
45
+ }
46
+ )
47
+ ]
48
+ }
49
+ );
20
50
  };
21
- ModeTransition.displayName = 'ChatInputModeTransition';
51
+ ModeTransition.displayName = "ChatInputModeTransition";
22
52
 
23
53
  export { ModeTransition };
24
54
  //# sourceMappingURL=ModeTransition.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ModeTransition.js","sources":["src/internal/ModeTransition.tsx"],"sourcesContent":["import { type FC, type ReactNode } from 'react';\nimport { motion, type TargetAndTransition, useReducedMotion } from 'motion/react';\n\nimport CoreTokens from '@hh.ru/magritte-design-tokens/core-tokens';\n\nimport styles from '../styles.less';\n\ntype ChatInputMode = 'text' | 'voice';\n\nexport interface ModeTransitionProps {\n /** Активный режим; смена запускает кроссфейд контента и анимацию высоты оболочки */\n mode: ChatInputMode;\n /** Контент текстового режима (сетка со слотами и вводом) */\n text: ReactNode;\n /** Контент режима записи голоса */\n voice: ReactNode;\n}\n\nconst EASE_BASE = CoreTokens['magritte-semantic-animation-ease-base'];\n\nconst MODE_TRANSITION_DURATION_MS = CoreTokens['magritte-semantic-animation-time-s'].duration;\nconst INSTANT_TRANSITION = { duration: 0 } as const;\n\nconst MODE_CONTENT_TRANSITION = {\n duration: MODE_TRANSITION_DURATION_MS / 1000,\n ease: EASE_BASE,\n} as const;\n\nconst VISIBLE_LAYER: TargetAndTransition = { opacity: 1, visibility: 'visible' };\nconst HIDDEN_LAYER: TargetAndTransition = { opacity: 0, transitionEnd: { visibility: 'hidden' } };\n\nexport const ModeTransition: FC<ModeTransitionProps> = ({ mode, text, voice }) => {\n const transition = useReducedMotion() ? INSTANT_TRANSITION : MODE_CONTENT_TRANSITION;\n const isVoice = mode === 'voice';\n\n return (\n <motion.div\n className={styles.modeContainer}\n animate={{ height: isVoice ? 40 : 'auto' }}\n transition={transition}\n >\n <motion.div\n initial={false}\n animate={isVoice ? HIDDEN_LAYER : VISIBLE_LAYER}\n transition={transition}\n aria-hidden={isVoice}\n >\n {text}\n </motion.div>\n <motion.div\n className={styles.voiceOverlay}\n initial={false}\n animate={isVoice ? VISIBLE_LAYER : HIDDEN_LAYER}\n transition={transition}\n aria-hidden={!isVoice}\n >\n {voice}\n </motion.div>\n </motion.div>\n );\n};\n\nModeTransition.displayName = 'ChatInputModeTransition';\n"],"names":["_jsxs","_jsx"],"mappings":";;;;;AAkBA,MAAM,SAAS,GAAG,UAAU,CAAC,uCAAuC,CAAC,CAAC;AAEtE,MAAM,2BAA2B,GAAG,UAAU,CAAC,oCAAoC,CAAC,CAAC,QAAQ,CAAC;AAC9F,MAAM,kBAAkB,GAAG,EAAE,QAAQ,EAAE,CAAC,EAAW,CAAC;AAEpD,MAAM,uBAAuB,GAAG;IAC5B,QAAQ,EAAE,2BAA2B,GAAG,IAAI;AAC5C,IAAA,IAAI,EAAE,SAAS;CACT,CAAC;AAEX,MAAM,aAAa,GAAwB,EAAE,OAAO,EAAE,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC;AACjF,MAAM,YAAY,GAAwB,EAAE,OAAO,EAAE,CAAC,EAAE,aAAa,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,CAAC;AAE3F,MAAM,cAAc,GAA4B,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAI;AAC7E,IAAA,MAAM,UAAU,GAAG,gBAAgB,EAAE,GAAG,kBAAkB,GAAG,uBAAuB,CAAC;AACrF,IAAA,MAAM,OAAO,GAAG,IAAI,KAAK,OAAO,CAAC;IAEjC,QACIA,KAAC,MAAM,CAAC,GAAG,EACP,EAAA,SAAS,EAAE,MAAM,CAAC,aAAa,EAC/B,OAAO,EAAE,EAAE,MAAM,EAAE,OAAO,GAAG,EAAE,GAAG,MAAM,EAAE,EAC1C,UAAU,EAAE,UAAU,EAEtB,QAAA,EAAA,CAAAC,GAAA,CAAC,MAAM,CAAC,GAAG,EAAA,EACP,OAAO,EAAE,KAAK,EACd,OAAO,EAAE,OAAO,GAAG,YAAY,GAAG,aAAa,EAC/C,UAAU,EAAE,UAAU,iBACT,OAAO,EAAA,QAAA,EAEnB,IAAI,EAAA,CACI,EACbA,GAAA,CAAC,MAAM,CAAC,GAAG,IACP,SAAS,EAAE,MAAM,CAAC,YAAY,EAC9B,OAAO,EAAE,KAAK,EACd,OAAO,EAAE,OAAO,GAAG,aAAa,GAAG,YAAY,EAC/C,UAAU,EAAE,UAAU,iBACT,CAAC,OAAO,YAEpB,KAAK,EAAA,CACG,CACJ,EAAA,CAAA,EACf;AACN,EAAE;AAEF,cAAc,CAAC,WAAW,GAAG,yBAAyB;;;;"}
1
+ {"version":3,"file":"ModeTransition.js","sources":["src/internal/ModeTransition.tsx"],"sourcesContent":["import { type FC, type ReactNode } from 'react';\nimport { motion, type TargetAndTransition, useReducedMotion } from 'motion/react';\n\nimport CoreTokens from '@hh.ru/magritte-design-tokens/core-tokens';\n\nimport styles from '../styles.less';\n\ntype ChatInputMode = 'text' | 'voice';\n\nexport interface ModeTransitionProps {\n /** Активный режим; смена запускает кроссфейд контента и анимацию высоты оболочки */\n mode: ChatInputMode;\n /** Контент текстового режима (сетка со слотами и вводом) */\n text: ReactNode;\n /** Контент режима записи голоса */\n voice: ReactNode;\n}\n\nconst EASE_BASE = CoreTokens['magritte-semantic-animation-ease-base'];\n\nconst MODE_TRANSITION_DURATION_MS = CoreTokens['magritte-semantic-animation-time-s'].duration;\nconst INSTANT_TRANSITION = { duration: 0 } as const;\n\nconst MODE_CONTENT_TRANSITION = {\n duration: MODE_TRANSITION_DURATION_MS / 1000,\n ease: EASE_BASE,\n} as const;\n\nconst VISIBLE_LAYER: TargetAndTransition = { opacity: 1, visibility: 'visible' };\nconst HIDDEN_LAYER: TargetAndTransition = { opacity: 0, transitionEnd: { visibility: 'hidden' } };\n\nexport const ModeTransition: FC<ModeTransitionProps> = ({ mode, text, voice }) => {\n const transition = useReducedMotion() ? INSTANT_TRANSITION : MODE_CONTENT_TRANSITION;\n const isVoice = mode === 'voice';\n\n return (\n <motion.div\n className={styles.modeContainer}\n animate={{ height: isVoice ? 40 : 'auto' }}\n transition={transition}\n >\n <motion.div\n initial={false}\n animate={isVoice ? HIDDEN_LAYER : VISIBLE_LAYER}\n transition={transition}\n aria-hidden={isVoice}\n >\n {text}\n </motion.div>\n <motion.div\n className={styles.voiceOverlay}\n initial={false}\n animate={isVoice ? VISIBLE_LAYER : HIDDEN_LAYER}\n transition={transition}\n aria-hidden={!isVoice}\n >\n {voice}\n </motion.div>\n </motion.div>\n );\n};\n\nModeTransition.displayName = 'ChatInputModeTransition';\n"],"names":[],"mappings":";;;;;AAkBA,MAAM,SAAA,GAAY,WAAW,uCAAuC,CAAA,CAAA;AAEpE,MAAM,2BAAA,GAA8B,UAAW,CAAA,oCAAoC,CAAE,CAAA,QAAA,CAAA;AACrF,MAAM,kBAAA,GAAqB,EAAE,QAAA,EAAU,CAAE,EAAA,CAAA;AAEzC,MAAM,uBAA0B,GAAA;AAAA,EAC5B,UAAU,2BAA8B,GAAA,GAAA;AAAA,EACxC,IAAM,EAAA,SAAA;AACV,CAAA,CAAA;AAEA,MAAM,aAAqC,GAAA,EAAE,OAAS,EAAA,CAAA,EAAG,YAAY,SAAU,EAAA,CAAA;AAC/E,MAAM,YAAA,GAAoC,EAAE,OAAS,EAAA,CAAA,EAAG,eAAe,EAAE,UAAA,EAAY,UAAW,EAAA,CAAA;AAEzF,MAAM,iBAA0C,CAAC,EAAE,IAAM,EAAA,IAAA,EAAM,OAAY,KAAA;AAC9E,EAAM,MAAA,UAAA,GAAa,gBAAiB,EAAA,GAAI,kBAAqB,GAAA,uBAAA,CAAA;AAC7D,EAAA,MAAM,UAAU,IAAS,KAAA,OAAA,CAAA;AAEzB,EACI,uBAAA,IAAA;AAAA,IAAC,MAAO,CAAA,GAAA;AAAA,IAAP;AAAA,MACG,WAAW,MAAO,CAAA,aAAA;AAAA,MAClB,OAAS,EAAA,EAAE,MAAQ,EAAA,OAAA,GAAU,KAAK,MAAO,EAAA;AAAA,MACzC,UAAA;AAAA,MAEA,QAAA,EAAA;AAAA,wBAAA,GAAA;AAAA,UAAC,MAAO,CAAA,GAAA;AAAA,UAAP;AAAA,YACG,OAAS,EAAA,KAAA;AAAA,YACT,OAAA,EAAS,UAAU,YAAe,GAAA,aAAA;AAAA,YAClC,UAAA;AAAA,YACA,aAAa,EAAA,OAAA;AAAA,YAEZ,QAAA,EAAA,IAAA;AAAA,WAAA;AAAA,SACL;AAAA,wBACA,GAAA;AAAA,UAAC,MAAO,CAAA,GAAA;AAAA,UAAP;AAAA,YACG,WAAW,MAAO,CAAA,YAAA;AAAA,YAClB,OAAS,EAAA,KAAA;AAAA,YACT,OAAA,EAAS,UAAU,aAAgB,GAAA,YAAA;AAAA,YACnC,UAAA;AAAA,YACA,eAAa,CAAC,OAAA;AAAA,YAEb,QAAA,EAAA,KAAA;AAAA,WAAA;AAAA,SACL;AAAA,OAAA;AAAA,KAAA;AAAA,GACJ,CAAA;AAER,EAAA;AAEA,cAAA,CAAe,WAAc,GAAA,yBAAA;;;;"}
@@ -7,7 +7,7 @@ export declare const useFocus: <T extends HTMLTextAreaElement = HTMLTextAreaElem
7
7
  export declare const TextInput: import("react").ForwardRefExoticComponent<Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, "value"> & {
8
8
  value: string;
9
9
  maxHeight?: number;
10
- layout: "vertical" | "horizontal";
10
+ layout: 'vertical' | 'horizontal';
11
11
  onSend?: VoidFunction;
12
12
  /** Ширина контейнера; её изменение пере-измеряет layout (перенос текста зависит от ширины) */
13
13
  containerWidth?: number;
@@ -5,143 +5,163 @@ import classNames from 'classnames';
5
5
  import { useMultipleRefs } from '@hh.ru/magritte-common-use-multiple-refs';
6
6
  import { useSendOnEnter } from './useSendOnEnter.js';
7
7
  import { useScrollbar, Scrollbar } from '@hh.ru/magritte-ui-scrollbar';
8
- import { s as styles } from '../styles-CHMi0EYL.js';
8
+ import { s as styles } from '../styles-DofLkp5v.js';
9
9
  import '@hh.ru/magritte-common-keyboard';
10
10
 
11
11
  const MIN_HEIGHT = 20;
12
- // допуск на субпиксельные значения скролла
13
12
  const HIDDEN_CONTENT_THRESHOLD = 4;
14
- // вертикальные паддинги ввода - источник правды здесь, ставятся инлайном (в LESS только
15
- // горизонтальные): фейд и раскладка считают по одним и тем же значениям без чтения стилей из DOM.
16
- // spacious - ввод на своём ряду (не в нижнем, где контролы), base - все остальные раскладки
17
13
  const VERTICAL_PADDING = {
18
- base: { top: 8, bottom: 8 },
19
- spacious: { top: 12, bottom: 16 },
14
+ base: { top: 8, bottom: 8 },
15
+ spacious: { top: 12, bottom: 16 }
20
16
  };
21
17
  const clamp = (value, min, max) => Math.max(Math.min(max, value), min);
22
- const useTextInputHeight = ({ inputRef, layout, maxHeight, containerWidth, value, onScrollHeightChange, }) => {
23
- const [currentState, setCurrentState] = useState(() => ({
24
- layout,
25
- height: MIN_HEIGHT,
26
- }));
27
- const prevScrollHeightRef = useRef(null);
28
- useLayoutEffect(() => {
29
- const input = inputRef.current;
30
- if (!input) {
31
- return;
32
- }
33
- // изменение реального scrollHeight (например, рост контента при упоре в maxHeight) —
34
- // повод пересчитать зависимое (скроллбар); читаем до измерительных тоглов
35
- if (prevScrollHeightRef.current !== null && prevScrollHeightRef.current !== input.scrollHeight) {
36
- onScrollHeightChange?.();
37
- }
38
- prevScrollHeightRef.current = input.scrollHeight;
39
- // высота контента на ширине конкретного слота: .measure схлопывает высоту и паддинги
40
- // (scrollHeight = чистая высота контента), layout-vertical выбирает вертикальный/горизонтальный слот
41
- const measureContentHeight = (targetVertical) => {
42
- input.classList.add(styles.measure);
43
- input.classList.toggle(styles.layoutVertical, targetVertical);
44
- const height = input.scrollHeight;
45
- input.classList.toggle(styles.layoutVertical, currentState.layout === 'vertical');
46
- input.classList.remove(styles.measure);
47
- return height;
48
- };
49
- // лейаут определяется по горизонтальному слоту (влезает ли контент в одну строку). Замер
50
- // горизонтального слота — форс-рефлоу, поэтому сначала снимаем заведомо известные случаи:
51
- // внешний layout='vertical' и перенос строки в значении гарантируют вертикаль, пустое
52
- // значение — горизонталь; замеряем только неоднозначный однострочный случай (перенос по ширине).
53
- // Высоту всегда берём на ширине целевого слота — иначе размер меряется на старом месте.
54
- let isVertical;
55
- if (layout === 'vertical' || value.includes('\n')) {
56
- isVertical = true;
57
- }
58
- else if (value === '') {
59
- isVertical = false;
60
- }
61
- else {
62
- isVertical = measureContentHeight(false) > MIN_HEIGHT;
63
- }
64
- const contentHeight = isVertical ? measureContentHeight(true) : MIN_HEIGHT;
65
- const newState = {
66
- layout: isVertical ? 'vertical' : 'horizontal',
67
- height: clamp(contentHeight, MIN_HEIGHT, maxHeight ?? contentHeight),
68
- };
69
- if (Object.keys(currentState).some((key) => currentState[key] !== newState[key])) {
70
- setCurrentState(newState);
71
- }
72
- // currentState не должно быть в зависимостях, иначе зациклится обновление
73
- // eslint-disable-next-line react-hooks/exhaustive-deps
74
- }, [layout, maxHeight, value, inputRef, containerWidth]);
75
- return currentState;
18
+ const useTextInputHeight = ({
19
+ inputRef,
20
+ layout,
21
+ maxHeight,
22
+ containerWidth,
23
+ value,
24
+ onScrollHeightChange
25
+ }) => {
26
+ const [currentState, setCurrentState] = useState(() => ({
27
+ layout,
28
+ height: MIN_HEIGHT
29
+ }));
30
+ const prevScrollHeightRef = useRef(null);
31
+ useLayoutEffect(() => {
32
+ const input = inputRef.current;
33
+ if (!input) {
34
+ return;
35
+ }
36
+ if (prevScrollHeightRef.current !== null && prevScrollHeightRef.current !== input.scrollHeight) {
37
+ onScrollHeightChange?.();
38
+ }
39
+ prevScrollHeightRef.current = input.scrollHeight;
40
+ const measureContentHeight = (targetVertical) => {
41
+ input.classList.add(styles.measure);
42
+ input.classList.toggle(styles.layoutVertical, targetVertical);
43
+ const height = input.scrollHeight;
44
+ input.classList.toggle(styles.layoutVertical, currentState.layout === "vertical");
45
+ input.classList.remove(styles.measure);
46
+ return height;
47
+ };
48
+ let isVertical;
49
+ if (layout === "vertical" || value.includes("\n")) {
50
+ isVertical = true;
51
+ } else if (value === "") {
52
+ isVertical = false;
53
+ } else {
54
+ isVertical = measureContentHeight(false) > MIN_HEIGHT;
55
+ }
56
+ const contentHeight = isVertical ? measureContentHeight(true) : MIN_HEIGHT;
57
+ const newState = {
58
+ layout: isVertical ? "vertical" : "horizontal",
59
+ height: clamp(contentHeight, MIN_HEIGHT, maxHeight ?? contentHeight)
60
+ };
61
+ if (Object.keys(currentState).some(
62
+ (key) => currentState[key] !== newState[key]
63
+ )) {
64
+ setCurrentState(newState);
65
+ }
66
+ }, [layout, maxHeight, value, inputRef, containerWidth]);
67
+ return currentState;
76
68
  };
77
69
  const useFocus = () => {
78
- const [isFocused, setFocused] = useState(false);
79
- const focusHandler = useCallback((event) => {
80
- setFocused(event.target.classList.contains('focus-visible'));
81
- }, []);
82
- const blurHandler = useCallback(() => {
83
- setFocused(false);
84
- }, []);
85
- return { focusHandler, blurHandler, isFocused };
70
+ const [isFocused, setFocused] = useState(false);
71
+ const focusHandler = useCallback((event) => {
72
+ setFocused(event.target.classList.contains("focus-visible"));
73
+ }, []);
74
+ const blurHandler = useCallback(() => {
75
+ setFocused(false);
76
+ }, []);
77
+ return { focusHandler, blurHandler, isFocused };
86
78
  };
87
- const TextInputComponent = ({ value, maxHeight, layout, onSend, containerWidth, hasCentral, hasBothSideControls, onKeyDown, onScroll, className, style, ...textareaProps }, forwardedRef) => {
88
- const inputRef = useRef(null);
89
- const { hasVerticalScroll, verticalScrollbarProps, scrollContainerRef, resizeCallbackRef, hoverContainerCssClass } = useScrollbar({ axis: 'vertical' });
90
- const refCallback = useMultipleRefs(inputRef, scrollContainerRef, forwardedRef);
91
- // ResizeObserver внутри useScrollbar видит только изменения размера textarea:
92
- // после упора в maxHeight растет лишь scrollHeight - пересчитываем трек по нему
93
- const handleScrollHeightChange = useCallback(() => {
94
- resizeCallbackRef.current?.();
95
- }, [resizeCallbackRef]);
96
- const inputParams = useTextInputHeight({
97
- inputRef,
98
- layout,
99
- maxHeight,
79
+ const TextInputComponent = ({
80
+ value,
81
+ maxHeight,
82
+ layout,
83
+ onSend,
84
+ containerWidth,
85
+ hasCentral,
86
+ hasBothSideControls,
87
+ onKeyDown,
88
+ onScroll,
89
+ className,
90
+ style,
91
+ ...textareaProps
92
+ }, forwardedRef) => {
93
+ const inputRef = useRef(null);
94
+ const { hasVerticalScroll, verticalScrollbarProps, scrollContainerRef, resizeCallbackRef, hoverContainerCssClass } = useScrollbar({ axis: "vertical" });
95
+ const refCallback = useMultipleRefs(inputRef, scrollContainerRef, forwardedRef);
96
+ const handleScrollHeightChange = useCallback(() => {
97
+ resizeCallbackRef.current?.();
98
+ }, [resizeCallbackRef]);
99
+ const inputParams = useTextInputHeight({
100
+ inputRef,
101
+ layout,
102
+ maxHeight,
103
+ value,
104
+ containerWidth,
105
+ onScrollHeightChange: handleScrollHeightChange
106
+ });
107
+ const verticalPadding = hasCentral || hasBothSideControls && inputParams.layout === "vertical" ? VERTICAL_PADDING.spacious : VERTICAL_PADDING.base;
108
+ const [hasHiddenTop, setHasHiddenTop] = useState(false);
109
+ const [hasHiddenBottom, setHasHiddenBottom] = useState(false);
110
+ const updateFades = useCallback(() => {
111
+ const input = inputRef.current;
112
+ if (!input) {
113
+ return;
114
+ }
115
+ const hiddenTop = input.scrollTop - verticalPadding.top;
116
+ const hiddenBottom = input.scrollHeight - input.scrollTop - input.clientHeight - verticalPadding.bottom;
117
+ setHasHiddenTop(hiddenTop > HIDDEN_CONTENT_THRESHOLD);
118
+ setHasHiddenBottom(hiddenBottom > HIDDEN_CONTENT_THRESHOLD);
119
+ }, [verticalPadding.top, verticalPadding.bottom]);
120
+ useLayoutEffect(() => {
121
+ updateFades();
122
+ }, [value, inputParams.height, containerWidth, updateFades]);
123
+ const handleKeyDown = useSendOnEnter({ onSend, onKeyDown });
124
+ const handleScroll = useCallback(
125
+ (event) => {
126
+ updateFades();
127
+ onScroll?.(event);
128
+ },
129
+ [updateFades, onScroll]
130
+ );
131
+ const { isFocused, focusHandler, blurHandler } = useFocus();
132
+ return /* @__PURE__ */ jsxs("div", { className: classNames(styles.textInputWrapper, hoverContainerCssClass), children: [
133
+ /* @__PURE__ */ jsx(
134
+ "textarea",
135
+ {
136
+ enterKeyHint: "enter",
137
+ ...textareaProps,
138
+ onFocus: focusHandler,
139
+ onBlur: blurHandler,
140
+ className: classNames(
141
+ styles.textInput,
142
+ {
143
+ [styles.layoutVertical]: inputParams.layout === "vertical",
144
+ [styles.withTopFade]: hasHiddenTop,
145
+ [styles.withBottomFade]: hasHiddenBottom,
146
+ [styles.textInputFocused]: isFocused
147
+ },
148
+ className
149
+ ),
150
+ style: {
151
+ ...style,
152
+ height: inputParams.height,
153
+ paddingTop: verticalPadding.top,
154
+ paddingBottom: verticalPadding.bottom
155
+ },
156
+ ref: refCallback,
100
157
  value,
101
- containerWidth,
102
- onScrollHeightChange: handleScrollHeightChange,
103
- });
104
- const verticalPadding = hasCentral || (hasBothSideControls && inputParams.layout === 'vertical')
105
- ? VERTICAL_PADDING.spacious
106
- : VERTICAL_PADDING.base;
107
- // фейд у краёв показываем, только когда за соответствующим краем есть непроскролленный контент
108
- const [hasHiddenTop, setHasHiddenTop] = useState(false);
109
- const [hasHiddenBottom, setHasHiddenBottom] = useState(false);
110
- const updateFades = useCallback(() => {
111
- const input = inputRef.current;
112
- if (!input) {
113
- return;
114
- }
115
- // верхний и нижний паддинги - часть скроллируемой области, но контента в них нет:
116
- // остаток скролла в их пределах не значит, что текст скрыт
117
- const hiddenTop = input.scrollTop - verticalPadding.top;
118
- const hiddenBottom = input.scrollHeight - input.scrollTop - input.clientHeight - verticalPadding.bottom;
119
- setHasHiddenTop(hiddenTop > HIDDEN_CONTENT_THRESHOLD);
120
- setHasHiddenBottom(hiddenBottom > HIDDEN_CONTENT_THRESHOLD);
121
- }, [verticalPadding.top, verticalPadding.bottom]);
122
- // позиция скролла отслеживается через onScroll, здесь - изменения контента, высоты и ширины
123
- // контейнера: при упоре в maxHeight высота не меняется, но текст перетекает и за краями
124
- // может появиться/исчезнуть скрытый контент
125
- useLayoutEffect(() => {
126
- updateFades();
127
- }, [value, inputParams.height, containerWidth, updateFades]);
128
- const handleKeyDown = useSendOnEnter({ onSend, onKeyDown });
129
- const handleScroll = useCallback((event) => {
130
- updateFades();
131
- onScroll?.(event);
132
- }, [updateFades, onScroll]);
133
- const { isFocused, focusHandler, blurHandler } = useFocus();
134
- return (jsxs("div", { className: classNames(styles.textInputWrapper, hoverContainerCssClass), children: [jsx("textarea", { enterKeyHint: "enter", ...textareaProps, onFocus: focusHandler, onBlur: blurHandler, className: classNames(styles.textInput, {
135
- [styles.layoutVertical]: inputParams.layout === 'vertical',
136
- [styles.withTopFade]: hasHiddenTop,
137
- [styles.withBottomFade]: hasHiddenBottom,
138
- [styles.textInputFocused]: isFocused,
139
- }, className), style: {
140
- ...style,
141
- height: inputParams.height,
142
- paddingTop: verticalPadding.top,
143
- paddingBottom: verticalPadding.bottom,
144
- }, ref: refCallback, value: value, onKeyDown: handleKeyDown, onScroll: handleScroll, "data-qa": "text-input" }), hasVerticalScroll && (jsx("div", { className: styles.scrollbarCell, children: jsx(Scrollbar, { ...verticalScrollbarProps }) }))] }));
158
+ onKeyDown: handleKeyDown,
159
+ onScroll: handleScroll,
160
+ "data-qa": "text-input"
161
+ }
162
+ ),
163
+ hasVerticalScroll && /* @__PURE__ */ jsx("div", { className: styles.scrollbarCell, children: /* @__PURE__ */ jsx(Scrollbar, { ...verticalScrollbarProps }) })
164
+ ] });
145
165
  };
146
166
  const TextInput = forwardRef(TextInputComponent);
147
167