Source: form/json-schema/childApplicators.js

/**
 * Containers conceived to represent JSON Schema child applicators, as they are
 * described by the [JSON Schema RFC Draft](https://json-schema.org/draft/2019-09/json-schema-core.html#rfc.section.9.3).
 *
 * @module childApplicators
 */
import { ElementType, State, TitleType } from '../formElementDefinitions.js';
import FormElement from '../formElement.js';
import { AddButton, GeneratorIcon, LabelTitle } from '../components.js';
import {
  arrangeAdditionalItems,
  arrangeAdditionalProperties,
  arrangeItems,
  arrangeProperties,
} from '../layouts.js';

/**
 * Interface for classes implementing containers that represent [JSON Schema
 * child applicators](https://json-schema.org/draft/2019-09/json-schema-core.html#rfc.section.9.3).
 *
 * @interface ChildApplicator
 */

/**
 * The handler that includes the child applicator container.
 *
 * @member {module:childApplicators~ChildrenHandler} module:childApplicators~ChildApplicator#handler
 */

/**
 * A button that triggers the addition of new child JSON Schema form elements.
 *
 * @member {module:components~Component|undefined} module:childApplicators~ChildApplicator#addButton
 */

/**
 * The title of the child applicator container.
 *
 * @member {module:components~Component|undefined} module:childApplicators~ChildApplicator#title
 */

/**
 * The title icon of the child applicator container.
 *
 * @member {module:components~Component|undefined} module:childApplicators~ChildApplicator#titleIcon
 */

/**
 * A map to all contained child JSON Schema objects indexed by an number value.
 *
 * @member {Map.<number, module:formElement~FormElement>} module:childApplicators~ChildApplicator#childByIndex
 */

/**
 * Reference to the `<div>` element containing the form element representation
 * of all child JSON Schema objects.
 *
 * @member {HTMLDivElement} module:childApplicators~ChildApplicator#childrenDiv
 */

/**
 * Reference to the DOM object that contains the HTML structure of the JSON
 * Schema child applicator.
 *
 * @member {HTMLDivElement} module:childApplicators~ChildApplicator#domElement
 */

/**
 * Adds a new child JSON Schema form element to the container.
 *
 * @function
 *
 * @name module:childApplicators~ChildApplicator#addChild
 */

/**
 * Disables the child applicator container.
 *
 * @function
 *
 * @name module:childApplicators~ChildApplicator#disable
 */

/**
 * Enables the child applicator container.
 *
 * @function
 *
 * @name module:childApplicators~ChildApplicator#enable
 */

/**
 * Returns the part of the JSON instance that corresponds to the given JSON
 * Schema child applicator.
 *
 * @function
 *
 * @returns {object} The portion related to the given child applicator of the
 * complete JSON Schema instance.
 *
 * @name module:childApplicators~ChildApplicator#getChildInstance
 */

/**
 * Removes a child JSON Schema form element from the container.
 *
 * @function
 *
 * @param {number} index The key mapping to the child form element to be
 * removed.
 *
 * @name module:childApplicators~ChildApplicator#removeChild
 */

/**
 * The abstract handler for child applicator containers.
 *
 * @abstract
 */
class ChildrenHandler {
  /**
   * Class constructor.
   *
   * @param {module:formElement~FormElement} parent The parent form element
   * containing the child applicator containers to be handled.
   */
  constructor(parent) {
    /**
     * The parent form element containing the handled child applicator
     * containers.
     *
     * @type {module:formElement~FormElement}
     */
    this.parent = parent;

    /**
     * The handled child applicator containers.
     *
     * @type {object.<string, module:childApplicators~ChildApplicator>}
     */
    this.applicators = {};

    /**
     * The set of add buttons associated to the handled child applicator
     * containers.
     *
     * @type {Set}
     */
    this.addButtons = new Set();

    /**
     * The set of remove buttons associated to the child form elements.
     *
     * @type {Set}
     */
    this.removeButtons = new Set();

    /**
     * Reference to the DOM object that contains the HTML structure of the
     * component.
     *
     * @type {HTMLDivElement}
     */
    this.domElement = document.createElement('div');
  }

  /**
   * Returns the total count of child form elements from all the different
   * child applicator containers.
   *
   * @returns {number} The total count of child form elements.
   */
  getChildCount() {
    return Object.values(this.applicators).reduce(
      (acc, ca) => acc + ca.childByIndex.size,
      0
    );
  }

  /** Enables the add buttons from all the child applicator containers. */
  enableAddButtons() {
    for (const b of this.addButtons) b.enable();
  }

