nexus-shell 0.2.8 → 0.2.10

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/README.md CHANGED
@@ -28,8 +28,10 @@ import { Boxes, Files } from 'lucide-react';
28
28
  import {
29
29
  AppTitle,
30
30
  ShellLayout,
31
+ chatPanel,
31
32
  componentRegistry,
32
33
  initializeShell,
34
+ terminalPanel,
33
35
  useLayoutStore,
34
36
  } from 'nexus-shell';
35
37
  import 'nexus-shell/style.css';
@@ -38,7 +40,13 @@ import 'nexus-shell/style.css';
38
40
  componentRegistry.register('editor', Editor);
39
41
 
40
42
  initializeShell({
41
- panels: [{ id: 'files', label: 'Explorer', icon: Files, component: FileExplorer }],
43
+ panels: [
44
+ { id: 'files', label: 'Explorer', icon: Files, component: FileExplorer },
45
+ terminalPanel(),
46
+ ],
47
+ // Chat on the right rail. Move it to `panels` for the left, or register
48
+ // the component as a tab — nothing about it is bound to one edge.
49
+ inspectorPanels: [chatPanel()],
42
50
  commands: [
43
51
  { id: 'file.save', label: 'File: Save', keybinding: 'Control+s', execute: save },
44
52
  ],
@@ -56,6 +64,23 @@ createRoot(document.getElementById('root')!).render(
56
64
  That is a working IDE: dockable tabs, a command palette on `Cmd/Ctrl+Shift+P`, a
57
65
  terminal, a chat pane, and a themable frame.
58
66
 
67
+ ## Where things go
68
+
69
+ The shell has four places a view can live, and nothing is nailed to one of
70
+ them. Chat and the terminal are ordinary registrations, so they go wherever you
71
+ put them — including into a dockable tab, alongside your own views.
72
+
73
+ | Place | How |
74
+ | :-- | :-- |
75
+ | Left rail | `panels: [...]` |
76
+ | Right rail | `inspectorPanels: [...]` |
77
+ | Dockable tab | `componentRegistry.register(id, Component)` + `addTab(id)` |
78
+ | Bottom of the workspace | A tab in the bottom row of your `initialLayoutJson` |
79
+
80
+ Anything hosted in a pane or a tab is told so, and drops its own title bar —
81
+ `SidebarPane` and the tab strip already draw one. Pass `chrome` explicitly to
82
+ force it either way.
83
+
59
84
  ## Growing an app
60
85
 
61
86
  You add capability by **registering** it. None of this changes the layout, which
@@ -67,6 +92,8 @@ is what lets plugins and distant code contribute to the shell.
67
92
  | An action or hotkey | `commandRegistry.registerCommand({ id, label, keybinding, execute })` — appears in the palette automatically |
68
93
  | A menu entry | `menuRegistry.registerMenu('File', { id, label, commandId })` |
69
94
  | A sidebar panel | Add it to `panels`; it becomes an activity bar icon |
95
+ | An inspector or properties panel | Add it to `inspectorPanels`; it docks right, with its own rail |
96
+ | Chat or a terminal, anywhere | `chatPanel()` / `terminalPanel()` in `panels` or `inspectorPanels`, or register the component as a tab |
70
97
  | A status bar item | `useStatusBarStore.getState().addWidget(...)` |
71
98
  | A dialog from anywhere | `await useModalStore.getState().openConfirm('Sure?')` |
72
99
  | An unsaved-changes guard | `useLayoutStore.getState().setTabDirty(tabId, true)` |
@@ -114,8 +141,8 @@ shell's own frame and keep every registration working.
114
141
 
115
142
  | Component | What it is |
116
143
  | :-- | :-- |
117
- | `ActivityBar` | Vertical icon rail for switching sidebar panels |
118
- | `ChatPane` | Docked chat transcript with slash-command autocomplete |
144
+ | `ActivityBar` | Vertical icon rail for switching side panels, either edge |
145
+ | `ChatPane` | Chat transcript with slash-command autocomplete; fills any host |
119
146
  | `CommandPalette` | Filterable, keyboard-navigable command list |
120
147
  | `ContextMenu` | Floating menu positioned at viewport coordinates |
121
148
  | `DataGrid` | Sortable, filterable table with optional virtualisation |
@@ -124,9 +151,10 @@ shell's own frame and keep every registration working.
124
151
  | `QuickSearch` | Compact search field with grouped results, for the menu bar |
125
152
  | `SearchWidget` | Full-height search panel with autocomplete, for the sidebar |
126
153
  | `SettingsPanel` | Theme picker body for the settings sidebar |
127
- | `SidebarPane` | Titled, closable container for sidebar content |
154
+ | `SidebarPane` | Titled, closable side panel; `side` docks it left or right |
128
155
  | `StatusBar` | Footer with left / center / right item groups |
129
- | `TerminalPane` | Bottom-docked terminal log and input |
156
+ | `TerminalPane` | Terminal log and input; fills any host |
157
+ | `PaneHostProvider` | Tells hosted components a title bar is already drawn |
130
158
  | `ThemeSwitcher` | Segmented theme control |
131
159
  | `TreeWidget` | Virtualised tree for any hierarchy, with drag-to-move and a data-driven menu |
132
160
  | `GraphCanvas` | Infinite pannable, zoomable field for node-and-edge editing |
@@ -138,6 +166,9 @@ shell's own frame and keep every registration working.
138
166
  | `useGraphLayout` | Auto layout engines, with a freeform escape for hand placement |
139
167
  | `layeredLayout` | Layered tree layout in four directions; handles cycles |
140
168
  | `gridLayout` | Uniform grid, ignoring edges |
169
+ | `PropertyPanel` | Inspector for the current selection, with mixed-value editing |
170
+ | `TextField` … `StaticField` | Nine composable property fields, usable on their own |
171
+ | `FieldShell` | The label / description / error frame, for fields of your own |
141
172
  | `UserProfile` | Avatar and identity widget |
142
173
 
143
174
  ## Theming
@@ -58,7 +58,9 @@ export declare const edgePath: (from: IPoint, to: IPoint, fromPort: GraphPort, r
58
58
  */
59
59
  export declare const GraphEdge: ({ edge, source, target, routing, selected, arrow, onSelect, onContextMenu, className, }: GraphEdgeProps) => import("react/jsx-runtime").JSX.Element;
60
60
  export interface GraphEdgeLayerProps {
61
+ /** The edges to draw. Anything SVG works — this is just the surface. */
61
62
  children?: React.ReactNode;
63
+ /** Extra classes merged onto the `<svg>` element. */
62
64
  className?: string;
63
65
  }
64
66
  /**
@@ -0,0 +1,56 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ /**
4
+ * What a component can learn about the thing hosting it.
5
+ *
6
+ * A pane, a dockable tab and a bare `<div>` give a component very different
7
+ * surroundings, and the difference that matters is whether a title bar has
8
+ * already been drawn. Without this, a chat pane inside a `SidebarPane` renders
9
+ * two headers and two close buttons.
10
+ */
11
+ export interface IPaneHost {
12
+ /**
13
+ * The host already draws a title bar and a close affordance, so a hosted
14
+ * component should not draw its own.
15
+ */
16
+ chrome: boolean;
17
+ /** Where the host sits, when it knows. Useful for choosing a layout. */
18
+ placement?: 'left' | 'right' | 'bottom' | 'tab';
19
+ }
20
+ /**
21
+ * Read the surrounding host, or `null` when there isn't one.
22
+ *
23
+ * Prefer {@link useHostChrome}, which folds in an explicit prop.
24
+ */
25
+ export declare const usePaneHost: () => IPaneHost | null;
26
+ /**
27
+ * Whether this component should draw its own title bar.
28
+ *
29
+ * The host provides the *default*; an explicit prop always wins. That ordering
30
+ * is deliberate — auto-detection keeps the common case free of configuration,
31
+ * but a component that can only be told implicitly is one you cannot unit-test
32
+ * without building a host around it, and cannot force when the detection is
33
+ * wrong.
34
+ *
35
+ * @param explicit The component's own `chrome` prop, if the caller set one.
36
+ */
37
+ export declare const useHostChrome: (explicit?: boolean) => boolean;
38
+ export interface PaneHostProviderProps extends IPaneHost {
39
+ /** Everything that should see this host. */
40
+ children: ReactNode;
41
+ }
42
+ /**
43
+ * Declare that everything inside is hosted.
44
+ *
45
+ * `SidebarPane` and the shell's tab factory do this for you. Wrap your own
46
+ * container in it if you draw a title bar and want hosted components to defer.
47
+ *
48
+ * @example
49
+ * ```tsx
50
+ * <PaneHostProvider chrome placement="bottom">
51
+ * <MyTitleBar />
52
+ * <ConnectedTerminalPane />
53
+ * </PaneHostProvider>
54
+ * ```
55
+ */
56
+ export declare const PaneHostProvider: ({ children, ...host }: PaneHostProviderProps) => import("react/jsx-runtime").JSX.Element;
@@ -11,6 +11,16 @@ export interface ShellLayoutProps {
11
11
  * renders its `component` in the sidebar when selected.
12
12
  */
13
13
  panels?: ISidebarPanel[];
14
+ /**
15
+ * Right-hand panels — inspectors, properties, anything about the current
16
+ * selection rather than about navigation.
17
+ *
18
+ * Registered exactly like `panels`, against a separate store, so the two
19
+ * sides open and close independently: file tree on the left and properties on
20
+ * the right, both at once. The right-hand rail appears only once at least one
21
+ * is registered.
22
+ */
23
+ inspectorPanels?: ISidebarPanel[];
14
24
  /** Slash commands available in the chat pane. */
15
25
  slashCommands?: ISlashCommandConfig[];
16
26
  /** Menu structure, keyed by top-level menu name. Items dispatch by `commandId`. */
@@ -92,4 +102,4 @@ export interface ShellLayoutProps {
92
102
  * );
93
103
  * ```
94
104
  */
95
- export declare const ShellLayout: ({ panels, slashCommands, menuConfig, statusBarConfig, title, rightMenuBarContent, centerMenuBarContent, layoutModel, initialLayoutJson, disableLocalStorage, onLayoutChange, }: ShellLayoutProps) => import("react/jsx-runtime").JSX.Element;
105
+ export declare const ShellLayout: ({ panels, inspectorPanels, slashCommands, menuConfig, statusBarConfig, title, rightMenuBarContent, centerMenuBarContent, layoutModel, initialLayoutJson, disableLocalStorage, onLayoutChange, }: ShellLayoutProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,65 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ /** Props every field in this family accepts. */
4
+ export interface FieldProps<T> {
5
+ /** Label shown above the control. */
6
+ label: string;
7
+ /** Current value. `undefined` renders as empty. */
8
+ value: T | undefined;
9
+ /** Called with the new value. */
10
+ onChange?: (value: T) => void;
11
+ /** One line under the label explaining the property. */
12
+ description?: string;
13
+ /**
14
+ * Selected subjects hold different values. The control shows a placeholder
15
+ * instead of a value, and editing applies to all of them.
16
+ */
17
+ mixed?: boolean;
18
+ /** Render read-only. */
19
+ disabled?: boolean;
20
+ /** Validation message, shown in the destructive colour. */
21
+ error?: string;
22
+ /** Extra classes merged onto the field wrapper. */
23
+ className?: string;
24
+ }
25
+ export interface FieldShellProps {
26
+ /** Label shown above the control, or beside it when `inline`. */
27
+ label: string;
28
+ /** Associates the label with the control. */
29
+ htmlFor?: string;
30
+ /** One line under the control explaining the property. Hidden while `error` is set. */
31
+ description?: string;
32
+ /** Validation message, shown in the destructive colour in place of the description. */
33
+ error?: string;
34
+ /** Selected subjects hold different values — renders the *Mixed* marker beside the label. */
35
+ mixed?: boolean;
36
+ /** The control itself. */
37
+ children: ReactNode;
38
+ /**
39
+ * Render the label beside the control rather than above. Suits checkboxes,
40
+ * where a label on its own line reads as a heading for nothing.
41
+ */
42
+ inline?: boolean;
43
+ /** Extra classes merged onto the field wrapper. */
44
+ className?: string;
45
+ }
46
+ /**
47
+ * The label, description and error frame shared by every property field.
48
+ *
49
+ * Exported so a bespoke field looks like the built-in ones without copying
50
+ * their markup — which is exactly what went wrong in the examples this family
51
+ * replaces, where the same label styling was pasted into three inspectors.
52
+ *
53
+ * @example
54
+ * ```tsx
55
+ * <FieldShell label="Opacity" description="0 to 1" htmlFor="opacity">
56
+ * <MyCustomSlider id="opacity" />
57
+ * </FieldShell>
58
+ * ```
59
+ */
60
+ export declare const FieldShell: ({ label, htmlFor, description, error, mixed, children, inline, className, }: FieldShellProps) => import("react/jsx-runtime").JSX.Element;
61
+ /** Shared control styling, so every field lines up. */
62
+ export declare const CONTROL_CLASS: string;
63
+ export declare const borderClass: (error?: string) => "border-border" | "border-destructive";
64
+ /** A stable id per field, so labels associate without the caller supplying one. */
65
+ export declare const fieldId: (label: string, suffix?: string) => string;
@@ -0,0 +1,132 @@
1
+ import { ReactNode } from 'react';
2
+ import { IPropertyAccessor, IPropertyValue } from '../../lib/properties';
3
+
4
+ /**
5
+ * One editable property.
6
+ *
7
+ * `type` names a renderer in the field registry. The built-ins are listed in
8
+ * {@link BUILT_IN_FIELD_TYPES}, and any string works once you register a
9
+ * renderer for it.
10
+ */
11
+ export interface IPropertyField<S = unknown> extends IPropertyAccessor<S> {
12
+ /** Label shown above the control. */
13
+ label: string;
14
+ /** Field type. Defaults to `"text"`. */
15
+ type?: string;
16
+ /** One line under the label explaining the property. */
17
+ description?: string;
18
+ /** Group heading this field sits under. Ungrouped fields come first. */
19
+ group?: string;
20
+ /** Render read-only. */
21
+ disabled?: boolean;
22
+ /**
23
+ * Hide the field for the current selection — a property that only applies to
24
+ * some node kinds, say. Receives the selected subjects.
25
+ */
26
+ when?: (subjects: S[]) => boolean;
27
+ /** Return a message to show the field as invalid. */
28
+ validate?: (value: unknown, subjects: S[]) => string | undefined;
29
+ /**
30
+ * Anything the renderer needs: `options` for a select, `rows` for a text
31
+ * area, `unit` for a number, or whatever your own field type expects.
32
+ */
33
+ props?: Record<string, unknown>;
34
+ }
35
+ /** What a field renderer receives. */
36
+ export interface FieldRendererProps<S = unknown> {
37
+ field: IPropertyField<S>;
38
+ /** The shared value across the selection, and whether subjects disagree. */
39
+ state: IPropertyValue;
40
+ /** Apply a value to every selected subject. */
41
+ onChange: (value: unknown) => void;
42
+ /** The selected subjects, for a renderer that needs more than one value. */
43
+ subjects: S[];
44
+ /** Validation message, if `validate` returned one. */
45
+ error?: string;
46
+ }
47
+ /** Renders one field. Register your own to extend the panel. */
48
+ export type FieldRenderer<S = unknown> = (props: FieldRendererProps<S>) => ReactNode;
49
+ /**
50
+ * The field types the panel understands out of the box.
51
+ *
52
+ * Spread it to add your own, or replace an entry to change how a type renders
53
+ * everywhere:
54
+ *
55
+ * ```tsx
56
+ * <PropertyPanel
57
+ * fieldTypes={{ ...BUILT_IN_FIELD_TYPES, latlong: LatLongField }}
58
+ * />
59
+ * ```
60
+ */
61
+ export declare const BUILT_IN_FIELD_TYPES: Record<string, FieldRenderer<any>>;
62
+ export interface PropertyPanelProps<S = unknown> {
63
+ /**
64
+ * The selected subjects. Zero renders the empty state, one edits normally,
65
+ * several edit together with a mixed indicator where values differ.
66
+ */
67
+ subjects: S[];
68
+ /** The properties to show. */
69
+ fields: IPropertyField<S>[];
70
+ /**
71
+ * Called with every subject updated. The panel produces new copies and never
72
+ * mutates, so this is the whole edit — apply it to your state.
73
+ */
74
+ onChange?: (updated: S[], field: IPropertyField<S>, value: unknown) => void;
75
+ /**
76
+ * Field renderers by type. Defaults to {@link BUILT_IN_FIELD_TYPES}; spread
77
+ * it to add your own without losing the built-ins.
78
+ */
79
+ fieldTypes?: Record<string, FieldRenderer<S>>;
80
+ /** Shown when nothing is selected. */
81
+ emptyState?: ReactNode;
82
+ /**
83
+ * Heading above the fields. Receives the selection, so it can read
84
+ * "3 nodes selected" as easily as a name.
85
+ */
86
+ title?: ReactNode | ((subjects: S[]) => ReactNode);
87
+ /** Group headings render in this order. Unlisted groups follow, in first-seen order. */
88
+ groupOrder?: string[];
89
+ /** Extra classes merged onto the root element. */
90
+ className?: string;
91
+ /** Accessible label for the region. Defaults to `"Properties"`. */
92
+ 'aria-label'?: string;
93
+ }
94
+ /**
95
+ * An inspector: the properties of whatever is selected, as editable fields.
96
+ *
97
+ * Give it the selection and a list of field descriptors. It reads each
98
+ * property across every selected subject, renders the matching field, and hands
99
+ * back new copies on edit — it holds no state and mutates nothing.
100
+ *
101
+ * Three selection states are handled for you:
102
+ *
103
+ * - **none** — the empty state.
104
+ * - **one** — ordinary editing.
105
+ * - **several** — shared values edit together; where subjects disagree the
106
+ * field shows *Mixed* rather than one subject's value, and editing applies to
107
+ * all of them.
108
+ *
109
+ * That last case is the one hand-rolled inspectors usually skip, and skipping
110
+ * it is how a multi-selection edit silently flattens data.
111
+ *
112
+ * Every field is also exported on its own — reach for `TextField` and friends
113
+ * when a property needs bespoke arrangement, and use `FieldShell` to make a
114
+ * custom control look like the rest.
115
+ *
116
+ * @example
117
+ * ```tsx
118
+ * <PropertyPanel
119
+ * subjects={selectedNodes}
120
+ * title={(nodes) => nodes.length > 1 ? `${nodes.length} nodes` : 'Node'}
121
+ * fields={[
122
+ * { key: 'data.label', label: 'Label' },
123
+ * { key: 'kind', label: 'Type', type: 'select',
124
+ * props: { options: [{ value: 'idea', label: 'Idea' }] } },
125
+ * { key: 'data.notes', label: 'Notes', type: 'textarea', group: 'Detail' },
126
+ * { key: 'data.tags', label: 'Tags', type: 'tags', group: 'Detail' },
127
+ * ]}
128
+ * onChange={(updated) => setNodes(merge(nodes, updated))}
129
+ * />
130
+ * ```
131
+ */
132
+ export declare const PropertyPanel: <S>({ subjects, fields, onChange, fieldTypes, emptyState, title, groupOrder, className, "aria-label": ariaLabel, }: PropertyPanelProps<S>) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,101 @@
1
+ import { ReactNode } from 'react';
2
+ import { FieldProps } from './FieldShell';
3
+
4
+ export interface TextFieldProps extends FieldProps<string> {
5
+ /** Hint shown in an empty control. Replaced by the mixed placeholder when `mixed`. */
6
+ placeholder?: string;
7
+ /** Input type, for the browser's own affordances. Defaults to `"text"`. */
8
+ inputType?: 'text' | 'url' | 'email' | 'tel' | 'password';
9
+ }
10
+ /** A single-line text property. */
11
+ export declare const TextField: ({ label, value, onChange, description, mixed, disabled, error, placeholder, inputType, className, }: TextFieldProps) => import("react/jsx-runtime").JSX.Element;
12
+ export interface TextAreaFieldProps extends FieldProps<string> {
13
+ /** Hint shown in an empty control. Replaced by the mixed placeholder when `mixed`. */
14
+ placeholder?: string;
15
+ /** Visible rows. Defaults to `4`. */
16
+ rows?: number;
17
+ }
18
+ /** A multi-line text property — notes, descriptions, evidence. */
19
+ export declare const TextAreaField: ({ label, value, onChange, description, mixed, disabled, error, placeholder, rows, className, }: TextAreaFieldProps) => import("react/jsx-runtime").JSX.Element;
20
+ export interface ISelectOption {
21
+ value: string;
22
+ label: string;
23
+ /** Render unselectable — an unavailable choice you still want visible. */
24
+ disabled?: boolean;
25
+ }
26
+ export interface SelectFieldProps extends FieldProps<string> {
27
+ /** The choices, in the order they should appear. */
28
+ options: ISelectOption[];
29
+ /** Shown as the first, unselected entry. */
30
+ placeholder?: string;
31
+ }
32
+ /** A choice from a fixed set. */
33
+ export declare const SelectField: ({ label, value, onChange, description, mixed, disabled, error, options, placeholder, className, }: SelectFieldProps) => import("react/jsx-runtime").JSX.Element;
34
+ export type CheckboxFieldProps = FieldProps<boolean>;
35
+ /**
36
+ * A boolean property.
37
+ *
38
+ * When `mixed`, the box renders indeterminate — neither on nor off — which is
39
+ * the only honest state for a selection that disagrees.
40
+ */
41
+ export declare const CheckboxField: ({ label, value, onChange, description, mixed, disabled, error, className, }: CheckboxFieldProps) => import("react/jsx-runtime").JSX.Element;
42
+ export interface NumberFieldProps extends FieldProps<number> {
43
+ /** Lowest accepted value. Enforced by the browser, not by this component. */
44
+ min?: number;
45
+ /** Highest accepted value. Enforced by the browser, not by this component. */
46
+ max?: number;
47
+ /** Increment for the spinner and arrow keys. */
48
+ step?: number;
49
+ /** Unit shown inside the control's trailing edge, e.g. `"px"`. */
50
+ unit?: string;
51
+ /** Hint shown in an empty control. Replaced by the mixed placeholder when `mixed`. */
52
+ placeholder?: string;
53
+ }
54
+ /** A numeric property. */
55
+ export declare const NumberField: ({ label, value, onChange, description, mixed, disabled, error, min, max, step, unit, placeholder, className, }: NumberFieldProps) => import("react/jsx-runtime").JSX.Element;
56
+ export interface ColorFieldProps extends FieldProps<string> {
57
+ /** Show the hex value in an editable text box beside the swatch. Default `true`. */
58
+ showValue?: boolean;
59
+ }
60
+ /** A colour property, as a swatch plus an editable hex value. */
61
+ export declare const ColorField: ({ label, value, onChange, description, mixed, disabled, error, showValue, className, }: ColorFieldProps) => import("react/jsx-runtime").JSX.Element;
62
+ export interface DateFieldProps extends FieldProps<string> {
63
+ /** Include a time component. Defaults to `false`. */
64
+ withTime?: boolean;
65
+ /** Earliest selectable date, in the same ISO form as `value`. */
66
+ min?: string;
67
+ /** Latest selectable date, in the same ISO form as `value`. */
68
+ max?: string;
69
+ }
70
+ /**
71
+ * A date property, as an ISO `yyyy-mm-dd` string (or `yyyy-mm-ddThh:mm` with
72
+ * `withTime`). A string rather than a `Date` so it round-trips through JSON
73
+ * unchanged.
74
+ */
75
+ export declare const DateField: ({ label, value, onChange, description, mixed, disabled, error, withTime, min, max, className, }: DateFieldProps) => import("react/jsx-runtime").JSX.Element;
76
+ export interface TagFieldProps extends FieldProps<string[]> {
77
+ /** Hint shown in the draft box. Defaults to `"Add and press Enter"`. */
78
+ placeholder?: string;
79
+ /** Offer these as a datalist while typing. */
80
+ suggestions?: string[];
81
+ }
82
+ /**
83
+ * A list-of-strings property.
84
+ *
85
+ * Enter or the add button commits the draft; duplicates and blanks are
86
+ * rejected silently rather than with an error, since neither is a mistake worth
87
+ * interrupting for.
88
+ */
89
+ export declare const TagField: ({ label, value, onChange, description, mixed, disabled, error, placeholder, suggestions, className, }: TagFieldProps) => import("react/jsx-runtime").JSX.Element;
90
+ export interface StaticFieldProps extends Omit<FieldProps<ReactNode>, 'onChange'> {
91
+ /** Render in a monospaced face — ids, hashes, coordinates. */
92
+ mono?: boolean;
93
+ }
94
+ /**
95
+ * A read-only property: an id, a timestamp, a computed total.
96
+ *
97
+ * Present because inspectors are full of values worth showing and not worth
98
+ * editing, and a disabled text input reads as "you may not edit this yet"
99
+ * rather than "this is derived".
100
+ */
101
+ export declare const StaticField: ({ label, value, description, mixed, mono, className, }: StaticFieldProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Property editing.
3
+ *
4
+ * An inspector is one of the things every application with a canvas, a tree or
5
+ * a selection ends up needing, and one of the things most often hand-rolled
6
+ * three times over. This family covers it at two levels: composable fields you
7
+ * arrange yourself, and a descriptor-driven panel built on them.
8
+ *
9
+ * Nothing here is graph-specific — the subjects can be nodes, edges, tree
10
+ * items, rows, or an application's settings object.
11
+ */
12
+ export { FieldShell, CONTROL_CLASS, borderClass, fieldId, type FieldShellProps, type FieldProps, } from './FieldShell';
13
+ export { TextField, TextAreaField, SelectField, CheckboxField, NumberField, ColorField, DateField, TagField, StaticField, type TextFieldProps, type TextAreaFieldProps, type SelectFieldProps, type CheckboxFieldProps, type NumberFieldProps, type ColorFieldProps, type DateFieldProps, type TagFieldProps, type StaticFieldProps, type ISelectOption, } from './fields';
14
+ export { PropertyPanel, BUILT_IN_FIELD_TYPES, type PropertyPanelProps, type IPropertyField, type FieldRenderer, type FieldRendererProps, } from './PropertyPanel';
@@ -1,4 +1,5 @@
1
1
  import { LucideIcon } from 'lucide-react';
2
+ import { PaneSide } from './SidebarPane';
2
3
 
3
4
  /** A single icon button in the {@link ActivityBar}. */
4
5
  export interface IActivityBarItem {
@@ -24,17 +25,25 @@ export interface ActivityBarProps {
24
25
  * deselect it — is the caller's decision, not the component's.
25
26
  */
26
27
  onSelect?: (id: string) => void;
28
+ /**
29
+ * Which edge of the shell the rail is docked to. Defaults to `"left"`.
30
+ *
31
+ * The divider and the active-item indicator both mirror, so a right-hand rail
32
+ * marks its selection on the edge facing the content.
33
+ */
34
+ side?: PaneSide;
27
35
  /** Extra classes merged onto the root `<aside>`. */
28
36
  className?: string;
29
37
  /** Accessible label for the landmark. Defaults to `"Activity Bar"`. */
30
38
  'aria-label'?: string;
31
39
  }
32
40
  /**
33
- * The narrow vertical icon rail on the left edge of the shell, used to switch
34
- * which panel the sidebar shows.
41
+ * The narrow vertical icon rail at the edge of the shell, used to switch which
42
+ * panel the adjacent {@link SidebarPane} shows.
35
43
  *
36
- * Controlled — it holds no selection state. For the store-backed variant used
37
- * by `ShellLayout`, see `ConnectedActivityBar`.
44
+ * Controlled — it holds no selection state. `side` docks it left or right; for
45
+ * the store-backed variants used by `ShellLayout`, see `ConnectedActivityBar`
46
+ * and `ConnectedInspectorBar`.
38
47
  *
39
48
  * @example
40
49
  * ```tsx
@@ -45,4 +54,4 @@ export interface ActivityBarProps {
45
54
  * />
46
55
  * ```
47
56
  */
48
- export declare const ActivityBar: ({ items, bottomItems, activeId, onSelect, className, "aria-label": ariaLabel, }: ActivityBarProps) => import("react/jsx-runtime").JSX.Element;
57
+ export declare const ActivityBar: ({ items, bottomItems, activeId, onSelect, side, className, "aria-label": ariaLabel, }: ActivityBarProps) => import("react/jsx-runtime").JSX.Element;
@@ -38,17 +38,31 @@ export interface ChatPaneProps {
38
38
  placeholder?: string;
39
39
  /** Shown in place of the transcript when `messages` is empty. */
40
40
  emptyState?: React.ReactNode;
41
+ /**
42
+ * Draw the title bar.
43
+ *
44
+ * Left unset, this follows the host: inside a `SidebarPane` or a dockable tab
45
+ * the title is already drawn, so the pane omits its own. Set it explicitly to
46
+ * force either way — which is also what lets the component be tested without
47
+ * a host around it.
48
+ */
49
+ chrome?: boolean;
41
50
  /** Extra classes merged onto the root element. */
42
51
  className?: string;
43
52
  }
44
53
  /**
45
- * A docked chat panel: a scrolling transcript over a composer with slash-command
54
+ * A chat panel: a scrolling transcript over a composer with slash-command
46
55
  * autocomplete.
47
56
  *
48
57
  * Fully controlled — it owns only the in-progress input and the suggestion
49
58
  * highlight. Messages, sending and visibility belong to the caller, so the same
50
59
  * component works against a local array, a websocket, or an LLM endpoint. For
51
- * the store-backed variant used by `ShellLayout`, see `ConnectedChatPane`.
60
+ * the store-backed variant, see `ConnectedChatPane`.
61
+ *
62
+ * **It fills whatever hosts it.** Register it as a sidebar panel, an inspector
63
+ * panel or a dockable tab and it takes that shape; it carries no width or edge
64
+ * of its own. Inside a host that draws a title bar it omits its own header —
65
+ * see `chrome` to override.
52
66
  *
53
67
  * @example
54
68
  * ```tsx
@@ -60,4 +74,4 @@ export interface ChatPaneProps {
60
74
  * />
61
75
  * ```
62
76
  */
63
- export declare const ChatPane: ({ messages, onSend, slashCommands, onSlashCommand, onClose, title, placeholder, emptyState, className, }: ChatPaneProps) => import("react/jsx-runtime").JSX.Element;
77
+ export declare const ChatPane: ({ messages, onSend, slashCommands, onSlashCommand, onClose, title, placeholder, emptyState, chrome, className, }: ChatPaneProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,5 +1,7 @@
1
1
  import { ReactNode } from 'react';
2
2
 
3
+ /** Which edge of the shell a pane or rail is docked to. */
4
+ export type PaneSide = 'left' | 'right';
3
5
  export interface SidebarPaneProps {
4
6
  /**
5
7
  * Heading text. Rendered uppercase with letter-spacing; pass it in natural
@@ -15,23 +17,40 @@ export interface SidebarPaneProps {
15
17
  onClose?: () => void;
16
18
  /** Fixed pane width, as a CSS length. Defaults to `"300px"`. */
17
19
  width?: string;
20
+ /**
21
+ * Which edge of the shell the pane is docked to. Defaults to `"left"`.
22
+ *
23
+ * Only the divider moves — a right-hand pane is the same container, so an
24
+ * inspector does not need a component of its own.
25
+ */
26
+ side?: PaneSide;
18
27
  /** Extra classes merged onto the root `<aside>`. */
19
28
  className?: string;
20
29
  }
21
30
  /**
22
- * The collapsible panel beside the {@link ActivityBar}: a titled header with an
23
- * optional close button, over a scrolling body.
31
+ * A docked side panel: a titled header with an optional close button, over a
32
+ * scrolling body.
24
33
  *
25
34
  * A pure container — it decides nothing about which panel is showing and holds
26
- * no state. Render whatever you like as `children`. For the store-backed
27
- * variant that resolves the active panel from `useSidebarStore`, see
28
- * `ConnectedSidebarPane`.
35
+ * no state. Render whatever you like as `children`.
36
+ *
37
+ * `side` docks it left (beside the {@link ActivityBar}) or right, where it
38
+ * becomes an inspector. Both are the same component deliberately: before this
39
+ * prop existed every application hand-rolled its own right-hand
40
+ * `<div className="w-80 border-l …">`, and they all drifted apart.
41
+ *
42
+ * For the store-backed variants used by `ShellLayout`, see
43
+ * `ConnectedSidebarPane` and `ConnectedInspectorPane`.
29
44
  *
30
45
  * @example
31
46
  * ```tsx
32
47
  * <SidebarPane title="Explorer" onClose={() => setActive(null)}>
33
48
  * <TreeWidget data={files} />
34
49
  * </SidebarPane>
50
+ *
51
+ * <SidebarPane title="Properties" side="right" width="320px">
52
+ * <PropertyPanel subjects={selected} fields={fields} />
53
+ * </SidebarPane>
35
54
  * ```
36
55
  */
37
- export declare const SidebarPane: ({ title, children, onClose, width, className, }: SidebarPaneProps) => import("react/jsx-runtime").JSX.Element;
56
+ export declare const SidebarPane: ({ title, children, onClose, width, side, className, }: SidebarPaneProps) => import("react/jsx-runtime").JSX.Element;