entangle-ui 0.6.2 → 0.7.0

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 (152) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +5 -3
  3. package/dist/esm/_virtual/_commonjsHelpers.js +6 -0
  4. package/dist/esm/_virtual/_commonjsHelpers.js.map +1 -0
  5. package/dist/esm/_virtual/cssesc.js +5 -1
  6. package/dist/esm/_virtual/cssesc.js.map +1 -1
  7. package/dist/esm/_virtual/picocolors.js +8 -0
  8. package/dist/esm/_virtual/picocolors.js.map +1 -0
  9. package/dist/esm/_virtual/picocolors2.js +4 -0
  10. package/dist/esm/_virtual/picocolors2.js.map +1 -0
  11. package/dist/esm/assets/src/components/editor/ChatPanel/{ChatPanel.css.ts.vanilla-DPIGnZ4A.css → ChatPanel.css.ts.vanilla-BI5569ZO.css} +81 -1
  12. package/dist/esm/assets/src/components/feedback/EmptyState/EmptyState.css.ts.vanilla-B_TWsTOW.css +58 -0
  13. package/dist/esm/assets/src/components/feedback/Spinner/Spinner.css.ts.vanilla-DEUewqdK.css +80 -0
  14. package/dist/esm/assets/src/components/layout/Divider/Divider.css.ts.vanilla-CPvd_RW9.css +72 -0
  15. package/dist/esm/assets/src/components/layout/ListItem/ListItem.css.ts.vanilla-BwAZrX2f.css +68 -0
  16. package/dist/esm/assets/src/components/layout/PageHeader/PageHeader.css.ts.vanilla-DdbyyWAN.css +70 -0
  17. package/dist/esm/assets/src/components/layout/SplitPane/{SplitPane.css.ts.vanilla-BFxdvwyI.css → SplitPane.css.ts.vanilla-BGFZ7zDa.css} +5 -0
  18. package/dist/esm/assets/src/components/primitives/Badge/Badge.css.ts.vanilla-DxCUcxYW.css +95 -0
  19. package/dist/esm/assets/src/components/primitives/Code/Code.css.ts.vanilla-mayBqLDM.css +19 -0
  20. package/dist/esm/assets/src/components/primitives/TextArea/TextArea.css.ts.vanilla-DTOMjGkp.css +85 -0
  21. package/dist/esm/assets/src/theme/{darkTheme.css.ts.vanilla-DCe89yCJ.css → darkTheme.css.ts.vanilla-ab1WD4dr.css} +3 -0
  22. package/dist/esm/assets/src/theme/globalScrollbars.css.ts.vanilla-BAJwnUEJ.css +21 -0
  23. package/dist/esm/assets/src/utils/animations.css.ts.vanilla-DOVlpljP.css +58 -0
  24. package/dist/esm/components/editor/ChatPanel/ChatInput.js +18 -7
  25. package/dist/esm/components/editor/ChatPanel/ChatInput.js.map +1 -1
  26. package/dist/esm/components/editor/ChatPanel/ChatMarkdownRenderer.js +268 -0
  27. package/dist/esm/components/editor/ChatPanel/ChatMarkdownRenderer.js.map +1 -0
  28. package/dist/esm/components/editor/ChatPanel/ChatMessage.js +15 -3
  29. package/dist/esm/components/editor/ChatPanel/ChatMessage.js.map +1 -1
  30. package/dist/esm/components/editor/ChatPanel/ChatMessageList.js +19 -5
  31. package/dist/esm/components/editor/ChatPanel/ChatMessageList.js.map +1 -1
  32. package/dist/esm/components/editor/ChatPanel/ChatPanel.css.js +14 -2
  33. package/dist/esm/components/editor/ChatPanel/ChatPanel.css.js.map +1 -1
  34. package/dist/esm/components/editor/ChatPanel/ChatPanel.js +15 -3
  35. package/dist/esm/components/editor/ChatPanel/ChatPanel.js.map +1 -1
  36. package/dist/esm/components/editor/ChatPanel/useChatInput.js +3 -3
  37. package/dist/esm/components/editor/ChatPanel/useChatInput.js.map +1 -1
  38. package/dist/esm/components/editor/ChatPanel/useChatScroll.js +59 -2
  39. package/dist/esm/components/editor/ChatPanel/useChatScroll.js.map +1 -1
  40. package/dist/esm/components/feedback/EmptyState/EmptyState.css.js +13 -0
  41. package/dist/esm/components/feedback/EmptyState/EmptyState.css.js.map +1 -0
  42. package/dist/esm/components/feedback/EmptyState/EmptyState.js +43 -0
  43. package/dist/esm/components/feedback/EmptyState/EmptyState.js.map +1 -0
  44. package/dist/esm/components/feedback/Spinner/Spinner.css.js +16 -0
  45. package/dist/esm/components/feedback/Spinner/Spinner.css.js.map +1 -0
  46. package/dist/esm/components/feedback/Spinner/Spinner.js +50 -0
  47. package/dist/esm/components/feedback/Spinner/Spinner.js.map +1 -0
  48. package/dist/esm/components/layout/Divider/Divider.css.js +9 -0
  49. package/dist/esm/components/layout/Divider/Divider.css.js.map +1 -0
  50. package/dist/esm/components/layout/Divider/Divider.js +51 -0
  51. package/dist/esm/components/layout/Divider/Divider.js.map +1 -0
  52. package/dist/esm/components/layout/ListItem/ListItem.css.js +10 -0
  53. package/dist/esm/components/layout/ListItem/ListItem.css.js.map +1 -0
  54. package/dist/esm/components/layout/ListItem/ListItem.js +45 -0
  55. package/dist/esm/components/layout/ListItem/ListItem.js.map +1 -0
  56. package/dist/esm/components/layout/PageHeader/PageHeader.css.js +13 -0
  57. package/dist/esm/components/layout/PageHeader/PageHeader.css.js.map +1 -0
  58. package/dist/esm/components/layout/PageHeader/PageHeader.js +29 -0
  59. package/dist/esm/components/layout/PageHeader/PageHeader.js.map +1 -0
  60. package/dist/esm/components/layout/SplitPane/SplitPane.css.js +1 -1
  61. package/dist/esm/components/navigation/Tabs/TabPanel.js +6 -3
  62. package/dist/esm/components/navigation/Tabs/TabPanel.js.map +1 -1
  63. package/dist/esm/components/navigation/Tabs/Tabs.js +3 -1
  64. package/dist/esm/components/navigation/Tabs/Tabs.js.map +1 -1
  65. package/dist/esm/components/primitives/Badge/Badge.css.js +12 -0
  66. package/dist/esm/components/primitives/Badge/Badge.css.js.map +1 -0
  67. package/dist/esm/components/primitives/Badge/Badge.js +67 -0
  68. package/dist/esm/components/primitives/Badge/Badge.js.map +1 -0
  69. package/dist/esm/components/primitives/Code/Code.css.js +7 -0
  70. package/dist/esm/components/primitives/Code/Code.css.js.map +1 -0
  71. package/dist/esm/components/primitives/Code/Code.js +24 -0
  72. package/dist/esm/components/primitives/Code/Code.js.map +1 -0
  73. package/dist/esm/components/primitives/TextArea/TextArea.css.js +10 -0
  74. package/dist/esm/components/primitives/TextArea/TextArea.css.js.map +1 -0
  75. package/dist/esm/components/primitives/TextArea/TextArea.js +97 -0
  76. package/dist/esm/components/primitives/TextArea/TextArea.js.map +1 -0
  77. package/dist/esm/index.js +12 -1
  78. package/dist/esm/index.js.map +1 -1
  79. package/dist/esm/node_modules/@emotion/hash/dist/emotion-hash.esm.js +56 -0
  80. package/dist/esm/node_modules/@emotion/hash/dist/emotion-hash.esm.js.map +1 -0
  81. package/dist/esm/node_modules/@vanilla-extract/css/adapter/dist/vanilla-extract-css-adapter.esm.js +32 -1
  82. package/dist/esm/node_modules/@vanilla-extract/css/adapter/dist/vanilla-extract-css-adapter.esm.js.map +1 -1
  83. package/dist/esm/node_modules/@vanilla-extract/css/dist/taggedTemplateLiteral-10998315.esm.js +13 -0
  84. package/dist/esm/node_modules/@vanilla-extract/css/dist/taggedTemplateLiteral-10998315.esm.js.map +1 -0
  85. package/dist/esm/node_modules/@vanilla-extract/css/dist/transformCss-fd0786e1.esm.js +837 -3
  86. package/dist/esm/node_modules/@vanilla-extract/css/dist/transformCss-fd0786e1.esm.js.map +1 -1
  87. package/dist/esm/node_modules/@vanilla-extract/css/dist/vanilla-extract-css.esm.js +244 -5
  88. package/dist/esm/node_modules/@vanilla-extract/css/dist/vanilla-extract-css.esm.js.map +1 -1
  89. package/dist/esm/node_modules/@vanilla-extract/css/fileScope/dist/vanilla-extract-css-fileScope.esm.js +18 -0
  90. package/dist/esm/node_modules/@vanilla-extract/css/fileScope/dist/vanilla-extract-css-fileScope.esm.js.map +1 -0
  91. package/dist/esm/node_modules/@vanilla-extract/css/injectStyles/dist/vanilla-extract-css-injectStyles.esm.js +23 -0
  92. package/dist/esm/node_modules/@vanilla-extract/css/injectStyles/dist/vanilla-extract-css-injectStyles.esm.js.map +1 -0
  93. package/dist/esm/node_modules/@vanilla-extract/private/dist/vanilla-extract-private.esm.js +38 -0
  94. package/dist/esm/node_modules/@vanilla-extract/private/dist/vanilla-extract-private.esm.js.map +1 -0
  95. package/dist/esm/node_modules/css-what/lib/es/parse.js +424 -0
  96. package/dist/esm/node_modules/css-what/lib/es/parse.js.map +1 -0
  97. package/dist/esm/node_modules/css-what/lib/es/types.js +29 -0
  98. package/dist/esm/node_modules/css-what/lib/es/types.js.map +1 -0
  99. package/dist/esm/node_modules/dedent/dist/dedent.js +3 -1
  100. package/dist/esm/node_modules/dedent/dist/dedent.js.map +1 -1
  101. package/dist/esm/node_modules/deep-object-diff/mjs/diff.js +40 -0
  102. package/dist/esm/node_modules/deep-object-diff/mjs/diff.js.map +1 -0
  103. package/dist/esm/node_modules/deep-object-diff/mjs/utils.js +9 -0
  104. package/dist/esm/node_modules/deep-object-diff/mjs/utils.js.map +1 -0
  105. package/dist/esm/node_modules/media-query-parser/dist/media-query-parser.esm.js +1386 -0
  106. package/dist/esm/node_modules/media-query-parser/dist/media-query-parser.esm.js.map +1 -0
  107. package/dist/esm/node_modules/modern-ahocorasick/dist/index.js +102 -0
  108. package/dist/esm/node_modules/modern-ahocorasick/dist/index.js.map +1 -0
  109. package/dist/esm/node_modules/picocolors/picocolors.js +87 -0
  110. package/dist/esm/node_modules/picocolors/picocolors.js.map +1 -0
  111. package/dist/esm/theme/ThemeProvider.js +17 -3
  112. package/dist/esm/theme/ThemeProvider.js.map +1 -1
  113. package/dist/esm/theme/contract.css.js +1 -1
  114. package/dist/esm/theme/createCustomTheme.js +33 -0
  115. package/dist/esm/theme/createCustomTheme.js.map +1 -0
  116. package/dist/esm/theme/darkTheme.css.js +2 -2
  117. package/dist/esm/theme/globalScrollbars.css.js +6 -0
  118. package/dist/esm/theme/globalScrollbars.css.js.map +1 -0
  119. package/dist/esm/utils/animations.css.js +13 -0
  120. package/dist/esm/utils/animations.css.js.map +1 -0
  121. package/dist/esm/utils/objects.js +22 -0
  122. package/dist/esm/utils/objects.js.map +1 -0
  123. package/dist/types/components/editor/ChatPanel/ChatMarkdownRenderer.d.ts +323 -0
  124. package/dist/types/components/editor/ChatPanel/ChatMarkdownRenderer.types.d.ts +46 -0
  125. package/dist/types/components/editor/ChatPanel/ChatMessage.d.ts +1 -0
  126. package/dist/types/components/editor/ChatPanel/ChatMessageList.d.ts +2 -1
  127. package/dist/types/components/editor/ChatPanel/ChatPanel.d.ts +1 -0
  128. package/dist/types/components/editor/ChatPanel/ChatPanel.types.d.ts +82 -1
  129. package/dist/types/components/feedback/EmptyState/EmptyState.d.ts +313 -0
  130. package/dist/types/components/feedback/EmptyState/EmptyState.types.d.ts +31 -0
  131. package/dist/types/components/feedback/Spinner/Spinner.d.ts +306 -0
  132. package/dist/types/components/feedback/Spinner/Spinner.types.d.ts +41 -0
  133. package/dist/types/components/layout/Divider/Divider.d.ts +307 -0
  134. package/dist/types/components/layout/Divider/Divider.types.d.ts +50 -0
  135. package/dist/types/components/layout/ListItem/ListItem.d.ts +312 -0
  136. package/dist/types/components/layout/ListItem/ListItem.types.d.ts +35 -0
  137. package/dist/types/components/layout/PageHeader/PageHeader.d.ts +311 -0
  138. package/dist/types/components/layout/PageHeader/PageHeader.types.d.ts +30 -0
  139. package/dist/types/components/navigation/Tabs/Tabs.types.d.ts +10 -0
  140. package/dist/types/components/primitives/Badge/Badge.d.ts +310 -0
  141. package/dist/types/components/primitives/Badge/Badge.types.d.ts +67 -0
  142. package/dist/types/components/primitives/Code/Code.d.ts +301 -0
  143. package/dist/types/components/primitives/Code/Code.types.d.ts +17 -0
  144. package/dist/types/components/primitives/TextArea/TextArea.d.ts +26 -0
  145. package/dist/types/components/primitives/TextArea/TextArea.types.d.ts +76 -0
  146. package/dist/types/index.d.ts +22 -2
  147. package/dist/types/theme/ThemeProvider.d.ts +14 -2
  148. package/dist/types/theme/contract.css.d.ts +3 -0
  149. package/dist/types/theme/createCustomTheme.d.ts +29 -0
  150. package/dist/types/theme/darkTheme.css.d.ts +3 -0
  151. package/dist/types/utils/animations.css.d.ts +18 -0
  152. package/package.json +1 -1
