Browse Source

wip Custom Property references

David Sehnal 5 years ago
parent
commit
aabb931d27

+ 3 - 2
src/mol-model-props/common/custom-element-property.ts

@@ -104,8 +104,9 @@ namespace CustomElementProperty {
             getParams: () => ({}),
             defaultValues: {},
             isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && !!modelProperty.get(ctx.structure.models[0]).value,
-            ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => {
-                return data.structure ? modelProperty.attach(ctx, data.structure.models[0]) : Promise.resolve()
+            ensureCustomProperties: {
+                attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? modelProperty.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
+                detach: (_, data: ThemeDataContext) => data.structure && data.structure.models[0].customProperties.reference(modelProperty.descriptor, false)
             }
         }
     }

+ 3 - 1
src/mol-model-props/common/custom-model-property.ts

@@ -51,7 +51,8 @@ namespace CustomModelProperty {
             getParams: builder.getParams,
             defaultParams: builder.defaultParams,
             isApplicable: builder.isApplicable,
-            attach: async (ctx: CustomProperty.Context, data: Model, props: Partial<PD.Values<Params>> = {}) => {
+            attach: async (ctx: CustomProperty.Context, data: Model, props: Partial<PD.Values<Params>> = {}, addRef) => {
+                if (addRef) data.customProperties.reference(builder.descriptor, true);
                 const property = get(data)
                 const p = { ...property.props, ...props }
                 if (property.data.value && PD.areEqual(builder.defaultParams, property.props, p)) return
@@ -59,6 +60,7 @@ namespace CustomModelProperty {
                 data.customProperties.add(builder.descriptor);
                 set(data, p, value);
             },
+            ref: (data: Model, add: boolean) => data.customProperties.reference(builder.descriptor, add),
             get: (data: Model) => get(data)?.data,
             set: (data: Model, props: Partial<PD.Values<Params>> = {}) => {
                 const property = get(data)

+ 2 - 1
src/mol-model-props/common/custom-property.ts

@@ -31,7 +31,8 @@ namespace CustomProperty {
         readonly getParams: (data: Data) => Params
         readonly defaultParams: Params
         readonly isApplicable: (data: Data) => boolean
-        readonly attach: (ctx: Context, data: Data, props?: Partial<PD.Values<Params>>) => Promise<void>
+        readonly attach: (ctx: Context, data: Data, props?: Partial<PD.Values<Params>>, addRef?: boolean) => Promise<void>
+        readonly ref: (data: Data, add: boolean) => void
         readonly get: (data: Data) => ValueBox<Value | undefined>
         readonly set: (data: Data, props: PD.Values<Params>, value?: Value) => void
     }

+ 3 - 1
src/mol-model-props/common/custom-structure-property.ts

@@ -51,7 +51,8 @@ namespace CustomStructureProperty {
             getParams: builder.getParams,
             defaultParams: builder.defaultParams,
             isApplicable: builder.isApplicable,
-            attach: async (ctx: CustomProperty.Context, data: Structure, props: Partial<PD.Values<Params>> = {}) => {
+            attach: async (ctx: CustomProperty.Context, data: Structure, props: Partial<PD.Values<Params>> = {}, addRef) => {
+                if (addRef) data.customPropertyDescriptors.reference(builder.descriptor, true);
                 if (builder.type === 'root') data = data.root
                 const property = get(data)
                 const p = { ...property.props, ...props }
@@ -60,6 +61,7 @@ namespace CustomStructureProperty {
                 data.customPropertyDescriptors.add(builder.descriptor);
                 set(data, p, value);
             },
+            ref: (data: Structure, add: boolean) => data.customPropertyDescriptors.reference(builder.descriptor, add),
             get: (data: Structure) => get(data).data,
             set: (data: Structure, props: Partial<PD.Values<Params>> = {}, value?: Value) => {
                 if (builder.type === 'root') data = data.root

+ 3 - 2
src/mol-model-props/computed/themes/accessible-surface-area.ts

@@ -71,7 +71,8 @@ export const AccessibleSurfaceAreaColorThemeProvider: ColorTheme.Provider<Access
     getParams: getAccessibleSurfaceAreaColorThemeParams,
     defaultValues: PD.getDefaultValues(AccessibleSurfaceAreaColorThemeParams),
     isApplicable: (ctx: ThemeDataContext) => !!ctx.structure,
-    ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => {
-        return data.structure ? AccessibleSurfaceAreaProvider.attach(ctx, data.structure) : Promise.resolve()
+    ensureCustomProperties: {
+        attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? AccessibleSurfaceAreaProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
+        detach: (_, data) => data.structure && data.structure.customPropertyDescriptors.reference(AccessibleSurfaceAreaProvider.descriptor, false)
     }
 }

+ 3 - 2
src/mol-model-props/computed/themes/interaction-type.ts

@@ -113,7 +113,8 @@ export const InteractionTypeColorThemeProvider: ColorTheme.Provider<InteractionT
     getParams: getInteractionTypeColorThemeParams,
     defaultValues: PD.getDefaultValues(InteractionTypeColorThemeParams),
     isApplicable: (ctx: ThemeDataContext) => !!ctx.structure,
-    ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => {
-        return data.structure ? InteractionsProvider.attach(ctx, data.structure) : Promise.resolve()
+    ensureCustomProperties: {
+        attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? InteractionsProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
+        detach: (_, data) => data.structure && data.structure.customPropertyDescriptors.reference(InteractionsProvider.descriptor, false)
     }
 }

+ 4 - 3
src/mol-model-props/integrative/cross-link-restraint/color.ts

@@ -68,7 +68,8 @@ export const CrossLinkColorThemeProvider: ColorTheme.Provider<CrossLinkColorThem
     getParams: getCrossLinkColorThemeParams,
     defaultValues: PD.getDefaultValues(CrossLinkColorThemeParams),
     isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && CrossLinkRestraint.isApplicable(ctx.structure),
-    ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => {
-        return data.structure ? CrossLinkRestraintProvider.attach(ctx, data.structure) : Promise.resolve()
-    }
+    ensureCustomProperties: {
+        attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? CrossLinkRestraintProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
+        detach: (_, data) => data.structure && data.structure.customPropertyDescriptors.reference(CrossLinkRestraintProvider.descriptor, false)
+    }    
 }

+ 3 - 2
src/mol-model-props/pdbe/themes/structure-quality-report.ts

@@ -102,7 +102,8 @@ export const StructureQualityReportColorThemeProvider: ColorTheme.Provider<Param
     },
     defaultValues: PD.getDefaultValues(StructureQualityReportColorThemeParams),
     isApplicable: (ctx: ThemeDataContext) => StructureQualityReport.isApplicable(ctx.structure?.models[0]),
-    ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => {
-        return data.structure ? StructureQualityReportProvider.attach(ctx, data.structure.models[0]) : Promise.resolve()
+    ensureCustomProperties: {
+        attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? StructureQualityReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
+        detach: (_, data) => data.structure && data.structure.models[0].customProperties.reference(StructureQualityReportProvider.descriptor, false)
     }
 }

+ 3 - 2
src/mol-model-props/rcsb/themes/assembly-symmetry-cluster.ts

@@ -98,7 +98,8 @@ export const AssemblySymmetryClusterColorThemeProvider: ColorTheme.Provider<Asse
     getParams: getAssemblySymmetryClusterColorThemeParams,
     defaultValues: PD.getDefaultValues(AssemblySymmetryClusterColorThemeParams),
     isApplicable: (ctx: ThemeDataContext) => AssemblySymmetry.isApplicable(ctx.structure),
-    ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => {
-        return data.structure ? AssemblySymmetryProvider.attach(ctx, data.structure) : Promise.resolve()
+    ensureCustomProperties: {
+        attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? AssemblySymmetryProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
+        detach: (_, data) => data.structure && data.structure.customPropertyDescriptors.reference(AssemblySymmetryProvider.descriptor, false)
     }
 }

+ 3 - 2
src/mol-model-props/rcsb/themes/density-fit.ts

@@ -67,7 +67,8 @@ export const DensityFitColorThemeProvider: ColorTheme.Provider<{}> = {
     getParams: () => ({}),
     defaultValues: PD.getDefaultValues({}),
     isApplicable: (ctx: ThemeDataContext) => ValidationReport.isApplicable(ctx.structure?.models[0]),
-    ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => {
-        return data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0]) : Promise.resolve()
+    ensureCustomProperties: {
+        attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
+        detach: (_, data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
     }
 }

+ 3 - 2
src/mol-model-props/rcsb/themes/geometry-quality.ts

@@ -107,7 +107,8 @@ export const GeometryQualityColorThemeProvider: ColorTheme.Provider<GeometricQua
     getParams: getGeometricQualityColorThemeParams,
     defaultValues: PD.getDefaultValues(getGeometricQualityColorThemeParams({})),
     isApplicable: (ctx: ThemeDataContext) => ValidationReport.isApplicable(ctx.structure?.models[0]),
-    ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => {
-        return data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0]) : Promise.resolve()
+    ensureCustomProperties: {
+        attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
+        detach: (_, data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
     }
 }

+ 3 - 2
src/mol-model-props/rcsb/themes/random-coil-index.ts

@@ -58,7 +58,8 @@ export const RandomCoilIndexColorThemeProvider: ColorTheme.Provider<{}> = {
     getParams: () => ({}),
     defaultValues: PD.getDefaultValues({}),
     isApplicable: (ctx: ThemeDataContext) => ValidationReport.isApplicable(ctx.structure?.models[0]),
-    ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => {
-        return data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0]) : Promise.resolve()
+    ensureCustomProperties: {
+        attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
+        detach: (_, data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
     }
 }

+ 16 - 1
src/mol-model/structure/common/custom-property.ts

@@ -41,6 +41,7 @@ namespace CustomPropertyDescriptor {
 class CustomProperties {
     private _list: CustomPropertyDescriptor[] = [];
     private _set = new Set<CustomPropertyDescriptor>();
+    private _refs = new Map<CustomPropertyDescriptor, number>();
 
     get all(): ReadonlyArray<CustomPropertyDescriptor> {
         return this._list;
@@ -53,7 +54,21 @@ class CustomProperties {
         this._set.add(desc);
     }
 
+    reference(desc: CustomPropertyDescriptor<any>, add: boolean) {
+        let refs = this._refs.get(desc);
+        if (refs === void 0) {
+            refs = 0;
+            this._refs.set(desc, refs);
+        }
+        refs += add ? 1 : -1;
+        this._refs.set(desc, Math.max(refs, 0));
+    }
+
+    hasReference(desc: CustomPropertyDescriptor<any>) {
+        return (this._refs.get(desc) || 0) > 0;
+    }
+
     has(desc: CustomPropertyDescriptor<any>): boolean {
-        return this._set.has(desc);
+        return this._refs.has(desc);
     }
 }

+ 24 - 2
src/mol-plugin-state/transforms/model.ts

@@ -701,6 +701,17 @@ const CustomModelProperties = PluginStateTransform.BuiltIn({
             await attachModelProps(a.data, ctx, taskCtx, params);
             return new SO.Molecule.Model(a.data, { label: 'Model Props' });
         });
+    },
+    update({ a, oldParams, newParams }, ctx: PluginContext) {
+        return Task.create('Custom Props', async taskCtx => {
+            for (const name of oldParams.autoAttach) {
+                const property = ctx.customModelProperties.get(name);
+                if (!property) continue;
+                a.data.customProperties.reference(property.descriptor, false);
+            }
+            await attachModelProps(a.data, ctx, taskCtx, newParams);
+            return StateTransformer.UpdateResult.Updated;
+        });
     }
 });
 async function attachModelProps(model: Model, ctx: PluginContext, taskCtx: RuntimeContext, params: ReturnType<CustomModelProperties['createDefaultParams']>) {
@@ -711,7 +722,7 @@ async function attachModelProps(model: Model, ctx: PluginContext, taskCtx: Runti
         const props = properties[name]
         if (autoAttach.includes(name)) {
             try {
-                await property.attach(propertyCtx, model, props)
+                await property.attach(propertyCtx, model, props, true)
             } catch (e) {
                 ctx.log.warn(`Error attaching model prop '${name}': ${e}`);
             }
@@ -736,6 +747,17 @@ const CustomStructureProperties = PluginStateTransform.BuiltIn({
             await attachStructureProps(a.data, ctx, taskCtx, params);
             return new SO.Molecule.Structure(a.data, { label: 'Structure Props' });
         });
+    },
+    update({ a, oldParams, newParams }, ctx: PluginContext) {
+        return Task.create('Custom Props', async taskCtx => {
+            for (const name of oldParams.autoAttach) {
+                const property = ctx.customModelProperties.get(name);
+                if (!property) continue;
+                a.data.customPropertyDescriptors.reference(property.descriptor, false);
+            }
+            await attachStructureProps(a.data, ctx, taskCtx, newParams);
+            return StateTransformer.UpdateResult.Updated;
+        });
     }
 });
 async function attachStructureProps(structure: Structure, ctx: PluginContext, taskCtx: RuntimeContext, params: ReturnType<CustomStructureProperties['createDefaultParams']>) {
@@ -746,7 +768,7 @@ async function attachStructureProps(structure: Structure, ctx: PluginContext, ta
         const props = properties[name]
         if (autoAttach.includes(name)) {
             try {
-                await property.attach(propertyCtx, structure, props)
+                await property.attach(propertyCtx, structure, props, true)
             } catch (e) {
                 ctx.log.warn(`Error attaching structure prop '${name}': ${e}`);
             }

+ 2 - 2
src/mol-plugin-state/transforms/representation.ts

@@ -15,7 +15,7 @@ import { BuiltInVolumeRepresentationsName } from '../../mol-repr/volume/registry
 import { VolumeParams } from '../../mol-repr/volume/representation';
 import { StateTransformer, StateObject } from '../../mol-state';
 import { Task } from '../../mol-task';
-import { BuiltInColorThemeName, ColorTheme, BuiltInColorThemes } from '../../mol-theme/color';
+import { BuiltInColorThemeName, ColorTheme } from '../../mol-theme/color';
 import { BuiltInSizeThemeName, SizeTheme } from '../../mol-theme/size';
 import { Theme, ThemeRegistryContext } from '../../mol-theme/theme';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
@@ -227,6 +227,7 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({
             const provider = plugin.structureRepresentation.registry.get(newParams.type.name)
             if (provider.ensureCustomProperties) await provider.ensureCustomProperties(propertyCtx, a.data)
             const props = { ...b.data.repr.props, ...newParams.type.params }
+            await Theme.releaseDependencies(propertyCtx, plugin.structureRepresentation.themeCtx, { structure: a.data }, oldParams)
             await Theme.ensureDependencies(propertyCtx, plugin.structureRepresentation.themeCtx, { structure: a.data }, newParams)
             b.data.repr.setTheme(Theme.create(plugin.structureRepresentation.themeCtx, { structure: a.data }, newParams));
             await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx);
@@ -238,7 +239,6 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({
         if (src.colorTheme.name !== 'uniform' || tar.colorTheme.name !== 'uniform') {
             return t <= 0.5 ? src : tar;
         }
-        BuiltInColorThemes
         const from = src.colorTheme.params.value as Color, to = tar.colorTheme.params.value as Color;
         const value = Color.interpolate(from, to, t);
         return {

+ 3 - 2
src/mol-plugin/behavior/dynamic/custom-props/computed/accessible-surface-area.ts

@@ -64,7 +64,7 @@ function accessibleSurfaceAreaLabel(loci: Loci): string | undefined {
         if (loci.elements.length === 0) return;
 
         const accessibleSurfaceArea = AccessibleSurfaceAreaProvider.get(loci.structure).value
-        if (!accessibleSurfaceArea) return;
+        if (!accessibleSurfaceArea || loci.structure.customPropertyDescriptors.hasReference(AccessibleSurfaceAreaProvider.descriptor)) return;
 
         const { getSerialIndex } = loci.structure.root.serialMapping
         const { area, serialResidueIndex } = accessibleSurfaceArea
@@ -73,6 +73,7 @@ function accessibleSurfaceAreaLabel(loci: Loci): string | undefined {
 
         for (const { indices, unit } of loci.elements) {
             const { elements } = unit
+
             OrderedSet.forEach(indices, idx => {
                 const rSI = serialResidueIndex[getSerialIndex(unit, elements[idx])]
                 if (rSI !== -1 && !seen.has(rSI)) {
@@ -88,7 +89,7 @@ function accessibleSurfaceAreaLabel(loci: Loci): string | undefined {
 
     } else if(loci.kind === 'structure-loci') {
         const accessibleSurfaceArea = AccessibleSurfaceAreaProvider.get(loci.structure).value
-        if (!accessibleSurfaceArea) return;
+        if (!accessibleSurfaceArea || loci.structure.customPropertyDescriptors.hasReference(AccessibleSurfaceAreaProvider.descriptor)) return;
 
         return `Accessible Surface Area <small>(Whole Structure)</small>: ${arraySum(accessibleSurfaceArea.area).toFixed(2)} \u212B<sup>2</sup>`;
     }

+ 2 - 2
src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts

@@ -25,13 +25,13 @@ export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: bo
 
         private labelPDBeValidation = (loci: Loci): string | undefined => {
             if (!this.params.showTooltip) return void 0;
-
+            
             switch (loci.kind) {
                 case 'element-loci':
                     if (loci.elements.length === 0) return void 0;
                     const e = loci.elements[0];
                     const u = e.unit;
-                    if (!u.model.customProperties.has(StructureQualityReportProvider.descriptor)) return void 0;
+                    if (!u.model.customProperties.has(StructureQualityReportProvider.descriptor) || !u.model.customProperties.hasReference(StructureQualityReportProvider.descriptor)) return void 0;
 
                     const se = StructureElement.Location.create(loci.structure, u, u.elements[OrderedSet.getAt(e.indices, 0)]);
                     const issues = StructureQualityReport.getIssues(se);

+ 3 - 0
src/mol-plugin/behavior/dynamic/custom-props/rcsb/validation-report.ts

@@ -90,6 +90,7 @@ function geometryQualityLabel(loci: Loci): string | undefined {
 
             const validationReport = ValidationReportProvider.get(unit.model).value
             if (!validationReport) return
+            if (unit.model.customProperties.hasReference(ValidationReportProvider.descriptor)) return
 
             const { bondOutliers, angleOutliers } = validationReport
             const eI = unit.elements[OrderedSet.start(indices)]
@@ -170,6 +171,7 @@ function densityFitLabel(loci: Loci): string | undefined {
         for (const { indices, unit } of loci.elements) {
             const validationReport = ValidationReportProvider.get(unit.model).value
             if (!validationReport) continue
+            if (unit.model.customProperties.hasReference(ValidationReportProvider.descriptor)) continue
 
             const { rsrz, rscc } = validationReport
             const residueIndex = unit.model.atomicHierarchy.residueAtomSegments.index
@@ -226,6 +228,7 @@ function randomCoilIndexLabel(loci: Loci): string | undefined {
         for (const { indices, unit } of loci.elements) {
             const validationReport = ValidationReportProvider.get(unit.model).value
             if (!validationReport) continue
+            if (unit.model.customProperties.hasReference(ValidationReportProvider.descriptor)) continue
 
             const { rci } = validationReport
             const residueIndex = unit.model.atomicHierarchy.residueAtomSegments.index

+ 4 - 3
src/mol-theme/color/secondary-structure.ts

@@ -118,7 +118,8 @@ export const SecondaryStructureColorThemeProvider: ColorTheme.Provider<Secondary
     getParams: getSecondaryStructureColorThemeParams,
     defaultValues: PD.getDefaultValues(SecondaryStructureColorThemeParams),
     isApplicable: (ctx: ThemeDataContext) => !!ctx.structure,
-    ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => {
-        return data.structure ? SecondaryStructureProvider.attach(ctx, data.structure) : Promise.resolve()
-    }
+    ensureCustomProperties: {
+        attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? SecondaryStructureProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
+        detach: (_, data) => data.structure && data.structure.customPropertyDescriptors.reference(SecondaryStructureProvider.descriptor, false)
+    }    
 }

+ 11 - 3
src/mol-theme/theme.ts

@@ -52,8 +52,13 @@ namespace Theme {
     }
 
     export async function ensureDependencies(ctx: CustomProperty.Context, theme: ThemeRegistryContext, data: ThemeDataContext, props: Props) {
-        await theme.colorThemeRegistry.get(props.colorTheme.name).ensureCustomProperties?.(ctx, data)
-        await theme.sizeThemeRegistry.get(props.sizeTheme.name).ensureCustomProperties?.(ctx, data)
+        await theme.colorThemeRegistry.get(props.colorTheme.name).ensureCustomProperties?.attach(ctx, data)
+        await theme.sizeThemeRegistry.get(props.sizeTheme.name).ensureCustomProperties?.attach(ctx, data)
+    }
+
+    export async function releaseDependencies(ctx: CustomProperty.Context, theme: ThemeRegistryContext, data: ThemeDataContext, props: Props) {
+        await theme.colorThemeRegistry.get(props.colorTheme.name).ensureCustomProperties?.detach(ctx, data)
+        await theme.sizeThemeRegistry.get(props.sizeTheme.name).ensureCustomProperties?.detach(ctx, data)
     }
 }
 
@@ -66,7 +71,10 @@ export interface ThemeProvider<T extends ColorTheme<P> | SizeTheme<P>, P extends
     readonly getParams: (ctx: ThemeDataContext) => P
     readonly defaultValues: PD.Values<P>
     readonly isApplicable: (ctx: ThemeDataContext) => boolean
-    readonly ensureCustomProperties?: (ctx: CustomProperty.Context, data: ThemeDataContext) => Promise<void>
+    readonly ensureCustomProperties?: {
+        attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => Promise<void>,
+        detach: (ctx: CustomProperty.Context, data: ThemeDataContext) => void
+    }
 }
 
 function getTypes(list: { name: string, provider: ThemeProvider<any, any> }[]) {