@hanzo/ui 8.1.2 → 8.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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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"}
@@ -15,6 +15,26 @@ const jsx_runtime_1 = require("react/jsx-runtime");
15
15
  * The send control is the same button in both states: it submits, and while a
16
16
  * turn is in flight it stops. Two buttons in one place was the thing every
17
17
  * surface got subtly different.
18
+ *
19
+ * Two keys are deliberately absent, because the surfaces do not agree that they
20
+ * exist. ESCAPE is bound in none of the three main composers — only in chat's
21
+ * two SUB-composers, where it dismisses the thing they float over
22
+ * (`SelectionAsk.tsx:85`, `EditMessage.tsx:124`), which is the host's concern
23
+ * and not the field's. ARROWUP recalls the last turn in exactly one composer,
24
+ * chat's (`useHandleKeyUp.ts:117`), and it does it by finding
25
+ * `document.getElementById('edit-' + parentMessageId)` and clicking it — it is
26
+ * a binding to their edit-in-place, not a general history recall, and there is
27
+ * nothing here for it to reach. Adding either would be inventing an interaction
28
+ * and then asking three surfaces to adopt it.
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.
18
38
  */
19
39
  const gui_1 = require("@hanzo/gui");
20
40
  const lucide_icons_2_1 = require("@hanzogui/lucide-icons-2");
@@ -23,6 +43,35 @@ const slot_1 = require("../backends/gui/slot.cjs");
23
43
  const send_1 = require("./send.cjs");
24
44
  const PAD = 8;
25
45
  const MIN_ROWS = 1;
46
+ /**
47
+ * How tall the field may grow before it scrolls, px.
48
+ *
49
+ * 200 is the number three independent composers reached — hanzo.app `/chat`
50
+ * (`page.tsx:184`, `Math.min(el.scrollHeight, 200)`), hanzo/chat's
51
+ * `AnswerComposer.tsx:134`, and the extension's `answer/Composer.tsx:81`
52
+ * (`maxH={200}`). The two that differ are both fitting a NARROWER frame — the
53
+ * extension's in-page overlay caps at 110 (`content-script.ts:1340`) and its
54
+ * sidebar at 120 — and the one that differs upward, chat's main composer at
55
+ * `max-h-[45vh]`, is the full-page surface. So 200 is the default and the
56
+ * ceiling is a prop.
57
+ *
58
+ * Uncapped is not an option: the field would grow with the draft until a long
59
+ * paste owned the viewport and pushed the thread it belongs to off screen.
60
+ * Nothing capped it here before.
61
+ */
62
+ const CEILING = 200;
63
+ /**
64
+ * Floor for the field, px.
65
+ *
66
+ * `Textarea` is a form control and stands 64px tall by default, which is right
67
+ * for a message body in a form and wrong for a composer: it is three lines of
68
+ * chrome before anything is typed. Every surface sets its own — 44 in chat
69
+ * (`ChatForm.tsx:334`) and in the app builder (`COMPOSER_MIN_H`), 48 in the app
70
+ * `/chat`. 44 is both the mode and this package's touch-target floor, so the
71
+ * one-line composer is exactly as tall as the smallest thing you are allowed to
72
+ * tap.
73
+ */
74
+ const FLOOR = 44;
26
75
  /**
27
76
  * The resting placeholder.
28
77
  *
@@ -32,7 +81,7 @@ const MIN_ROWS = 1;
32
81
  * changes — which is what a model-named placeholder forces a surface to do.
33
82
  */
34
83
  exports.ASK = 'Ask anything';
35
- function Composer({ value, onChange, onSend, onStop, busy = false, disabled = false, placeholder = exports.ASK, rows = MIN_ROWS, children, hint, }) {
84
+ function Composer({ value, onChange, onSend, onStop, busy = false, disabled = false, placeholder = exports.ASK, rows = MIN_ROWS, maxHeight = CEILING, children, hint, }) {
36
85
  const sendable = (0, send_1.ready)(value, busy, disabled);
37
86
  const keyed = (e) => {
38
87
  const key = e.key ?? e.nativeEvent?.key ?? '';
@@ -52,5 +101,5 @@ function Composer({ value, onChange, onSend, onStop, busy = false, disabled = fa
52
101
  if (sendable)
53
102
  onSend();
54
103
  };
55
- 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, "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 }) })] })] }));
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 }) })] })] }));
56
105
  }
@@ -21,10 +21,12 @@ export interface ComposerProps {
21
21
  placeholder?: string;
22
22
  /** Row floor for the field. It grows with the draft from here. */
23
23
  rows?: number;
24
+ /** Ceiling for that growth, px. Past it the field scrolls instead. */
25
+ maxHeight?: number;
24
26
  /** Toolbar slot, rendered at the start of the footer row. */
25
27
  children?: ReactNode;
26
28
  /** Hint shown at the end of the footer row. */
27
29
  hint?: string;
28
30
  }
29
- export declare function Composer({ value, onChange, onSend, onStop, busy, disabled, placeholder, rows, children, hint, }: ComposerProps): import("react/jsx-runtime").JSX.Element;
31
+ export declare function Composer({ value, onChange, onSend, onStop, busy, disabled, placeholder, rows, maxHeight, children, hint, }: ComposerProps): import("react/jsx-runtime").JSX.Element;
30
32
  //# sourceMappingURL=Composer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Composer.d.ts","sourceRoot":"","sources":["../../src/chat/Composer.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAStC;;;;;;;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,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,QAAQ,EACR,IAAI,GACL,EAAE,aAAa,2CA8Df"}
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"}
@@ -11,6 +11,26 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
11
  * The send control is the same button in both states: it submits, and while a
