+ ): UnresolvedCompositeEditorData | MultipleEditorData | undefined {
+ const allMatchingEditors = this.getAllCompatibleModelClasses(modelClasses).map(
+ (compatibleConstructor): UnresolvedCompositeEditorData => ({
+ title: this.modelLibrary.lookupModelMetadata(compatibleConstructor)!.displayName,
+ modelClass: compatibleConstructor,
+ propertyTypeInstance: typeInstance,
+ getPropertyLocation: getPropertyLocation as () => PropertyLocation
,
+ kind: EditorKind.Unresolved
+ })
+ );
+
+ if (allMatchingEditors.length === 0) {
+ return undefined;
+ }
+ if (allMatchingEditors.length === 1) {
+ return {
+ ...allMatchingEditors[0],
+ title: displayName
+ };
+ }
+
+ return {
+ title: displayName,
+ compatibleEditors: allMatchingEditors,
+ kind: EditorKind.Multiple
+ };
+ }
+
+ private getAllCompatibleModelClasses(modelClasses: ObjectConstructable[]): ObjectConstructable[] {
+ return uniq(modelClasses.flatMap(modeClass => this.modelLibrary.getAllCompatibleModelClasses(modeClass)));
+ }
+
+ private getThemeEditorForClass(
+ modelClass: ObjectConstructable
+ ): UnresolvedCompositeEditorData | MultipleEditorData | undefined {
+ if (this.rendererLibrary.hasRenderer(modelClass)) {
+ return this.getEditorMatchingModelClasses([Theme], 'Theme', { key: '_theme' }, model =>
+ this.themeManager.getPropertyLocationForTheme(model)
+ );
+ }
+
+ return undefined;
+ }
+
+ private getDataEditorForClass(
+ modelClass: ObjectConstructable
+ ): UnresolvedCompositeEditorData | MultipleEditorData | undefined {
+ // Always defined, we've already done this lookup to get this far
+ const modelMetadata = this.modelLibrary.lookupModelMetadata(modelClass)!;
+
+ return this.getEditorMatchingModelClasses(modelMetadata.supportedDataSourceTypes, 'Data', { key: '_data' }, model =>
+ this.dataSourceManager.getPropertyLocationForData(model)
+ );
+ }
+}
+
+export type NestedEditorData = UnresolvedCompositeEditorData | LeafEditorData | MultipleEditorData;
+
+interface EditorMetadata {
+ /**
+ * Renderable object for editor
+ */
+ renderer: UnknownConstructable;
+}
+
+/**
+ * Discriminating enum for editor subtypes
+ */
+export enum EditorKind {
+ /**
+ * Indicates composite type
+ * @see CompositeEditorData
+ */
+ Composite,
+ /**
+ * Indicates leaf type
+ * @see LeafEditorData
+ */
+ Leaf,
+ /**
+ * Indicates multiple type
+ * @see MultipleEditorData
+ */
+ Multiple,
+ /**
+ * Indicates unresolved type
+ * @see UnresolvedCompositeEditorData
+ */
+ Unresolved
+}
+
+/**
+ * Data representing an editor or editor fragment for the referenced `modelClass`
+ */
+export interface EditorData {
+ /**
+ * Display title for editor
+ */
+ title: string;
+
+ /**
+ * Discriminator
+ */
+ kind: EditorKind;
+}
+
+/**
+ * Data for a grouping of editors based on a model. This does not represent an editor itself, but should
+ * be composed of subeditors.
+ */
+export interface CompositeEditorData extends EditorData {
+ /**
+ * Editors for properties of this model
+ */
+ subeditors: NestedEditorData[];
+
+ /**
+ * @inheritdoc
+ */
+ kind: EditorKind.Composite;
+
+ /**
+ * Editor for the theme of this model. Undefined if model is not themable.
+ */
+ themeEditor?: UnresolvedCompositeEditorData | MultipleEditorData;
+
+ /**
+ * Editor for the data source of this model. Undefined if model does not support data
+ */
+ dataEditor?: UnresolvedCompositeEditorData | MultipleEditorData;
+}
+
+/**
+ * Represents an editor for a leaf property - that is a primitive or primitive collection,
+ * as opposed to a model
+ */
+export interface LeafEditorData extends EditorData {
+ /**
+ * Validator for property, returning a string on validation failure or undefined otherwise
+ */
+ validator(value: unknown): string | undefined;
+
+ /**
+ * Renderable object for editor
+ */
+ editor: UnknownConstructable;
+
+ /**
+ * Metadata for the editable property
+ */
+ propertyMetadata: ModelPropertyMetadata