nexus-shell 0.2.8 → 0.2.9
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 +3 -0
- package/dist/components/graph/GraphEdge.d.ts +2 -0
- package/dist/components/properties/FieldShell.d.ts +65 -0
- package/dist/components/properties/PropertyPanel.d.ts +132 -0
- package/dist/components/properties/fields.d.ts +101 -0
- package/dist/components/properties/index.d.ts +14 -0
- package/dist/index.d.ts +2 -0
- package/dist/lib/properties.d.ts +67 -0
- package/dist/nexus-shell.es.js +2316 -1684
- package/dist/nexus-shell.umd.js +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -138,6 +138,9 @@ shell's own frame and keep every registration working.
|
|
|
138
138
|
| `useGraphLayout` | Auto layout engines, with a freeform escape for hand placement |
|
|
139
139
|
| `layeredLayout` | Layered tree layout in four directions; handles cycles |
|
|
140
140
|
| `gridLayout` | Uniform grid, ignoring edges |
|
|
141
|
+
| `PropertyPanel` | Inspector for the current selection, with mixed-value editing |
|
|
142
|
+
| `TextField` … `StaticField` | Nine composable property fields, usable on their own |
|
|
143
|
+
| `FieldShell` | The label / description / error frame, for fields of your own |
|
|
141
144
|
| `UserProfile` | Avatar and identity widget |
|
|
142
145
|
|
|
143
146
|
## 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,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';
|
package/dist/index.d.ts
CHANGED
|
@@ -59,5 +59,7 @@ export { UserProfile, type UserProfileProps, type IUserProfile, type IUserProfil
|
|
|
59
59
|
export * from './components/graph';
|
|
60
60
|
export { BUILT_IN_LAYOUTS, gridLayout, layeredLayout, type GraphLayout, type GridLayoutOptions, type LayeredLayoutOptions, type LayoutDirection, } from './lib/layout';
|
|
61
61
|
export { DEFAULT_NODE_SIZE, IDENTITY_VIEWPORT, clampScale, edgesOf, findNeighbour, fitViewport, graphBounds, isConnected, nextId, nodeRect, placeRelativeTo, portPoint, rectCenter, removeNode, resolvePorts, toGraphSpace, toScreenSpace, unionRect, viewportRect, centerOn, zoomAt, type Direction, type GraphPort, type IGraphEdge, type IGraphNode, type IPoint, type IRect, type IViewport, } from './lib/graph';
|
|
62
|
+
export * from './components/properties';
|
|
63
|
+
export { getPath, setPath, readValue, readValues, writeValue, writeValues, type IPropertyAccessor, type IPropertyValue, } from './lib/properties';
|
|
62
64
|
export { cn } from './lib/cn';
|
|
63
65
|
export { BUNDLED_THEMES, BUNDLED_THEME_CLASSES, themeClass, type IBundledTheme, type BundledThemeId, } from './lib/themes';
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading and writing property values across one or many subjects.
|
|
3
|
+
*
|
|
4
|
+
* The awkward parts of a property panel are not the inputs — they are knowing
|
|
5
|
+
* what to show when several things are selected and their values disagree, and
|
|
6
|
+
* writing a nested value back without mutating anything. Both live here, as
|
|
7
|
+
* pure functions, so they can be tested without rendering.
|
|
8
|
+
*/
|
|
9
|
+
/** A value shared by every selected subject, or a marker that they differ. */
|
|
10
|
+
export interface IPropertyValue<T = unknown> {
|
|
11
|
+
/** The common value, or `undefined` when subjects disagree or none are selected. */
|
|
12
|
+
value: T | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* Subjects hold different values for this property.
|
|
15
|
+
*
|
|
16
|
+
* A field showing `mixed` must not display one subject's value as if it were
|
|
17
|
+
* everyone's — that is how a multi-selection edit silently flattens data.
|
|
18
|
+
*/
|
|
19
|
+
mixed: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Read a dot-separated path, e.g. `"data.label"`.
|
|
23
|
+
*
|
|
24
|
+
* Returns `undefined` for a missing path rather than throwing, because a
|
|
25
|
+
* property panel routinely renders objects that don't have every field yet.
|
|
26
|
+
*/
|
|
27
|
+
export declare const getPath: (subject: unknown, path: string) => unknown;
|
|
28
|
+
/**
|
|
29
|
+
* Return a copy of `subject` with `path` set to `value`.
|
|
30
|
+
*
|
|
31
|
+
* Every object along the path is cloned, so the original is untouched and React
|
|
32
|
+
* sees a new reference at each level it needs to. Missing intermediate objects
|
|
33
|
+
* are created.
|
|
34
|
+
*/
|
|
35
|
+
export declare const setPath: <T>(subject: T, path: string, value: unknown) => T;
|
|
36
|
+
/** How a property is read from and written to a subject. */
|
|
37
|
+
export interface IPropertyAccessor<S = unknown, V = unknown> {
|
|
38
|
+
/** Dot path, used for both reading and writing unless overridden. */
|
|
39
|
+
key: string;
|
|
40
|
+
/** Custom read. Defaults to `getPath(subject, key)`. */
|
|
41
|
+
get?: (subject: S) => V;
|
|
42
|
+
/** Custom write, returning a new subject. Defaults to `setPath`. */
|
|
43
|
+
set?: (subject: S, value: V) => S;
|
|
44
|
+
}
|
|
45
|
+
/** Read a property from one subject, honouring a custom accessor. */
|
|
46
|
+
export declare const readValue: <S, V>(subject: S, accessor: IPropertyAccessor<S, V>) => V;
|
|
47
|
+
/** Write a property to one subject, honouring a custom accessor. */
|
|
48
|
+
export declare const writeValue: <S, V>(subject: S, accessor: IPropertyAccessor<S, V>, value: V) => S;
|
|
49
|
+
/**
|
|
50
|
+
* Read a property across a selection.
|
|
51
|
+
*
|
|
52
|
+
* With one subject this is just its value. With several, it is the shared value
|
|
53
|
+
* — or `mixed`, if they disagree. With none, it is empty and not mixed.
|
|
54
|
+
*
|
|
55
|
+
* Comparison is by `Object.is`, so two structurally equal objects at the same
|
|
56
|
+
* path read as mixed. That is deliberate: a panel cannot know whether deep
|
|
57
|
+
* equality is meaningful for an arbitrary payload, and claiming agreement it
|
|
58
|
+
* hasn't verified is the more damaging error.
|
|
59
|
+
*/
|
|
60
|
+
export declare const readValues: <S, V>(subjects: S[], accessor: IPropertyAccessor<S, V>) => IPropertyValue<V>;
|
|
61
|
+
/**
|
|
62
|
+
* Apply a value to every subject, returning new copies.
|
|
63
|
+
*
|
|
64
|
+
* Subjects that already hold the value are returned unchanged by identity, so
|
|
65
|
+
* an edit across a large selection only invalidates what actually moved.
|
|
66
|
+
*/
|
|
67
|
+
export declare const writeValues: <S, V>(subjects: S[], accessor: IPropertyAccessor<S, V>, value: V) => S[];
|