Skip to content
Docs version
@hungpvq/vue-map@1.2.0@hungpvq/vue-map-core@1.2.0@hungpvq/vue-map-dataset@1.2.0@hungpvq/vue-map-draw@1.2.0@hungpvq/map-draw@1.2.0@hungpvq/react-map@1.2.0@hungpvq/react-map-core@1.2.0@hungpvq/react-map-dataset@1.2.0@hungpvq/react-map-draw@1.2.0

Layer Control ​

Editable layer list: create, group, reorder, delete, run menus. Click a group name (or the pencil) to rename it.

Props ​

PropDescriptionTypeRequiredDefault Value
mapIdstringfalse--
dragIdstringfalse--
btnWidthnumberfalse40
controlOrderCSS flex order for the standalone buttonnumberfalse0
position'top-left', 'top-right', 'bottom-left', 'bottom-right'false'bottom-right'
controlLayout'standalone' (corner), 'toolbar' (always in toolbar), 'button' (always corner; not auto-promoted on mobile)'standalone' | 'toolbar' | 'button'false'standalone'
controlVisiblebooleanfalsetrue

Map also accepts buttonInMobile ('button' | 'toolbar' | 'menu', default 'button'). On viewports ≤640px:

  • 'button' — leave corner buttons unchanged
  • 'toolbar' — move declared control buttons into a single ToolbarControl host (except controlLayout="button"). Mount one ToolbarControl in the map slot
  • 'menu' — hide per-control corner buttons; fan out intact clusters by position into corner stacks with outside-in More. Mount one ToolbarControl.

See ToolbarControl.

PropTypeDefaultEffect
showboolean—Open the panel
disabledCreatebooleanfalseHide create-layer
disabledCreateGroupbooleanfalseHide create-group and hide Add to group in ⋮
disabledDeleteAllbooleanfalseHide delete-all
disabledMovebooleanfalseHide Move up/down in ⋮
menuContextobject | (() => object)—Bag for setHidden / setDisabled
globalVisibilityMode'override' | 'sync''sync'Header eye: mask map only vs sync every layer show
createLayerTypesLayerType[]all typesAllowlist for nested CreateControl type select

menuContext is merged with readonly, disabledMove, disabledCreateGroup and any parent provideMenuConditionContext / MenuConditionProvider. See Menus.

globalVisibilityMode:

  • sync (default) — header eye sets every list item’s show to the all-button value and applies on the map.
  • override — header eye hides/restores layers on the map only; each row’s intended show is unchanged.

createLayerTypes is forwarded to the nested CreateControl (with controlVisible={false} so CreateControl’s own chrome button stays hidden). Omit for all LAYER_TYPES.

Nested CreateControl always uses controlVisible={false}; open create via LayerControl’s +. Mount CreateControl alone when you want its map chrome button. Events: none. Visibility / opacity fire on the list node (toggleShow, changeOpacity) — see Events. Dialogs from ⋮ menus need ComponentManagementControl. Create-layer uses CreateControl internally.

Keyboard: / focuses the layer search input for that map ([data-map-layer-search][data-map-id="<mapId>"], with rAF retry until mounted). Esc closes the top open control and blurs search only if that map’s search was focused.

LayerControl mounts a sidebar panel. Pass a plain title string for the panel switcher menu; use Vue #title / React titleNode only for styled header text. Header layout: [ title | after-title ] …… [ extra-btn ] — menus with location: 'title' render in after-title / afterTitle, not trailing chrome.

VueReact
Store / switcher:title="trans('map.layer-control.title')"title={trans('map.layer-control.title')}
Styled header#title slottitleNode={<span className="…">…</span>}
Menus in header#after-titleafterTitle

Do not pass a ReactNode as React title — the switcher store coerces non-strings to '' and blank menu labels appear.

Thin host / core helpers ​

LayerControl stays a UI host. Shared orchestration lives in @hungpvq/map-dataset:

HelperRole
getLayerControlTitleMenuState(roots)Aggregate location: 'title' menus across dataset list views for #after-title / afterTitle
registerAddGeojsonHereForMap(mapId, addDataset)Register context-menu “Add GeoJSON here” + default items; returns unregister for unmount
syncListViewLayerOrder(map, views)Apply drag-reorder / index to MapLibre layers (used by LayerList)
applyAllLayerVisibility(items, map, show, mode)Header eye: override (map mask) or sync (intended show)
syncAllLayerIntendedShow(items, map, show)Set every list item show and apply on map

Custom LayerControl forks should call registerAddGeojsonHereForMap on mount (and its cleanup on unmount) when the map also mounts MapContextMenuControl.

Layer list rows refresh when datasets are added/removed via useMapDataset(mapId).datasetVersion (pass the mapId ref/computed, not mapId.value — plain store bag is not framework-reactive) — see useMapDataset.

Layer list types (custom UIs) ​

Flat rows and drag trees use shared types from @hungpvq/map-dataset (not adapter-local aliases):

TypeRole
IListViewUIList protocol on a dataset part
LayerListItemFlat row for LayerControl drag list (IListViewUI + tree Item, group narrowed to { id, name })
LayerListTreeNodeTreeItem<LayerListItem>
LayerListGroupTreeGroupTree<LayerListItem>
ListViewGroupRefNormalized { id, name } group on a flat row

Use convertListToTree / convertTreeToList / mergeEmptyGroups with LayerListItem[]. See List UI.

When MapContextMenuControl is on the same map, Quick analysis includes Buffer 500 m here, Buffer 1 km here, and Buffer 5 km here. Clicking one adds a GeoJSON circle layer (with a Fill bound extra button).

Slots / render props ​

NameVueReact
titleListslot { mapId }ReactNode | ({ mapId }) => ReactNode
endListslot { mapId }same
defaultextra childrenchildren

Vue ​

vue
<script setup lang="ts">
import { reactive } from 'vue';
import { Map, BaseMapCard } from '@hungpvq/vue-map-core';
import { LayerControl, ComponentManagementControl } from '@hungpvq/vue-map-dataset';
import '@hungpvq/map-core/style.css';
import '@hungpvq/map-dataset/style.css';
import '@hungpvq/vue-map-core/style.css';
import '@hungpvq/vue-map-dataset/style.css';
import '@hungpvq/vue-draggable/style.css';

const menuUi = reactive({ role: 'admin', canUsePen: true });
</script>

<template>
  <Map>
    <LayerControl
      position="top-left"
      show
      :disabled-create="false"
      :disabled-create-group="false"
      :disabled-delete-all="false"
      :disabled-move="false"
      :menu-context="menuUi"
    >
      <template #titleList="{ mapId }">
        <button type="button">Extra header ({{ mapId }})</button>
      </template>
      <template #endList="{ mapId }">
        <BaseMapCard :mapId="mapId" />
      </template>
    </LayerControl>
    <ComponentManagementControl />
  </Map>
</template>

React ​

tsx
<LayerControl
  position="top-left"
  show
  disabledCreateGroup={false}
  disabledMove={false}
  menuContext={{ role: 'admin', canUsePen: true }}
  titleList={<button type="button">Extra header</button>}
  endList={({ mapId }) => <BaseMapCard mapId={mapId} />}
/>
<ComponentManagementControl />

Search behavior ​

  • Search input uses data-map-layer-search + data-map-id and is focused by / shortcut (bindMapKeyboardShortcuts) for that map only.
  • Filtering is debounced on the client (~150ms) and matching text is highlighted in row titles.
  • When the query has no result, the panel shows search-empty instead of the regular empty-state.