@hanzo/ui 8.1.3 → 8.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist/backends/gui/textarea.cjs +35 -3
  2. package/dist/backends/gui/textarea.d.ts +34 -5
  3. package/dist/backends/gui/textarea.d.ts.map +1 -1
  4. package/dist/backends/gui/textarea.js +35 -3
  5. package/dist/backends/gui/textarea.js.map +1 -1
  6. package/dist/chat/Code.cjs +2 -2
  7. package/dist/chat/Code.d.ts +16 -3
  8. package/dist/chat/Code.d.ts.map +1 -1
  9. package/dist/chat/Code.js +2 -2
  10. package/dist/chat/Code.js.map +1 -1
  11. package/dist/chat/Composer.cjs +16 -10
  12. package/dist/chat/Composer.d.ts +59 -3
  13. package/dist/chat/Composer.d.ts.map +1 -1
  14. package/dist/chat/Composer.js +16 -10
  15. package/dist/chat/Composer.js.map +1 -1
  16. package/dist/chat/Failure.cjs +2 -2
  17. package/dist/chat/Failure.d.ts +29 -3
  18. package/dist/chat/Failure.d.ts.map +1 -1
  19. package/dist/chat/Failure.js +2 -2
  20. package/dist/chat/Failure.js.map +1 -1
  21. package/dist/chat/Header.cjs +2 -2
  22. package/dist/chat/Header.d.ts +20 -3
  23. package/dist/chat/Header.d.ts.map +1 -1
  24. package/dist/chat/Header.js +2 -2
  25. package/dist/chat/Header.js.map +1 -1
  26. package/dist/chat/Message.cjs +10 -4
  27. package/dist/chat/Message.d.ts +42 -3
  28. package/dist/chat/Message.d.ts.map +1 -1
  29. package/dist/chat/Message.js +10 -4
  30. package/dist/chat/Message.js.map +1 -1
  31. package/dist/chat/Sources.cjs +33 -10
  32. package/dist/chat/Sources.d.ts +46 -6
  33. package/dist/chat/Sources.d.ts.map +1 -1
  34. package/dist/chat/Sources.js +32 -9
  35. package/dist/chat/Sources.js.map +1 -1
  36. package/dist/chat/Step.cjs +2 -2
  37. package/dist/chat/Step.d.ts +4 -3
  38. package/dist/chat/Step.d.ts.map +1 -1
  39. package/dist/chat/Step.js +2 -2
  40. package/dist/chat/Step.js.map +1 -1
  41. package/dist/chat/Thread.cjs +38 -13
  42. package/dist/chat/Thread.d.ts +55 -3
  43. package/dist/chat/Thread.d.ts.map +1 -1
  44. package/dist/chat/Thread.js +38 -13
  45. package/dist/chat/Thread.js.map +1 -1
  46. package/dist/chat/index.cjs +19 -2
  47. package/dist/chat/index.d.ts +18 -1
  48. package/dist/chat/index.d.ts.map +1 -1
  49. package/dist/chat/index.js +18 -1
  50. package/dist/chat/index.js.map +1 -1
  51. package/dist/chat/pure.cjs +41 -0
  52. package/dist/chat/pure.d.ts +3 -0
  53. package/dist/chat/pure.d.ts.map +1 -0
  54. package/dist/chat/pure.js +35 -0
  55. package/dist/chat/pure.js.map +1 -0
  56. package/dist/chat/send.cjs +10 -1
  57. package/dist/chat/send.d.ts +9 -0
  58. package/dist/chat/send.d.ts.map +1 -1
  59. package/dist/chat/send.js +10 -1
  60. package/dist/chat/send.js.map +1 -1
  61. package/dist/chat/stick.cjs +1 -1
  62. package/dist/chat/stick.js +1 -1
  63. package/dist/gallery.cjs +5 -4
  64. package/dist/gallery.d.ts.map +1 -1
  65. package/dist/gallery.js +5 -4
  66. package/dist/gallery.js.map +1 -1
  67. package/dist/product/CopyButton.cjs +2 -2
  68. package/dist/product/CopyButton.d.ts +12 -2
  69. package/dist/product/CopyButton.d.ts.map +1 -1
  70. package/dist/product/CopyButton.js +2 -2
  71. package/dist/product/CopyButton.js.map +1 -1
  72. package/dist/product/pure.cjs +12 -1
  73. package/dist/product/pure.d.ts +2 -0
  74. package/dist/product/pure.d.ts.map +1 -1
  75. package/dist/product/pure.js +7 -0
  76. package/dist/product/pure.js.map +1 -1
  77. package/dist/styles.css +14 -2
  78. package/package.json +7 -1
@@ -43,15 +43,47 @@ const slot_1 = require("./slot.cjs");
43
43
  /** Floor for the grown row count. */
44
44
  const MIN_ROWS = 3;
45
45
  const rowsOf = (v) => typeof v === "string" ? v.split("\n").length : 0;
