mapbox / mapbox.js

Mapbox JavaScript API, a Leaflet Plugin
mapbox.com/mapbox.js/
Other
1.92k stars 385 forks source link

mapbox-address-autofill component too aggressive with css injection in shadow root #1375

Open togakangaroo opened 11 months ago

togakangaroo commented 11 months ago

I am using the AddressAutofill component from @mapbox/search-js-react and am using material ui for my input. When using them in conjunction

    <AddressAutofill
      accessToken={mapboxUiApiKey}
      onRetrieve={(x: AddressAutofillRetrieveResponse) =>
        setSelectedMapboxObject(x.features[0] ?? null)
      }
    >
      <TextField
        label="Address Search"
        variant="outlined"
        fullWidth
        margin="normal"
        value={addressInput}
        onChange={(e) => setAddressInput(e.target.value)}
      />
    </AddressAutofill>

I get a nasty alignment error

label is not aligned properly

After several hours investigating why this is happening, I can see that mapbox generates a mapbox-address-autofill shadow root which imparts a css style of

* { box-sizing: border-box !important; }

on everything - including my MUI input!.

This is going to break MUI's internal calculation of input height and is just all-around bad practice and not being good to your users.

Worse, the solution to this is not straightforward since !important flags cannot be passed to style blocks in MUI. That's why you don't use !important on stuff with wide css specifiers!!!

togakangaroo commented 11 months ago

I'll also post the workaround for anyone else who is dealing with this. The trick is that you have to used styled components to properly place a css of box-sizing: content-box !important; on the input itself.

Doing this is a pain since - as was mentioned - you can't just use a style block. You also can't use a css prop as documented by eg emotion. I'm not 100% on why, but I assume that its because that autofill component does some sort of cloning thing and since this isn't a "standard" react property it is not picked up by the clone.

There a couple options I've found that work. From @emotion/react you cannot use any of the straightforward apis. However, you can use the ClassNames component with a render children prop to surround your TextField and the cx/css functions to create something that can go into inputProps={{className: cx(...)}}. This works but is super verbose and ugly.

The other option is to create a custom styled input component. You don't have to use emotion for this, but it works well

const ContentBoxInput = styled.input`
  box-sizing: content-box !important;
`

and then force the TextField to use that

      <TextField
        label="Address Search"
        variant="outlined"
        fullWidth
        margin="normal"
        value={addressInput}
        onChange={(e) => setAddressInput(e.target.value)}
        InputProps={{
          inputComponent: ContentBoxInput,
        }}
      />

note that it is important to use a TitleCase InputProps here, not the camelCase one