@@ -0,0 +1,19 @@
1
+ .Code_codeRecipe__13vd650 {
2
+ font-family: var(--etui-font-family-mono);
3
+ background: var(--etui-color-bg-inset);
4
+ color: var(--etui-color-text-primary);
5
+ padding: 1px var(--etui-spacing-sm);
6
+ border-radius: var(--etui-radius-sm);
7
+ line-height: 1.4;
8
+ white-space: nowrap;
9
+ box-sizing: border-box;
10
+ }
11
+ .Code_codeRecipe_size_xs__13vd651 {
12
+ font-size: 0.8em;
13
+ }
14
+ .Code_codeRecipe_size_sm__13vd652 {
15
+ font-size: 0.9em;
16
+ }
17
+ .Code_codeRecipe_size_md__13vd653 {
18
+ font-size: 1em;
19
+ }
@@ -0,0 +1,85 @@
1
+ .TextArea_textAreaContainerStyle__nwf0m90 {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: var(--etui-spacing-xs);
5
+ width: 100%;
6
+ }
7
+ .TextArea_textAreaWrapperRecipe__nwf0m91 {
8
+ position: relative;
9
+ display: flex;
10
+ border: 1px solid;
11
+ border-radius: var(--etui-radius-md);
12
+ transition: all var(--etui-transition-normal);
13
+ box-sizing: border-box;
14
+ }
15
+ .TextArea_textAreaWrapperRecipe_size_sm__nwf0m92 {
16
+ min-height: 48px;
17
+ padding: var(--etui-spacing-xs) var(--etui-spacing-sm);
18
+ }
19
+ .TextArea_textAreaWrapperRecipe_size_md__nwf0m93 {
20
+ min-height: 64px;
21
+ padding: var(--etui-spacing-sm) var(--etui-spacing-md);
22
+ }
23
+ .TextArea_textAreaWrapperRecipe_size_lg__nwf0m94 {
24
+ min-height: 88px;
25
+ padding: var(--etui-spacing-md) var(--etui-spacing-lg);
26
+ }
27
+ .TextArea_textAreaWrapperRecipe_error_true__nwf0m95 {
28
+ border-color: var(--etui-color-border-error);
29
+ }
30
+ .TextArea_textAreaWrapperRecipe_disabled_true__nwf0m97 {
31
+ background: var(--etui-color-surface-disabled);
32
+ opacity: 0.5;
33
+ cursor: not-allowed;
34
+ }
35
+ .TextArea_textAreaWrapperRecipe_disabled_false__nwf0m98 {
36
+ background: var(--etui-color-surface-default);
37
+ }
38
+ .TextArea_textAreaWrapperRecipe_compound_0__nwf0m9b {
39
+ border-color: var(--etui-color-border-focus);
40
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--etui-color-accent-primary) 12.5%, transparent);
41
+ }
42
+ .TextArea_textAreaWrapperRecipe_compound_1__nwf0m9c {
43
+ border-color: var(--etui-color-border-default);
44
+ }
45
+ .TextArea_textAreaWrapperRecipe_compound_1__nwf0m9c:hover:not(:focus-within) {
46
+ border-color: var(--etui-color-border-focus);
47
+ }
48
+ .TextArea_textAreaWrapperRecipe_compound_2__nwf0m9d {
49
+ border-color: var(--etui-color-border-default);
50
+ }
51
+ .TextArea_textAreaRecipe__nwf0m9e {
52
+ flex: 1;
53
+ width: 100%;
54
+ border: none;
55
+ outline: none;
56
+ background: transparent;
57
+ font-family: var(--etui-font-family-sans);
58
+ color: var(--etui-color-text-primary);
59
+ line-height: var(--etui-line-height-normal);
60
+ box-sizing: border-box;
61
+ }
62
+ .TextArea_textAreaRecipe__nwf0m9e::placeholder {
63
+ color: var(--etui-color-text-muted);
64
+ }
65
+ .TextArea_textAreaRecipe__nwf0m9e:disabled {
66
+ cursor: not-allowed;
67
+ }
68
+ .TextArea_textAreaRecipe_size_sm__nwf0m9f {
69
+ font-size: var(--etui-font-size-md);
70
+ }
71
+ .TextArea_textAreaRecipe_size_md__nwf0m9g {
72
+ font-size: var(--etui-font-size-md);
73
+ }
74
+ .TextArea_textAreaRecipe_size_lg__nwf0m9h {
75
+ font-size: var(--etui-font-size-lg);
76
+ }
77
+ .TextArea_textAreaRecipe_monospace_true__nwf0m9i {
78
+ font-family: var(--etui-font-family-mono);
79
+ }
80
+ .TextArea_textAreaFooterStyle__nwf0m9j {
81
+ display: flex;
82
+ justify-content: flex-end;
83
+ font-size: var(--etui-font-size-xs);
84
+ color: var(--etui-color-text-muted);
85
+ }
@@ -3,11 +3,14 @@
3
3
  --etui-color-bg-secondary: #2d2d2d;