  /** Disables the add buttons from all the child applicator containers. */
  disableAddButtons() {
    for (const b of this.addButtons) b.disable();
  }

  /** Enables the remove buttons from all the child form elements. */
  enableRemoveButtons() {
    for (const b of this.removeButtons) b.enable();
  }

  /** Disables the remove buttons from all the child form elements. */
  disableRemoveButtons() {
    for (const b of this.removeButtons) b.disable();
  }

  /** Enables all the child applicator containers. */
  enable() {
    for (const a of Object.values(this.applicators)) a.enable();

    updateButtons(this);
  }

  /** Disables all the child applicator containers. */
  disable() {
    for (const a of Object.values(this.applicators)) a.disable();

    this.disableAddButtons();
    this.disableRemoveButtons();
  }
}

/**
 * Updates the state of add and remove buttons associated to child applicator
 * containers and form elements of a handler.
 *
 * @param {module:childApplicators~ChildrenHandler} handler The handler whose
 * buttons are to be updated.
 *
 * @modifies handler
 */
function updateButtons(handler) {
  updateAddButtons(handler);
  updateRemoveButtons(handler);
}

/**
 * Updates the state of the add buttons associated to the child applicator
 * containers of a handler.
 *
 * @param {module:childApplicators~ChildrenHandler} handler The handler whose
 * buttons are to be updated.
 *
 * @modifies handler
 */
function updateAddButtons(handler) {
  const maxChildren = Number.isInteger(handler.parent.keywords.maxItems)
    ? handler.parent.keywords.maxItems
    : handler.parent.keywords.maxProperties;

  if (Number.isInteger(maxChildren)) {
    if (handler.getChildCount() < maxChildren) handler.enableAddButtons();
    else if (handler.getChildCount() === maxChildren)
      handler.disableAddButtons();
    else {
      console.warn(`Maximum number of children (${maxChildren}) of the form
element ${handler.parent.pointer} has been overflown.`);
      handler.disableAddButtons();
    }
  }
}

/**
 * Updates the state of the add buttons associated to the child form elements of
 * a handler.
 *
 * @param {module:childApplicators~ChildrenHandler} handler The handler whose
 * buttons are to be updated.
 *
 * @modifies handler
 */
function updateRemoveButtons(handler) {
  const minChildren = Number.isInteger(handler.parent.keywords.minItems)
    ? handler.parent.keywords.minItems
    : handler.parent.keywords.minProperties;

  if (Number.isInteger(minChildren)) {
    if (handler.getChildCount() > minChildren) handler.enableRemoveButtons();
    else if (handler.getChildCount() === minChildren)
      handler.disableRemoveButtons();
    else {
      console.warn(`Minimum number of children (${minChildren}) of the form
element ${handler.parent.pointer} has been underflown.`);
      handler.disableRemoveButtons();
    }
  }
}

/**
 * Adds a child form element to a child applicator container, updating the
 * buttons state.
 *
 * @param {number} index The key identifying the child form element in the child
 * applicator container.
 * @param {module:formElement~FormElement} child The child form element to add
 * to the child applicator container.
 * @param {module:childApplicators~ChildApplicator} childApplicator The child
 * applicator container which the child form element will be added to.
 *
 * @modifies handler
 */
function addChildToApplicator(index, child, childApplicator) {
  childApplicator.childByIndex.set(index, child);
  childApplicator.childrenDiv.appendChild(child.layout.root);
  childApplicator.handler.removeButtons.add(child.titleIcon);
  updateButtons(childApplicator.handler);
}

/**
 * Removes a child form element from a child applicator container, updating the
 * buttons state.
 *
 * @param {number} index The key identifying the child form element in the child
 * applicator container.
 * @param {module:childApplicators~ChildApplicator} childApplicator The child
 * applicator container which the child form element will be removed from.
 *
 * @modifies handler
 */
function removeChildFromApplicator(index, childApplicator) {
  childApplicator.childByIndex.get(index).layout.root.remove();
  childApplicator.handler.removeButtons.delete(
    childApplicator.childByIndex.get(index).titleIcon
  );
  childApplicator.childByIndex.delete(index);
  updateButtons(childApplicator.handler);
}

/**
 * Handler for child applicator containers of `array`-typed form elements.
 *
 * @augments ChildrenHandler
 */
