ConfigField = ({
  field,
  configuration,
  setProp,
  onChange,
  props,
  setter,
  isStyle,
  subProp,
  valuePostfix,
}) => {
  /**
   * @param {object} v
   * @returns {void}
   */
  const options = React.useContext(optionsCtx);

  const myOnChange = (v0) => {
    const v = valuePostfix && (v0 || v0 === 0) ? v0 + valuePostfix : v0;
    setProp((prop) => {
      if (setter) setter(prop, field.name, v);
      else if (configuration) {
        if (!prop.configuration) prop.configuration = {};
        prop.configuration[field.name] = v;
      } else if (isStyle) {
        if (!prop.style) prop.style = {};
        prop.style[field.name] = v;
      } else if (subProp) {
        if (!prop[subProp]) prop[subProp] = {};
        prop[subProp][field.name] = v;
      } else prop[field.name] = v;
    });
    onChange && onChange(field.name, v, setProp);
  };
  let stored_value = configuration
    ? configuration[field.name]
    : isStyle
      ? props.style[field.name]
      : subProp
        ? props[subProp]?.[field.name]
        : props[field.name];

  let value = or_if_undef(stored_value, field.default);
  if (valuePostfix)
    value = `${value}`.replaceAll(valuePostfix || "__nosuchstring", "");
  if (field.input_type === "fromtype") field.input_type = null;
  if (
    field.type &&
    (field.type.name === "String" || field.type === "String") &&
    field.attributes?.options
  ) {
    field.input_type = "select";
    field.options =
      typeof field.attributes?.options === "string"
        ? field.attributes?.options.split(",").map((s) => s.trim())
        : [...field.attributes?.options];
    if (!field.required && field.options) field.options.unshift("");
  }
  const field_type = field.input_type || field.type.name || field.type;
  const hasSelect =
    (field_type === "String" && field.attributes?.options) ||
    field_type === "select";
  const getOptions = () =>
    typeof field?.attributes?.options === "string"
      ? field.attributes?.options.split(",").map((s) => s.trim())
      : field?.attributes?.options || field.options;
  if (
    typeof field.default !== "undefined" &&
    typeof stored_value === "undefined"
  ) {
    useEffect(() => {
      myOnChange(field.default);
    }, []);
  } else if (hasSelect && typeof value === "undefined") {
    //pick first value to mimic html form behaviour
    const options = getOptions();
    let o;
    if (options && (o = options[0]))
      useEffect(() => {
        myOnChange(typeof o === "string" ? o : o.value || o.name || o);
      }, []);
  }

  const dispatch = {
    String() {
      if (field.attributes?.options) {
        const options = getOptions();
        return (
          <select
            className={`field-${field?.name} form-control form-select`}
            value={value || ""}
            onChange={(e) => e.target && myOnChange(e.target.value)}
            onBlur={(e) => e.target && myOnChange(e.target.value)}
          >
            {options.map((o, ix) => (
              <option
                key={ix}
                value={typeof o === "string" ? o : o.value || o.name}
              >
                {typeof o === "string" ? o : o.label}
              </option>
            ))}
          </select>
        );
      } else
        return (
          <input
            type="text"
            className={`field-${field?.name} form-control`}
            value={value || ""}
            spellCheck={false}
            onChange={(e) => e.target && myOnChange(e.target.value)}
          />
        );
    },
    Font: () => (
      <select
        className="fontselect form-control form-select"
        value={value || ""}
        onChange={(e) => e.target && myOnChange(e.target.value)}
        onBlur={(e) => e.target && myOnChange(e.target.value)}
      >
        <option value={""}></option>
        {Object.entries(options.fonts || {})
          .sort()
          .map(([nm, ff], ix) => (
            <option key={ix} value={ff}>
              {nm}
            </option>
          ))}
      </select>
    ),
    Integer: () => (
      <input
        type="number"
        className={`field-${field?.name} form-control`}
        step={field.step || 1}
        min={field.min}
        max={field.max}
        value={value || ""}
        onChange={(e) => e.target && myOnChange(e.target.value)}
      />
    ),
    Float: () => (
      <input
        type="number"
        className={`field-${field?.name} form-control`}
        value={value || ""}
        step={0.01}
        max={or_if_undef(field?.attributes?.max, undefined)}
        min={or_if_undef(field?.attributes?.min, undefined)}
        onChange={(e) => e.target && myOnChange(e.target.value)}
      />
    ),
    Color: () => <ColorInput value={value} onChange={(c) => myOnChange(c)} />,
    Bool: () => (
      <div className="form-check">
        <input
          type="checkbox"
          className={`field-${field?.name} form-check-input`}
          checked={value}
          onChange={(e) => e.target && myOnChange(e.target.checked)}
        />
        <label className="form-check-label">{field.label}</label>
      </div>
    ),
    textarea: () => (
      <textarea
        rows="6"
        type="text"
        className={`field-${field?.name} form-control`}
        value={value}
        spellCheck={false}
        onChange={(e) => e.target && myOnChange(e.target.value)}
      />
    ),
    code: () => (
      <textarea
        rows="6"
        type="text"
        className={`field-${field?.name} form-control`}
        value={value}
        onChange={(e) => e.target && myOnChange(e.target.value)}
        spellCheck={false}
      />
    ),
    select: () => {
      if (field.class?.includes?.("selectizable")) {
        const seloptions = field.options.map((o, ix) =>
          o.name && o.label
            ? { value: o.name, label: o.label }
            : o.value && o.label
              ? { value: o.value, label: o.label }
              : { value: o, label: o }
        );
        return (
          <Select
            options={seloptions}
            value={seloptions.find((so) => value === so.value)}
            onChange={(e) =>
              (e.name && myOnChange(e.name)) ||
              (e.value && myOnChange(e.value)) ||
              (typeof e === "string" && myOnChange(e))
            }
            onBlur={(e) =>
              (e.name && myOnChange(e.name)) ||
              (e.value && myOnChange(e.value)) ||
              (typeof e === "string" && myOnChange(e))
            }
            menuPortalTarget={document.body}
            styles={{ menuPortal: (base) => ({ ...base, zIndex: 19999 }) }}
          ></Select>
        );
      } else
        return (
          <select
            className={`field-${field?.name} form-control form-select`}
            value={value || ""}
            onChange={(e) => e.target && myOnChange(e.target.value)}
            onBlur={(e) => e.target && myOnChange(e.target.value)}
          >
            {(field.options || []).map((o, ix) =>
              o.name && o.label ? (
                <option key={ix} value={o.name}>
                  {o.label}
                </option>
              ) : o.value && o.label ? (
                <option key={ix} value={o.value}>
                  {o.label}
                </option>
              ) : (
                <option key={ix}>{o}</option>
              )
            )}
          </select>
        );
    },
    btn_select: () => (
      <div className="btn-group w-100" role="group">
        {field.options.map((o, ix) => (
          <button
            key={ix}
            title={o.title || o.value}
            type="button"
            style={{ width: `${Math.floor(100 / field.options.length)}%` }}
            className={`field-${field?.name} btn btn-sm btn-${
              value !== o.value ? "outline-" : ""
            }secondary ${field.btnClass || ""}`}
            onClick={() => myOnChange(o.value)}
          >
            {o.label}
          </button>
        ))}
      </div>
    ),
    DimUnits: () => {
      let styleVal, styleDim;
      if (isStyle && value === "auto") {
        styleVal = "";
        styleDim = "auto";
      } else if ((isStyle || subProp) && value && typeof value === "string") {
        const matches = value.match(/^([-]?[0-9]+\.?[0-9]*)(.*)/);
        if (matches) {
          styleVal = matches[1];
          styleDim = matches[2];
        }
      }
      return (
        <Fragment>
          {styleDim !== "auto" && (
            <input
              type="number"
              value={(isStyle || subProp ? styleVal : value) || ""}
              className={`field-${field?.name} w-50 form-control-sm d-inline dimunit`}
              disabled={field.autoable && styleDim === "auto"}
              onChange={(e) =>
                e?.target &&
                myOnChange(
                  isStyle || subProp
                    ? `${e.target.value}${styleDim || "px"}`
                    : e.target.value
                )
              }
            />
          )}
          <SelectUnits
            value={or_if_undef(
              configuration
                ? configuration[field.name + "Unit"]
                : isStyle || subProp
                  ? styleDim
                  : props[field.name + "Unit"],
              "px"
            )}
            autoable={field.autoable}
            className={`field-${field?.name} w-${
              styleDim === "auto" ? 100 : 50
            } form-control-sm d-inline dimunit`}
            vert={!field.horiz}
            onChange={(e) => {
              if (!e.target) return;
              const target_value = e.target.value;
              setProp((prop) => {
                const myStyleVal =
                  target_value === "auto" && field.autoable && isStyle
                    ? ""
                    : styleVal;
                if (configuration)
                  prop.configuration[field.name + "Unit"] = target_value;
                else if (isStyle) {
                  prop.style[field.name] = `${or_if_undef(
                    myStyleVal,
                    0
                  )}${target_value}`;
                }
                if (subProp) {
                  if (!prop[subProp]) prop[subProp] = {};
                  prop[subProp][field.name] = `${or_if_undef(
                    myStyleVal,
                    0
                  )}${target_value}`;
                } else prop[field.name + "Unit"] = target_value;
              });
            }}
          />
        </Fragment>
      );
    },
  };
  const f = dispatch[field_type];
  return f ? f() : null;
}
@param {object} props
@param {object} props.field
@param {object} [props.configuration]
@param {function} props.setProp
@param {function} props.onChange
@param {object} props.props
@param {boolean} props.isStyle
@returns {select|input}
@category saltcorn-builder
@subcategory components / elements / utils
@namespace