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

Toolbar ​

Framework-agnostic button strip for map chrome when controls resolve to toolbar or menu layout. Pure protocol lives in @hungpvq/map-core/toolbar; Vue/React host ToolbarControl and wire it through useMapControl.

Related: Stable API — control layout, UniversalRegistry controls, Custom controls (useMapControl), map store TOOLBAR.

Mental model ​

Author options
  host.button  →  corner / auto-button (ModuleContainer)
  toolbar      →  strip (toolbar | menu layout)
        │
        ▼
createHostStrategy (useMapControl)
  standalone | button  →  createLiveToolbarStrategy(noop Toolbar)
  toolbar | menu       →  createLiveToolbarStrategy(MapToolbarStore api)
        │
        ▼
ToolbarControl: planToolbarLayout + planToolbarExpansion + shared click
PieceRole
host.button / toolbarIndependent corner vs strip author options
createHostStrategyAdapter entry: layout switch + expand ctx
createLiveToolbarStrategyOnly engine for all kinds (pass noop Toolbar for host-only)
createToolbarModuleApiRegister into the store only when layout is toolbar | menu
ToolbarControlRenders store buttons; overflow; expandable secondary row

Do not merge or fall back between host.button and toolbar (same object is fine for simple singles).

ts
const chrome = {
  kind: 'module-expandable',
  moduleId: 'mapThemeControl',
  expandableButton: ({ active }) => mdiButtonState(icon, { active, title }),
  buttons: themeModeButtons,
};
host: { button: chrome }, // auto: launcher; options L/R by position
toolbar: chrome,          // strip: launcher + secondary row

Kinds ​

single ​

ts
{
  kind: 'single', // optional default
  id: 'mapHomeControl',
  getState: () => mdiButtonState(path, { title: 'Home' }),
  onClick: (e) => { … },
}

module ​

ts
{
  kind: 'module',
  moduleId: 'mapZoomControl',
  order: 20,
  orientation: 'column',
  buttons: [
    { id: 'in', getState: () => …, onClick: () => … },
    { id: 'out', getState: () => …, onClick: () => … },
  ],
}

Store ids: `${moduleId}:${buttonId}`.

module-expandable ​

Primary row shows a launcher from expandableButton({ active }). Options live on a secondary row (strip) or beside the launcher (host auto, L/R by corner).

ts
{
  kind: 'module-expandable',
  moduleId: 'mapThemeControl',
  closeOnOutsideClick: true, // default
  expandableButton: ({ active }) =>
    mdiButtonState(icon, { title: 'Theme', active }),
  orientation: 'row',
  buttons: [
    { id: 'light', getState: () => …, onClick: () => setMode('light') },
    { id: 'dark', getState: () => …, onClick: () => setMode('dark') },
  ],
}
BehaviorDetail
Launcher activeSynced from expandedModuleId === moduleId
Click launchertoggleExpandedModule(moduleId) via handleToolbarButtonClick / host strategy
Strip secondaryOption buttons of that module (+ close control in ToolbarControl)
Host autoOptions to the right on *-left, to the left on *-right
CloseEscape / outside when closeOnOutsideClick !== false

Stamps: launcher role: 'launcher', expandable: true; options role: 'option', expandable: true.

Shared helpers ​

HelperUse
mapToolbarOptions / withLayoutToolbarOptionsMap UI state across all buttons
createHostStrategy / createLiveToolbarStrategyAdapter + shared engine
planToolbarLayout / planToolbarExpansionOverflow / primary vs secondary
handleToolbarButtonClick / shouldCloseExpandedOnOutsideClickLauncher toggle vs option action

Mount ToolbarControl when any control uses controlLayout: 'toolbar' or mobile buttonInMobile: 'toolbar' | 'menu'.