class ArrayHandler extends ChildrenHandler {
  /**
   * Class constructor.
   *
   * @param {module:formElement~FormElement} parent The parent form element
   * containing the child applicator containers to be handled.
   */
  constructor(parent) {
    super(parent);

    // Generates the "items" container <div> element.
    if (parent.keywords.items) {
      this.applicators.items = new Items(this);

      this.addButtons.add(this.applicators.items.addButton);

      for (const [, child] of this.applicators.items.childByIndex)
        this.removeButtons.add(child.titleIcon);

      this.domElement.appendChild(this.applicators.items.domElement);
    }

    // Generates the "additionalItems" container <div> element.
    if (parent.keywords.additionalItems) {
      this.applicators.additionalItems = new AdditionalItems(this);

      this.addButtons.add(this.applicators.additionalItems.addButton);

      for (const [, child] of this.applicators.additionalItems.childByIndex)
        this.removeButtons.add(child.titleIcon);

      this.domElement.appendChild(this.applicators.additionalItems.domElement);
    }

    updateButtons(this);
  }

  /**
   * Retrieves the JSON instance expressed by the child applicator containers
   * together with the inputted values.
   *
   * @returns {any} The JSON instance associated to the form element.
   */
  getInstance() {
    return Object.values(this.applicators).flatMap((a) => a.getChildInstance());
  }
}

/**
 * Handler for child applicator containers of `object`-typed form elements.
 *
 * @augments ChildrenHandler
 */
class ObjectHandler extends ChildrenHandler {
  /**
   * Class constructor.
   *
   * @param {module:formElement~FormElement} parent The parent form element
   * containing the child applicator containers to be handled.
   */
  constructor(parent) {
    super(parent);

    // Generates the "properties" container <div> element.
    if (parent.keywords.properties) {
      this.applicators.properties = new Properties(this);
      this.domElement.appendChild(this.applicators.properties.domElement);
    }

    // Generates the "additionalProperties" container <div> element.
    if (parent.keywords.additionalProperties) {
      this.applicators.additionalProperties = new AdditionalProperties(this);

      this.addButtons.add(this.applicators.additionalProperties.addButton);

      for (const [, child] of this.applicators.additionalProperties
        .childByIndex)
        this.removeButtons.add(child.titleIcon);

      this.domElement.appendChild(
        this.applicators.additionalProperties.domElement
      );
    }

    updateButtons(this);
  }

  /**
   * Retrieves the JSON instance expressed by the child applicator containers
   * together with the inputted values.
   *
   * @returns {any} The JSON instance associated to the form element.
   */
  getInstance() {
    return {
      ...Object.values(this.applicators).map((a) => a.getChildInstance()),
    };
  }
}

/**
 * A container representing the JSON Schema [`items` child applicator](https://json-schema.org/draft/2019-09/json-schema-core.html#rfc.section.9.3.1.1).
 *
 * This container does not define the {@link module:childApplicators~ChildApplicator#title}
 * nor the {@link module:childApplicators~ChildApplicator#titleIcon} members.
 *
 * @implements {module:childApplicators~ChildApplicator}
 */
class Items {
  /**
   * @param {module:childApplicators~ArrayHandler} handler The handler which the
   * child applicator container will be added to.
   */
  constructor(handler) {
    this.handler = handler;

    this.childByIndex = new Map(
      Array.from(
        {
          length: handler.parent.keywords.minItems,
        },
        (_, index) =>
          createItem(handler.parent, index, () => void this.removeChild(index))
      )
    );

    this.addButton = new AddButton(
      () => void this.addChild(),
      {
        disabled: !handler.parent.isEnabled(),
      },
      {
        bootstrap: handler.parent.formOptions.bootstrap,
        fontAwesome: handler.parent.formOptions.fontAwesome,
      }
    );

    this.childrenDiv = document.createElement('div');

    for (const [, child] of this.childByIndex)
      this.childrenDiv.appendChild(child.layout.root);

    this.domElement = arrangeItems(this.childrenDiv, this.addButton.domElement);
  }

  addChild() {
    // Ensures incremental key.
    const index =
      this.childByIndex && this.childByIndex.size
        ? Array.from(this.childByIndex).pop().shift() + 1
        : 0;

    const child = createItem(
      this.handler.parent,
      index,
      () => void this.removeChild(index)
    );

    addChildToApplicator(...child, this);
  }

  disable() {
    for (const [, child] of this.childByIndex) child.deactivate();
  }

  enable() {
    for (const [, child] of this.childByIndex) child.activate();
  }

  getChildInstance() {
    return Array.from(this.childByIndex).map(([, child]) =>
      child.getInstance()
    );
  }

