Repository: matplotlib/matplotlib
File: lib/matplotlib/widgets.py (Python)

Code before:
                raise ValueError(
                    f"layout {layout} has {n_rows * n_cols} positions but "
                    f"{n_labels} labels were provided"
                )
        case _:
            raise ValueError(
                "layout must be None, 'vertical', 'horizontal', or a (rows, cols) "
                f"tuple; got {layout!r}")

    # Define spacing in points for DPI-independent sizing
    fig = ax.get_figure(root=False)
    axes_width_display = 72 * ax.bbox.transformed(
        fig.dpi_scale_trans.inverted()
    ).width
    left_margin_display = 11  # points
    button_text_offset_display = 5.5  # points
    col_spacing_display = 11  # points

    # Convert to axes coordinates
    left_margin = left_margin_display / axes_width_display
    button_text_offset = button_text_offset_display / axes_width_display
    col_spacing = col_spacing_display / axes_width_display

    # Create temporary text objects to measure widths.
    # We create Text objects directly rather than using ax.text() since we're
    # only measuring them and don't need to add them to the axes.
    temp_texts = [
        mtext.Text(0, 0, text=label, transform=ax.transAxes, **props)
        for label, props in zip(labels, label_props)
    ]
    # Set figure reference so Text objects can access figure properties
    for text in temp_texts:
        text.set_figure(fig)
    # Calculate max text width per column (in axes coordinates)
    col_widths = [
        max(
            (
                text.get_window_extent(
                    ax.figure.canvas.get_renderer(),
                ).transformed(
                    fig.dpi_scale_trans.inverted()
                ).width * 72
                for text in temp_texts[col_idx::n_cols]
            ),
            default=0,
        )
        / axes_width_display
        for col_idx in range(n_cols)
    ]

    # Center rows vertically in the axes

Review comment (suggestion):
```suggestion
```
they can be added to the axes using `ax.add_artist` and using the internal objects directly is, I think, an unnotable pattern for mpl internals. 


Code after:
                    {k: [] for k in mpl.rcParams if k.startswith("keymap.")}))

    def stop_typing(self):
        if self.capturekeystrokes:
            self._on_stop_typing()
            self._on_stop_typing = None
            notifysubmit = True
        else:
            notifysubmit = False
        self.capturekeystrokes = False
        self.cursor.set_visible(False)
        self.ax.get_figure(root=True).canvas.draw()
        if notifysubmit and self.eventson:
            # Because process() might throw an error in the user's code, only
            # call it once we've already done our cleanup.
            self._observers.process('submit', self.text)

    @_call_with_reparented_event
    def _click(self, event):
        if self.ignore(event):
            return
        if not self.ax.contains(event)[0]:
            self.stop_typing()
            return
        if not self.eventson:
            return
        if event.canvas.mouse_grabber != self.ax:
            event.canvas.grab_mouse(self.ax)
        if not self.capturekeystrokes:
            self.begin_typing()
        self.cursor_index = self.text_disp._char_index_at(event.x)
        self._rendercursor()

    @_call_with_reparented_event
    def _resize(self, event):
        self.stop_typing()

    @_call_with_reparented_event
    def _motion(self, event):
        if self.ignore(event):
            return
        c = self.hovercolor if self.ax.contains(event)[0] else self.color
        if not colors.same_color(c, self.ax.get_facecolor()):
            self.ax.set_facecolor(c)
            if self.drawon:
                self.ax.get_figure(root=True).canvas.draw()

    def on_text_change(self, func):
        """
        When the text changes, call this *func* with event.