Skip to content

ui-mapbox: components with a hidden attribute never autoload #625

Description

@titouanmathis

Map children declared with hidden never autoload, because the whole @studiometa/ui-mapbox catalog uses the visible strategy.

Reproduction

<link rel="stylesheet" href="https://esm.sh/mapbox-gl@3.13.0/dist/mapbox-gl.css" />
<script type="module" src="https://esm.sh/@studiometa/ui-mapbox@1.10.0/autoload"></script>

<div
  data-component="MapboxMap"
  data-option-access-token=""
  data-option-zoom="10"
  data-option-center="[2.35, 48.86]"
  class="h-96 w-full">
  <div data-ref="container" class="h-full w-full"></div>

  <div
    hidden
    data-component="MapboxNavigationControl"
    data-option-position="top-right"
    data-option-show-compass
    data-option-show-zoom></div>

  <div
    data-component="MapboxGeolocateControl"
    data-option-position="top-right"
    data-option-track-user-location></div>

  <div hidden data-component="MapboxFullscreenControl" data-option-position="top-right"></div>
</div>

Only MapboxGeolocateControl mounts and is added to the map. MapboxNavigationControl and MapboxFullscreenControl never load, and no diagnostic is logged.

Cause

packages/ui-mapbox/src/catalog.ts declares a single strategy for the whole package:

export const catalog: ComponentCatalog = {
  packageName: '@studiometa/ui-mapbox',
  strategy: 'visible',};

Every one of the 14 generated manifest entries inherits strategy: 'visible'. The js-toolkit loader implements that strategy with an IntersectionObserver on the element itself. An element with the hidden attribute is not rendered, so it can never intersect the viewport, the observer callback never fires, and the dynamic import() never runs.

MapboxGeolocateControl mounts in the example above only because that element carries no hidden attribute.

This is not caught by the manifest children path either: MapboxMap deliberately no longer declares its children (packages/ui-mapbox/src/MapboxMap.ts). Each child is registered globally and resolves its map through $closest('MapboxMap'), so each child must be discovered on its own element.

The hidden attribute is the pattern the package README recommends for declarative-only elements, so the two recommendations contradict each other today.

Proposed fix

Keep visible for the rendered roots and give the declarative map children a strategy that does not depend on rendering:

  1. scripts/manifest-types.ts — add strategy?: ComponentLoadStrategy to CuratedComponentMetadata.
  2. scripts/generate-manifests.ts — serialize component.strategy ?? catalog.strategy.
  3. packages/ui-mapbox/src/catalog.ts — keep the package default visible for MapboxMap and StoreLocator, and set strategy: 'eager' on the 12 map children: MapboxCluster, MapboxClusterItem, MapboxFullscreenControl, MapboxGeocoder, MapboxGeolocateControl, MapboxImage, MapboxImages, MapboxLayer, MapboxMarker, MapboxNavigationControl, MapboxPopup, MapboxSource.
  4. Regenerate packages/ui-mapbox/src/manifest.ts.

eager stays cheap here: the loader only schedules tokens that are present in the markup, the child modules are small, and the heavy mapbox-gl import stays gated behind MapboxMap.mounted(). idle would also fix the bug, but it can delay controls and markers up to 2 s after the map appears.

Workaround

Override the strategy per element until this is fixed:

<div hidden data-load="eager" data-component="MapboxFullscreenControl" ></div>

Related

@studiometa/js-toolkit currently stays silent when a visible or interaction component can never receive its signal. A warning in that case would make this class of mistake obvious. That is a separate improvement to open on the js-toolkit repository if wanted.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions