For constructors that have options and merge, we would like to rewrite those to use our custom function optionize instead. Steps: 1. Rename the constuctor parameter from options to providedOptions. 2. Change `options = merge({...},options)` to `const options = optionize()({...},providedOptions)` 3. Add SelfOptions to capture the allowable options. 4. Move the documentation from the merge site to the SelfOptions site. 5. Combine MyTypeOptions = SelfOptions & ParentOptions; This is the ProvidedOptions. 6. Note: the new types must be outside of the class declaration and not just outside the constructor. 7. Remove now redundant types in the documentation, like `// {number} - initial/default value of friction for the model` becomes `// initial/default value of friction for the model` 8. Preserve the original documentation (ok to move it). Do not improve it, or remove it. 9. If there was a `// eslint-disable-next-line phet/bad-typescript-text` before the merge, remove it. 10. CAREFUL! The types must be defined outside (above) the class declaration, not in the area where the class properties are declared. #################################### # EXAMPLE 1: Subject: [PATCH] Export default class, see https://github.com/phetsims/energy-skate-park/issues/387 --- Index: js/common/model/ControlPoint.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/common/model/ControlPoint.ts b/js/common/model/ControlPoint.ts --- a/js/common/model/ControlPoint.ts (revision 6adea90f46d824f5ca3056b988f341ef7af8cf5e) +++ b/js/common/model/ControlPoint.ts (date 1736790173832) @@ -15,9 +15,8 @@ import Bounds2 from '../../../../dot/js/Bounds2.js'; import Vector2 from '../../../../dot/js/Vector2.js'; import Vector2Property from '../../../../dot/js/Vector2Property.js'; -import merge from '../../../../phet-core/js/merge.js'; -import IntentionalAny from '../../../../phet-core/js/types/IntentionalAny.js'; -import PhetioObject from '../../../../tandem/js/PhetioObject.js'; +import optionize from '../../../../phet-core/js/optionize.js'; +import PhetioObject, { PhetioObjectOptions } from '../../../../tandem/js/PhetioObject.js'; import Tandem from '../../../../tandem/js/Tandem.js'; import IOType from '../../../../tandem/js/types/IOType.js'; import NullableIO from '../../../../tandem/js/types/NullableIO.js'; @@ -25,6 +24,22 @@ import energySkatePark from '../../energySkatePark.js'; import EnergySkateParkModel from './EnergySkateParkModel.js'; +type SelfOptions = { + + // {boolean} - if true, this control point will be displayed on the track + visible?: boolean; + + // {boolean} - can this control point specifically be dragged? If true, the control point is draggable, + // changes opacity on over, and supports track splitting at the ControlPoint + interactive?: boolean; + + // {Bounds2|null} - if specified, the ControlPoint will also be constrained to these bounds during dragging, or + // when the track is bumped above ground, in model coordinates + limitBounds?: Bounds2 | null; +}; + +type ControlPointOptions = SelfOptions & PhetioObjectOptions; + export default class ControlPoint extends PhetioObject { public readonly limitBounds: Bounds2 | null; @@ -45,26 +60,17 @@ // whether the control point is currently being dragged public readonly draggingProperty: BooleanProperty; - public constructor( x: number, y: number, options: IntentionalAny ) { + public constructor( x: number, y: number, providedOptions: ControlPointOptions ) { - // eslint-disable-next-line phet/bad-typescript-text - options = merge( { - - // {boolean} - if true, this control point will be displayed on the track + const options = optionize()( { visible: true, - - // {boolean} - can this control point specifically be dragged? If true, the control point is draggable, - // changes opacity on over, and supports track splitting at the ControlPoint interactive: true, - - // {Bounds2|null} - if specified, the ControlPoint will also be constrained to these bounds during dragging, or - // when the track is bumped above ground, in model coordinates limitBounds: null, tandem: Tandem.REQUIRED, phetioType: ControlPoint.ControlPointIO, phetioState: PhetioObject.DEFAULT_OPTIONS.phetioState - }, options ); + }, providedOptions ); const tandem = options.tandem; super( options ); #################################### # EXAMPLE 2: BEFORE: // Updates the model with constant event intervals even if there is a drop in the framerate // so that simulation performance has no impact on physical behavior. public readonly eventTimer: EventTimer; public constructor( preferencesModel: EnergySkateParkPreferencesModel, tandem: Tandem, options?: IntentionalAny ) { super( { phetioType: EnergySkateParkModel.EnergySkateParkModelIO, tandem: tandem, phetioState: false } ); // eslint-disable-next-line phet/bad-typescript-text options = merge( { // {number} - initial/default value of friction for the model defaultFriction: EnergySkateParkConstants.DEFAULT_FRICTION, // {boolean} - if true, tracks can be dragged around the play area tracksDraggable: false, // {boolean} - if true, track control points can be dragged and track shapes can change tracksConfigurable: false, // @boolean - default for the speedValueVisibleProperty, whether or not the value of speed is displayed // on the speedometer defaultSpeedValueVisible: true, // passed to Skater skaterOptions: null }, options ); this.trackChangedEmitter = new Emitter(); AFTER: type SelfOptions = { // initial/default value of friction for the model defaultFriction?: number; // if true, tracks can be dragged around the play area tracksDraggable?: boolean; // if true, track control points can be dragged and track shapes can change tracksConfigurable?: boolean; // default for the speedValueVisibleProperty, whether or not the value of speed is displayed // on the speedometer defaultSpeedValueVisible?: boolean; // options passed to Skater skaterOptions?: IntentionalAny | null; }; type EnergySkateParkModelOptions = SelfOptions & PhetioObjectOptions; export default class EnergySkateParkModel extends PhetioObject { // emits an event whenever a track changes in some way (control points dragged, track split apart, // track dragged, track deleted or scene changed, etc...) public readonly trackChangedEmitter: Emitter; // etc.......... // Updates the model with constant event intervals even if there is a drop in the framerate // so that simulation performance has no impact on physical behavior. public readonly eventTimer: EventTimer; public constructor( preferencesModel: EnergySkateParkPreferencesModel, tandem: Tandem, providedOptions?: EnergySkateParkModelOptions ) { super( { phetioType: EnergySkateParkModel.EnergySkateParkModelIO, tandem: tandem, phetioState: false } ); const options = optionize()( { defaultFriction: EnergySkateParkConstants.DEFAULT_FRICTION, tracksDraggable: false, tracksConfigurable: false, defaultSpeedValueVisible: true, skaterOptions: null }, providedOptions ); this.trackChangedEmitter = new Emitter();