  removeChild(index) {
    removeChildFromApplicator(index, this);
  }
}

/**
 * Creates a JSON Schema form element to be added into the `items` child
 * applicator.
 *
 * @param {module:formElement~FormElement} parent The parent JSON Schema form
 * element.
 * @param {number} index The key identifying the child form element.
 * @param {Function} removeCallback The callback function to be called if the
 * newly created element is removed.
 *
 * @returns {module:formElement~FormElement} The child JSON Schema form element.
 */
const createItem = (parent, index, removeCallback) => [
  index,
  new FormElement(parent.keywords.items, {
    elementType: ElementType.REMOVABLE,
    titleType: TitleType.STATIC,
    state: yieldNewItemState(parent),
    pointer: `${parent.pointer}/items-${index}`,
    formOptions: parent.formOptions,
    removeCallback,
  }),
];

/**
 * Returns the state that should hold a newly created child of a given {@link
 * module:childApplicators.Items} container.
 *
 * @param {module:jsonSchema.JsonSchema} parent The parent JSON Schema form
 * element.
 *
 * @returns {module:formElementDefinitions~State} An object indicating the state
 * to be hold by a newly created child.
 */
const yieldNewItemState = (parent) => new State(parent.isEnabled(), null);

/**
 * A container representing the JSON Schema [`additionalItems` child applicator](https://json-schema.org/draft/2019-09/json-schema-core.html#additionalItems).
 *
 * @implements {module:childApplicators~ChildApplicator}
 */
class AdditionalItems {
  /**
   * @param {module:childApplicators~ArrayHandler} handler The handler which the
   * child applicator container will be added to.
   */
  constructor(handler) {
    this.handler = handler;

    this.childByIndex = new Map(
      Array.from(
        {
          length: additionalItemsMinimumChildren(handler.parent),
        },
        (_, index) =>
          createAdditionalItem(
            handler.parent,
            index,
            () => void this.removeChild(index)
          )
      )
    );

    this.addButton = new AddButton(
      () => void this.addChild(),
      {
        disabled: !handler.parent.isEnabled(),
      },
      {
        bootstrap: handler.parent.formOptions.bootstrap,
        fontAwesome: handler.parent.formOptions.fontAwesome,
      }
    );

    this.titleIcon = new GeneratorIcon({
      fontAwesome: handler.parent.formOptions.fontAwesome,
    });

    this.title = new LabelTitle(
      handler.parent.keywords.additionalItems.title,
      handler.parent.keywords.additionalItems.description,
      undefined,
      {
        bootstrap: handler.parent.formOptions.bootstrap,
        fontAwesome: handler.parent.formOptions.fontAwesome,
      }
    );

    this.childrenDiv = document.createElement('div');

    for (const [, child] of this.childByIndex)
      this.childrenDiv.appendChild(child.layout.root);

    this.domElement = arrangeAdditionalItems(
      this.titleIcon.domElement,
      this.title.domElement,
      this.childrenDiv,
      this.addButton.domElement
    );
  }

  addChild() {
    // Ensures incremental key.
    const index =
      this.childByIndex && this.childByIndex.size
        ? Array.from(this.childByIndex).pop().shift() + 1
        : 0;

    const child = createAdditionalItem(
      this.handler.parent,
      index,
      () => void this.removeChild(index)
    );

    addChildToApplicator(...child, this);
  }

  disable() {
    for (const [, child] of this.childByIndex) child.deactivate();
  }

  enable() {
    for (const [, child] of this.childByIndex) child.activate();
  }

  getChildInstance() {
    return Array.from(this.childByIndex).map(([, child]) =>
      child.getInstance()
    );
  }

  removeChild(index) {
    removeChildFromApplicator(index, this);
  }
}

/**
 * Creates a JSON Schema form element to be added into the `additionalItems`
 * child applicator.
 *
 * @param {module:formElement~FormElement} parent The parent JSON Schema form
 * element.
 * @param {number} index The key identifying the child form element.
 * @param {Function} removeCallback The callback function to be called if the
 * newly created element is removed.
 *
 * @returns {module:formElement~FormElement} The child JSON Schema form element.
 */
const createAdditionalItem = (parent, index, removeCallback) => [
  index,
  new FormElement(parent.keywords.additionalItems, {
    elementType: ElementType.REMOVABLE,
    titleType: TitleType.ADDED_ITEM,
    state: yieldNewAdditionalItemState(parent),
    pointer: `${parent.pointer}/additionalItems-${index}`,
    formOptions: parent.formOptions,
    removeCallback,
  }),
];

