@xenosystem/components 0.9.3 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/catalog.json CHANGED
@@ -399,6 +399,12 @@
399
399
  "optional": true,
400
400
  "doc": "`sm` = 20px (inside rows), `md` = 24px (toolbars, default)."
401
401
  },
402
+ {
403
+ "name": "variant",
404
+ "type": "'default' | 'primary' | 'danger'",
405
+ "optional": true,
406
+ "doc": "`primary` — the one emphasised action in a strip (Play); `danger` — armed / destructive (Record)."
407
+ },
402
408
  {
403
409
  "name": "className",
404
410
  "type": "string",
@@ -727,6 +733,198 @@
727
733
  }
728
734
  ]
729
735
  },
736
+ {
737
+ "name": "Readout",
738
+ "file": "controls.tsx",
739
+ "doc": "A value with its unit — the transport's position, a timeline's duration, a calendar's week number.",
740
+ "props": "ReadoutProps",
741
+ "propList": [
742
+ {
743
+ "name": "value",
744
+ "type": "ReactNode",
745
+ "optional": false,
746
+ "doc": "The figure — a timecode, a count, a duration. Rendered in tabular numerals."
747
+ },
748
+ {
749
+ "name": "unit",
750
+ "type": "ReactNode",
751
+ "optional": true,
752
+ "doc": "The small unit or format label beside it (`TC`, `frames`, `bars`)."
753
+ },
754
+ {
755
+ "name": "size",
756
+ "type": "'sm' | 'md'",
757
+ "optional": true,
758
+ "doc": "`md` (default) for the primary readout; `sm` for a secondary one (a duration beside the position)."
759
+ },
760
+ {
761
+ "name": "label",
762
+ "type": "string",
763
+ "optional": true,
764
+ "doc": "Accessible name; the figure is read as text."
765
+ },
766
+ {
767
+ "name": "className",
768
+ "type": "string",
769
+ "optional": true,
770
+ "doc": ""
771
+ }
772
+ ]
773
+ },
774
+ {
775
+ "name": "NumberInput",
776
+ "file": "controls.tsx",
777
+ "doc": "A compact numeric field with an inline caption — BPM, fps, a count — committed on change.",
778
+ "props": "NumberInputProps",
779
+ "propList": [
780
+ {
781
+ "name": "value",
782
+ "type": "number",
783
+ "optional": false,
784
+ "doc": ""
785
+ },
786
+ {
787
+ "name": "onChange",
788
+ "type": "(value: number) => void",
789
+ "optional": false,
790
+ "doc": ""
791
+ },
792
+ {
793
+ "name": "caption",
794
+ "type": "ReactNode",
795
+ "optional": true,
796
+ "doc": "Inline label, before the box (`BPM`). Also the accessible name unless `label` is set."
797
+ },
798
+ {
799
+ "name": "label",
800
+ "type": "string",
801
+ "optional": true,
802
+ "doc": ""
803
+ },
804
+ {
805
+ "name": "min",
806
+ "type": "number",
807
+ "optional": true,
808
+ "doc": ""
809
+ },
810
+ {
811
+ "name": "max",
812
+ "type": "number",
813
+ "optional": true,
814
+ "doc": ""
815
+ },
816
+ {
817
+ "name": "step",
818
+ "type": "number",
819
+ "optional": true,
820
+ "doc": ""
821
+ },
822
+ {
823
+ "name": "disabled",
824
+ "type": "boolean",
825
+ "optional": true,
826
+ "doc": ""
827
+ },
828
+ {
829
+ "name": "className",
830
+ "type": "string",
831
+ "optional": true,
832
+ "doc": ""
833
+ }
834
+ ]
835
+ },
836
+ {
837
+ "name": "Slider",
838
+ "file": "controls.tsx",
839
+ "doc": "A horizontal slider: track, fill and knob. The playhead scrubber; a zoom; a level.",
840
+ "props": "SliderProps",
841
+ "propList": [
842
+ {
843
+ "name": "progress",
844
+ "type": "number",
845
+ "optional": false,
846
+ "doc": "Position, 0–1."
847
+ },
848
+ {
849
+ "name": "label",
850
+ "type": "string",
851
+ "optional": false,
852
+ "doc": "Accessible name."
853
+ },
854
+ {
855
+ "name": "valueMin",
856
+ "type": "number",
857
+ "optional": true,
858
+ "doc": "The ARIA value triple — what a screen reader announces (ticks, seconds, percent)."
859
+ },
860
+ {
861
+ "name": "valueMax",
862
+ "type": "number",
863
+ "optional": true,
864
+ "doc": ""
865
+ },
866
+ {
867
+ "name": "valueNow",
868
+ "type": "number",
869
+ "optional": true,
870
+ "doc": ""
871
+ },
872
+ {
873
+ "name": "valueText",
874
+ "type": "string",
875
+ "optional": true,
876
+ "doc": ""
877
+ },
878
+ {
879
+ "name": "onPointerDown",
880
+ "type": "(e: PointerEvent<HTMLDivElement>) => void",
881
+ "optional": true,
882
+ "doc": "Pointer gestures on the track. The caller owns the maths (it knows the timebase); the unit owns the surface."
883
+ },
884
+ {
885
+ "name": "onPointerMove",
886
+ "type": "(e: PointerEvent<HTMLDivElement>) => void",
887
+ "optional": true,
888
+ "doc": ""
889
+ },
890
+ {
891
+ "name": "onPointerUp",
892
+ "type": "(e: PointerEvent<HTMLDivElement>) => void",
893
+ "optional": true,
894
+ "doc": ""
895
+ },
896
+ {
897
+ "name": "onPointerCancel",
898
+ "type": "(e: PointerEvent<HTMLDivElement>) => void",
899
+ "optional": true,
900
+ "doc": ""
901
+ },
902
+ {
903
+ "name": "onKeyDown",
904
+ "type": "(e: KeyboardEvent<HTMLDivElement>) => void",
905
+ "optional": true,
906
+ "doc": ""
907
+ },
908
+ {
909
+ "name": "disabled",
910
+ "type": "boolean",
911
+ "optional": true,
912
+ "doc": ""
913
+ },
914
+ {
915
+ "name": "className",
916
+ "type": "string",
917
+ "optional": true,
918
+ "doc": ""
919
+ },
920
+ {
921
+ "name": "trackRef",
922
+ "type": "Ref<HTMLDivElement>",
923
+ "optional": true,
924
+ "doc": "The track element, for a caller that measures it."
925
+ }
926
+ ]
927
+ },
730
928
  {
731
929
  "name": "PanelFrame",
732
930
  "file": "frame.tsx",
@@ -1,4 +1,4 @@
1
- import { ReactNode, HTMLAttributes, ReactElement } from 'react';
1
+ import { ReactNode, HTMLAttributes, PointerEvent, KeyboardEvent, Ref, ReactElement } from 'react';
2
2
 
3
3
  /**
4
4
  * Frame + strip primitives: `PanelFrame`, `Toolbar`, `ToolbarGroup`, `Divider`, `ScrollArea`.
@@ -515,6 +515,8 @@ interface IconButtonProps {
515
515
  disabled?: boolean;
516
516
  /** `sm` = 20px (inside rows), `md` = 24px (toolbars, default). */
517
517
  size?: 'sm' | 'md';
518
+ /** `primary` — the one emphasised action in a strip (Play); `danger` — armed / destructive (Record). */
519
+ variant?: 'default' | 'primary' | 'danger';
518
520
  /** Extra classes. */
519
521
  className?: string;
520
522
  }