4
4
  --etui-color-bg-tertiary: #3a3a3a;
5
5
  --etui-color-bg-elevated: #404040;
6
+ --etui-color-bg-inset: rgba(0, 0, 0, 0.2);
6
7
  --etui-color-surface-default: #2d2d2d;
7
8
  --etui-color-surface-hover: #363636;
8
9
  --etui-color-surface-active: #404040;
9
10
  --etui-color-surface-disabled: #1f1f1f;
10
11
  --etui-color-surface-white-overlay: rgba(255, 255, 255, 0.1);
12
+ --etui-color-surface-row: transparent;
13
+ --etui-color-surface-row-hover: rgba(255, 255, 255, 0.03);
11
14
  --etui-color-border-default: #4a4a4a;
12
15
  --etui-color-border-focus: #007acc;
13
16
  --etui-color-border-error: #f44336;
@@ -0,0 +1,21 @@
1
+ .etuiGlobalScrollbars * {
2
+ scrollbar-width: thin;
3
+ scrollbar-color: var(--etui-color-text-disabled) transparent;
4
+ }
5
+ .etuiGlobalScrollbars *::-webkit-scrollbar {
6
+ width: 8px;
7
+ height: 8px;
8
+ }
9
+ .etuiGlobalScrollbars *::-webkit-scrollbar-track {
10
+ background: transparent;
11
+ }
12
+ .etuiGlobalScrollbars *::-webkit-scrollbar-thumb {
13
+ background: var(--etui-color-text-disabled);
14
+ border-radius: 4px;
15
+ }
16
+ .etuiGlobalScrollbars *::-webkit-scrollbar-thumb:hover {
17
+ background: var(--etui-color-text-muted);
18
+ }
19
+ .etuiGlobalScrollbars *::-webkit-scrollbar-corner {
20
+ background: transparent;
21
+ }
@@ -0,0 +1,58 @@
1
+ @keyframes animations_spinKeyframe__1boy3ak0 {
2
+ from {
3
+ transform: rotate(0deg);
4
+ }
5
+ to {
6
+ transform: rotate(360deg);
7
+ }
8
+ }
9
+ @keyframes animations_pulseKeyframe__1boy3ak1 {
10
+ 0%, 100% {
11
+ opacity: 1;
12
+ }
13
+ 50% {
14
+ opacity: 0.4;
15
+ }
16
+ }
17
+ @keyframes animations_blinkKeyframe__1boy3ak2 {
18
+ 0%, 50% {
19
+ opacity: 1;
20
+ }
21
+ 51%, 100% {
22
+ opacity: 0;
23
+ }
24
+ }
25
+ @keyframes animations_fadeInKeyframe__1boy3ak3 {
26
+ from {
27
+ opacity: 0;
28
+ }
29
+ to {
30
+ opacity: 1;
31
+ }
32
+ }
33
+ .animations_animSpin__1boy3ak4 {
34
+ animation: animations_spinKeyframe__1boy3ak0 1s linear infinite;
35
+ }
36
+ .animations_animPulse__1boy3ak5 {
37
+ animation: animations_pulseKeyframe__1boy3ak1 1.5s ease-in-out infinite;
38
+ }
39
+ .animations_animBlink__1boy3ak6 {
40
+ animation: animations_blinkKeyframe__1boy3ak2 1s steps(1) infinite;
41
+ }
42
+ .animations_animFadeIn__1boy3ak7 {
43
+ animation: animations_fadeInKeyframe__1boy3ak3 var(--etui-transition-normal) ease-out;
44
+ }
45
+ @media (prefers-reduced-motion: reduce) {
46
+ .animations_animSpin__1boy3ak4 {
47
+ animation: none;
48
+ }
49
+ .animations_animPulse__1boy3ak5 {
50
+ animation: none;
51
+ }
52
+ .animations_animBlink__1boy3ak6 {
53
+ animation: none;
54
+ }
55
+ .animations_animFadeIn__1boy3ak7 {
56
+ animation: none;
57
+ }
58
+ }
@@ -20,6 +20,7 @@ const ChatInput = /*#__PURE__*/ React.memo(({ value: controlledValue, onChange:
20
20
  submitKey,
21
21
  onSubmit: handleSubmit,
22
22
  maxLines,
23
+ attachmentsCount: attachments?.length ?? 0,
23
24
  });
24
25
  // Support controlled mode
25
26
  const currentValue = controlledValue ?? chatInput.value;
@@ -29,29 +30,39 @@ const ChatInput = /*#__PURE__*/ React.memo(({ value: controlledValue, onChange:
29
30
  }
30
31
  chatInput.handleChange(e);
31
32
  }, [controlledOnChange, chatInput]);
33
+ const attachmentsCount = attachments?.length ?? 0;
32
34
  const handleKeyDown = useCallback((e) => {
33
35
  // In controlled mode, override the internal value check
34
36
  if (controlledValue !== undefined) {
35
37
  const isSubmitKey = submitKey === 'enter'
36
38
  ? e.key === 'Enter' && !e.shiftKey
37
39
  : e.key === 'Enter' && (e.ctrlKey || e.metaKey);
38
- if (isSubmitKey && controlledValue.trim()) {
40
+ if (isSubmitKey && (controlledValue.trim() || attachmentsCount > 0)) {
39
41
  e.preventDefault();
40
- onSubmit?.(controlledValue, attachments ?? []);
42
+ onSubmit?.(controlledValue.trim(), attachments ?? []);
41
43
  return;
42
44
  }
43
45
  }
44
46
  chatInput.handleKeyDown(e);
45
- }, [controlledValue, submitKey, onSubmit, attachments, chatInput]);
47
+ }, [
48
+ controlledValue,
49
+ submitKey,
50
+ onSubmit,
51
+ attachments,
52
+ attachmentsCount,
53
+ chatInput,
54
+ ]);
46
55
  const handleSendClick = useCallback(() => {
47
56
  const val = controlledValue ?? chatInput.value;
48
- if (val.trim()) {
49
- onSubmit?.(val, attachments ?? []);
57
+ const canSubmit = val.trim() || attachmentsCount > 0;
58
+ if (canSubmit) {
59
+ onSubmit?.(val.trim(), attachments ?? []);
50
60
  if (controlledValue === undefined) {
51
61
  chatInput.clear();
52
62
  }
53
63
  }
54
- }, [controlledValue, chatInput, onSubmit, attachments]);
64
+ }, [controlledValue, chatInput, onSubmit, attachments, attachmentsCount]);
65
+ const canSubmit = (currentValue.trim().length > 0 || attachmentsCount > 0) && !disabled;
55
66
  const lineHeight = 18; // approximate line-height in px