/**
 * Returns the state that should hold a newly created child of a given {@link
 * module:childApplicators.AdditionalItems} container.
 *
 * @param {module:formElement~FormElement} parent The parent JSON Schema form
 * element.
 *
 * @returns {module:formElementDefinitions~State} An object indicating the state
 * to be hold by a newly created child.
 */
const yieldNewAdditionalItemState = (parent) =>
  new State(parent.isEnabled(), null);

/**
 * Returns the minimum number of children that the {@link
 * module:childApplicators.AdditionalItems} container of a given JSON Schema
 * form element must have at initialization.
 *
 * @param {module:formElement~FormElement} parent The parent JSON Schema form
 * element.
 *
 * @returns {number} The minimum number of children that the container must have
 * at initialization.
 */
const additionalItemsMinimumChildren = (parent) => {
  if (parent.keywords.minItems)
    if (parent.keywords.items)
      if (Array.isArray(parent.keywords.items))
        // Handles the case of "items" keyword as an array of JSON Schemas.
        return parent.keywords.minItems - parent.keywords.items.length;
      else return 0;
    else return parent.keywords.minItems;
  else return 0;
};

/**
 * A container representing the JSON Schema [`properties` child applicator](https://json-schema.org/draft/2019-09/json-schema-core.html#rfc.section.9.3.2.1).
 *
 * This container does not define the {@link
 * module:childApplicators~ChildApplicator#addButton}, nor the {@link
 * module:childApplicators~ChildApplicator#title}, nor the {@link
 * module:childApplicators~ChildApplicator#titleIcon} members.
 *
 * @implements {module:childApplicators~ChildApplicator}
 */
class Properties {
  /**
   * @param {module:childApplicators~ArrayHandler} handler The handler which the
   * child applicator container will be added to.
   */
  constructor(handler) {
    this.handler = handler;
    this.childByIndex = new Map(
      Object.entries(
        handler.parent.keywords.properties
      ).map(([propertyKey, schema], index) =>
        createProperty(schema, propertyKey, handler.parent, index)
      )
    );

    this.childrenDiv = document.createElement('div');

    for (const [, child] of this.childByIndex)
      this.childrenDiv.appendChild(child.layout.root);

    this.domElement = arrangeProperties(this.childrenDiv);
  }

  addChild() {}

  disable() {
    for (const [, child] of this.childByIndex) child.deactivate();
  }

  enable() {
    for (const [, child] of this.childByIndex) child.activate();
  }

  getChildInstance() {
    return Object.fromEntries(
      Array.from(this.childByIndex)
        .filter(([, child]) => child.isEnabled())
        .map(([, child]) => [child.propertyKey, child.getInstance()])
    );
  }

  removeChild() {}
}

/**
 * Creates a JSON Schema form element to be added into the `properties` child
 * applicator.
 *
 * @param {object} schema The JSON Schema of the property to be expressed by the
 * child form element.
 * @param {string} propertyKey The key of the property to be expressed by the
 * child form element.
 * @param {module:formElement~FormElement} parent The parent JSON Schema form
 * element.
 * @param {number} index The key identifying the child form element.
 *
 * @returns {module:formElement~FormElement} The child JSON Schema form element.
 */
const createProperty = (schema, propertyKey, parent, index) => {
  const required =
    parent.keywords.required && parent.keywords.required.includes(propertyKey);

  const elementType = required ? ElementType.REQUIRED : ElementType.OPTIONAL;

  return [
    index,
    new FormElement(schema, {
      elementType,
      titleType: TitleType.STATIC,
      state: yieldNewPropertyState(parent, elementType),
      pointer: `${parent.pointer}/properties-${propertyKey}`,
      propertyKey,
      formOptions: parent.formOptions,
    }),
  ];
};

/**
 * Returns the state that should hold a newly created child of a given {@link
 * module:childApplicators.Properties} container.
 *
 * @param {module:formElement~FormElement} parent The parent JSON Schema form
 * element.
 * @param {string} childElementType The {@link
 * module:formElementDefinitions.ElementType} of the newly created child.
 *
 * @returns {module:formElementDefinitions~State} An object indicating the state
 * to be hold by a newly created child.
 */
const yieldNewPropertyState = (parent, childElementType) => {
  const d =
    childElementType === ElementType.OPTIONAL
      ? parent.formOptions.initTogglersOff
      : null;

  return new State(parent.isEnabled(), d);
};

