<commit_before>// Copyright 2022, University of Colorado Boulder

/**
 * Popupable trait
 *
 * @author <NAME> <<EMAIL>>
 * @author <NAME> (PhET Interactive Simulations)
 * @author <NAME> (PhET Interactive Simulations)
 * @author <NAME> (PixelZoom, Inc.)
 */

import BooleanProperty from '../../axon/js/BooleanProperty.js';
import Property from '../../axon/js/Property.js';
import Bounds2 from '../../dot/js/Bounds2.js';
import ScreenView from '../../joist/js/ScreenView.js';
import gracefulBind from '../../phet-core/js/gracefulBind.js';
import inheritance from '../../phet-core/js/inheritance.js';
import optionize from '../../phet-core/js/optionize.js';
import Constructor from '../../phet-core/js/types/Constructor.js';
import PickOptional from '../../phet-core/js/types/PickOptional.js';
import { FocusManager, Node, NodeOptions } from '../../scenery/js/imports.js';
import Tandem from '../../tandem/js/Tandem.js';
import sun from './sun.js';

type SelfOptions = {

  // Don't use PopupableNode here (it creates... a lot of type issues and circularity)
  showPopup?: ( popup: Node, isModal: boolean ) => void;
  hidePopup?: ( popup: Node, isModal: boolean ) => void;

  // modal popups prevent interaction with the rest of the sim while open
  isModal?: boolean;

  // If desired, the layoutBounds that should be used for layout
  layoutBounds?: Bounds2 | null;

  // The Node that receives focus when the Popupable is shown. If null, focus is not set.
  focusOnShowNode?: Node | null;

  // The Node that receives focus when the Popupable is closed. If null, focus will return
  // to the Node that had focus when the Dialog opened.
  focusOnHideNode?: Node | null;
};
export type PopupableOptions = SelfOptions & PickOptional<NodeOptions, 'tandem'>;