56
67
  const maxHeightPx = `${maxLines * lineHeight}px`;
57
68
  const shortcutLabel = submitKey === 'enter'
@@ -59,7 +70,7 @@ const ChatInput = /*#__PURE__*/ React.memo(({ value: controlledValue, onChange:
59
70
  : 'Ctrl+Enter to send, Enter for newline';
60
71
  return (jsxs("div", { ref: ref, className: cx(inputContainerStyle, className), style: style, "data-testid": testId, ...rest, children: [prefix && jsx("div", { className: inputPrefixStyle, children: prefix }), attachments && attachments.length > 0 && (jsx("div", { className: inputAttachmentsStyle, children: attachments.map(att => (jsx(ChatAttachmentChip, { attachment: att, removable: !!onRemoveAttachment, onRemove: onRemoveAttachment }, att.id))) })), jsxs("div", { className: inputWrapperStyle, children: [jsx("textarea", { ref: chatInput.textareaRef, id: `chat-input-${inputId}`, className: inputTextareaStyle, style: assignInlineVars({
61
72
  [inputMaxHeightVar]: maxHeightPx,
62
- }), value: currentValue, onChange: handleChange, onKeyDown: handleKeyDown, placeholder: placeholder, disabled: disabled, rows: 1, "aria-label": "Chat message input", "aria-keyshortcuts": shortcutLabel }), suffix, jsxs("div", { className: inputBottomBarStyle, children: [toolbar, streaming ? (jsx("button", { type: "button", className: inputStopButtonStyle, onClick: onStop, "aria-label": "Stop generating", children: jsx(StopIcon, {}) })) : (jsx("button", { type: "button", className: inputButtonStyle, onClick: handleSendClick, disabled: disabled || !currentValue.trim(), "aria-label": "Send message", children: jsx(SendIcon, {}) }))] })] })] }));
73
+ }), value: currentValue, onChange: handleChange, onKeyDown: handleKeyDown, placeholder: placeholder, disabled: disabled, rows: 1, "aria-label": "Chat message input", "aria-keyshortcuts": shortcutLabel }), suffix, jsxs("div", { className: inputBottomBarStyle, children: [toolbar, streaming ? (jsx("button", { type: "button", className: inputStopButtonStyle, onClick: onStop, "aria-label": "Stop generating", children: jsx(StopIcon, {}) })) : (jsx("button", { type: "button", className: inputButtonStyle, onClick: handleSendClick, disabled: !canSubmit, "aria-label": "Send message", children: jsx(SendIcon, {}) }))] })] })] }));
63
74
  });
