pushGroupProperty = (g, undoable) => {
  const children = g.childNodes
  const len = children.length
  const xform = g.getAttribute('transform')

  const glist = getTransformList(g)
  const m = transformListToTransform(glist).matrix

  const batchCmd = new BatchCommand('Push group properties')

  // TODO: get all fill/stroke properties from the group that we are about to destroy
  // "fill", "fill-opacity", "fill-rule", "stroke", "stroke-dasharray", "stroke-dashoffset",
  // "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity",
  // "stroke-width"
  // and then for each child, if they do not have the attribute (or the value is 'inherit')
  // then set the child's attribute

  const gangle = getRotationAngle(g)

  const gattrs = {
    filter: g.getAttribute('filter'),
    opacity: g.getAttribute('opacity')
  }
  let gfilter
  let gblur
  let changes
  const drawing = svgCanvas.getDrawing()

  for (let i = 0; i < len; i++) {
    const elem = children[i]

    if (elem.nodeType !== 1) {
      continue
    }

    if (gattrs.opacity !== null && gattrs.opacity !== 1) {
      // const c_opac = elem.getAttribute('opacity') || 1;
      const newOpac =
        Math.round((elem.getAttribute('opacity') || 1) * gattrs.opacity * 100) /
        100
      svgCanvas.changeSelectedAttribute('opacity', newOpac, [elem])
    }

    if (gattrs.filter) {
      let cblur = svgCanvas.getBlur(elem)
      const origCblur = cblur
      if (!gblur) {
        gblur = svgCanvas.getBlur(g)
      }
      if (cblur) {
        // Is this formula correct?
        cblur = Number(gblur) + Number(cblur)
      } else if (cblur === 0) {
        cblur = gblur
      }

      // If child has no current filter, get group's filter or clone it.
      if (!origCblur) {
        // Set group's filter to use first child's ID
        if (!gfilter) {
          gfilter = getRefElem(gattrs.filter)
        } else {
          // Clone the group's filter
          gfilter = drawing.copyElem(gfilter)
          findDefs().append(gfilter)

          // const filterElem = getRefElem(gfilter);
          const blurElem = getFeGaussianBlur(gfilter)
          // Change this in future for different filters
          const suffix =
            blurElem?.tagName === 'feGaussianBlur' ? 'blur' : 'filter'
          gfilter.id = elem.id + '_' + suffix
          svgCanvas.changeSelectedAttribute(
            'filter',
            'url(#' + gfilter.id + ')',
            [elem]
          )
        }
      } else {
        gfilter = getRefElem(elem.getAttribute('filter'))
      }
      // const filterElem = getRefElem(gfilter);
      const blurElem = getFeGaussianBlur(gfilter)

      // Update blur value
      if (cblur) {
        svgCanvas.changeSelectedAttribute('stdDeviation', cblur, [blurElem])
        svgCanvas.setBlurOffsets(gfilter, cblur)
      }
    }

    let chtlist = getTransformList(elem)

    // Don't process gradient transforms
    if (elem.tagName.includes('Gradient')) {
      chtlist = null
    }

    // Hopefully not a problem to add this. Necessary for elements like <desc/>
    if (!chtlist) {
      continue
    }

    // Apparently <defs> can get get a transformlist, but we don't want it to have one!
    if (elem.tagName === 'defs') {
      continue
    }

    if (glist.numberOfItems) {
      // TODO: if the group's transform is just a rotate, we can always transfer the
      // rotate() down to the children (collapsing consecutive rotates and factoring
      // out any translates)
      if (gangle && glist.numberOfItems === 1) {
        // [Rg] [Rc] [Mc]
        // we want [Tr] [Rc2] [Mc] where:
        //  - [Rc2] is at the child's current center but has the
        // sum of the group and child's rotation angles
        //  - [Tr] is the equivalent translation that this child
        // undergoes if the group wasn't there

        // [Tr] = [Rg] [Rc] [Rc2_inv]

        // get group's rotation matrix (Rg)
        const rgm = glist.getItem(0).matrix

        // get child's rotation matrix (Rc)
        let rcm = svgCanvas.getSvgRoot().createSVGMatrix()
        const cangle = getRotationAngle(elem)
        if (cangle) {
          rcm = chtlist.getItem(0).matrix
        }

        // get child's old center of rotation
        const cbox = utilsGetBBox(elem)
        const ceqm = transformListToTransform(chtlist).matrix
        const coldc = transformPoint(
          cbox.x + cbox.width / 2,
          cbox.y + cbox.height / 2,
          ceqm
        )

        // sum group and child's angles
        const sangle = gangle + cangle

        // get child's rotation at the old center (Rc2_inv)
        const r2 = svgCanvas.getSvgRoot().createSVGTransform()
        r2.setRotate(sangle, coldc.x, coldc.y)

        // calculate equivalent translate
        const trm = matrixMultiply(rgm, rcm, r2.matrix.inverse())

        // set up tlist
        if (cangle) {
          chtlist.removeItem(0)
        }

        if (sangle) {
          if (chtlist.numberOfItems) {
            chtlist.insertItemBefore(r2, 0)
          } else {
            chtlist.appendItem(r2)
          }
        }

        if (trm.e || trm.f) {
          const tr = svgCanvas.getSvgRoot().createSVGTransform()
          tr.setTranslate(trm.e, trm.f)
          if (chtlist.numberOfItems) {
            chtlist.insertItemBefore(tr, 0)
          } else {
            chtlist.appendItem(tr)
          }
        }
      } else {
        // more complicated than just a rotate
        // transfer the group's transform down to each child and then
        // call recalculateDimensions()
        const oldxform = elem.getAttribute('transform')
        changes = {}
        changes.transform = oldxform || ''

        const newxform = svgCanvas.getSvgRoot().createSVGTransform()

        // [ gm ] [ chm ] = [ chm ] [ gm' ]
        // [ gm' ] = [ chmInv ] [ gm ] [ chm ]
        const chm = transformListToTransform(chtlist).matrix
        const chmInv = chm.inverse()
        const gm = matrixMultiply(chmInv, m, chm)
        newxform.setMatrix(gm)
        chtlist.appendItem(newxform)
      }
      const cmd = recalculateDimensions(elem)
      if (cmd) {
        batchCmd.addSubCommand(cmd)
      }
    }
  }

  // remove transform and make it undo-able
  if (xform) {
    changes = {}
    changes.transform = xform
    g.setAttribute('transform', '')
    g.removeAttribute('transform')
    batchCmd.addSubCommand(new ChangeElementCommand(g, changes))
  }

  if (undoable && !batchCmd.isEmpty()) {
    return batchCmd
  }
  return undefined
}
Pushes all appropriate parent group properties down to its children, then
removes them from the group.
@function module:selected-elem.SvgCanvas#pushGroupProperty
@param {SVGAElement|SVGGElement} g
@param {boolean} undoable
@returns {BatchCommand|void}