const Popupable = <SuperType extends Constructor>( type: SuperType, optionsArgPosition: number ) => {
  assert && assert( _.includes( inheritance( type ), Node ), 'Only Node subtypes should mix Popupable' );

  return class extends type {

    public readonly layoutBounds: Bounds2 | null;

    private readonly _focusOnShowNode: Node | null;

    private readonly _focusOnHideNode: Node | null;

    // The Node to return focus to after the Popupable has been hidden. A reference to this Node is saved when
    // the Popupable is shown. By default focus is returned to Node that has focus when the Popupable is open
    // but can be overridden with focusOnHideNode.
    private _nodeToFocusOnHide: Node | null;

    // The node provided to showPopup, with the transform applied
    public readonly popupParent: Node;

    // Whether the popup is being shown
    public readonly isShowingProperty: Property<boolean>;

    // Support the same signature as the type we mix into.  However, we also have our own options, which we assume
    // are passed in the last arg.
    public constructor( ...args: any[] ) {
      super( ...args );

      const providedOptions = ( args[ optionsArgPosition ] || {} ) as PopupableOptions;

      // `config` is required for Popupable, to work well with ...args but all fields of the config are optional
      const requiredConfig = args[ args.length - 1 ];
      assert && assert( requiredConfig !== undefined, 'config object is required for Popupable.' );

      const showPopup = gracefulBind( 'phet.joist.sim.showPopup' ) as Exclude<PopupableOptions[ 'showPopup' ], undefined>;
      const hidePopup = gracefulBind( 'phet.joist.sim.hidePopup' ) as Exclude<PopupableOptions[ 'hidePopup' ], undefined>;

      assert && assert( typeof showPopup === 'function', 'showPopup is required, and must be provided if phet.joist.sim is not available.' );
      assert && assert( typeof hidePopup === 'function', 'hidePopup is required, and must be provided if phet.joist.sim is not available.' );

      const options = optionize<PopupableOptions>()( {
        showPopup: showPopup,
        hidePopup: hidePopup,
        isModal: true,
        layoutBounds: null,
        tandem: Tandem.OPTIONAL,
        focusOnShowNode: null,
        focusOnHideNode: null
      }, providedOptions );

      // see https://github.com/phetsims/joist/issues/293
      assert && assert( options.isModal, 'Non-modal popups not currently supported' );

      this.layoutBounds = options.layoutBounds;
      this._focusOnShowNode = options.focusOnShowNode;<commit_msg>disable lint for `any`, add TODO, see https://github.com/phetsims/sun/issues/777<commit_after>// Copyright 2022, University of Colorado Boulder

/**
 * Popupable trait
 *
 * @author <NAME> <<EMAIL>>
 * @author <NAME> (PhET Interactive Simulations)
 * @author <NAME> (PhET Interactive Simulations)
 * @author <NAME> (PixelZoom, Inc.)
 */

import BooleanProperty from '../../axon/js/BooleanProperty.js';
import Property from '../../axon/js/Property.js';
import Bounds2 from '../../dot/js/Bounds2.js';
import ScreenView from '../../joist/js/ScreenView.js';
import gracefulBind from '../../phet-core/js/gracefulBind.js';
import inheritance from '../../phet-core/js/inheritance.js';
import optionize from '../../phet-core/js/optionize.js';
import Constructor from '../../phet-core/js/types/Constructor.js';
import PickOptional from '../../phet-core/js/types/PickOptional.js';
import { FocusManager, Node, NodeOptions } from '../../scenery/js/imports.js';
import Tandem from '../../tandem/js/Tandem.js';
import sun from './sun.js';

type SelfOptions = {

  // Don't use PopupableNode here (it creates... a lot of type issues and circularity)
  showPopup?: ( popup: Node, isModal: boolean ) => void;
  hidePopup?: ( popup: Node, isModal: boolean ) => void;

  // modal popups prevent interaction with the rest of the sim while open
  isModal?: boolean;

  // If desired, the layoutBounds that should be used for layout
  layoutBounds?: Bounds2 | null;

  // The Node that receives focus when the Popupable is shown. If null, focus is not set.
  focusOnShowNode?: Node | null;

  // The Node that receives focus when the Popupable is closed. If null, focus will return
  // to the Node that had focus when the Dialog opened.
  focusOnHideNode?: Node | null;
};
export type PopupableOptions = SelfOptions & PickOptional<NodeOptions, 'tandem'>;

const Popupable = <SuperType extends Constructor>( type: SuperType, optionsArgPosition: number ) => {
  assert && assert( _.includes( inheritance( type ), Node ), 'Only Node subtypes should mix Popupable' );

  return class extends type {

    public readonly layoutBounds: Bounds2 | null;

    private readonly _focusOnShowNode: Node | null;

    private readonly _focusOnHideNode: Node | null;

    // The Node to return focus to after the Popupable has been hidden. A reference to this Node is saved when
    // the Popupable is shown. By default focus is returned to Node that has focus when the Popupable is open
    // but can be overridden with focusOnHideNode.
    private _nodeToFocusOnHide: Node | null;

    // The node provided to showPopup, with the transform applied
    public readonly popupParent: Node;

    // Whether the popup is being shown
    public readonly isShowingProperty: Property<boolean>;

    // Support the same signature as the type we mix into.  However, we also have our own options, which we assume
    // are passed in the last arg.
    // TODO - We're trying not to use "any", so how can we specify the types more specifically?  See https://github.com/phetsims/sun/issues/777.
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    public constructor( ...args: any[] ) {
      super( ...args );

      const providedOptions = ( args[ optionsArgPosition ] || {} ) as PopupableOptions;

      // `config` is required for Popupable, to work well with ...args but all fields of the config are optional
      const requiredConfig = args[ args.length - 1 ];
      assert && assert( requiredConfig !== undefined, 'config object is required for Popupable.' );

      const showPopup = gracefulBind( 'phet.joist.sim.showPopup' ) as Exclude<PopupableOptions[ 'showPopup' ], undefined>;
      const hidePopup = gracefulBind( 'phet.joist.sim.hidePopup' ) as Exclude<PopupableOptions[ 'hidePopup' ], undefined>;

      assert && assert( typeof showPopup === 'function', 'showPopup is required, and must be provided if phet.joist.sim is not available.' );
      assert && assert( typeof hidePopup === 'function', 'hidePopup is required, and must be provided if phet.joist.sim is not available.' );

      const options = optionize<PopupableOptions>()( {
        showPopup: showPopup,
        hidePopup: hidePopup,
        isModal: true,
        layoutBounds: null,
        tandem: Tandem.OPTIONAL,
        focusOnShowNode: null,
        focusOnHideNode: null
      }, providedOptions );

      // see https://github.com/phetsims/joist/issues/293
      assert && assert( options.isModal, 'Non-modal popups not currently supported' );

      this.layoutBounds = options.layoutBounds;
      this._focusOnShowNode = options.focusOnShowNode;
{'fraction_non_alphanumeric': 0.11570709893795417, 'fraction_numerical': 0.003353828954723309, 'mean_word_length': 3.3660322108345535, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 33, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5167513', 'n_tokens_mistral': 2541, 'n_tokens_neox': 2496, 'n_words': 922}