64
75
  ChatInput.displayName = 'ChatInput';
65
76
 
@@ -1 +1 @@
1
- {"version":3,"file":"ChatInput.js","sources":["../../../../../../src/components/editor/ChatPanel/ChatInput.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;AAoBA;AAEA;AAkBA;AAYA;AAEO;AAsBH;AAEA;;AAGE;;;AAMA;;AAED;;AAGD;AAEA;;AAGM;;AAEF;AACF;AAIF;;AAGI;AACE;;AAGI;AAEJ;;;;;;AAMF;AACF;AAIF;AACE;AACA;;AAEE;;;;;AAMJ;AACA;AAEA;AAEI;;;;;AAyEN;AAGF;;"}
1
+ {"version":3,"file":"ChatInput.js","sources":["../../../../../../src/components/editor/ChatPanel/ChatInput.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;AAoBA;AAEA;AAkBA;AAYA;AAEO;AAsBH;AAEA;;AAGE;;;AAMA;;AAEA;AACD;;AAGD;AAEA;;AAGM;;AAEF;AACF;AAIF;AAEA;;AAGI;AACE;;AAGI;AAEJ;;;;;;AAMF;AACF;;;;;;;AAQC;AAGH;AACE;;;;AAIE;;;;AAIJ;AAEA;AAGA;AACA;AAEA;AAEI;;;;;AAyEN;AAGF;;"}
@@ -0,0 +1,268 @@
1
+ "use client";
2
+ import { jsx, jsxs } from 'react/jsx-runtime';
3
+ import React, { useMemo } from 'react';
4
+ import { cx } from '../../../utils/cx.js';
5
+ import { Code } from '../../primitives/Code/Code.js';
6
+ import { ChatCodeBlock } from './ChatCodeBlock.js';
7
+ import { markdownTableStyle, markdownThStyle, markdownTdStyle, markdownHrStyle, markdownBlockquoteStyle, markdownListStyle, markdownListItemStyle, markdownHeadingStyle, markdownParagraphStyle, markdownRootStyle, markdownLinkStyle } from './ChatPanel.css.js';
8
+
9
+ function escapeHtmlInText(text) {
10
+ return text;
11
+ }
12
+ function renderInline(text, options, keyPrefix) {
13
+ const parts = [];
14
+ let rest = text;
15
+ let index = 0;
16
+ // Ordered by precedence. Each pattern captures the full match so we
17
+ // can slice around it, and the content(s) we want to render.
18
+ const patterns = [
19
+ {
20
+ regex: /`([^`]+)`/,
21
+ render: (m, key) => jsx(Code, { children: m[1] }, key),
22
+ },
23
+ {
24
+ regex: /!\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/,
25
+ render: (m, key) => (jsx("img", { src: m[2], alt: m[1] ?? '', style: { maxWidth: '100%' } }, key)),
26
+ },
27
+ {
28
+ regex: /\[([^\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/,
29
+ render: (m, key) => (jsx("a", { href: m[2], target: options.linkTarget, rel: options.linkTarget === '_blank' ? 'noopener noreferrer' : undefined, className: markdownLinkStyle, children: m[1] }, key)),
30
+ },
31
+ {
32
+ regex: /\*\*([^*]+)\*\*/,
33
+ render: (m, key) => jsx("strong", { children: m[1] }, key),
34
+ },
35
+ {
36
+ regex: /__([^_]+)__/,
37
+ render: (m, key) => jsx("strong", { children: m[1] }, key),
38
+ },
39
+ {
40
+ regex: /(?<![*\w])\*([^*\n]+)\*(?![*\w])/,
41
+ render: (m, key) => jsx("em", { children: m[1] }, key),
42
+ },
43
+ {
44
+ regex: /(?<![_\w])_([^_\n]+)_(?![_\w])/,
45
+ render: (m, key) => jsx("em", { children: m[1] }, key),
46
+ },
47
+ {
48
+ regex: /~~([^~]+)~~/,
49
+ render: (m, key) => jsx("del", { children: m[1] }, key),
50
+ },
51
+ ];
52
+ while (rest.length > 0) {
53
+ let best = null;
54
+ for (const { regex, render } of patterns) {
55
+ const match = regex.exec(rest);
56
+ if (!match)
57
+ continue;
58
+ if (best === null || match.index < best.start) {
59
+ best = {
60
+ start: match.index,
61
+ length: match[0].length,
62
+ node: render(match, `${keyPrefix}-i-${index++}`),
63
+ };
64
+ }
65
+ }
66
+ if (!best) {
67
+ parts.push(escapeHtmlInText(rest));
68
+ break;
69
+ }
70
+ if (best.start > 0) {
71
+ parts.push(escapeHtmlInText(rest.slice(0, best.start)));
72
+ }
73
+ parts.push(best.node);
74
+ rest = rest.slice(best.start + best.length);
75
+ }
76
+ return parts;
77
+ }
78
+ function parseBlocks(source, gfm) {
79
+ const lines = source.replace(/\r\n/g, '\n').split('\n');
80
+ const blocks = [];
81
+ let i = 0;
82
+ while (i < lines.length) {
83
+ const line = lines[i] ?? '';
84
+ // Skip blank lines between blocks.
85
+ if (/^\s*$/.test(line)) {
86
+ i++;
87
+ continue;
88
+ }
89
+ // Fenced code block
90
+ const fenceMatch = /^```(\w+)?\s*$/.exec(line);
91
+ if (fenceMatch) {
92
+ const language = fenceMatch[1];
93
+ const codeLines = [];
94
+ i++;
95
+ while (i < lines.length && !/^```\s*$/.test(lines[i] ?? '')) {
96
+ codeLines.push(lines[i] ?? '');
97
+ i++;
98
+ }
99
+ i++; // skip closing fence
100
+ blocks.push({
101
+ type: 'codeBlock',
102
+ content: codeLines.join('\n'),
103
+ language,
104
+ });
105
+ continue;
106
+ }
107
+ // Horizontal rule
108
+ if (/^\s*(---|\*\*\*|___)\s*$/.test(line)) {
109
+ blocks.push({ type: 'hr' });
110
+ i++;
111
+ continue;
112
+ }
113
+ // Heading (# through ######)
114
+ const headingMatch = /^(#{1,6})\s+(.*)$/.exec(line);
115
+ if (headingMatch) {
116
+ const hashes = headingMatch[1] ?? '';
117
+ const headingContent = headingMatch[2] ?? '';
118
+ blocks.push({
119
+ type: 'heading',
120
+ level: hashes.length,
121
+ content: headingContent.trim(),
122
+ });
123
+ i++;
124
+ continue;
125
+ }
126
+ // Blockquote — consecutive `> ` lines
127
+ if (/^\s*>/.test(line)) {
128
+ const quoteLines = [];
129
+ while (i < lines.length && /^\s*>/.test(lines[i] ?? '')) {
130
+ quoteLines.push((lines[i] ?? '').replace(/^\s*>\s?/, ''));
131
+ i++;
132
+ }
133
+ blocks.push({ type: 'blockquote', content: quoteLines.join('\n') });
134
+ continue;
135
+ }
136
+ // Unordered list — lines beginning with `- `, `* `, or `+ `
137
+ if (/^\s*[-*+]\s+/.test(line)) {
138
+ const items = [];
139
+ while (i < lines.length && /^\s*[-*+]\s+/.test(lines[i] ?? '')) {
140
+ items.push((lines[i] ?? '').replace(/^\s*[-*+]\s+/, ''));
141
+ i++;
142
+ }
143
+ blocks.push({ type: 'list', items });
144
+ continue;
145
+ }
146
+ // Ordered list — `1. ...`
147
+ if (/^\s*\d+\.\s+/.test(line)) {
148
+ const items = [];
149
+ while (i < lines.length && /^\s*\d+\.\s+/.test(lines[i] ?? '')) {
150
+ items.push((lines[i] ?? '').replace(/^\s*\d+\.\s+/, ''));
151
+ i++;
152
+ }
153
+ blocks.push({ type: 'orderedList', items });
154
+ continue;
155
+ }
156
+ // GFM pipe table — a header row, a separator `---|---`, then rows.
157
+ if (gfm &&
158
+ /\|/.test(line) &&
159
+ i + 1 < lines.length &&
160
+ /^\s*\|?\s*:?-+:?\s*(\|\s*:?-+:?\s*)+\|?\s*$/.test(lines[i + 1] ?? '')) {
161
+ const split = (row) => row
162
+ .replace(/^\s*\|/, '')
163
+ .replace(/\|\s*$/, '')
164
+ .split('|')
165
+ .map(cell => cell.trim());
166
+ const header = split(line);
167
+ const rawAlign = split(lines[i + 1] ?? '');
168
+ const align = rawAlign.map(cell => {
169
+ const left = cell.startsWith(':');
170
+ const right = cell.endsWith(':');
171
+ if (left && right)
172
+ return 'center';
173
+ if (right)
174
+ return 'right';
175
+ if (left)
176
+ return 'left';
177
+ return null;
178
+ });
179
+ const rows = [header];
180
+ i += 2;
181
+ while (i < lines.length && /\|/.test(lines[i] ?? '')) {
182
+ rows.push(split(lines[i] ?? ''));
183
+ i++;
184
+ }
185
+ blocks.push({ type: 'table', rows, align });
186
+ continue;
187
+ }
188
+ // Paragraph — consecutive non-blank non-block-starting lines.
189
+ const paraLines = [];
190
+ while (i < lines.length) {
191
+ const ln = lines[i] ?? '';
192
+ if (/^\s*$/.test(ln) ||
193
+ /^```/.test(ln) ||
194
+ /^(#{1,6})\s+/.test(ln) ||
195
+ /^\s*>/.test(ln) ||
196
+ /^\s*[-*+]\s+/.test(ln) ||
197
+ /^\s*\d+\.\s+/.test(ln) ||
198
+ /^\s*(---|\*\*\*|___)\s*$/.test(ln)) {
199
+ break;
200
+ }
201
+ paraLines.push(ln);
202
+ i++;
203
+ }
204
+ blocks.push({ type: 'paragraph', content: paraLines.join('\n') });
205
+ }
206
+ return blocks;
207
+ }
208
+ /**
209
+ * Lightweight markdown renderer for chat messages.
210
+ *
211
+ * Pass it to `ChatMessage.renderContent` to render assistant responses
212
+ * with bold / italic / inline code, links, fenced code blocks, lists,
213
+ * blockquotes, horizontal rules, and GFM pipe tables.
214
+ *
215
+ * For advanced markdown features, supply a custom `renderContent`
216
+ * function that uses your markdown library of choice.
217
+ *
218
+ * @example
219
+ * ```tsx
220
+ * <ChatMessage
221
+ * message={msg}
222
+ * renderContent={content => <ChatMarkdownRenderer content={content} />}
223
+ * />
224
+ * ```
225
+ */
226
+ const ChatMarkdownRenderer =
227
+ /*#__PURE__*/ React.memo(({ content, gfm = true, linkTarget = '_blank', components, className, style, testId, ref, ...rest }) => {
228
+ const blocks = useMemo(() => parseBlocks(content, gfm), [content, gfm]);
229
+ const CodeBlock = components?.codeBlock ??
230
+ (({ code, language }) => (jsx(ChatCodeBlock, { code: code, language: language })));
231
+ return (jsx("div", { ref: ref, className: cx(markdownRootStyle, className), style: style, "data-testid": testId, ...rest, children: blocks.map((block, idx) => {
232
+ const key = `b-${idx}`;
233
+ switch (block.type) {
234
+ case 'paragraph':
235
+ return (jsx("p", { className: markdownParagraphStyle, children: renderInline(block.content ?? '', { linkTarget }, key) }, key));
236
+ case 'heading': {
237
+ const level = Math.min(Math.max(block.level ?? 1, 1), 6);
238
+ const Tag = `h${level}`;
239
+ return (jsx(Tag, { className: markdownHeadingStyle, children: renderInline(block.content ?? '', { linkTarget }, key) }, key));
240
+ }
241
+ case 'list':
242
+ return (jsx("ul", { className: markdownListStyle, children: (block.items ?? []).map((item, j) => (jsx("li", { className: markdownListItemStyle, children: renderInline(item, { linkTarget }, `${key}-${j}`) }, `${key}-${j}`))) }, key));
243
+ case 'orderedList':
244
+ return (jsx("ol", { className: markdownListStyle, children: (block.items ?? []).map((item, j) => (jsx("li", { className: markdownListItemStyle, children: renderInline(item, { linkTarget }, `${key}-${j}`) }, `${key}-${j}`))) }, key));
245
+ case 'blockquote':
246
+ return (jsx("blockquote", { className: markdownBlockquoteStyle, children: renderInline(block.content ?? '', { linkTarget }, key) }, key));
247
+ case 'codeBlock':
248
+ return (jsx(CodeBlock, { code: block.content ?? '', language: block.language }, key));
249
+ case 'hr':
250
+ return jsx("hr", { className: markdownHrStyle }, key);
251
+ case 'table': {
252
+ const [header, ...rows] = block.rows ?? [];
253
+ const align = block.align ?? [];
254
+ return (jsxs("table", { className: markdownTableStyle, children: [jsx("thead", { children: jsx("tr", { children: (header ?? []).map((cell, j) => (jsx("th", { className: markdownThStyle, style: align[j]
255
+ ? { textAlign: align[j] ?? undefined }
256
+ : undefined, children: renderInline(cell, { linkTarget }, `${key}-h-${j}`) }, `${key}-h-${j}`))) }) }), jsx("tbody", { children: rows.map((row, rIdx) => (jsx("tr", { children: row.map((cell, j) => (jsx("td", { className: markdownTdStyle, style: align[j]
257
+ ? { textAlign: align[j] ?? undefined }
258
+ : undefined, children: renderInline(cell, { linkTarget }, `${key}-r-${rIdx}-${j}`) }, `${key}-r-${rIdx}-${j}`))) }, `${key}-r-${rIdx}`))) })] }, key));
259
+ }
260
+ default:
261
+ return null;
262
+ }
263
+ }) }));
264
+ });
265
+ ChatMarkdownRenderer.displayName = 'ChatMarkdownRenderer';
266
+
267
+ export { ChatMarkdownRenderer };
268
+ //# sourceMappingURL=ChatMarkdownRenderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChatMarkdownRenderer.js","sources":["../../../../../../src/components/editor/ChatPanel/ChatMarkdownRenderer.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;AAqCA;AACE;AACF;AAEA;;;;;;AAWE;AAIE;AACE;AACA;AACD;AACD;AACE;AACA;AAQD;AACD;AACE;;AAcD;AACD;AACE;AACA;AACD;AACD;AACE;AACA;AACD;AACD;AACE;AACA;AACD;AACD;AACE;AACA;AACD;AACD;AACE;AACA;AACD;;AAGH;;;;AAMI;;AACA;AACE;;AAEE;;;;;;;;;AAWN;AACE;;AAEF;AACA;;AAGF;AACF;AAoBA;AACE;;;AAIA;;;AAIE;AACE;;;;;;AAOA;;AAEA;AACA;;AAEE;;;;AAIA;AACA;;AAED;;;;AAKH;;AAEE;;;;;;;;;AAUE;;AAEA;AACD;AACD;;;;AAKF;;AAEE;AACE;AACA;;AAEF;;;;AAKF;;AAEE;AACE;AACA;;;;;;AAOJ;;AAEE;AACE;AACA;;;;;;AAOJ;AAEE;AACA;AACA;AAEA;AAEK;AACA;;;AAIL;AACA;;;;;AAIqB;AACnB;AAAW;AACX;AAAU;AACV;AACF;AAEA;;AAEA;AACE;AACA;;AAEF;;;;;AAMF;;AAEE;AAEE;AACA;AACA;AACA;AACA;AACA;;;AAIF;AACA;;AAEF;;AAGF;AACF;AAEA;;;;;;;;;;;;;;;;;AAiBG;;AAED;;AAcI;;AAMA;AASM;AACA;AACE;;;;AAcE;;;AAOF;;AAUA;;AAUA;;AAMA;;AAQA;AACE;;AAEA;AACA;AACA;;AAWgB;;;;AAsClB;AACE;;;AAKZ;AAGJ;;"}
@@ -3,8 +3,13 @@ import { jsx, jsxs } from 'react/jsx-runtime';
3
3
  import React from 'react';
4
4
  import { cx } from '../../../utils/cx.js';
5
5
  import { ChatBubble } from './ChatBubble.js';
6
- import { messageTextStyle, messageAvatarStyle, messageAvatarImgStyle, messageContentStyle, bubbleErrorStyle, messageErrorCaptionStyle, messageErrorIconStyle, messageTimestampStyle, messageRecipe } from './ChatPanel.css.js';
6
+ import { messageTextStyle, messageAvatarStyle, messageAvatarImgStyle, messageContentStyle, bubbleErrorStyle, messageErrorCaptionStyle, messageErrorIconStyle, messageTimestampStyle, messageRecipe, messageMaxWidthVarName } from './ChatPanel.css.js';
7
7
 
8
+ function resolveMaxWidth(value) {
9
+ if (value === undefined)
10
+ return undefined;
11
+ return typeof value === 'number' ? `${value}px` : value;
12
+ }
8
13
  function formatTimestamp(iso) {
9
14
  try {
10
15
  const date = new Date(iso);
@@ -25,11 +30,18 @@ function getInitials(name) {
25
30
  .slice(0, 2);
26
31
  }
27
32
  const ErrorIcon = () => (jsxs("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", "aria-hidden": "true", children: [jsx("circle", { cx: "6", cy: "6", r: "5", stroke: "currentColor", strokeWidth: "1.2" }), jsx("line", { x1: "6", y1: "3.5", x2: "6", y2: "6.5", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round" }), jsx("circle", { cx: "6", cy: "8.5", r: "0.6", fill: "currentColor" })] }));
28
- const ChatMessage = /*#__PURE__*/ React.memo(({ message, showTimestamp = false, showAvatar = false, actions, renderContent, className, style, testId, ref, ...rest }) => {
33
+ const ChatMessage = /*#__PURE__*/ React.memo(({ message, showTimestamp = false, showAvatar = false, actions, renderContent, maxWidth, className, style, testId, ref, ...rest }) => {
29
34
  const { role, content, status, timestamp, avatar, displayName } = message;
30
35
  const isError = status === 'error';
31
36
  const renderedContent = renderContent ? (renderContent(content)) : (jsx("span", { className: messageTextStyle, children: content }));
32
- return (jsxs("div", { ref: ref, className: cx(messageRecipe({ role }), className), style: style, "data-testid": testId, ...rest, children: [showAvatar && role !== 'system' && (jsx("div", { className: messageAvatarStyle, "aria-hidden": "true", children: avatar ? (jsx("img", { src: avatar, alt: displayName ?? role, className: messageAvatarImgStyle })) : (getInitials(displayName ?? role)) })), jsxs("div", { className: messageContentStyle, children: [jsx(ChatBubble, { role: role, className: isError ? bubbleErrorStyle : undefined, children: renderedContent }), isError && (jsxs("span", { className: messageErrorCaptionStyle, children: [jsx("span", { className: messageErrorIconStyle, children: jsx(ErrorIcon, {}) }), "Error generating response"] })), actions && jsx("div", { children: actions }), showTimestamp && timestamp && (jsx("span", { className: messageTimestampStyle, children: formatTimestamp(timestamp) }))] })] }));
37
+ const resolvedMaxWidth = resolveMaxWidth(maxWidth);
38
+ const rootStyle = resolvedMaxWidth
39
+ ? {
40
+ [messageMaxWidthVarName]: resolvedMaxWidth,
41
+ ...style,
42
+ }
43
+ : (style ?? {});
44
+ return (jsxs("div", { ref: ref, className: cx(messageRecipe({ role }), className), style: rootStyle, "data-testid": testId, ...rest, children: [showAvatar && role !== 'system' && (jsx("div", { className: messageAvatarStyle, "aria-hidden": "true", children: avatar ? (jsx("img", { src: avatar, alt: displayName ?? role, className: messageAvatarImgStyle })) : (getInitials(displayName ?? role)) })), jsxs("div", { className: messageContentStyle, children: [jsx(ChatBubble, { role: role, className: isError ? bubbleErrorStyle : undefined, children: renderedContent }), isError && (jsxs("span", { className: messageErrorCaptionStyle, children: [jsx("span", { className: messageErrorIconStyle, children: jsx(ErrorIcon, {}) }), "Error generating response"] })), actions && jsx("div", { children: actions }), showTimestamp && timestamp && (jsx("span", { className: messageTimestampStyle, children: formatTimestamp(timestamp) }))] })] }));
33
45
  });
34
46
  ChatMessage.displayName = 'ChatMessage';
35
47
 
@@ -1 +1 @@
1
- {"version":3,"file":"ChatMessage.js","sources":["../../../../../../src/components/editor/ChatPanel/ChatMessage.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;AAkBA;AACE;AACE;AACA;;AACA;AACA;;AAEJ;AAEA;AACE;AAAW;AACX;;;;AAIG;AACA;AACL;AAEA;AAsBO;AAaH;AACA;;AAQA;AAiDF;AAGF;;"}
1
+ {"version":3,"file":"ChatMessage.js","sources":["../../../../../../src/components/editor/ChatPanel/ChatMessage.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;AAmBA;;AAG2B;AACzB;AACF;AAEA;AACE;AACE;AACA;;AACA;AACA;;AAEJ;AAEA;AACE;AAAW;AACX;;;;AAIG;AACA;AACL;AAEA;AAsBO;AAcH;AACA;;AAQA;;AAEE;;AAEI;AACuB;AAC3B;AAEF;AAiDF;AAGF;;"}
@@ -1,13 +1,13 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from 'react/jsx-runtime';
3
- import React from 'react';
3
+ import React, { useMemo, useImperativeHandle } from 'react';
4
4
  import { useChatScroll } from './useChatScroll.js';
5
5
  import { ChatMessage } from './ChatMessage.js';
6
6
  import { ScrollArea } from '../../layout/ScrollArea/ScrollArea.js';
7
7
  import { messageListContentStyle, newMessagesBannerStyle } from './ChatPanel.css.js';
8
8
 
9
- const ChatMessageList = /*#__PURE__*/ React.memo(({ messages, renderMessage, emptyState, autoScroll = true, className, style, testId, ref, ...rest }) => {
10
- const { scrollContainerRef, hasNewMessages, scrollToBottom } = useChatScroll({
9
+ const ChatMessageList = /*#__PURE__*/ React.memo(({ messages, renderMessage, emptyState, autoScroll = true, scrollApiRef, className, style, testId, ref, ...rest }) => {
10
+ const { scrollContainerRef, scrollContentRef, hasNewMessages, scrollToBottom, scrollTo, scrollToElement, } = useChatScroll({
11
11
  messages,
12
12
  enabled: autoScroll,
13
13
  });
@@ -21,10 +21,24 @@ const ChatMessageList = /*#__PURE__*/ React.memo(({ messages, renderMessage, emp
21
21
  ref.current = node;
22
22
  }
23
23
  }, [scrollContainerRef, ref]);
24
+ const isAtBottomGetter = React.useCallback(() => {
25
+ const container = scrollContainerRef.current;
26
+ if (!container)
27
+ return true;
28
+ const { scrollTop, scrollHeight, clientHeight } = container;
29
+ return scrollHeight - scrollTop - clientHeight <= 100;
30
+ }, [scrollContainerRef]);
31
+ const scrollApi = useMemo(() => ({
32
+ scrollToBottom,
33
+ scrollTo,
34
+ scrollToElement,
35
+ isAtBottom: isAtBottomGetter,
36
+ }), [scrollToBottom, scrollTo, scrollToElement, isAtBottomGetter]);
37
+ useImperativeHandle(scrollApiRef, () => scrollApi, [scrollApi]);
24
38
  if (messages.length === 0 && emptyState) {
25
- return (jsx(ScrollArea, { ref: mergedRef, autoFill: true, className: className, style: style, testId: testId, ...rest, children: jsx("div", { className: messageListContentStyle, role: "log", "aria-live": "polite", children: emptyState }) }));
39
+ return (jsx(ScrollArea, { ref: mergedRef, autoFill: true, className: className, style: style, testId: testId, ...rest, children: jsx("div", { ref: scrollContentRef, className: messageListContentStyle, role: "log", "aria-live": "polite", children: emptyState }) }));
26
40
  }
27
- return (jsx(ScrollArea, { ref: mergedRef, autoFill: true, className: className, style: style, testId: testId, ...rest, children: jsxs("div", { className: messageListContentStyle, role: "log", "aria-live": "polite", children: [messages.map((message, index) => renderMessage ? (jsx(React.Fragment, { children: renderMessage(message, index) }, message.id)) : (jsx(ChatMessage, { message: message }, message.id))), hasNewMessages && (jsx("button", { className: newMessagesBannerStyle, onClick: () => scrollToBottom('smooth'), type: "button", children: "New messages" }))] }) }));
41
+ return (jsx(ScrollArea, { ref: mergedRef, autoFill: true, className: className, style: style, testId: testId, ...rest, children: jsxs("div", { ref: scrollContentRef, className: messageListContentStyle, role: "log", "aria-live": "polite", children: [messages.map((message, index) => renderMessage ? (jsx(React.Fragment, { children: renderMessage(message, index) }, message.id)) : (jsx(ChatMessage, { message: message }, message.id))), hasNewMessages && (jsx("button", { className: newMessagesBannerStyle, onClick: () => scrollToBottom('smooth'), type: "button", children: "New messages" }))] }) }));
28
42
  });
29
43
  ChatMessageList.displayName = 'ChatMessageList';
30
44
 
@@ -1 +1 @@
1
- {"version":3,"file":"ChatMessageList.js","sources":["../../../../../../src/components/editor/ChatPanel/ChatMessageList.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;AAYO;;;AAeC;AACD;;;AAMG;AAEF;;;;AAGG;;AAEL;;AAKA;;;AAoDJ;AAGF;;"}
1
+ {"version":3,"file":"ChatMessageList.js","sources":["../../../../../../src/components/editor/ChatPanel/ChatMessageList.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;AAeO;AAaH;;AASE;AACD;;;AAMK;AAEF;;;;AAGG;;AAEL;AAIF;AACE;AACA;AAAgB;;AAEhB;AACF;AAEA;;;;AAKI;;AAKJ;;AAGE;;;AA0DJ;AAGF;;"}