12
12
  * turn is in flight it stops. Two buttons in one place was the thing every
13
13
  * surface got subtly different.
14
+ *
15
+ * Two keys are deliberately absent, because the surfaces do not agree that they
16
+ * exist. ESCAPE is bound in none of the three main composers — only in chat's
17
+ * two SUB-composers, where it dismisses the thing they float over
18
+ * (`SelectionAsk.tsx:85`, `EditMessage.tsx:124`), which is the host's concern
19
+ * and not the field's. ARROWUP recalls the last turn in exactly one composer,
20
+ * chat's (`useHandleKeyUp.ts:117`), and it does it by finding
21
+ * `document.getElementById('edit-' + parentMessageId)` and clicking it — it is
22
+ * a binding to their edit-in-place, not a general history recall, and there is
23
+ * nothing here for it to reach. Adding either would be inventing an interaction
24
+ * and then asking three surfaces to adopt it.
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.
14
34
  */
15
35
  import { SizableText, XStack, YStack } from '@hanzo/gui';
16
36
  import { ArrowUp, Square } from '@hanzogui/lucide-icons-2';
@@ -19,6 +39,35 @@ import { slot } from '../backends/gui/slot.js';
19
39
  import { ready, sends } from './send.js';
20
40
  const PAD = 8;
21
41
  const MIN_ROWS = 1;
42
+ /**
43
+ * How tall the field may grow before it scrolls, px.
44
+ *
45
+ * 200 is the number three independent composers reached — hanzo.app `/chat`
46
+ * (`page.tsx:184`, `Math.min(el.scrollHeight, 200)`), hanzo/chat's
47
+ * `AnswerComposer.tsx:134`, and the extension's `answer/Composer.tsx:81`
48
+ * (`maxH={200}`). The two that differ are both fitting a NARROWER frame — the
49
+ * extension's in-page overlay caps at 110 (`content-script.ts:1340`) and its
50
+ * sidebar at 120 — and the one that differs upward, chat's main composer at
51
+ * `max-h-[45vh]`, is the full-page surface. So 200 is the default and the
52
+ * ceiling is a prop.
53
+ *
54
+ * Uncapped is not an option: the field would grow with the draft until a long
55
+ * paste owned the viewport and pushed the thread it belongs to off screen.
56
+ * Nothing capped it here before.
57
+ */
58
+ const CEILING = 200;
59
+ /**
60
+ * Floor for the field, px.
61
+ *
62
+ * `Textarea` is a form control and stands 64px tall by default, which is right
63
+ * for a message body in a form and wrong for a composer: it is three lines of
64
+ * chrome before anything is typed. Every surface sets its own — 44 in chat
65
+ * (`ChatForm.tsx:334`) and in the app builder (`COMPOSER_MIN_H`), 48 in the app
66
+ * `/chat`. 44 is both the mode and this package's touch-target floor, so the
67
+ * one-line composer is exactly as tall as the smallest thing you are allowed to
68
+ * tap.
69
+ */
70
+ const FLOOR = 44;
22
71
  /**
23
72
  * The resting placeholder.
24
73
  *
@@ -28,7 +77,7 @@ const MIN_ROWS = 1;
28
77
  * changes — which is what a model-named placeholder forces a surface to do.
29
78
  */
30
79
  export const ASK = 'Ask anything';
31
- export function Composer({ value, onChange, onSend, onStop, busy = false, disabled = false, placeholder = ASK, rows = MIN_ROWS, children, hint, }) {
80
+ export function Composer({ value, onChange, onSend, onStop, busy = false, disabled = false, placeholder = ASK, rows = MIN_ROWS, maxHeight = CEILING, children, hint, }) {
32
81
  const sendable = ready(value, busy, disabled);
33
82
  const keyed = (e) => {
34
83
  const key = e.key ?? e.nativeEvent?.key ?? '';
@@ -48,6 +97,6 @@ export function Composer({ value, onChange, onSend, onStop, busy = false, disabl
48
97
  if (sendable)
49
98
  onSend();
50
99
  };
51
- 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, "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 }) })] })] }));
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 }) })] })] }));
52
101
  }
53
102
  //# 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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"}
package/dist/styles.css CHANGED
@@ -2439,6 +2439,7 @@
2439
2439
  :root ._ls-f-letterSpa1360334205{letter-spacing:var(--f-letterSpacing-1);}
2440
2440
  :root ._maxH-0px{max-height:0px;}
2441
2441
  :root ._maxH-16px{max-height:16px;}
2442
+ :root ._maxH-200px{max-height:200px;}
2442
2443
  :root ._maxH-300px{max-height:300px;}
2443
2444
  :root ._maxH-32px{max-height:32px;}
2444
2445
  :root ._maxH-c-size-1{max-height:var(--c-size-1);}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/ui",
3
- "version": "8.1.2",
3
+ "version": "8.1.4",
4
4
  "type": "module",
5
5
  "description": "Hanzo UI — the one canonical Hanzo component library, on @hanzo/gui + @hanzo/design. ONE substrate: every component renders through @hanzo/gui primitives, so the same import works on web, native (expo) and desktop (Tauri). Self-contained (theme.css), presentational, host-agnostic, clean-room.",
6
6
  "exports": {