@xenosystem/components 0.9.0 → 0.9.2

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.
package/dist/catalog.json CHANGED
@@ -665,10 +665,72 @@
665
665
  }
666
666
  ]
667
667
  },
668
+ {
669
+ "name": "Composer",
670
+ "file": "controls.tsx",
671
+ "doc": "",
672
+ "props": "ComposerProps",
673
+ "propList": [
674
+ {
675
+ "name": "value",
676
+ "type": "string",
677
+ "optional": false,
678
+ "doc": "Controlled draft."
679
+ },
680
+ {
681
+ "name": "onChange",
682
+ "type": "(value: string) => void",
683
+ "optional": false,
684
+ "doc": "Every keystroke."
685
+ },
686
+ {
687
+ "name": "onSubmit",
688
+ "type": "() => void",
689
+ "optional": true,
690
+ "doc": "Enter (or Shift+Enter when `submitOnEnter` is false) — the caller decides what \"submit\" means."
691
+ },
692
+ {
693
+ "name": "placeholder",
694
+ "type": "string",
695
+ "optional": true,
696
+ "doc": ""
697
+ },
698
+ {
699
+ "name": "label",
700
+ "type": "string",
701
+ "optional": true,
702
+ "doc": "Accessible name; defaults to the placeholder."
703
+ },
704
+ {
705
+ "name": "submitOnEnter",
706
+ "type": "boolean",
707
+ "optional": true,
708
+ "doc": "Enter submits and Shift+Enter breaks the line (the chat convention). `false` swaps them."
709
+ },
710
+ {
711
+ "name": "disabled",
712
+ "type": "boolean",
713
+ "optional": true,
714
+ "doc": ""
715
+ },
716
+ {
717
+ "name": "autoFocus",
718
+ "type": "boolean",
719
+ "optional": true,
720
+ "doc": ""
721
+ },
722
+ {
723
+ "name": "className",
724
+ "type": "string",
725
+ "optional": true,
726
+ "doc": ""
727
+ }
728
+ ]
729
+ },
668
730
  {
669
731
  "name": "PanelFrame",
670
732
  "file": "frame.tsx",
671
- "doc": "The canonical standalone panel frame — header bar + content well, separated by the panel gap.\r\n\n@example\r\n```tsx\r\n<PanelFrame title=\"Layers\" actions={<IconButton icon={<PlusIcon />} label=\"Add layer\" />}>\r\n <RowList>…</RowList>\r\n</PanelFrame>\r\n```",
733
+ "doc": "The canonical standalone panel frame — header bar + content well, separated by the panel gap.\n\n@example\n```tsx\n<PanelFrame title=\"Layers\" actions={<IconButton icon={<PlusIcon />} label=\"Add layer\" />}>\n <RowList>…</RowList>\n</PanelFrame>\n```",
672
734
  "props": "PanelFrameProps",
673
735
  "propList": [
674
736
  {
@@ -712,7 +774,7 @@
712
774
  {
713
775
  "name": "Toolbar",
714
776
  "file": "frame.tsx",
715
- "doc": "A dense horizontal control strip. Height matches the panel header so a toolbar stacked under a\r\nframe header reads as one continuous chrome band.\r\n\n@example\r\n```tsx\r\n<Toolbar left={<SearchField value={q} onChange={setQ} />} right={<IconButton … />} />\r\n```",
777
+ "doc": "A dense horizontal control strip. Height matches the panel header so a toolbar stacked under a\nframe header reads as one continuous chrome band.\n\n@example\n```tsx\n<Toolbar left={<SearchField value={q} onChange={setQ} />} right={<IconButton … />} />\n```",
716
778
  "props": "ToolbarProps",
717
779
  "propList": [
718
780
  {
@@ -796,7 +858,7 @@
796
858
  {
797
859
  "name": "ScrollArea",
798
860
  "file": "frame.tsx",
799
- "doc": "A flex-filling scroll container with the canonical thin rectangular thumb and an unbroken\r\nheight chain (`flex: 1 1 0; min-height: 0`) — the shape DESIGN_SYSTEM §3.1 requires so panels\r\nnever reach for `max-height: calc(100vh - N)`.",
861
+ "doc": "A flex-filling scroll container with the canonical thin rectangular thumb and an unbroken\nheight chain (`flex: 1 1 0; min-height: 0`) — the shape DESIGN_SYSTEM §3.1 requires so panels\nnever reach for `max-height: calc(100vh - N)`.",
800
862
  "props": "ScrollAreaProps",
801
863
  "propList": [
802
864
  {
@@ -813,10 +875,30 @@
813
875
  }
814
876
  ]
815
877
  },
878
+ {
879
+ "name": "Column",
880
+ "file": "frame.tsx",
881
+ "doc": "The panel's vertical stack — a toolbar, a flexing body, a status bar — filling the panel with an\nunbroken height chain (`height: 100%; min-height: 0`). Every block wrote `<div style={{ display:\n'flex', flexDirection: 'column', height: '100%', minHeight: 0 }}>` by hand; as a unit it is the root\na declaration can name.",
882
+ "props": "ColumnProps",
883
+ "propList": [
884
+ {
885
+ "name": "children",
886
+ "type": "ReactNode",
887
+ "optional": true,
888
+ "doc": ""
889
+ },
890
+ {
891
+ "name": "className",
892
+ "type": "string",
893
+ "optional": true,
894
+ "doc": ""
895
+ }
896
+ ]
897
+ },
816
898
  {
817
899
  "name": "Badge",
818
900
  "file": "indicators.tsx",
819
- "doc": "A small rectangular chip. Never interactive — `DESIGN_SYSTEM.md` §7: \"If a badge needs to be\nclickable, it's a ghost button, not a badge.\"",
901
+ "doc": "A small rectangular chip. Never interactive — `DESIGN_SYSTEM.md` §7: \"If a badge needs to be\r\nclickable, it's a ghost button, not a badge.\"",
820
902
  "props": "BadgeProps",
821
903
  "propList": [
822
904
  {
@@ -880,7 +962,7 @@
880
962
  {
881
963
  "name": "StatusBadge",
882
964
  "file": "indicators.tsx",
883
- "doc": "Dot + label. Color is never the sole indicator (`DESIGN_SYSTEM.md` §11) — the word carries the\nstate and the dot reinforces it.",
965
+ "doc": "Dot + label. Color is never the sole indicator (`DESIGN_SYSTEM.md` §11) — the word carries the\r\nstate and the dot reinforces it.",
884
966
  "props": "StatusBadgeProps",
885
967
  "propList": [
886
968
  {
@@ -932,7 +1014,7 @@
932
1014
  {
933
1015
  "name": "StatTile",
934
1016
  "file": "indicators.tsx",
935
- "doc": "Label + big value tile — the KPI unit of the future `metrics` panel.\n\n@example\n```tsx\n<StatTile label=\"p95 frame\" value=\"14.2 ms\" sub=\"last 60 s\" />\n```",
1017
+ "doc": "Label + big value tile — the KPI unit of the future `metrics` panel.\r\n\n@example\r\n```tsx\r\n<StatTile label=\"p95 frame\" value=\"14.2 ms\" sub=\"last 60 s\" />\r\n```",
936
1018
  "props": "StatTileProps",
937
1019
  "propList": [
938
1020
  {
@@ -976,7 +1058,7 @@
976
1058
  {
977
1059
  "name": "ProportionBar",
978
1060
  "file": "indicators.tsx",
979
- "doc": "A horizontal stacked bar — the monochromatic replacement for a pie/donut chart.\n\nSlices are separated by white ALPHA, not hue: `DESIGN_SYSTEM.md` §13 forbids chromatic color\noutside status contexts, and a categorical palette is exactly that. When no `opacity` is supplied\nthe bar ramps evenly from bright to dim across the slices.",
1061
+ "doc": "A horizontal stacked bar — the monochromatic replacement for a pie/donut chart.\r\n\nSlices are separated by white ALPHA, not hue: `DESIGN_SYSTEM.md` §13 forbids chromatic color\r\noutside status contexts, and a categorical palette is exactly that. When no `opacity` is supplied\r\nthe bar ramps evenly from bright to dim across the slices.",
980
1062
  "props": "ProportionBarProps",
981
1063
  "propList": [
982
1064
  {
@@ -1002,7 +1084,7 @@
1002
1084
  {
1003
1085
  "name": "StatusBar",
1004
1086
  "file": "indicators.tsx",
1005
- "doc": "The panel footer strip. Census: layers and history already ship a byte-identical one\n(`2px 6px`, 9px, muted, header surface, space-between) — this is that, once.",
1087
+ "doc": "The panel footer strip. Census: layers and history already ship a byte-identical one\r\n(`2px 6px`, 9px, muted, header surface, space-between) — this is that, once.",
1006
1088
  "props": "StatusBarProps",
1007
1089
  "propList": [
1008
1090
  {
@@ -1034,7 +1116,7 @@
1034
1116
  {
1035
1117
  "name": "Sparkline",
1036
1118
  "file": "indicators.tsx",
1037
- "doc": "A monochrome trend line.\n\nLifted from `xeno-post`'s `ui/kit.tsx`, which is the only sparkline implementation in the\necosystem. It lives here rather than in a panel because three consumers want it — `metrics` (which\nshipped a local copy first), `chart`, and the future `runs` panel — and three copies of a mark is\nhow a design system stops being one.\n\nDistinct from {@link ProportionBar}: a line is a different MARK, not a variant. The two are not\ninterchangeable and neither is a special case of the other.\n\nDegenerate inputs render an empty well rather than throwing: a series of one point has no slope,\nand a flat series has no range to normalize against (it draws a centred straight line).",
1119
+ "doc": "A monochrome trend line.\r\n\nLifted from `xeno-post`'s `ui/kit.tsx`, which is the only sparkline implementation in the\r\necosystem. It lives here rather than in a panel because three consumers want it — `metrics` (which\r\nshipped a local copy first), `chart`, and the future `runs` panel — and three copies of a mark is\r\nhow a design system stops being one.\r\n\nDistinct from {@link ProportionBar}: a line is a different MARK, not a variant. The two are not\r\ninterchangeable and neither is a special case of the other.\r\n\nDegenerate inputs render an empty well rather than throwing: a series of one point has no slope,\r\nand a flat series has no range to normalize against (it draws a centred straight line).",
1038
1120
  "props": "SparklineProps",
1039
1121
  "propList": [
1040
1122
  {
@@ -93,6 +93,18 @@ interface ScrollAreaProps {
93
93
  * never reach for `max-height: calc(100vh - N)`.
94
94
  */
95
95
  declare function ScrollArea({ children, className }: ScrollAreaProps): ReactNode;
96
+ /** Props for {@link Column}. */
97
+ interface ColumnProps {
98
+ children?: ReactNode;
99
+ className?: string;
100
+ }
101
+ /**
102
+ * The panel's vertical stack — a toolbar, a flexing body, a status bar — filling the panel with an
103
+ * unbroken height chain (`height: 100%; min-height: 0`). Every block wrote `<div style={{ display:
104
+ * 'flex', flexDirection: 'column', height: '100%', minHeight: 0 }}>` by hand; as a unit it is the root
105
+ * a declaration can name.
106
+ */
107
+ declare function Column({ children, className }: ColumnProps): ReactNode;
96
108
 
97
109
  /**
98
110
  * `Section` — the collapsible titled group.
@@ -652,6 +664,32 @@ interface FieldProps {
652
664
  }
653
665
  /** One label + control row inside a {@link FieldGroup}. */
654
666
  declare function Field({ label, children, hint, htmlFor, stacked, className, }: FieldProps): ReactNode;
667
+ /** Props for {@link Composer}. */
668
+ interface ComposerProps {
669
+ /** Controlled draft. */
670
+ value: string;
671
+ /** Every keystroke. */
672
+ onChange: (value: string) => void;
673
+ /** Enter (or Shift+Enter when `submitOnEnter` is false) — the caller decides what "submit" means. */
674
+ onSubmit?: () => void;
675
+ placeholder?: string;
676
+ /** Accessible name; defaults to the placeholder. */
677
+ label?: string;
678
+ /** Enter submits and Shift+Enter breaks the line (the chat convention). `false` swaps them. */
679
+ submitOnEnter?: boolean;
680
+ disabled?: boolean;
681
+ autoFocus?: boolean;
682
+ className?: string;
683
+ }
684
+ /**
685
+ * The message box a person types a turn into — the agent panel's composer, a comment box. One unit so
686
+ * the submit convention is decided ONCE (Enter sends, Shift+Enter breaks) and every block agrees.
687
+ * Extracted 2026-09-18 from the agent block's raw `<textarea>` — the one part of that view that was
688
+ * not a chrome unit, which is what stood between it and being a declaration (xeno-apps SPEC §8d).
689
+ */
690
+ /** The submit convention, as a pure decision so it is testable and shared: Enter sends, Shift+Enter breaks. */
691
+ declare function composerWantsSubmit(key: string, shiftKey: boolean, submitOnEnter?: boolean): boolean;
692
+ declare function Composer({ value, onChange, onSubmit, placeholder, label, submitOnEnter, disabled, autoFocus, className, }: ComposerProps): ReactNode;
655
693
 
656
694
  /**
657
695
  * The three terminal states: `EmptyState`, `LoadingState`, `ErrorState`.
@@ -1131,6 +1169,7 @@ declare const COMPONENTS: {
1131
1169
  readonly 'chrome/IconButton': typeof IconButton;
1132
1170
  readonly 'chrome/TextButton': typeof TextButton;
1133
1171
  readonly 'chrome/SearchField': typeof SearchField;
1172
+ readonly 'chrome/Composer': typeof Composer;
1134
1173
  readonly 'chrome/SegmentedControl': typeof SegmentedControl;
1135
1174
  readonly 'chrome/Toggle': typeof Toggle;
1136
1175
  readonly 'chrome/FieldGroup': typeof FieldGroup;
@@ -1143,6 +1182,7 @@ declare const COMPONENTS: {
1143
1182
  readonly 'chrome/ToolbarGroup': typeof ToolbarGroup;
1144
1183
  readonly 'chrome/Divider': typeof Divider;
1145
1184
  readonly 'chrome/ScrollArea': typeof ScrollArea;
1185
+ readonly 'chrome/Column': typeof Column;
1146
1186
  readonly 'chrome/Badge': typeof Badge;
1147
1187
  readonly 'chrome/StatusDot': typeof StatusDot;
1148
1188
  readonly 'chrome/StatusBadge': typeof StatusBadge;
@@ -1178,4 +1218,4 @@ interface GalleryEntry {
1178
1218
  /** The entries, one per unit × state. Adding a unit to {@link COMPONENTS} without an entry here fails the family's test. */
1179
1219
  declare function gallery(): GalleryEntry[];
1180
1220
 
1181
- export { Badge, type BadgeProps, CLS_BADGE, CLS_BAR, CLS_BAR_SEGMENT, CLS_DIVIDER, CLS_DOT, CLS_EMPTY, CLS_ERROR, CLS_FIELD, CLS_FIELD_CONTROL, CLS_FIELD_GROUP, CLS_FIELD_HINT, CLS_FIELD_LABEL, CLS_ICON_BUTTON, CLS_ICON_FRAME, CLS_LOADING, CLS_PANEL, CLS_PANEL_CONTENT, CLS_PANEL_CONTENT_NO_HEADER, CLS_PANEL_HEADER, CLS_PANEL_TITLE, CLS_ROW, CLS_ROWLIST, CLS_ROW_ICON, CLS_ROW_LABEL, CLS_ROW_META, CLS_ROW_TRAILING, CLS_SCROLL, CLS_SEARCH, CLS_SEARCH_INPUT, CLS_SECTION, CLS_SECTION_ACTIONS, CLS_SECTION_BODY, CLS_SECTION_CHEVRON, CLS_SECTION_HEADER, CLS_SECTION_TITLE, CLS_SECTION_TRIGGER, CLS_SEGMENT, CLS_SEGMENTED, CLS_STAT, CLS_STATE, CLS_STATE_HINT, CLS_STATE_ICON, CLS_STATE_PROGRESS, CLS_STATE_TITLE, CLS_STATUSBAR, CLS_STAT_LABEL, CLS_STAT_SUB, CLS_STAT_VALUE, CLS_TEXT_BUTTON, CLS_TOGGLE, CLS_TOGGLE_KNOB, CLS_TOOLBAR, CLS_TOOLBAR_GROUP, COMPONENTS, ChevronGlyph, ClearGlyph, Divider, type DividerProps, EmptyState, type EmptyStateProps, ErrorState, type ErrorStateProps, Field, FieldGroup, type FieldGroupProps, type FieldProps, type GalleryEntry, IconButton, type IconButtonProps, IconFrame, type IconFrameProps, LoadingState, type LoadingStateProps, PANEL_COLORS, PANEL_DISABLED_OPACITY, PANEL_METRICS, PANEL_PRIMITIVES_CSS, PANEL_PRIMITIVES_STYLE_ID, PANEL_TONES, type PanelColors, PanelFrame, type PanelFrameProps, type PanelMetrics, type PanelTone, type PrimitivesInjectTarget, ProportionBar, type ProportionBarProps, type ProportionSegment, Row, RowList, type RowListProps, type RowProps, type RowState, ScrollArea, type ScrollAreaProps, SearchField, type SearchFieldProps, SearchGlyph, Section, type SectionProps, type SegmentOption, SegmentedControl, type SegmentedControlProps, Sparkline, type SparklineProps, StatTile, type StatTileProps, StatusBadge, type StatusBadgeProps, StatusBar, type StatusBarProps, StatusDot, type StatusDotProps, TextButton, type TextButtonProps, Toggle, type ToggleProps, Toolbar, ToolbarGroup, type ToolbarGroupProps, type ToolbarProps, badgeClass, cx, dotClass, ensurePanelPrimitivesInjected, gallery, iconButtonClass, nextSegmentValue, rowClass, sectionClass, usePanelPrimitives };
1221
+ export { Badge, type BadgeProps, CLS_BADGE, CLS_BAR, CLS_BAR_SEGMENT, CLS_DIVIDER, CLS_DOT, CLS_EMPTY, CLS_ERROR, CLS_FIELD, CLS_FIELD_CONTROL, CLS_FIELD_GROUP, CLS_FIELD_HINT, CLS_FIELD_LABEL, CLS_ICON_BUTTON, CLS_ICON_FRAME, CLS_LOADING, CLS_PANEL, CLS_PANEL_CONTENT, CLS_PANEL_CONTENT_NO_HEADER, CLS_PANEL_HEADER, CLS_PANEL_TITLE, CLS_ROW, CLS_ROWLIST, CLS_ROW_ICON, CLS_ROW_LABEL, CLS_ROW_META, CLS_ROW_TRAILING, CLS_SCROLL, CLS_SEARCH, CLS_SEARCH_INPUT, CLS_SECTION, CLS_SECTION_ACTIONS, CLS_SECTION_BODY, CLS_SECTION_CHEVRON, CLS_SECTION_HEADER, CLS_SECTION_TITLE, CLS_SECTION_TRIGGER, CLS_SEGMENT, CLS_SEGMENTED, CLS_STAT, CLS_STATE, CLS_STATE_HINT, CLS_STATE_ICON, CLS_STATE_PROGRESS, CLS_STATE_TITLE, CLS_STATUSBAR, CLS_STAT_LABEL, CLS_STAT_SUB, CLS_STAT_VALUE, CLS_TEXT_BUTTON, CLS_TOGGLE, CLS_TOGGLE_KNOB, CLS_TOOLBAR, CLS_TOOLBAR_GROUP, COMPONENTS, ChevronGlyph, ClearGlyph, Column, type ColumnProps, Composer, type ComposerProps, Divider, type DividerProps, EmptyState, type EmptyStateProps, ErrorState, type ErrorStateProps, Field, FieldGroup, type FieldGroupProps, type FieldProps, type GalleryEntry, IconButton, type IconButtonProps, IconFrame, type IconFrameProps, LoadingState, type LoadingStateProps, PANEL_COLORS, PANEL_DISABLED_OPACITY, PANEL_METRICS, PANEL_PRIMITIVES_CSS, PANEL_PRIMITIVES_STYLE_ID, PANEL_TONES, type PanelColors, PanelFrame, type PanelFrameProps, type PanelMetrics, type PanelTone, type PrimitivesInjectTarget, ProportionBar, type ProportionBarProps, type ProportionSegment, Row, RowList, type RowListProps, type RowProps, type RowState, ScrollArea, type ScrollAreaProps, SearchField, type SearchFieldProps, SearchGlyph, Section, type SectionProps, type SegmentOption, SegmentedControl, type SegmentedControlProps, Sparkline, type SparklineProps, StatTile, type StatTileProps, StatusBadge, type StatusBadgeProps, StatusBar, type StatusBarProps, StatusDot, type StatusDotProps, TextButton, type TextButtonProps, Toggle, type ToggleProps, Toolbar, ToolbarGroup, type ToolbarGroupProps, type ToolbarProps, badgeClass, composerWantsSubmit, cx, dotClass, ensurePanelPrimitivesInjected, gallery, iconButtonClass, nextSegmentValue, rowClass, sectionClass, usePanelPrimitives };
@@ -25,6 +25,7 @@ var CLS_ICON_BUTTON = "xc-iconbtn";
25
25
  var CLS_TEXT_BUTTON = "xc-textbtn";
26
26
  var CLS_SEARCH = "xc-search";
27
27
  var CLS_SEARCH_INPUT = "xc-search-input";
28
+ var CLS_COMPOSER = "xc-composer";
28
29
  var CLS_SEGMENTED = "xc-segmented";
29
30
  var CLS_SEGMENT = "xc-segment";
30
31
  var CLS_TOGGLE = "xc-toggle";
@@ -52,6 +53,7 @@ var CLS_STAT_SUB = "xc-stat-sub";
52
53
  var CLS_BAR = "xc-bar";
53
54
  var CLS_BAR_SEGMENT = "xc-bar-segment";
54
55
  var CLS_SCROLL = "xc-scroll";
56
+ var CLS_COLUMN = "xc-column";
55
57
  var PANEL_TONES = Object.freeze([
56
58
  "neutral",
57
59
  "info",
@@ -653,6 +655,42 @@ div.xc-section-trigger {
653
655
  .xc-search-input::placeholder {
654
656
  color: ${DIM};
655
657
  }
658
+
659
+ /* \u2500\u2500 Column \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
660
+ .xc-column {
661
+ display: flex;
662
+ flex-direction: column;
663
+ height: 100%;
664
+ min-height: 0;
665
+ }
666
+
667
+ /* \u2500\u2500 Composer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
668
+ .xc-composer {
669
+ display: block;
670
+ width: 100%;
671
+ min-height: 64px;
672
+ box-sizing: border-box;
673
+ padding: 8px 10px;
674
+ resize: vertical;
675
+ border: none;
676
+ border-top: 1px solid ${BORDER};
677
+ background: transparent;
678
+ color: ${FG};
679
+ font: inherit;
680
+ font-size: ${PANEL_METRICS.fontBody}px;
681
+ line-height: 1.45;
682
+ outline: none;
683
+ }
684
+ .xc-composer::placeholder {
685
+ color: ${DIM};
686
+ }
687
+ .xc-composer:focus-visible {
688
+ box-shadow: inset 0 0 0 1px ${PANEL_COLORS.focus};
689
+ }
690
+ .xc-composer:disabled {
691
+ opacity: ${PANEL_DISABLED_OPACITY};
692
+ cursor: not-allowed;
693
+ }
656
694
  .xc-search-clear {
657
695
  display: flex;
658
696
  align-items: center;
@@ -1121,6 +1159,10 @@ function ScrollArea({ children, className }) {
1121
1159
  usePanelPrimitives();
1122
1160
  return /* @__PURE__ */ jsx("div", { className: cx(CLS_SCROLL, className), children });
1123
1161
  }
1162
+ function Column({ children, className }) {
1163
+ usePanelPrimitives();
1164
+ return /* @__PURE__ */ jsx("div", { className: cx(CLS_COLUMN, className), children });
1165
+ }
1124
1166
 
1125
1167
  // src/chrome/section.tsx
1126
1168
  import { useState as useState3 } from "react";
@@ -1392,6 +1434,42 @@ function Field({
1392
1434
  hint != null ? /* @__PURE__ */ jsx2("div", { className: CLS_FIELD_HINT, id: hintId, children: hint }) : null
1393
1435
  ] });
1394
1436
  }
1437
+ function composerWantsSubmit(key, shiftKey, submitOnEnter = true) {
1438
+ if (key !== "Enter") return false;
1439
+ return submitOnEnter ? !shiftKey : shiftKey;
1440
+ }
1441
+ function Composer({
1442
+ value,
1443
+ onChange,
1444
+ onSubmit,
1445
+ placeholder = "Type a message\u2026",
1446
+ label,
1447
+ submitOnEnter = true,
1448
+ disabled,
1449
+ autoFocus,
1450
+ className
1451
+ }) {
1452
+ usePanelPrimitives();
1453
+ const handleKeyDown = (e) => {
1454
+ if (!onSubmit || !composerWantsSubmit(e.key, e.shiftKey, submitOnEnter)) return;
1455
+ e.preventDefault();
1456
+ onSubmit();
1457
+ };
1458
+ return /* @__PURE__ */ jsx2(
1459
+ "textarea",
1460
+ {
1461
+ className: cx(CLS_COMPOSER, className),
1462
+ value,
1463
+ placeholder,
1464
+ "aria-label": label ?? placeholder,
1465
+ disabled,
1466
+ autoFocus,
1467
+ spellCheck: true,
1468
+ onChange: (e) => onChange(e.target.value),
1469
+ onKeyDown: handleKeyDown
1470
+ }
1471
+ );
1472
+ }
1395
1473
 
1396
1474
  // src/chrome/section.tsx
1397
1475
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -1714,6 +1792,7 @@ var COMPONENTS = {
1714
1792
  "chrome/IconButton": IconButton,
1715
1793
  "chrome/TextButton": TextButton,
1716
1794
  "chrome/SearchField": SearchField,
1795
+ "chrome/Composer": Composer,
1717
1796
  "chrome/SegmentedControl": SegmentedControl,
1718
1797
  "chrome/Toggle": Toggle,
1719
1798
  "chrome/FieldGroup": FieldGroup,
@@ -1726,6 +1805,7 @@ var COMPONENTS = {
1726
1805
  "chrome/ToolbarGroup": ToolbarGroup,
1727
1806
  "chrome/Divider": Divider,
1728
1807
  "chrome/ScrollArea": ScrollArea,
1808
+ "chrome/Column": Column,
1729
1809
  "chrome/Badge": Badge,
1730
1810
  "chrome/StatusDot": StatusDot,
1731
1811
  "chrome/StatusBadge": StatusBadge,
@@ -1761,6 +1841,9 @@ function gallery() {
1761
1841
  form("TextButton", "disabled", () => /* @__PURE__ */ jsx7(TextButton, { onClick: noop, disabled: true, children: "Clear all" }));
1762
1842
  form("SearchField", "empty", () => /* @__PURE__ */ jsx7(SearchField, { value: "", onChange: noop, placeholder: "Filter rows\u2026", label: "Filter" }));
1763
1843
  form("SearchField", "filled", () => /* @__PURE__ */ jsx7(SearchField, { value: "render", onChange: noop, placeholder: "Filter rows\u2026", label: "Filter" }));
1844
+ form("Composer", "empty", () => /* @__PURE__ */ jsx7(Composer, { value: "", onChange: noop, onSubmit: noop, placeholder: "Ask the agent\u2026" }));
1845
+ form("Composer", "draft", () => /* @__PURE__ */ jsx7(Composer, { value: "Summarise the last run and list the failing steps.", onChange: noop, onSubmit: noop }));
1846
+ form("Composer", "disabled", () => /* @__PURE__ */ jsx7(Composer, { value: "", onChange: noop, placeholder: "Not connected", disabled: true }));
1764
1847
  form("SegmentedControl", "first", () => /* @__PURE__ */ jsx7(SegmentedControl, { options: SEGMENTS, value: "all", onChange: noop, label: "Level" }));
1765
1848
  form("SegmentedControl", "last", () => /* @__PURE__ */ jsx7(SegmentedControl, { options: SEGMENTS, value: "warnings", onChange: noop, label: "Level" }));
1766
1849
  form("Toggle", "off", () => /* @__PURE__ */ jsx7(Toggle, { checked: false, onChange: noop, label: "Follow output" }));
@@ -1788,6 +1871,11 @@ function gallery() {
1788
1871
  /* @__PURE__ */ jsx7(Divider, { vertical: true }),
1789
1872
  /* @__PURE__ */ jsx7("span", { children: "b" })
1790
1873
  ] }));
1874
+ door("Column", "default", () => /* @__PURE__ */ jsxs7(Column, { children: [
1875
+ /* @__PURE__ */ jsx7(Toolbar, { left: /* @__PURE__ */ jsx7(TextButton, { onClick: noop, children: "Action" }) }),
1876
+ /* @__PURE__ */ jsx7(ScrollArea, { children: /* @__PURE__ */ jsx7("p", { style: { margin: 8 }, children: "body" }) }),
1877
+ /* @__PURE__ */ jsx7(StatusBar, { left: "status" })
1878
+ ] }));
1791
1879
  door("ScrollArea", "default", () => /* @__PURE__ */ jsx7(ScrollArea, { children: Array.from({ length: 30 }, (_, i) => /* @__PURE__ */ jsxs7("p", { style: { margin: 4 }, children: [
1792
1880
  "line ",
1793
1881
  i + 1
@@ -1878,6 +1966,8 @@ export {
1878
1966
  COMPONENTS,
1879
1967
  ChevronGlyph,
1880
1968
  ClearGlyph,
1969
+ Column,
1970
+ Composer,
1881
1971
  Divider,
1882
1972
  EmptyState,
1883
1973
  ErrorState,
@@ -1911,6 +2001,7 @@ export {
1911
2001
  Toolbar,
1912
2002
  ToolbarGroup,
1913
2003
  badgeClass,
2004
+ composerWantsSubmit,
1914
2005
  cx,
1915
2006
  dotClass,
1916
2007
  ensurePanelPrimitivesInjected,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xenosystem/components",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "description": "XENO Components \u2014 rung 2 of the front ladder: molecules composed from @xenosystem/elements, with no host contract. Families as subpaths: /auth, /navigation, /feedback, /data.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",