/**
 * A container representing the JSON Schema [`additionalProperties` child
 * applicator](https://json-schema.org/draft/2019-09/json-schema-core.html#additionalProperties).
 *
 * @implements {module:childApplicators~ChildApplicator}
 */
class AdditionalProperties {
  /**
   * @param {module:childApplicators~ArrayHandler} handler The handler which the
   * child applicator container will be added to.
   */
  constructor(handler) {
    this.handler = handler;

    this.childByIndex = new Map(
      Array.from(
        {
          length: additionalPropertiesMinimumChildren(handler.parent),
        },
        (_, index) =>
          createAdditionalProperty(
            handler.parent,
            index,
            () => void this.removeChild(index)
          )
      )
    );

    this.addButton = new AddButton(
      () => void this.addChild(),
      {
        disabled: !handler.parent.isEnabled(),
      },
      {
        bootstrap: handler.parent.formOptions.bootstrap,
        fontAwesome: handler.parent.formOptions.fontAwesome,
      }
    );

    this.titleIcon = new GeneratorIcon({
      fontAwesome: handler.parent.formOptions.fontAwesome,
    });

    this.title = new LabelTitle(
      handler.parent.keywords.additionalProperties.title,
      handler.parent.keywords.additionalProperties.description,
      undefined,
      {
        bootstrap: handler.parent.formOptions.bootstrap,
        fontAwesome: handler.parent.formOptions.fontAwesome,
      }
    );

    this.childrenDiv = document.createElement('div');

    for (const [, child] of this.childByIndex)
      this.childrenDiv.appendChild(child.layout.root);

    this.domElement = arrangeAdditionalProperties(
      this.titleIcon.domElement,
      this.title.domElement,
      this.childrenDiv,
      this.addButton.domElement
    );
  }

  addChild() {
    // Ensures incremental key.
    const index =
      this.childByIndex && this.childByIndex.size
        ? Array.from(this.childByIndex).pop().shift() + 1
        : 0;
    const child = createAdditionalProperty(
      this.handler.parent,
      index,
      () => void this.removeChild(index)
    );

    addChildToApplicator(...child, this);
  }

  disable() {
    for (const [, child] of this.childByIndex) child.deactivate();
  }

  enable() {
    for (const [, child] of this.childByIndex) child.activate();
  }

  getChildInstance() {
    return Object.fromEntries(
      Array.from(this.childByIndex).map(([, child]) => [
        child.title.getValue(),
        child.getInstance(),
      ])
    );
  }

  removeChild(index) {
    removeChildFromApplicator(index, this);
  }
}

/**
 * Creates a JSON Schema form element to be added into the
 * `additionalProperties` child applicator.
 *
 * @param {module:formElement~FormElement} parent The parent JSON Schema form
 * element.
 * @param {number} index The key identifying the child form element.
 * @param {Function} removeCallback The callback function to be called if the
 * newly created element is removed.
 *
 * @returns {module:formElement~FormElement} The child JSON Schema form element.
 */
const createAdditionalProperty = (parent, index, removeCallback) => [
  index,
  new FormElement(parent.keywords.additionalProperties, {
    elementType: ElementType.REMOVABLE,
    titleType: TitleType.FIELD,
    state: yieldNewAdditionalPropertyState(parent),
    pointer: `${parent.pointer}/additionalProperties-${index}`,
    formOptions: parent.formOptions,
    removeCallback,
  }),
];

/**
 * Returns the state that should hold a newly created child of a given {@link
 * module:childApplicators.AdditionalProperties} container.
 *
 * @param {module:formElement~FormElement} parent The parent JSON Schema form
 * element.
 *
 * @returns {module:formElementDefinitions~State} An object indicating the state
 * to be hold by a newly created child.
 */
const yieldNewAdditionalPropertyState = (parent) =>
  new State(parent.isEnabled(), null);

/**
 * Returns the minimum number of children that the {@link
 * module:childApplicators.AdditionalProperties} container of a given JSON
 * Schema form element must have at initialization.
 *
 * @param {module:formElement~FormElement} parent The parent JSON Schema form
 * element.
 *
 * @returns {number} The minimum number of children that the container must have
 * at initialization.
 */
const additionalPropertiesMinimumChildren = (parent) => {
  if (parent.keywords.minProperties)
    if (parent.keywords.properties)
      return (
        parent.keywords.minProperties -
        Object.keys(parent.keywords.properties).length
      );
    else return parent.keywords.minProperties;
  else return 0;
};

export { ArrayHandler, ObjectHandler };