@@ -526,7 +528,7 @@ interface IconButtonProps {
526
528
  * <IconButton icon={<TrashGlyph />} label="Delete layer" onClick={remove} disabled={!selection} />
527
529
  * ```
528
530
  */
529
- declare function IconButton({ icon, label, onClick, active, disabled, size, className, }: IconButtonProps): ReactNode;
531
+ declare function IconButton({ icon, label, onClick, active, disabled, size, variant, className, }: IconButtonProps): ReactNode;
530
532
  /** Props for {@link TextButton}. */
531
533
  interface TextButtonProps {
532
534
  /** Label. Census copy patterns: `+ Add`, `Clear`, `Cancel`, `Unbind`. */
@@ -719,6 +721,59 @@ interface ComposerProps {
719
721
  /** The submit convention, as a pure decision so it is testable and shared: Enter sends, Shift+Enter breaks. */
720
722
  declare function composerWantsSubmit(key: string, shiftKey: boolean, submitOnEnter?: boolean): boolean;
721
723
  declare function Composer({ value, onChange, onSubmit, placeholder, label, submitOnEnter, disabled, autoFocus, className, }: ComposerProps): ReactNode;
724
+ /** Props for {@link Readout}. */
725
+ interface ReadoutProps {
726
+ /** The figure — a timecode, a count, a duration. Rendered in tabular numerals. */
727
+ value: ReactNode;
728
+ /** The small unit or format label beside it (`TC`, `frames`, `bars`). */
729
+ unit?: ReactNode;
730
+ /** `md` (default) for the primary readout; `sm` for a secondary one (a duration beside the position). */
731
+ size?: 'sm' | 'md';
732
+ /** Accessible name; the figure is read as text. */
733
+ label?: string;
734
+ className?: string;
735
+ }
736
+ /** A value with its unit — the transport's position, a timeline's duration, a calendar's week number. */
737
+ declare function Readout({ value, unit, size, label, className }: ReadoutProps): ReactNode;
738
+ /** Props for {@link NumberInput}. */
739
+ interface NumberInputProps {
740
+ value: number;
741
+ onChange: (value: number) => void;
742
+ /** Inline label, before the box (`BPM`). Also the accessible name unless `label` is set. */
743
+ caption?: ReactNode;
744
+ label?: string;
745
+ min?: number;
746
+ max?: number;
747
+ step?: number;
748
+ disabled?: boolean;
749
+ className?: string;
750
+ }
751
+ /** A compact numeric field with an inline caption — BPM, fps, a count — committed on change. */
752
+ declare function NumberInput({ value, onChange, caption, label, min, max, step, disabled, className }: NumberInputProps): ReactNode;
753
+ /** Props for {@link Slider}. */
754
+ interface SliderProps {
755
+ /** Position, 0–1. */
756
+ progress: number;
757
+ /** Accessible name. */
758
+ label: string;
759
+ /** The ARIA value triple — what a screen reader announces (ticks, seconds, percent). */
760
+ valueMin?: number;
761
+ valueMax?: number;
762
+ valueNow?: number;
763
+ valueText?: string;
764
+ /** Pointer gestures on the track. The caller owns the maths (it knows the timebase); the unit owns the surface. */
765
+ onPointerDown?: (e: PointerEvent<HTMLDivElement>) => void;
766
+ onPointerMove?: (e: PointerEvent<HTMLDivElement>) => void;
767
+ onPointerUp?: (e: PointerEvent<HTMLDivElement>) => void;
768
+ onPointerCancel?: (e: PointerEvent<HTMLDivElement>) => void;
769
+ onKeyDown?: (e: KeyboardEvent<HTMLDivElement>) => void;
770
+ disabled?: boolean;
771
+ className?: string;
772
+ /** The track element, for a caller that measures it. */
773
+ trackRef?: Ref<HTMLDivElement>;
774
+ }
775
+ /** A horizontal slider: track, fill and knob. The playhead scrubber; a zoom; a level. */
776
+ declare function Slider({ progress, label, valueMin, valueMax, valueNow, valueText, onPointerDown, onPointerMove, onPointerUp, onPointerCancel, onKeyDown, disabled, className, trackRef }: SliderProps): ReactNode;
722
777
 
723
778
  /**
724
779
  * The three terminal states: `EmptyState`, `LoadingState`, `ErrorState`.
@@ -1199,6 +1254,9 @@ declare const COMPONENTS: {
1199
1254
  readonly 'chrome/TextButton': typeof TextButton;
1200
1255
  readonly 'chrome/SearchField': typeof SearchField;
1201
1256
  readonly 'chrome/Composer': typeof Composer;
1257
+ readonly 'chrome/Readout': typeof Readout;
1258
+ readonly 'chrome/NumberInput': typeof NumberInput;
1259
+ readonly 'chrome/Slider': typeof Slider;
1202
1260
  readonly 'chrome/SegmentedControl': typeof SegmentedControl;
1203
1261
  readonly 'chrome/Toggle': typeof Toggle;
1204
1262
  readonly 'chrome/FieldGroup': typeof FieldGroup;
@@ -1249,4 +1307,4 @@ interface GalleryEntry {
1249
1307
  /** The entries, one per unit × state. Adding a unit to {@link COMPONENTS} without an entry here fails the family's test. */
1250
1308
  declare function gallery(): GalleryEntry[];
1251
1309
 
1252
- 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, Stack, type StackProps, StatTile, type StatTileProps, StatusBadge, type StatusBadgeProps, StatusBar, type StatusBarProps, StatusDot, type StatusDotProps, Text, TextButton, type TextButtonProps, type TextProps, Toggle, type ToggleProps, Toolbar, ToolbarGroup, type ToolbarGroupProps, type ToolbarProps, badgeClass, composerWantsSubmit, cx, dotClass, ensurePanelPrimitivesInjected, gallery, iconButtonClass, nextSegmentValue, rowClass, sectionClass, usePanelPrimitives };
1310
+ 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, NumberInput, type NumberInputProps, 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, Readout, type ReadoutProps, Row, RowList, type RowListProps, type RowProps, type RowState, ScrollArea, type ScrollAreaProps, SearchField, type SearchFieldProps, SearchGlyph, Section, type SectionProps, type SegmentOption, SegmentedControl, type SegmentedControlProps, Slider, type SliderProps, Sparkline, type SparklineProps, Stack, type StackProps, StatTile, type StatTileProps, StatusBadge, type StatusBadgeProps, StatusBar, type StatusBarProps, StatusDot, type StatusDotProps, Text, TextButton, type TextButtonProps, type TextProps, Toggle, type ToggleProps, Toolbar, ToolbarGroup, type ToolbarGroupProps, type ToolbarProps, badgeClass, composerWantsSubmit, cx, dotClass, ensurePanelPrimitivesInjected, gallery, iconButtonClass, nextSegmentValue, rowClass, sectionClass, usePanelPrimitives };
@@ -56,6 +56,9 @@ var CLS_SCROLL = "xc-scroll";
56
56
  var CLS_COLUMN = "xc-column";
57
57
  var CLS_STACK = "xc-stack";
58
58
  var CLS_TEXT = "xc-text";
59
+ var CLS_READOUT = "xc-readout";
60
+ var CLS_NUMBER = "xc-number";
61
+ var CLS_SLIDER = "xc-slider";
59
62
  var PANEL_TONES = Object.freeze([
60
63
  "neutral",
61
64
  "info",
@@ -702,6 +705,111 @@ div.xc-section-trigger {
702
705
  font-variant-numeric: tabular-nums;
703
706
  }
704
707
 
708
+ /* \u2500\u2500 IconButton variants \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 */
709
+ .xc-iconbtn--primary {
710
+ background: ${PANEL_COLORS.well};
711
+ width: ${PANEL_METRICS.iconButton + 6}px;
712
+ }
713
+ .xc-iconbtn--danger {
714
+ color: var(--xeno-danger, ${PANEL_COLORS.error});
715
+ }
716
+ .xc-iconbtn--danger.xc-iconbtn--active {
717
+ background: color-mix(in srgb, var(--xeno-danger, ${PANEL_COLORS.error}) 18%, transparent);
718
+ }
719
+
720
+ /* \u2500\u2500 Readout \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 */
721
+ .xc-readout {
722
+ display: inline-flex;
723
+ align-items: baseline;
724
+ gap: 4px;
725
+ min-width: 0;
726
+ color: ${FG};
727
+ font-variant-numeric: tabular-nums;
728
+ letter-spacing: 0.02em;
729
+ white-space: nowrap;
730
+ }
731
+ .xc-readout--md .xc-readout-value { font-size: ${PANEL_METRICS.fontBody + 1}px; }
732
+ .xc-readout--sm .xc-readout-value { font-size: ${PANEL_METRICS.fontMeta}px; color: ${MUTED}; }
733
+ .xc-readout-unit {
734
+ font-size: ${PANEL_METRICS.fontMeta - 2}px;
735
+ letter-spacing: 0.06em;
736
+ text-transform: uppercase;
737
+ color: ${MUTED};
738
+ }
739
+
740
+ /* \u2500\u2500 NumberInput \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 */
741
+ .xc-number {
742
+ display: inline-flex;
743
+ align-items: center;
744
+ gap: 3px;
745
+ flex: 0 0 auto;
746
+ }
747
+ .xc-number-caption {
748
+ font-size: ${PANEL_METRICS.fontMeta - 2}px;
749
+ letter-spacing: 0.06em;
750
+ text-transform: uppercase;
751
+ color: ${MUTED};
752
+ }
753
+ .xc-number-input {
754
+ width: 44px;
755
+ height: ${PANEL_METRICS.controlHeightSm}px;
756
+ padding: 0 4px;
757
+ box-sizing: border-box;
758
+ border: 1px solid ${BORDER};
759
+ border-radius: ${PANEL_METRICS.radius}px;
760
+ background: ${PANEL_COLORS.well};
761
+ color: ${FG};
762
+ font: inherit;
763
+ font-size: ${PANEL_METRICS.fontMeta}px;
764
+ font-variant-numeric: tabular-nums;
765
+ outline: none;
766
+ }
767
+ .xc-number-input:focus-visible {
768
+ border-color: ${PANEL_COLORS.ring};
769
+ }
770
+ .xc-number--disabled {
771
+ opacity: ${PANEL_DISABLED_OPACITY};
772
+ }
773
+
774
+ /* \u2500\u2500 Slider \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 */
775
+ .xc-slider {
776
+ position: relative;
777
+ height: 10px;
778
+ box-sizing: border-box;
779
+ border-radius: ${PANEL_METRICS.radius}px;
780
+ background: ${PANEL_COLORS.well};
781
+ cursor: pointer;
782
+ outline: none;
783
+ touch-action: none;
784
+ }
785
+ .xc-slider:focus-visible {
786
+ box-shadow: inset 0 0 0 1px ${PANEL_COLORS.focus};
787
+ }
788
+ .xc-slider-fill {
789
+ position: absolute;
790
+ left: 0;
791
+ top: 0;
792
+ bottom: 0;
793
+ border-radius: ${PANEL_METRICS.radius}px;
794
+ background: ${PANEL_COLORS.rowSelected};
795
+ pointer-events: none;
796
+ }
797
+ .xc-slider-knob {
798
+ position: absolute;
799
+ top: 1px;
800
+ bottom: 1px;
801
+ width: 4px;
802
+ margin-left: -2px;
803
+ border-radius: 1px;
804
+ background: ${FG};
805
+ pointer-events: none;
806
+ }
807
+ .xc-slider--disabled {
808
+ opacity: ${PANEL_DISABLED_OPACITY};
809
+ cursor: default;
810
+ }
811
+
812
+
705
813
  /* \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 */
706
814
  .xc-composer {
707
815
  display: block;
@@ -1281,6 +1389,7 @@ function IconButton({
1281
1389
  active,
1282
1390
  disabled,
1283
1391
  size = "md",
1392
+ variant = "default",
1284
1393
  className
1285
1394
  }) {
1286
1395
  usePanelPrimitives();
@@ -1293,7 +1402,7 @@ function IconButton({
1293
1402
  "aria-pressed": active,
1294
1403
  disabled,
1295
1404
  onClick,
1296
- className: iconButtonClass({ active, disabled }, cx(`${"xc-iconbtn"}--${size}`, className)),
1405
+ className: iconButtonClass({ active, disabled }, cx(`${"xc-iconbtn"}--${size}`, variant !== "default" && `xc-iconbtn--${variant}`, className)),
1297
1406
  children: icon
1298
1407
  }
1299
1408
  );
@@ -1517,6 +1626,64 @@ function Composer({
1517
1626
  }
1518
1627
  );
1519
1628
  }
1629
+ function Readout({ value, unit, size = "md", label, className }) {
1630
+ usePanelPrimitives();
1631
+ return /* @__PURE__ */ jsxs2("span", { className: cx(CLS_READOUT, `${CLS_READOUT}--${size}`, className), "aria-label": label, role: label ? "status" : void 0, children: [
1632
+ /* @__PURE__ */ jsx2("span", { className: `${CLS_READOUT}-value`, children: value }),
1633
+ unit !== void 0 && unit !== null ? /* @__PURE__ */ jsx2("span", { className: `${CLS_READOUT}-unit`, children: unit }) : null
1634
+ ] });
1635
+ }
1636
+ function NumberInput({ value, onChange, caption, label, min, max, step, disabled, className }) {
1637
+ usePanelPrimitives();
1638
+ return /* @__PURE__ */ jsxs2("label", { className: cx(CLS_NUMBER, disabled && `${CLS_NUMBER}--disabled`, className), children: [
1639
+ caption !== void 0 ? /* @__PURE__ */ jsx2("span", { className: `${CLS_NUMBER}-caption`, children: caption }) : null,
1640
+ /* @__PURE__ */ jsx2(
1641
+ "input",
1642
+ {
1643
+ type: "number",
1644
+ className: `${CLS_NUMBER}-input`,
1645
+ value,
1646
+ min,
1647
+ max,
1648
+ step,
1649
+ disabled,
1650
+ "aria-label": label ?? (typeof caption === "string" ? caption : void 0),
1651
+ onChange: (e) => {
1652
+ const n = Number(e.target.value);
1653
+ if (Number.isFinite(n)) onChange(n);
1654
+ }
1655
+ }
1656
+ )
1657
+ ] });
1658
+ }
1659
+ function Slider({ progress, label, valueMin = 0, valueMax = 1, valueNow, valueText, onPointerDown, onPointerMove, onPointerUp, onPointerCancel, onKeyDown, disabled, className, trackRef }) {
1660
+ usePanelPrimitives();
1661
+ const pct = `${Math.max(0, Math.min(1, progress)) * 100}%`;
1662
+ return /* @__PURE__ */ jsxs2(
1663
+ "div",
1664
+ {
1665
+ ref: trackRef,
1666
+ className: cx(CLS_SLIDER, disabled && `${CLS_SLIDER}--disabled`, className),
1667
+ role: "slider",
1668
+ "aria-label": label,
1669
+ "aria-valuemin": valueMin,
1670
+ "aria-valuemax": valueMax,
1671
+ "aria-valuenow": valueNow ?? progress,
1672
+ "aria-valuetext": valueText,
1673
+ "aria-disabled": disabled || void 0,
1674
+ tabIndex: disabled ? -1 : 0,
1675
+ onPointerDown: disabled ? void 0 : onPointerDown,
1676
+ onPointerMove: disabled ? void 0 : onPointerMove,
1677
+ onPointerUp: disabled ? void 0 : onPointerUp,
1678
+ onPointerCancel: disabled ? void 0 : onPointerCancel,
1679
+ onKeyDown: disabled ? void 0 : onKeyDown,
1680
+ children: [
1681
+ /* @__PURE__ */ jsx2("span", { className: `${CLS_SLIDER}-fill`, style: { width: pct } }),
1682
+ /* @__PURE__ */ jsx2("span", { className: `${CLS_SLIDER}-knob`, style: { left: pct } })
1683
+ ]
1684
+ }
1685
+ );
1686
+ }
1520
1687
 
1521
1688
  // src/chrome/section.tsx
1522
1689
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -1840,6 +2007,9 @@ var COMPONENTS = {
1840
2007
  "chrome/TextButton": TextButton,
1841
2008
  "chrome/SearchField": SearchField,
1842
2009
  "chrome/Composer": Composer,
2010
+ "chrome/Readout": Readout,
2011
+ "chrome/NumberInput": NumberInput,
2012
+ "chrome/Slider": Slider,
1843
2013
  "chrome/SegmentedControl": SegmentedControl,
1844
2014
  "chrome/Toggle": Toggle,
1845
2015
  "chrome/FieldGroup": FieldGroup,
@@ -1886,6 +2056,15 @@ function gallery() {
1886
2056
  };
1887
2057
  form("IconButton", "default", () => /* @__PURE__ */ jsx7(IconButton, { icon: /* @__PURE__ */ jsx7(SearchGlyph, {}), label: "Search", onClick: noop }));
1888
2058
  form("IconButton", "active", () => /* @__PURE__ */ jsx7(IconButton, { icon: /* @__PURE__ */ jsx7(SearchGlyph, {}), label: "Search", onClick: noop, active: true }));
2059
+ form("IconButton", "primary", () => /* @__PURE__ */ jsx7(IconButton, { icon: /* @__PURE__ */ jsx7(ChevronGlyph, {}), label: "Play", onClick: noop, variant: "primary" }));
2060
+ form("IconButton", "danger-armed", () => /* @__PURE__ */ jsx7(IconButton, { icon: /* @__PURE__ */ jsx7(ClearGlyph, {}), label: "Record", onClick: noop, variant: "danger", active: true }));
2061
+ form("Readout", "timecode", () => /* @__PURE__ */ jsx7(Readout, { value: "00:01:23:12", unit: "TC", label: "Position" }));
2062
+ form("Readout", "secondary", () => /* @__PURE__ */ jsx7(Readout, { value: "00:04:00:00", size: "sm" }));
2063
+ form("NumberInput", "default", () => /* @__PURE__ */ jsx7(NumberInput, { value: 120, onChange: noop, caption: "BPM", min: 20, max: 300 }));
2064
+ form("NumberInput", "disabled", () => /* @__PURE__ */ jsx7(NumberInput, { value: 24, onChange: noop, caption: "FPS", disabled: true }));
2065
+ form("Slider", "quarter", () => /* @__PURE__ */ jsx7(Slider, { progress: 0.25, label: "Playhead", valueMax: 100, valueNow: 25, valueText: "25%" }));
2066
+ form("Slider", "end", () => /* @__PURE__ */ jsx7(Slider, { progress: 1, label: "Playhead" }));
2067
+ form("Slider", "disabled", () => /* @__PURE__ */ jsx7(Slider, { progress: 0.5, label: "Playhead", disabled: true }));
1889
2068
  form("TextButton", "default", () => /* @__PURE__ */ jsx7(TextButton, { onClick: noop, children: "Clear all" }));
1890
2069
  form("TextButton", "disabled", () => /* @__PURE__ */ jsx7(TextButton, { onClick: noop, disabled: true, children: "Clear all" }));
1891
2070
  form("SearchField", "empty", () => /* @__PURE__ */ jsx7(SearchField, { value: "", onChange: noop, placeholder: "Filter rows\u2026", label: "Filter" }));
@@ -2040,6 +2219,7 @@ export {
2040
2219
  IconButton,
2041
2220
  IconFrame,
2042
2221
  LoadingState,
2222
+ NumberInput,
2043
2223
  PANEL_COLORS,
2044
2224
  PANEL_DISABLED_OPACITY,
2045
2225
  PANEL_METRICS,
@@ -2048,6 +2228,7 @@ export {
2048
2228
  PANEL_TONES,
2049
2229
  PanelFrame,
2050
2230
  ProportionBar,
2231
+ Readout,
2051
2232
  Row,
2052
2233
  RowList,
2053
2234
  ScrollArea,
@@ -2055,6 +2236,7 @@ export {
2055
2236
  SearchGlyph,
2056
2237
  Section,
2057
2238
  SegmentedControl,
2239
+ Slider,
2058
2240
  Sparkline,
2059
2241
  Stack,
2060
2242
  StatTile,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xenosystem/components",
3
- "version": "0.9.3",
3
+ "version": "0.10.0",
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",