Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bring-your-own-map for Web Components #395

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

ianthetechie
Copy link
Contributor

Background

The Ferrostar Web Component currently initializes the MapLibre map internally within its shadow DOM structure, and provides a post-init configuration callback to allow users to change how it behaves. However, there are some use cases where it would be nice for developer end users to bring their own map (ex: if they already have a MapLibre component in Vue) which is already initialized.

Implementation overview

This PR aims to address that by adding an optional slot for the map view. Whenever anything is supplied for this slot, the default MapLibre init logic will not apply. Otherwise, things will work as before and users should not notice any difference.

Current status

I'm opening this initially as a draft PR because I am not 100% sure of all the documentation / implications for styling, given that (it would seem) inserting the external map doesn't inherit the styling due to shadow DOM/scoping rules. I assume this is not an issue for a fully componentized setup like Vue (which is, to my understanding, the primary if not exclusive use case).

Opening this now for feedback from interested parties + those more knowledgeable about modern frontend dev than myself. Once we get feedback on whether this is working for interested stakeholders + is the correct approach, I'll finish the documentation and finish the review process.

How to test

Here's how to test in your own "real" project.

  1. Ensure you have a dev environment set up including wasm-pack etc.; Refer to [The Ferrostar Book]
  2. Clone the repo and check out this branch.
  3. Within the web directory, execute npm run build
  4. In your own project's package.json, change the ref from npm to your local build. For example:
"@stadiamaps/ferrostar-webcomponents": "file:../ferrostar/web",
  1. Pass your own map container via the slot AND set the map property on your Ferrostar map component after you've initialized it. See this commit for a minimal example (not working due to my aforementioned half-baked impl that doesn't have a separate MapLibre component and is missing something with scoped CSS).

@lseelenbinder
Copy link
Member

The slot based approach may be tricky here, since it implies that the map is a child (and always a child) of the navigation. Is there a reason the map must be part of the navigation's dom? Could it not be adjacent?

It also means it's tricky to have a map that remains for both navigation and non-navigation usage. (If you're not instantiating navigation, the map also doesn't exist without some kind of "teleportation" of the map from outside the navigation's dom.)

Any thoughts or something I'm missing?

@ianthetechie
Copy link
Contributor Author

The slot based approach may be tricky here, since it implies that the map is a child (and always a child) of the navigation. Is there a reason the map must be part of the navigation's dom? Could it not be adjacent?

Indeed it is a bit tricky. the reason it is currently part of the navigation component from a hierarchy perspective is (IIUC) that the navigation component is responsible for adding things like the current instruction banner (top center) and trip progress view (bottom center) as children of the map.

On mobile, there are canonical patterns for accomplishing this. Web seems a bit more fragmented. Any suggestions on how to do this better given your knowledge of MapLibre? Could we perhaps use the addControl API to add the instruction banner and trip progress view to the map dynamically while holding a reference?

It also means it's tricky to have a map that remains for both navigation and non-navigation usage. (If you're not instantiating navigation, the map also doesn't exist without some kind of "teleportation" of the map from outside the navigation's dom.)

The map (via the ferrosar map component) is definitely usable outside navigation mode as-is, but I'm open to suggestions for improvement. We even have hooks for when you go in and out of navigation, so you can show/hide/add/whatever (ex: demonstrations hide the search box).

Any suggestions on a better way to structure this?

@lseelenbinder
Copy link
Member

I would definitely try addControl to see if it works. In my opinion it is the best way to add things to a map right now, and doesn't require anything other than a reference to the map itself.

@ianthetechie
Copy link
Contributor Author

It looks like the addControl interface is exclusive to corner positions 🤔 https://maplibre.org/maplibre-gl-js/docs/API/classes/Map/#addcontrol

We really need top/bottom center though for the instruction banner and trip progress view.

image

This seems like it might be a simple upstream PR to enable more control positions, but isn't available today. Thoughts?

@lseelenbinder
Copy link
Member

That is a bit problematic, then.

I do think that in most cases the navigation and map are sibling components, not parent-child components, but I'd like to get input from someone actually using this before we make the decision.

@bjtrounson
Copy link
Contributor

Just a thought on this, and this is probably a hacky solution but it's a similar approach I've taken for the react-native port since it has a similar issue.

Wrapping the map view in the div that has a relative position and make sure it span 100% of the usable space. Then using absolute positioning on all the navigation components so they just overlay on top of the map view.

This should even allow for bringing your own map since it would completely detach them from the map view.

Could maybe wrap this in a separate component to make it feel like it's a needed wrapper to uses for end users. e.g. map-layout-view

 render() {
    return html`
      <style>
        ${this.customStyles}
      </style>
      <div style="position: relative; width: 100%; height: 100%;">
        <div id="map"></div>
       <instructions-view .tripState=${this._tripState}></instructions-view>
       <div id="bottom-component">
            <trip-progress-view
              .tripState=${this._tripState}
            ></trip-progress-view>
            <button
              id="stop-button"
              @click=${this.stopNavigation}
              ?hidden=${!this._tripState}
            >
              <img src=${CloseSvg} alt="Stop navigation" class="icon" />
            </button>
        </div>
     </div>
    `;
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants