@xenosystem/components 0.9.0 → 0.9.1

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,6 +665,68 @@
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",
@@ -816,7 +878,7 @@
816
878
  {
817
879
  "name": "Badge",
818
880
  "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.\"",
881
+ "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
882
  "props": "BadgeProps",
821
883
  "propList": [
822
884
  {
@@ -880,7 +942,7 @@
880
942
  {
881
943
  "name": "StatusBadge",
882
944
  "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.",
945
+ "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
946
  "props": "StatusBadgeProps",
885
947
  "propList": [
886
948
  {
@@ -932,7 +994,7 @@
932
994
  {
933
995
  "name": "StatTile",
934
996
  "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```",
997
+ "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
998
  "props": "StatTileProps",
937
999
  "propList": [
938
1000
  {
@@ -976,7 +1038,7 @@
976
1038
  {
977
1039
  "name": "ProportionBar",
978
1040
  "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.",
1041
+ "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
1042
  "props": "ProportionBarProps",
981
1043
  "propList": [
982
1044
  {
@@ -1002,7 +1064,7 @@
1002
1064
  {
1003
1065
  "name": "StatusBar",
1004
1066
  "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.",
1067
+ "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
1068
  "props": "StatusBarProps",
1007
1069
  "propList": [
1008
1070
  {
@@ -1034,7 +1096,7 @@
1034
1096
  {
1035
1097
  "name": "Sparkline",
1036
1098
  "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).",
1099
+ "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
1100
  "props": "SparklineProps",
1039
1101
  "propList": [
1040
1102
  {
@@ -652,6 +652,32 @@ interface FieldProps {
652
652
  }
653
653
  /** One label + control row inside a {@link FieldGroup}. */
654
654
  declare function Field({ label, children, hint, htmlFor, stacked, className, }: FieldProps): ReactNode;
655
+ /** Props for {@link Composer}. */
656
+ interface ComposerProps {
657
+ /** Controlled draft. */
658
+ value: string;
659
+ /** Every keystroke. */
660
+ onChange: (value: string) => void;
661
+ /** Enter (or Shift+Enter when `submitOnEnter` is false) — the caller decides what "submit" means. */
662
+ onSubmit?: () => void;
663
+ placeholder?: string;
664
+ /** Accessible name; defaults to the placeholder. */
665
+ label?: string;
666
+ /** Enter submits and Shift+Enter breaks the line (the chat convention). `false` swaps them. */
667
+ submitOnEnter?: boolean;
668
+ disabled?: boolean;
669
+ autoFocus?: boolean;
670
+ className?: string;
671
+ }
672
+ /**
673
+ * The message box a person types a turn into — the agent panel's composer, a comment box. One unit so
674
+ * the submit convention is decided ONCE (Enter sends, Shift+Enter breaks) and every block agrees.
675
+ * Extracted 2026-09-18 from the agent block's raw `<textarea>` — the one part of that view that was
676
+ * not a chrome unit, which is what stood between it and being a declaration (xeno-apps SPEC §8d).
677
+ */
678
+ /** The submit convention, as a pure decision so it is testable and shared: Enter sends, Shift+Enter breaks. */
679
+ declare function composerWantsSubmit(key: string, shiftKey: boolean, submitOnEnter?: boolean): boolean;
680
+ declare function Composer({ value, onChange, onSubmit, placeholder, label, submitOnEnter, disabled, autoFocus, className, }: ComposerProps): ReactNode;
655
681
 
656
682
  /**
657
683
  * The three terminal states: `EmptyState`, `LoadingState`, `ErrorState`.
@@ -1131,6 +1157,7 @@ declare const COMPONENTS: {
1131
1157
  readonly 'chrome/IconButton': typeof IconButton;
1132
1158
  readonly 'chrome/TextButton': typeof TextButton;
1133
1159
  readonly 'chrome/SearchField': typeof SearchField;
1160
+ readonly 'chrome/Composer': typeof Composer;
1134
1161
  readonly 'chrome/SegmentedControl': typeof SegmentedControl;
1135
1162
  readonly 'chrome/Toggle': typeof Toggle;
1136
1163
  readonly 'chrome/FieldGroup': typeof FieldGroup;
@@ -1178,4 +1205,4 @@ interface GalleryEntry {
1178
1205
  /** The entries, one per unit × state. Adding a unit to {@link COMPONENTS} without an entry here fails the family's test. */
1179
1206
  declare function gallery(): GalleryEntry[];
1180
1207
 
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 };
1208
+ 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, 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";
@@ -653,6 +654,34 @@ div.xc-section-trigger {
653
654
  .xc-search-input::placeholder {
654
655
  color: ${DIM};
655
656
  }
657
+
658
+ /* \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 */
659
+ .xc-composer {
660
+ display: block;
661
+ width: 100%;
662
+ min-height: 64px;
663
+ box-sizing: border-box;
664
+ padding: 8px 10px;
665
+ resize: vertical;
666
+ border: none;
667
+ border-top: 1px solid ${BORDER};
668
+ background: transparent;
669
+ color: ${FG};
670
+ font: inherit;
671
+ font-size: ${PANEL_METRICS.fontBody}px;
672
+ line-height: 1.45;
673
+ outline: none;
674
+ }
675
+ .xc-composer::placeholder {
676
+ color: ${DIM};
677
+ }
678
+ .xc-composer:focus-visible {
679
+ box-shadow: inset 0 0 0 1px ${PANEL_COLORS.focus};
680
+ }
681
+ .xc-composer:disabled {
682
+ opacity: ${PANEL_DISABLED_OPACITY};
683
+ cursor: not-allowed;
684
+ }
656
685
  .xc-search-clear {
657
686
  display: flex;
658
687
  align-items: center;
@@ -1392,6 +1421,42 @@ function Field({
1392
1421
  hint != null ? /* @__PURE__ */ jsx2("div", { className: CLS_FIELD_HINT, id: hintId, children: hint }) : null
1393
1422
  ] });
1394
1423
  }
1424
+ function composerWantsSubmit(key, shiftKey, submitOnEnter = true) {
1425
+ if (key !== "Enter") return false;
1426
+ return submitOnEnter ? !shiftKey : shiftKey;
1427
+ }
1428
+ function Composer({
1429
+ value,
1430
+ onChange,
1431
+ onSubmit,
1432
+ placeholder = "Type a message\u2026",
1433
+ label,
1434
+ submitOnEnter = true,
1435
+ disabled,
1436
+ autoFocus,
1437
+ className
1438
+ }) {
1439
+ usePanelPrimitives();
1440
+ const handleKeyDown = (e) => {
1441
+ if (!onSubmit || !composerWantsSubmit(e.key, e.shiftKey, submitOnEnter)) return;
1442
+ e.preventDefault();
1443
+ onSubmit();
1444
+ };
1445
+ return /* @__PURE__ */ jsx2(
1446
+ "textarea",
1447
+ {
1448
+ className: cx(CLS_COMPOSER, className),
1449
+ value,
1450
+ placeholder,
1451
+ "aria-label": label ?? placeholder,
1452
+ disabled,
1453
+ autoFocus,
1454
+ spellCheck: true,
1455
+ onChange: (e) => onChange(e.target.value),
1456
+ onKeyDown: handleKeyDown
1457
+ }
1458
+ );
1459
+ }
1395
1460
 
1396
1461
  // src/chrome/section.tsx
1397
1462
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -1714,6 +1779,7 @@ var COMPONENTS = {
1714
1779
  "chrome/IconButton": IconButton,
1715
1780
  "chrome/TextButton": TextButton,
1716
1781
  "chrome/SearchField": SearchField,
1782
+ "chrome/Composer": Composer,
1717
1783
  "chrome/SegmentedControl": SegmentedControl,
1718
1784
  "chrome/Toggle": Toggle,
1719
1785
  "chrome/FieldGroup": FieldGroup,
@@ -1761,6 +1827,9 @@ function gallery() {
1761
1827
  form("TextButton", "disabled", () => /* @__PURE__ */ jsx7(TextButton, { onClick: noop, disabled: true, children: "Clear all" }));
1762
1828
  form("SearchField", "empty", () => /* @__PURE__ */ jsx7(SearchField, { value: "", onChange: noop, placeholder: "Filter rows\u2026", label: "Filter" }));
1763
1829
  form("SearchField", "filled", () => /* @__PURE__ */ jsx7(SearchField, { value: "render", onChange: noop, placeholder: "Filter rows\u2026", label: "Filter" }));
1830
+ form("Composer", "empty", () => /* @__PURE__ */ jsx7(Composer, { value: "", onChange: noop, onSubmit: noop, placeholder: "Ask the agent\u2026" }));
1831
+ form("Composer", "draft", () => /* @__PURE__ */ jsx7(Composer, { value: "Summarise the last run and list the failing steps.", onChange: noop, onSubmit: noop }));
1832
+ form("Composer", "disabled", () => /* @__PURE__ */ jsx7(Composer, { value: "", onChange: noop, placeholder: "Not connected", disabled: true }));
1764
1833
  form("SegmentedControl", "first", () => /* @__PURE__ */ jsx7(SegmentedControl, { options: SEGMENTS, value: "all", onChange: noop, label: "Level" }));
1765
1834
  form("SegmentedControl", "last", () => /* @__PURE__ */ jsx7(SegmentedControl, { options: SEGMENTS, value: "warnings", onChange: noop, label: "Level" }));
1766
1835
  form("Toggle", "off", () => /* @__PURE__ */ jsx7(Toggle, { checked: false, onChange: noop, label: "Follow output" }));
@@ -1878,6 +1947,7 @@ export {
1878
1947
  COMPONENTS,
1879
1948
  ChevronGlyph,
1880
1949
  ClearGlyph,
1950
+ Composer,
1881
1951
  Divider,
1882
1952
  EmptyState,
1883
1953
  ErrorState,
@@ -1911,6 +1981,7 @@ export {
1911
1981
  Toolbar,
1912
1982
  ToolbarGroup,
1913
1983
  badgeClass,
1984
+ composerWantsSubmit,
1914
1985
  cx,
1915
1986
  dotClass,
1916
1987
  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.1",
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",