46
- const Textarea = /* @__PURE__ */ React.forwardRef(function Textarea({ rows, value, defaultValue, ...props }, ref) {
46
+ const Textarea = /* @__PURE__ */ React.forwardRef(function Textarea({ rows, value, defaultValue, style, ...props }, ref) {
47
47
  const [typed, setTyped] = React.useState(() => rowsOf(defaultValue));
48
48
  const uncontrolled = value === undefined;
49
49
  const invalid = props["aria-invalid"];
50
- return ((0, jsx_runtime_1.jsx)(gui_1.TextArea, { ref: ref, ...(0, slot_1.slot)("textarea"), rows: Math.max(rows ?? MIN_ROWS, uncontrolled ? typed : rowsOf(value)), value: value, defaultValue: defaultValue, onChangeText: uncontrolled ? (t) => setTyped(rowsOf(t)) : undefined, ...control_1.FIELD, width: "100%", minH: 64,
50
+ const node = React.useRef(null);
51
+ const [grown, setGrown] = React.useState(undefined);
52
+ // Both refs reach the same host: ours to measure it, the caller's because a
53
+ // caller that hands one over must still get it. Written as a callback so
54
+ // neither is dropped on the other's behalf.
55
+ const hold = React.useCallback((el) => {
56
+ node.current = el;
57
+ if (typeof ref === "function")
58
+ ref(el);
59
+ else if (ref)
60
+ ref.current = el;
61
+ }, [ref]);
62
+ // The value the field is actually showing — a controlled field is told, an
63
+ // uncontrolled one has to be watched, and both have to re-measure.
64
+ const shown = uncontrolled ? typed : value;
65
+ React.useLayoutEffect(() => {
66
+ const el = node.current;
67
+ if (!el || typeof el.scrollHeight !== "number")
68
+ return;
69
+ // Released to `auto` first, or `scrollHeight` reports the height it already
70
+ // has and the field can only ever grow. Restored in the same synchronous
71
+ // block, before paint, so nothing flashes.
72
+ const held = el.style.height;
73
+ el.style.height = "auto";
74
+ const measured = el.scrollHeight;
75
+ el.style.height = held;
76
+ setGrown((h) => (h === measured ? h : measured));
77
+ }, [shown]);
78
+ return ((0, jsx_runtime_1.jsx)(gui_1.TextArea, { ref: hold, ...(0, slot_1.slot)("textarea"), rows: Math.max(rows ?? MIN_ROWS, uncontrolled ? typed : rowsOf(value)), value: value, defaultValue: defaultValue, onChangeText: uncontrolled ? (t) => setTyped(rowsOf(t)) : undefined, ...control_1.FIELD, width: "100%", minH: 64,
51
79
  // The gutter has to be NAMED. Left unsaid it came from gui's
52
80
  // `textAreaSizeVariant`, which computes its own horizontal padding and
53
81
  // landed on 20 — so the one field a form makes tall was also the one
54
82
  // indented differently from the two above it.
55
- borderColor: invalid && invalid !== "false" ? "$red7" : "$borderColor", placeholderTextColor: "$color10", fontSize: "$3", opacity: props.disabled ? 0.5 : 1, ...props }));
83
+ borderColor: invalid && invalid !== "false" ? "$red7" : "$borderColor", placeholderTextColor: "$color10", fontSize: "$3", opacity: props.disabled ? 0.5 : 1, ...props,
84
+ // After the caller's props: the measurement is this component's answer to
85
+ // its own question, and a caller's `style` should not silently turn the
86
+ // growth off. A caller wanting a fixed height says so with `height`.
87
+ style: { ...style, ...(grown ? { height: grown } : null) } }));
56
88
  });
57
89
  exports.Textarea = Textarea;
@@ -1,12 +1,41 @@
1
1
  import * as React from "react";
2
2
  import { TextArea } from "@hanzo/gui";
3
3
  /**
4
- * Textarea — standard-token multiline field.
4
+ * Textarea — standard-token multiline field that grows with what is in it.
5
5
  *
6
- * Auto-grow is line-driven rather than DOM-measured, so web, native and desktop
7
- * grow identically: the row count follows the value and gui's
8
- * `textAreaSizeVariant` turns rows into height.
9
- * `rows` is the floor, not a fixed size.
6
+ * Growth used to be LINE-DRIVEN — `rows` from `value.split('\n').length` — on
7
+ * the stated grounds that counting newlines is the one measurement web, native
8
+ * and desktop agree on. It counts the wrong thing. A draft that wraps without
9
+ * ever containing a newline never raises the count, so the most ordinary input
10
+ * there is, one long paragraph, scrolls inside the floor while you type.
11
+ * Measured in Chromium at 420px wide: 700 characters and no `\n` gave
12
+ * `clientHeight 44` around `scrollHeight 160`.
13
+ *
14
+ * The fix is to measure the content. Two things that look like the measurement
15
+ * are not available here, and both were tried:
16
+ *
17
+ * `onContentSizeChange` is the RN API for exactly this, and
18
+ * react-native-web implements it by reading `scrollHeight`
19
+ * (`dist/exports/TextInput/index.js:192`). It never fires through this
20
+ * component — measured, zero calls, no warning. gui's `TextArea` is
21
+ * `styled(Input, { render: 'textarea' })` from `@hanzogui/web`: a Tamagui
22
+ * styled component over a real DOM `<textarea>`, so RNW's `TextInput` is not
23
+ * on the path and neither is its implementation of that prop.
24
+ *
25
+ * A `height` STYLE PROP would be compiled by gui into one atomic class per
26
+ * distinct pixel value, so a field that grows through forty heights mints
27
+ * forty rules at runtime — none of them in the packaged sheet.
28
+ *
29
+ * So the height is measured off the node and applied INLINE, which is the one
30
+ * channel that carries a per-render value without going through the atomic
31
+ * compiler. `rows` stays the floor and `maxH` stays the ceiling; CSS clamps
32
+ * between them and the field scrolls past the ceiling, so neither bound is
33
+ * re-implemented in script.
34
+ *
35
+ * The measurement is guarded on `scrollHeight` actually being a number, so on a
36
+ * target where the host is not a DOM element the effect no-ops and the
37
+ * line-driven floor is what remains — today's behaviour, unchanged, rather than
38
+ * a broken one.
10
39
  */
11
40
  /**
12
41
  * Typed from the gui component it renders, exactly as `Input` is — the field
@@ -1 +1 @@
1
- {"version":3,"file":"textarea.d.ts","sourceRoot":"","sources":["../../../src/backends/gui/textarea.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAUrC;;;;;;;GAOG;AACH;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,QAAQ,CAAC,EAAE,UAAU,CAAC,GACjF,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,UAAU,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC,CAAA;AAE9D,QAAA,MAAM,QAAQ,wGA4Bb,CAAA;AAED,OAAO,EAAE,QAAQ,EAAE,CAAA"}
1
+ {"version":3,"file":"textarea.d.ts","sourceRoot":"","sources":["../../../src/backends/gui/textarea.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAUrC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,QAAQ,CAAC,EAAE,UAAU,CAAC,GACjF,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,UAAU,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC,CAAA;AAE9D,QAAA,MAAM,QAAQ,wGAiEb,CAAA;AAED,OAAO,EAAE,QAAQ,EAAE,CAAA"}
@@ -7,16 +7,48 @@ import { slot } from "./slot.js";
7
7
  /** Floor for the grown row count. */
8
8
  const MIN_ROWS = 3;
9
9
  const rowsOf = (v) => typeof v === "string" ? v.split("\n").length : 0;
10
- const Textarea = /* @__PURE__ */ React.forwardRef(function Textarea({ rows, value, defaultValue, ...props }, ref) {
10
+ const Textarea = /* @__PURE__ */ React.forwardRef(function Textarea({ rows, value, defaultValue, style, ...props }, ref) {
11
11
  const [typed, setTyped] = React.useState(() => rowsOf(defaultValue));
12
12
  const uncontrolled = value === undefined;
13
13
  const invalid = props["aria-invalid"];
14
- return (_jsx(TextArea, { ref: ref, ...slot("textarea"), rows: Math.max(rows ?? MIN_ROWS, uncontrolled ? typed : rowsOf(value)), value: value, defaultValue: defaultValue, onChangeText: uncontrolled ? (t) => setTyped(rowsOf(t)) : undefined, ...FIELD, width: "100%", minH: 64,
14
+ const node = React.useRef(null);
15
+ const [grown, setGrown] = React.useState(undefined);
16
+ // Both refs reach the same host: ours to measure it, the caller's because a
17
+ // caller that hands one over must still get it. Written as a callback so
18
+ // neither is dropped on the other's behalf.
19
+ const hold = React.useCallback((el) => {
20
+ node.current = el;
21
+ if (typeof ref === "function")
22
+ ref(el);
23
+ else if (ref)
24
+ ref.current = el;
25
+ }, [ref]);
26
+ // The value the field is actually showing — a controlled field is told, an
27
+ // uncontrolled one has to be watched, and both have to re-measure.
28
+ const shown = uncontrolled ? typed : value;
29
+ React.useLayoutEffect(() => {
30
+ const el = node.current;
31
+ if (!el || typeof el.scrollHeight !== "number")
32
+ return;
33
+ // Released to `auto` first, or `scrollHeight` reports the height it already
34
+ // has and the field can only ever grow. Restored in the same synchronous
35
+ // block, before paint, so nothing flashes.
36
+ const held = el.style.height;
37
+ el.style.height = "auto";
38
+ const measured = el.scrollHeight;
39
+ el.style.height = held;
40
+ setGrown((h) => (h === measured ? h : measured));
41
+ }, [shown]);
42
+ return (_jsx(TextArea, { ref: hold, ...slot("textarea"), rows: Math.max(rows ?? MIN_ROWS, uncontrolled ? typed : rowsOf(value)), value: value, defaultValue: defaultValue, onChangeText: uncontrolled ? (t) => setTyped(rowsOf(t)) : undefined, ...FIELD, width: "100%", minH: 64,
15
43
  // The gutter has to be NAMED. Left unsaid it came from gui's
16
44
  // `textAreaSizeVariant`, which computes its own horizontal padding and
17
45
  // landed on 20 — so the one field a form makes tall was also the one
18
46
  // indented differently from the two above it.
19
- borderColor: invalid && invalid !== "false" ? "$red7" : "$borderColor", placeholderTextColor: "$color10", fontSize: "$3", opacity: props.disabled ? 0.5 : 1, ...props }));
47
+ borderColor: invalid && invalid !== "false" ? "$red7" : "$borderColor", placeholderTextColor: "$color10", fontSize: "$3", opacity: props.disabled ? 0.5 : 1, ...props,
48
+ // After the caller's props: the measurement is this component's answer to
49
+ // its own question, and a caller's `style` should not silently turn the
50
+ // growth off. A caller wanting a fixed height says so with `height`.
51
+ style: { ...style, ...(grown ? { height: grown } : null) } }));
20
52
  });
21
53
  export { Textarea };
22
54
  //# sourceMappingURL=textarea.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"textarea.js","sourceRoot":"","sources":["../../../src/backends/gui/textarea.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AACrC,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAA;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAE7B,qCAAqC;AACrC,MAAM,QAAQ,GAAG,CAAC,CAAA;AAElB,MAAM,MAAM,GAAG,CAAC,CAAU,EAAE,EAAE,CAC5B,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAA;AAkBlD,MAAM,QAAQ,GAAG,eAAe,CAAC,KAAK,CAAC,UAAU,CAC/C,SAAS,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,YAAY,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG;IAC9D,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAA;IACpE,MAAM,YAAY,GAAG,KAAK,KAAK,SAAS,CAAA;IACxC,MAAM,OAAO,GAAG,KAAK,CAAC,cAAc,CAAC,CAAA;IACrC,OAAO,CACL,KAAC,QAAQ,IACP,GAAG,EAAE,GAAY,KACb,IAAI,CAAC,UAAU,CAAC,EACpB,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EACtE,KAAK,EAAE,KAA2B,EAClC,YAAY,EAAE,YAAkC,EAChD,YAAY,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,KACpE,KAAK,EACT,KAAK,EAAC,MAAM,EACZ,IAAI,EAAE,EAAE;QACR,6DAA6D;QAC7D,uEAAuE;QACvE,qEAAqE;QACrE,8CAA8C;QAC9C,WAAW,EAAE,OAAO,IAAI,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,EACtE,oBAAoB,EAAC,UAAU,EAC/B,QAAQ,EAAC,IAAI,EACb,OAAO,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAC5B,KAAiC,GACtC,CACH,CAAA;AACD,CAAC,CACF,CAAA;AAED,OAAO,EAAE,QAAQ,EAAE,CAAA"}
1
+ {"version":3,"file":"textarea.js","sourceRoot":"","sources":["../../../src/backends/gui/textarea.tsx"],"names":[],"mappings":"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"}
@@ -30,7 +30,7 @@ const CopyButton_1 = require("../product/CopyButton.cjs");
30
30
  const MONO = {
31
31
  fontFamily: 'var(--font-geist-mono, ui-monospace, SFMono-Regular, Menlo, monospace)',
32
32
  };
33
- function Code({ children, language = 'text', value, actions }) {
33
+ function Code({ children, language = 'text', value, actions, ...props }) {
34
34
  const text = value ?? (typeof children === 'string' ? children : '');
35
- return ((0, jsx_runtime_1.jsxs)(gui_1.YStack, { ...(0, slot_1.slot)('code'), width: "100%", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$color2", overflow: "hidden", children: [(0, jsx_runtime_1.jsxs)(gui_1.XStack, { items: "center", gap: "$1", px: "$3", py: "$1.5", borderBottomWidth: 1, borderColor: "$borderColor", children: [(0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$1", style: MONO, color: "$color11", children: language }), (0, jsx_runtime_1.jsx)(gui_1.XStack, { flex: 1 }), actions, text ? (0, jsx_runtime_1.jsx)(CopyButton_1.CopyButton, { value: text }) : null] }), (0, jsx_runtime_1.jsx)(gui_1.ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, children: (0, jsx_runtime_1.jsx)(gui_1.YStack, { px: "$3", py: "$2.5", children: (0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$1", style: MONO, lineHeight: 20, children: children }) }) })] }));
35
+ return ((0, jsx_runtime_1.jsxs)(gui_1.YStack, { ...(0, slot_1.slot)('code'), width: "100%", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$color2", overflow: "hidden", ...props, children: [(0, jsx_runtime_1.jsxs)(gui_1.XStack, { items: "center", gap: "$1", px: "$3", py: "$1.5", borderBottomWidth: 1, borderColor: "$borderColor", children: [(0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$1", style: MONO, color: "$color11", children: language }), (0, jsx_runtime_1.jsx)(gui_1.XStack, { flex: 1 }), actions, text ? (0, jsx_runtime_1.jsx)(CopyButton_1.CopyButton, { value: text }) : null] }), (0, jsx_runtime_1.jsx)(gui_1.ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, children: (0, jsx_runtime_1.jsx)(gui_1.YStack, { px: "$3", py: "$2.5", children: (0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$1", style: MONO, lineHeight: 20, children: children }) }) })] }));
36
36
  }
@@ -1,5 +1,18 @@
1
- import { type ReactNode } from 'react';
2
- export interface CodeProps {
1
+ /**
2
+ * Code — a labelled, copyable code region.
3
+ *
4
+ * The language label and the controls live in a bar above the code, never
5
+ * floating over it: an overlaid copy button covers the first line, which is
6
+ * where the prompt or the import sits and the part most often being read.
7
+ *
8
+ * Highlighting is not here. Surfaces disagree on the highlighter (and on whether
9
+ * they want one at all), and a package that picked would either impose a large
10
+ * dependency or be bypassed. `children` is rendered as-is, so a surface can pass
11
+ * plain text or its own highlighted nodes.
12
+ */
13
+ import { YStack } from '@hanzo/gui';
14
+ import { type ComponentProps, type ReactNode } from 'react';
15
+ export interface CodeProps extends Omit<ComponentProps<typeof YStack>, 'children'> {
3
16
  children?: ReactNode;
4
17
  /** Shown in the label bar. */
5
18
  language?: string;
@@ -8,5 +21,5 @@ export interface CodeProps {
8
21
  /** Extra controls in the label bar, before copy. */
9
22
  actions?: ReactNode;
10
23
  }
11
- export declare function Code({ children, language, value, actions }: CodeProps): import("react/jsx-runtime").JSX.Element;
24
+ export declare function Code({ children, language, value, actions, ...props }: CodeProps): import("react/jsx-runtime").JSX.Element;
12
25
  //# sourceMappingURL=Code.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Code.d.ts","sourceRoot":"","sources":["../../src/chat/Code.tsx"],"names":[],"mappings":"AAeA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAkBtC,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,8BAA8B;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,mFAAmF;IACnF,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,oDAAoD;IACpD,OAAO,CAAC,EAAE,SAAS,CAAA;CACpB;AAED,wBAAgB,IAAI,CAAC,EAAE,QAAQ,EAAE,QAAiB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,SAAS,2CAyD9E"}
1
+ {"version":3,"file":"Code.d.ts","sourceRoot":"","sources":["../../src/chat/Code.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH,OAAO,EAAmC,MAAM,EAAE,MAAM,YAAY,CAAA;AACpE,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAkB3D,MAAM,WAAW,SAAU,SAAQ,IAAI,CAAC,cAAc,CAAC,OAAO,MAAM,CAAC,EAAE,UAAU,CAAC;IAChF,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,8BAA8B;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,mFAAmF;IACnF,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,oDAAoD;IACpD,OAAO,CAAC,EAAE,SAAS,CAAA;CACpB;AAED,wBAAgB,IAAI,CAAC,EAAE,QAAQ,EAAE,QAAiB,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,KAAK,EAAE,EAAE,SAAS,2CA0DxF"}
package/dist/chat/Code.js CHANGED
@@ -27,8 +27,8 @@ import { CopyButton } from '../product/CopyButton.js';
27
27
  const MONO = {
28
28
  fontFamily: 'var(--font-geist-mono, ui-monospace, SFMono-Regular, Menlo, monospace)',
29
29
  };
30
- export function Code({ children, language = 'text', value, actions }) {
30
+ export function Code({ children, language = 'text', value, actions, ...props }) {
31
31
  const text = value ?? (typeof children === 'string' ? children : '');
32
- return (_jsxs(YStack, { ...slot('code'), width: "100%", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$color2", overflow: "hidden", children: [_jsxs(XStack, { items: "center", gap: "$1", px: "$3", py: "$1.5", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsx(SizableText, { size: "$1", style: MONO, color: "$color11", children: language }), _jsx(XStack, { flex: 1 }), actions, text ? _jsx(CopyButton, { value: text }) : null] }), _jsx(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, children: _jsx(YStack, { px: "$3", py: "$2.5", children: _jsx(SizableText, { size: "$1", style: MONO, lineHeight: 20, children: children }) }) })] }));
32
+ return (_jsxs(YStack, { ...slot('code'), width: "100%", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$color2", overflow: "hidden", ...props, children: [_jsxs(XStack, { items: "center", gap: "$1", px: "$3", py: "$1.5", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsx(SizableText, { size: "$1", style: MONO, color: "$color11", children: language }), _jsx(XStack, { flex: 1 }), actions, text ? _jsx(CopyButton, { value: text }) : null] }), _jsx(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, children: _jsx(YStack, { px: "$3", py: "$2.5", children: _jsx(SizableText, { size: "$1", style: MONO, lineHeight: 20, children: children }) }) })] }));
33
33
  }
34
34
  //# sourceMappingURL=Code.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Code.js","sourceRoot":"","sources":["../../src/chat/Code.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAGpE,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAElD;;;;;;;;GAQG;AACH,MAAM,IAAI,GAAG;IACX,UAAU,EAAE,wEAAwE;CAC5E,CAAA;AAYV,MAAM,UAAU,IAAI,CAAC,EAAE,QAAQ,EAAE,QAAQ,GAAG,MAAM,EAAE,KAAK,EAAE,OAAO,EAAa;IAC7E,MAAM,IAAI,GAAG,KAAK,IAAI,CAAC,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IAEpE,OAAO,CACL,MAAC,MAAM,OACD,IAAI,CAAC,MAAM,CAAC,EAChB,KAAK,EAAC,MAAM,EACZ,OAAO,EAAC,IAAI,EACZ,WAAW,EAAE,CAAC,EACd,WAAW,EAAC,cAAc,EAC1B,EAAE,EAAC,SAAS,EACZ,QAAQ,EAAC,QAAQ,aAEjB,MAAC,MAAM,IACL,KAAK,EAAC,QAAQ,EACd,GAAG,EAAC,IAAI,EACR,EAAE,EAAC,IAAI,EACP,EAAE,EAAC,MAAM,EACT,iBAAiB,EAAE,CAAC,EACpB,WAAW,EAAC,cAAc,aAQ1B,KAAC,WAAW,IAAC,IAAI,EAAC,IAAI,EAAC,KAAK,EAAE,IAAI,EAAE,KAAK,EAAC,UAAU,YACjD,QAAQ,GACG,EACd,KAAC,MAAM,IAAC,IAAI,EAAE,CAAC,GAAI,EAClB,OAAO,EACP,IAAI,CAAC,CAAC,CAAC,KAAC,UAAU,IAAC,KAAK,EAAE,IAAI,GAAI,CAAC,CAAC,CAAC,IAAI,IACnC,EAeT,KAAC,UAAU,IAAC,UAAU,QAAC,8BAA8B,EAAE,KAAK,YAC1D,KAAC,MAAM,IAAC,EAAE,EAAC,IAAI,EAAC,EAAE,EAAC,MAAM,YACvB,KAAC,WAAW,IAAC,IAAI,EAAC,IAAI,EAAC,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,YAC/C,QAAQ,GACG,GACP,GACE,IACN,CACV,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"Code.js","sourceRoot":"","sources":["../../src/chat/Code.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAGpE,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAElD;;;;;;;;GAQG;AACH,MAAM,IAAI,GAAG;IACX,UAAU,EAAE,wEAAwE;CAC5E,CAAA;AAYV,MAAM,UAAU,IAAI,CAAC,EAAE,QAAQ,EAAE,QAAQ,GAAG,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,KAAK,EAAa;IACvF,MAAM,IAAI,GAAG,KAAK,IAAI,CAAC,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IAEpE,OAAO,CACL,MAAC,MAAM,OACD,IAAI,CAAC,MAAM,CAAC,EAChB,KAAK,EAAC,MAAM,EACZ,OAAO,EAAC,IAAI,EACZ,WAAW,EAAE,CAAC,EACd,WAAW,EAAC,cAAc,EAC1B,EAAE,EAAC,SAAS,EACZ,QAAQ,EAAC,QAAQ,KACb,KAAK,aAET,MAAC,MAAM,IACL,KAAK,EAAC,QAAQ,EACd,GAAG,EAAC,IAAI,EACR,EAAE,EAAC,IAAI,EACP,EAAE,EAAC,MAAM,EACT,iBAAiB,EAAE,CAAC,EACpB,WAAW,EAAC,cAAc,aAQ1B,KAAC,WAAW,IAAC,IAAI,EAAC,IAAI,EAAC,KAAK,EAAE,IAAI,EAAE,KAAK,EAAC,UAAU,YACjD,QAAQ,GACG,EACd,KAAC,MAAM,IAAC,IAAI,EAAE,CAAC,GAAI,EAClB,OAAO,EACP,IAAI,CAAC,CAAC,CAAC,KAAC,UAAU,IAAC,KAAK,EAAE,IAAI,GAAI,CAAC,CAAC,CAAC,IAAI,IACnC,EAeT,KAAC,UAAU,IAAC,UAAU,QAAC,8BAA8B,EAAE,KAAK,YAC1D,KAAC,MAAM,IAAC,EAAE,EAAC,IAAI,EAAC,EAAE,EAAC,MAAM,YACvB,KAAC,WAAW,IAAC,IAAI,EAAC,IAAI,EAAC,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,YAC/C,QAAQ,GACG,GACP,GACE,IACN,CACV,CAAA;AACH,CAAC"}
@@ -27,14 +27,10 @@ const jsx_runtime_1 = require("react/jsx-runtime");
27
27
  * nothing here for it to reach. Adding either would be inventing an interaction
28
28
  * and then asking three surfaces to adopt it.
29
29
  *
30
- * Growth is `Textarea`'s and it is LINE-DRIVEN, not DOM-measured: the row count
31
- * follows the newlines in the value, which is what lets web, native and desktop
32
- * grow identically. The tradeoff is real and worth knowing before you adopt —
33
- * a draft that soft-wraps without ever containing a `\n` does not raise the row
34
- * count, so a long unbroken paragraph scrolls inside the floor instead of
35
- * growing the field. Fixing that means measuring `scrollHeight`, which is the
36
- * DOM-only path the five hand-rolled composers each took and the reason none of
37
- * them runs on native. It is a `Textarea` decision, not a chat one.
30
+ * Growth is `Textarea`'s, and since 8.1.4 it follows the CONTENT rather than the
31
+ * newline count — so a long paragraph that wraps without ever containing a `\n`
32
+ * raises the field instead of scrolling inside it. `rows` is the floor and
33
+ * `maxHeight` the ceiling; both are bounds, and neither is re-implemented here.
38
34
  */
39
35
  const gui_1 = require("@hanzo/gui");
40
36
  const lucide_icons_2_1 = require("@hanzogui/lucide-icons-2");
@@ -81,7 +77,7 @@ const FLOOR = 44;
81
77
  * changes — which is what a model-named placeholder forces a surface to do.
82
78
  */
83
79
  exports.ASK = 'Ask anything';
84
- function Composer({ value, onChange, onSend, onStop, busy = false, disabled = false, placeholder = exports.ASK, rows = MIN_ROWS, maxHeight = CEILING, children, hint, }) {
80
+ function Composer({ value, onChange, onSend, onStop, busy = false, disabled = false, placeholder = exports.ASK, rows = MIN_ROWS, maxHeight = CEILING, children, hint, label, field, send, ...props }) {
85
81
  const sendable = (0, send_1.ready)(value, busy, disabled);
86
82
  const keyed = (e) => {
87
83
  const key = e.key ?? e.nativeEvent?.key ?? '';
@@ -101,5 +97,15 @@ function Composer({ value, onChange, onSend, onStop, busy = false, disabled = fa
101
97
  if (sendable)
102
98
  onSend();
103
99
  };
104
- return ((0, jsx_runtime_1.jsxs)(gui_1.YStack, { ...(0, slot_1.slot)('composer'), width: "100%", rounded: "$5", borderWidth: 1, borderColor: "$borderColor", bg: "$background", p: PAD, gap: PAD, children: [(0, jsx_runtime_1.jsx)(gui_2.Textarea, { ...(0, slot_1.slot)('composer-field'), rows: rows, value: value, placeholder: placeholder, disabled: disabled, onChangeText: onChange, onKeyDown: keyed, borderWidth: 0, minH: FLOOR, maxH: maxHeight, "aria-label": placeholder }), (0, jsx_runtime_1.jsxs)(gui_1.XStack, { items: "center", gap: PAD, children: [children, (0, jsx_runtime_1.jsx)(gui_1.XStack, { flex: 1 }), hint ? ((0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$1", color: "$color10", children: hint })) : null, (0, jsx_runtime_1.jsx)(gui_2.Button, { ...(0, slot_1.slot)('composer-send'), size: "sm", disabled: busy ? !onStop : !sendable, onPress: busy ? onStop : onSend, "aria-label": busy ? 'Stop' : 'Send', children: busy ? (0, jsx_runtime_1.jsx)(lucide_icons_2_1.Square, { size: 14 }) : (0, jsx_runtime_1.jsx)(lucide_icons_2_1.ArrowUp, { size: 16 }) })] })] }));
100
+ return ((0, jsx_runtime_1.jsxs)(gui_1.YStack, { ...(0, slot_1.slot)('composer'), width: "100%", rounded: "$5", borderWidth: 1, borderColor: "$borderColor", bg: "$background", p: PAD, gap: PAD, ...props, children: [(0, jsx_runtime_1.jsx)(gui_2.Textarea, { ...(0, slot_1.slot)('composer-field'), rows: rows, placeholder: placeholder, disabled: disabled, borderWidth: 0, minH: FLOOR, maxH: maxHeight, "aria-label": label ?? placeholder, ...field,
101
+ // After `field`, and deliberately. These four ARE the composer; a
102
+ // caller's `onKeyDown` landing on top of `keyed` would take the Enter
103
+ // rule and the IME guard with it, type-check, and go on looking
104
+ // correct until a Japanese writer typed into it. So the caller's runs
105
+ // FIRST and this one defers to it if it claimed the key.
106
+ value: value, onChangeText: onChange, onKeyDown: (e) => {
107
+ field?.onKeyDown?.(e);
108
+ if (!e?.defaultPrevented)
109
+ keyed(e);
110
+ } }), (0, jsx_runtime_1.jsxs)(gui_1.XStack, { items: "center", gap: PAD, children: [children, (0, jsx_runtime_1.jsx)(gui_1.XStack, { flex: 1 }), hint ? ((0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$1", color: "$color10", children: hint })) : null, send ?? ((0, jsx_runtime_1.jsx)(gui_2.Button, { ...(0, slot_1.slot)('composer-send'), size: "sm", disabled: busy ? !onStop : !sendable, onPress: busy ? onStop : onSend, "aria-label": busy ? 'Stop' : 'Send', children: busy ? (0, jsx_runtime_1.jsx)(lucide_icons_2_1.Square, { size: 14 }) : (0, jsx_runtime_1.jsx)(lucide_icons_2_1.ArrowUp, { size: 16 }) }))] })] }));
105
111
  }
@@ -1,4 +1,38 @@
1
- import type { ReactNode } from 'react';
1
+ /**
2
+ * Composer — the one chat input for every Hanzo surface.
3
+ *
4
+ * Presentational and controlled: it owns no draft, no transport and no model
5
+ * state. `children` is the toolbar slot, which is where a surface puts its
6
+ * `ModelSelector`, attachment control or mode chips — that keeps the shell the
7
+ * same everywhere while the affordances stay per-surface.
8
+ *
9
+ * The send control is the same button in both states: it submits, and while a
10
+ * turn is in flight it stops. Two buttons in one place was the thing every
11
+ * surface got subtly different.
12
+ *
13
+ * Two keys are deliberately absent, because the surfaces do not agree that they
14
+ * exist. ESCAPE is bound in none of the three main composers — only in chat's
15
+ * two SUB-composers, where it dismisses the thing they float over
16
+ * (`SelectionAsk.tsx:85`, `EditMessage.tsx:124`), which is the host's concern
17
+ * and not the field's. ARROWUP recalls the last turn in exactly one composer,
18
+ * chat's (`useHandleKeyUp.ts:117`), and it does it by finding
19
+ * `document.getElementById('edit-' + parentMessageId)` and clicking it — it is
20
+ * a binding to their edit-in-place, not a general history recall, and there is
21
+ * nothing here for it to reach. Adding either would be inventing an interaction
22
+ * and then asking three surfaces to adopt it.
23
+ *
24
+ * Growth is `Textarea`'s, and since 8.1.4 it follows the CONTENT rather than the
25
+ * newline count — so a long paragraph that wraps without ever containing a `\n`
26
+ * raises the field instead of scrolling inside it. `rows` is the floor and
27
+ * `maxHeight` the ceiling; both are bounds, and neither is re-implemented here.
28
+ */
29
+ import { YStack } from '@hanzo/gui';
30
+ import type { ComponentProps, ReactNode } from 'react';
31
+ import { Textarea } from '../backends/gui/index.js';
32
+ /** `onChange` is dropped: this component already has one and it carries the
33
+ * draft, not a DOM change event. */
34
+ type Stack = Omit<ComponentProps<typeof YStack>, 'children' | 'onChange'>;
35
+ type FieldProps = Omit<ComponentProps<typeof Textarea>, 'value' | 'onChangeText'>;
2
36
  /**
3
37
  * The resting placeholder.
4
38
  *
@@ -8,7 +42,7 @@ import type { ReactNode } from 'react';
8
42
  * changes — which is what a model-named placeholder forces a surface to do.
9
43
  */
10
44
  export declare const ASK = "Ask anything";
11
- export interface ComposerProps {
45
+ export interface ComposerProps extends Stack {
12
46
  value: string;
13
47
  onChange: (value: string) => void;
14
48
  /** Called when the draft is submitted. Clearing `value` is the host's job. */
@@ -27,6 +61,28 @@ export interface ComposerProps {
27
61
  children?: ReactNode;
28
62
  /** Hint shown at the end of the footer row. */
29
63
  hint?: string;
64
+ /**
65
+ * The accessible name of the field.
66
+ *
67
+ * Distinct from `placeholder`, which it used to be derived from. A surface
68
+ * whose placeholder rotates on a timer — hanzo.app's does — renamed the
69
+ * control on every tick, so a screen reader re-announced the field while the
70
+ * writer was typing into it. The name is stable and the placeholder is
71
+ * decoration; deriving one from the other conflates them.
72
+ */
73
+ label?: string;
74
+ /** The field's own props — a ref, a testid, focus handlers, a paste handler. */
75
+ field?: FieldProps;
76
+ /**
77
+ * Replaces the send control entirely.
78
+ *
79
+ * The default is the right one — one button that submits and stops, which is
80
+ * what every surface got subtly different — but a surface with a second
81
+ * commit mode has nowhere else to put it, and `children` is the START of the
82
+ * footer row.
83
+ */
84
+ send?: ReactNode;
30
85
  }
31
- export declare function Composer({ value, onChange, onSend, onStop, busy, disabled, placeholder, rows, maxHeight, children, hint, }: ComposerProps): import("react/jsx-runtime").JSX.Element;
86
+ export declare function Composer({ value, onChange, onSend, onStop, busy, disabled, placeholder, rows, maxHeight, children, hint, label, field, send, ...props }: ComposerProps): import("react/jsx-runtime").JSX.Element;
87
+ export {};
32
88
  //# sourceMappingURL=Composer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Composer.d.ts","sourceRoot":"","sources":["../../src/chat/Composer.tsx"],"names":[],"mappings":"AAoCA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAwCtC;;;;;;;GAOG;AACH,eAAO,MAAM,GAAG,iBAAiB,CAAA;AAEjC,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,8EAA8E;IAC9E,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB,0DAA0D;IAC1D,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,QAAQ,EACR,MAAM,EACN,MAAM,EACN,IAAY,EACZ,QAAgB,EAChB,WAAiB,EACjB,IAAe,EACf,SAAmB,EACnB,QAAQ,EACR,IAAI,GACL,EAAE,aAAa,2CAgEf"}
1
+ {"version":3,"file":"Composer.d.ts","sourceRoot":"","sources":["../../src/chat/Composer.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,EAAuB,MAAM,EAAE,MAAM,YAAY,CAAA;AAExD,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtD,OAAO,EAAU,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAIlD;qCACqC;AACrC,KAAK,KAAK,GAAG,IAAI,CAAC,cAAc,CAAC,OAAO,MAAM,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC,CAAA;AACzE,KAAK,UAAU,GAAG,IAAI,CAAC,cAAc,CAAC,OAAO,QAAQ,CAAC,EAAE,OAAO,GAAG,cAAc,CAAC,CAAA;AAoCjF;;;;;;;GAOG;AACH,eAAO,MAAM,GAAG,iBAAiB,CAAA;AAEjC,MAAM,WAAW,aAAc,SAAQ,KAAK;IAC1C,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,8EAA8E;IAC9E,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB,0DAA0D;IAC1D,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,gFAAgF;IAChF,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;CACjB;AAED,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,QAAQ,EACR,MAAM,EACN,MAAM,EACN,IAAY,EACZ,QAAgB,EAChB,WAAiB,EACjB,IAAe,EACf,SAAmB,EACnB,QAAQ,EACR,IAAI,EACJ,KAAK,EACL,KAAK,EACL,IAAI,EACJ,GAAG,KAAK,EACT,EAAE,aAAa,2CA4Ef"}
@@ -23,14 +23,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
23
23
  * nothing here for it to reach. Adding either would be inventing an interaction
24
24
  * and then asking three surfaces to adopt it.
25
25
  *
26
- * Growth is `Textarea`'s and it is LINE-DRIVEN, not DOM-measured: the row count
27
- * follows the newlines in the value, which is what lets web, native and desktop
28
- * grow identically. The tradeoff is real and worth knowing before you adopt —
29
- * a draft that soft-wraps without ever containing a `\n` does not raise the row
30
- * count, so a long unbroken paragraph scrolls inside the floor instead of
31
- * growing the field. Fixing that means measuring `scrollHeight`, which is the
32
- * DOM-only path the five hand-rolled composers each took and the reason none of
33
- * them runs on native. It is a `Textarea` decision, not a chat one.
26
+ * Growth is `Textarea`'s, and since 8.1.4 it follows the CONTENT rather than the
27
+ * newline count — so a long paragraph that wraps without ever containing a `\n`
28
+ * raises the field instead of scrolling inside it. `rows` is the floor and
29
+ * `maxHeight` the ceiling; both are bounds, and neither is re-implemented here.
34
30
  */
35
31
  import { SizableText, XStack, YStack } from '@hanzo/gui';
36
32
  import { ArrowUp, Square } from '@hanzogui/lucide-icons-2';
@@ -77,7 +73,7 @@ const FLOOR = 44;
77
73
  * changes — which is what a model-named placeholder forces a surface to do.
78
74
  */
79
75
  export const ASK = 'Ask anything';
80
- export function Composer({ value, onChange, onSend, onStop, busy = false, disabled = false, placeholder = ASK, rows = MIN_ROWS, maxHeight = CEILING, children, hint, }) {
76
+ export function Composer({ value, onChange, onSend, onStop, busy = false, disabled = false, placeholder = ASK, rows = MIN_ROWS, maxHeight = CEILING, children, hint, label, field, send, ...props }) {
81
77
  const sendable = ready(value, busy, disabled);
82
78
  const keyed = (e) => {
83
79
  const key = e.key ?? e.nativeEvent?.key ?? '';
@@ -97,6 +93,16 @@ export function Composer({ value, onChange, onSend, onStop, busy = false, disabl
97
93
  if (sendable)
98
94
  onSend();
99
95
  };
100
- return (_jsxs(YStack, { ...slot('composer'), width: "100%", rounded: "$5", borderWidth: 1, borderColor: "$borderColor", bg: "$background", p: PAD, gap: PAD, children: [_jsx(Textarea, { ...slot('composer-field'), rows: rows, value: value, placeholder: placeholder, disabled: disabled, onChangeText: onChange, onKeyDown: keyed, borderWidth: 0, minH: FLOOR, maxH: maxHeight, "aria-label": placeholder }), _jsxs(XStack, { items: "center", gap: PAD, children: [children, _jsx(XStack, { flex: 1 }), hint ? (_jsx(SizableText, { size: "$1", color: "$color10", children: hint })) : null, _jsx(Button, { ...slot('composer-send'), size: "sm", disabled: busy ? !onStop : !sendable, onPress: busy ? onStop : onSend, "aria-label": busy ? 'Stop' : 'Send', children: busy ? _jsx(Square, { size: 14 }) : _jsx(ArrowUp, { size: 16 }) })] })] }));
96
+ return (_jsxs(YStack, { ...slot('composer'), width: "100%", rounded: "$5", borderWidth: 1, borderColor: "$borderColor", bg: "$background", p: PAD, gap: PAD, ...props, children: [_jsx(Textarea, { ...slot('composer-field'), rows: rows, placeholder: placeholder, disabled: disabled, borderWidth: 0, minH: FLOOR, maxH: maxHeight, "aria-label": label ?? placeholder, ...field,
97
+ // After `field`, and deliberately. These four ARE the composer; a
98
+ // caller's `onKeyDown` landing on top of `keyed` would take the Enter
99
+ // rule and the IME guard with it, type-check, and go on looking
100
+ // correct until a Japanese writer typed into it. So the caller's runs
101
+ // FIRST and this one defers to it if it claimed the key.
102
+ value: value, onChangeText: onChange, onKeyDown: (e) => {
103
+ field?.onKeyDown?.(e);
104
+ if (!e?.defaultPrevented)
105
+ keyed(e);
106
+ } }), _jsxs(XStack, { items: "center", gap: PAD, children: [children, _jsx(XStack, { flex: 1 }), hint ? (_jsx(SizableText, { size: "$1", color: "$color10", children: hint })) : null, send ?? (_jsx(Button, { ...slot('composer-send'), size: "sm", disabled: busy ? !onStop : !sendable, onPress: busy ? onStop : onSend, "aria-label": busy ? 'Stop' : 'Send', children: busy ? _jsx(Square, { size: 14 }) : _jsx(ArrowUp, { size: 16 }) }))] })] }));
101
107
  }
102
108
  //# sourceMappingURL=Composer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Composer.js","sourceRoot":"","sources":["../../src/chat/Composer.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Composer.js","sourceRoot":"","sources":["../../src/chat/Composer.tsx"],"names":[],"mappings":"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"}
@@ -33,12 +33,12 @@ const lucide_icons_2_1 = require("@hanzogui/lucide-icons-2");
33
33
  const gui_2 = require("../backends/gui/index.cjs");
34
34
  const ink_1 = require("../backends/gui/ink.cjs");
35
35
  const slot_1 = require("../backends/gui/slot.cjs");
36
- function Failure({ children, onRetry, retryLabel = 'Try again' }) {
36
+ function Failure({ children, onRetry, retryLabel = 'Try again', ...props }) {
37
37
  return ((0, jsx_runtime_1.jsxs)(gui_1.YStack, { ...(0, slot_1.slot)('failure'), role: "alert", "aria-live": "assertive", width: "100%", gap: "$2", px: "$3", py: "$2.5", rounded: "$4", borderWidth: 1,
38
38
  // The edge again — see `product/tone.ts`. Its `stopped` register is
39
39
  // `$color4` behind a `$color9` border for exactly this: with no hue to
40
40
  // spend, an outline is what separates "this one" from its neighbours.
41
- borderColor: "$color9", bg: "$color4", children: [(0, ink_1.ink)(children, undefined, { size: '$2', color: '$color12' }), onRetry ? (
41
+ borderColor: "$color9", bg: "$color4", ...props, children: [(0, ink_1.ink)(children, undefined, { size: '$2', color: '$color12' }), onRetry ? (
42
42
  // The real Button, not a `role="button"` box with an `onPress`. A View
43
43
  // wearing the role takes focus and then does nothing on Enter or Space,
44
44
  // because the activation behaviour belongs to the element and not to
@@ -1,10 +1,36 @@
1
- import type { ReactNode } from 'react';
2
- export interface FailureProps {
1
+ /**
2
+ * Failure — the turn did not complete, and what to do about it.
3
+ *
4
+ * Distinct from a `Step` that errored: a step can fail inside an answer that
5
+ * still arrives, this is the answer not arriving. hanzo/chat keeps the same
6
+ * separation — `ToolCall`'s error state, and separately `MessageContent.tsx:153`
7
+ * swapping the whole body for `ErrorMessage`.
8
+ *
9
+ * Four surfaces draw this line and no two draw it the same way: chat's
10
+ * `ErrorBox` (`components/Messages/Content/Error.tsx`), the app builder's
11
+ * `$red8` text with the prose instruction "Something went wrong — please try
12
+ * again" (`ask-ai/chat-thread.tsx:258`), the extension's `.ae-error` div
13
+ * (`AnswerEngine.tsx:336`), and hanzo.app `/chat`, which is the interesting
14
+ * one: it sets `Message.error` at `app/chat/page.tsx:292` and never reads it
15
+ * anywhere in the render, so a stream that dies reports through a detached
16
+ * banner and the turn itself says nothing.
17
+ *
18
+ * Two of the four offer a way to try again. Both put it next to the message
19
+ * rather than in the hover strip, because a hover affordance on the thing that
20
+ * just failed is not reachable on a touch device.
21
+ *
22
+ * `role="alert"` with `aria-live="assertive"` is chat's, and it is right: a
23
+ * turn failing is the one event in a conversation that has to interrupt, since
24
+ * the reader is waiting on prose that is never going to come.
25
+ */
26
+ import { YStack } from '@hanzo/gui';
27
+ import type { ComponentProps, ReactNode } from 'react';
28
+ export interface FailureProps extends Omit<ComponentProps<typeof YStack>, 'children'> {
3
29
  /** What went wrong, in the surface's own words. */
4
30
  children?: ReactNode;
5
31
  /** Offer to run the turn again. Omit and no control renders. */
6
32
  onRetry?: () => void;
7
33
  retryLabel?: string;
8
34
  }
9
- export declare function Failure({ children, onRetry, retryLabel }: FailureProps): import("react/jsx-runtime").JSX.Element;
35
+ export declare function Failure({ children, onRetry, retryLabel, ...props }: FailureProps): import("react/jsx-runtime").JSX.Element;
10
36
  //# sourceMappingURL=Failure.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Failure.d.ts","sourceRoot":"","sources":["../../src/chat/Failure.tsx"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAMtC,MAAM,WAAW,YAAY;IAC3B,mDAAmD;IACnD,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,wBAAgB,OAAO,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAwB,EAAE,EAAE,YAAY,2CAkCpF"}
1
+ {"version":3,"file":"Failure.d.ts","sourceRoot":"","sources":["../../src/chat/Failure.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAAU,MAAM,EAAE,MAAM,YAAY,CAAA;AAE3C,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAMtD,MAAM,WAAW,YAAa,SAAQ,IAAI,CAAC,cAAc,CAAC,OAAO,MAAM,CAAC,EAAE,UAAU,CAAC;IACnF,mDAAmD;IACnD,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,wBAAgB,OAAO,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAwB,EAAE,GAAG,KAAK,EAAE,EAAE,YAAY,2CAmC9F"}
@@ -30,12 +30,12 @@ import { RefreshCw } from '@hanzogui/lucide-icons-2';
30
30
  import { Button } from '../backends/gui/index.js';
31
31
  import { ink } from '../backends/gui/ink.js';
32
32
  import { slot } from '../backends/gui/slot.js';
33
- export function Failure({ children, onRetry, retryLabel = 'Try again' }) {
33
+ export function Failure({ children, onRetry, retryLabel = 'Try again', ...props }) {
34
34
  return (_jsxs(YStack, { ...slot('failure'), role: "alert", "aria-live": "assertive", width: "100%", gap: "$2", px: "$3", py: "$2.5", rounded: "$4", borderWidth: 1,
35
35
  // The edge again — see `product/tone.ts`. Its `stopped` register is
36
36
  // `$color4` behind a `$color9` border for exactly this: with no hue to
37
37
  // spend, an outline is what separates "this one" from its neighbours.
38
- borderColor: "$color9", bg: "$color4", children: [ink(children, undefined, { size: '$2', color: '$color12' }), onRetry ? (
38
+ borderColor: "$color9", bg: "$color4", ...props, children: [ink(children, undefined, { size: '$2', color: '$color12' }), onRetry ? (
39
39
  // The real Button, not a `role="button"` box with an `onPress`. A View
40
40
  // wearing the role takes focus and then does nothing on Enter or Space,
41
41
  // because the activation behaviour belongs to the element and not to
@@ -1 +1 @@
1
- {"version":3,"file":"Failure.js","sourceRoot":"","sources":["../../src/chat/Failure.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AAGpD,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AACxC,OAAO,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAA;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAU3C,MAAM,UAAU,OAAO,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,GAAG,WAAW,EAAgB;IACnF,OAAO,CACL,MAAC,MAAM,OACD,IAAI,CAAC,SAAS,CAAC,EACnB,IAAI,EAAC,OAAO,eACF,WAAW,EACrB,KAAK,EAAC,MAAM,EACZ,GAAG,EAAC,IAAI,EACR,EAAE,EAAC,IAAI,EACP,EAAE,EAAC,MAAM,EACT,OAAO,EAAC,IAAI,EACZ,WAAW,EAAE,CAAC;QACd,oEAAoE;QACpE,uEAAuE;QACvE,sEAAsE;QACtE,WAAW,EAAC,SAAS,EACrB,EAAE,EAAC,SAAS,aAEX,GAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,EAC3D,OAAO,CAAC,CAAC,CAAC;YACT,uEAAuE;YACvE,wEAAwE;YACxE,qEAAqE;YACrE,uEAAuE;YACvE,+BAA+B;YAC/B,KAAC,MAAM,IAAC,IAAI,EAAC,YAAY,YACvB,MAAC,MAAM,OAAK,IAAI,CAAC,eAAe,CAAC,EAAE,OAAO,EAAC,SAAS,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAE,OAAO,aAC7E,KAAC,SAAS,IAAC,IAAI,EAAE,EAAE,GAAI,EACtB,UAAU,IACJ,GACF,CACV,CAAC,CAAC,CAAC,IAAI,IACD,CACV,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"Failure.js","sourceRoot":"","sources":["../../src/chat/Failure.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AAGpD,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AACxC,OAAO,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAA;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAU3C,MAAM,UAAU,OAAO,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,GAAG,WAAW,EAAE,GAAG,KAAK,EAAgB;IAC7F,OAAO,CACL,MAAC,MAAM,OACD,IAAI,CAAC,SAAS,CAAC,EACnB,IAAI,EAAC,OAAO,eACF,WAAW,EACrB,KAAK,EAAC,MAAM,EACZ,GAAG,EAAC,IAAI,EACR,EAAE,EAAC,IAAI,EACP,EAAE,EAAC,MAAM,EACT,OAAO,EAAC,IAAI,EACZ,WAAW,EAAE,CAAC;QACd,oEAAoE;QACpE,uEAAuE;QACvE,sEAAsE;QACtE,WAAW,EAAC,SAAS,EACrB,EAAE,EAAC,SAAS,KACR,KAAK,aAER,GAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,EAC3D,OAAO,CAAC,CAAC,CAAC;YACT,uEAAuE;YACvE,wEAAwE;YACxE,qEAAqE;YACrE,uEAAuE;YACvE,+BAA+B;YAC/B,KAAC,MAAM,IAAC,IAAI,EAAC,YAAY,YACvB,MAAC,MAAM,OAAK,IAAI,CAAC,eAAe,CAAC,EAAE,OAAO,EAAC,SAAS,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAE,OAAO,aAC7E,KAAC,SAAS,IAAC,IAAI,EAAE,EAAE,GAAI,EACtB,UAAU,IACJ,GACF,CACV,CAAC,CAAC,CAAC,IAAI,IACD,CACV,CAAA;AACH,CAAC"}
@@ -18,8 +18,8 @@ const jsx_runtime_1 = require("react/jsx-runtime");
18
18
  const gui_1 = require("@hanzo/gui");
19
19
  const lucide_icons_2_1 = require("@hanzogui/lucide-icons-2");
20
20
  const slot_1 = require("../backends/gui/slot.cjs");
21
- function Header({ title, onOpenMenu, children }) {
22
- return ((0, jsx_runtime_1.jsxs)(gui_1.XStack, { ...(0, slot_1.slot)('chat-header'), width: "100%", height: 48, shrink: 0, items: "center", gap: "$2", px: "$3", borderBottomWidth: 1, borderColor: "$borderColor", bg: "$background", children: [(0, jsx_runtime_1.jsxs)(gui_1.XStack, { items: "center", gap: "$1", shrink: 1, children: [(0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$2", fontWeight: "500", numberOfLines: 1, children: title }), onOpenMenu ? ((0, jsx_runtime_1.jsx)(HeaderButton, { label: "Conversation options", onPress: onOpenMenu, children: (0, jsx_runtime_1.jsx)(lucide_icons_2_1.Ellipsis, { size: 16 }) })) : null] }), (0, jsx_runtime_1.jsx)(gui_1.XStack, { flex: 1 }), children] }));
21
+ function Header({ title, onOpenMenu, children, leading, ...props }) {
22
+ return ((0, jsx_runtime_1.jsxs)(gui_1.XStack, { ...(0, slot_1.slot)('chat-header'), width: "100%", height: 48, shrink: 0, items: "center", gap: "$2", px: "$3", borderBottomWidth: 1, borderColor: "$borderColor", bg: "$background", ...props, children: [leading, (0, jsx_runtime_1.jsxs)(gui_1.XStack, { items: "center", gap: "$1", shrink: 1, children: [(0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$2", fontWeight: "500", numberOfLines: 1, children: title }), onOpenMenu ? ((0, jsx_runtime_1.jsx)(HeaderButton, { label: "Conversation options", onPress: onOpenMenu, children: (0, jsx_runtime_1.jsx)(lucide_icons_2_1.Ellipsis, { size: 16 }) })) : null] }), (0, jsx_runtime_1.jsx)(gui_1.XStack, { flex: 1 }), children] }));
23
23
  }
24
24
  function HeaderButton({ label, onPress, children }) {
25
25
  return ((0, jsx_runtime_1.jsx)(gui_1.XStack, { width: 32, height: 32, items: "center", justify: "center", rounded: "$3", cursor: "pointer", opacity: 0.7, onPress: onPress, role: "button", tabIndex: 0, "aria-label": label, ...(0, slot_1.tip)(label), hoverStyle: { bg: '$color4', opacity: 1 }, pressStyle: { bg: '$color5' }, children: children }));
@@ -1,12 +1,29 @@
1
- import type { ReactNode } from 'react';
2
- export interface HeaderProps {
1
+ /**
2
+ * Header — the conversation title bar.
3
+ *
4
+ * Title on the left, `children` as the action slot on the right. The actions are
5
+ * a slot rather than a fixed set because what belongs there is per-surface:
6
+ * hanzo.chat shares and opens a details rail, the console shows the owning
7
+ * project, hanzo.app shows neither.
8
+ */
9
+ import { XStack } from '@hanzo/gui';
10
+ import type { ComponentProps, ReactNode } from 'react';
11
+ export interface HeaderProps extends Omit<ComponentProps<typeof XStack>, 'children'> {
3
12
  title: string;
4
13
  /** Overflow menu next to the title. Omit to hide it. */
5
14
  onOpenMenu?: () => void;
6
15
  /** Action slot, rendered at the end of the row. */
7
16
  children?: ReactNode;
17
+ /**
18
+ * Slot BEFORE the title — a rail toggle, a back arrow.
19
+ *
20
+ * hanzo.app `/chat` leads with a sidebar toggle and kept its own header
21
+ * rather than adopt this one, because the row began at the title and there
22
+ * was nowhere to put it.
23
+ */
24
+ leading?: ReactNode;
8
25
  }
9
- export declare function Header({ title, onOpenMenu, children }: HeaderProps): import("react/jsx-runtime").JSX.Element;
26
+ export declare function Header({ title, onOpenMenu, children, leading, ...props }: HeaderProps): import("react/jsx-runtime").JSX.Element;
10
27
  export interface HeaderButtonProps {
11
28
  /** Required — the accessible name for an icon-only control. */
12
29
  label: string;
@@ -1 +1 @@
1
- {"version":3,"file":"Header.d.ts","sourceRoot":"","sources":["../../src/chat/Header.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAItC,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,wDAAwD;IACxD,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;IACvB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,SAAS,CAAA;CACrB;AAED,wBAAgB,MAAM,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,WAAW,2CAiClE;AAED,MAAM,WAAW,iBAAiB;IAChC,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,QAAQ,CAAC,EAAE,SAAS,CAAA;CACrB;AAED,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,iBAAiB,2CAqB3E;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,EAAE,KAAe,EAAE,OAAO,EAAE,EAAE,gBAAgB,2CAuBzE;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB;AAED,wBAAgB,WAAW,CAAC,EAAE,KAA8B,EAAE,OAAO,EAAE,EAAE,gBAAgB,2CAMxF;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;;;;;GAMG;AACH,wBAAgB,KAAK,CAAC,EAAE,QAAQ,EAAE,KAAW,EAAE,EAAE,UAAU,2CAe1D"}
1
+ {"version":3,"file":"Header.d.ts","sourceRoot":"","sources":["../../src/chat/Header.tsx"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,OAAO,EAAe,MAAM,EAAU,MAAM,YAAY,CAAA;AAExD,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAItD,MAAM,WAAW,WAAY,SAAQ,IAAI,CAAC,cAAc,CAAC,OAAO,MAAM,CAAC,EAAE,UAAU,CAAC;IAClF,KAAK,EAAE,MAAM,CAAA;IACb,wDAAwD;IACxD,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;IACvB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,SAAS,CAAA;CACpB;AAED,wBAAgB,MAAM,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,KAAK,EAAE,EAAE,WAAW,2CAmCrF;AAED,MAAM,WAAW,iBAAiB;IAChC,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,QAAQ,CAAC,EAAE,SAAS,CAAA;CACrB;AAED,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,iBAAiB,2CAqB3E;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,EAAE,KAAe,EAAE,OAAO,EAAE,EAAE,gBAAgB,2CAuBzE;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB;AAED,wBAAgB,WAAW,CAAC,EAAE,KAA8B,EAAE,OAAO,EAAE,EAAE,gBAAgB,2CAMxF;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;;;;;GAMG;AACH,wBAAgB,KAAK,CAAC,EAAE,QAAQ,EAAE,KAAW,EAAE,EAAE,UAAU,2CAe1D"}