Package Exports
- resium-entity-context-menu
Readme
Entity Context Menu for React/Cesium
A lightweight, type-safe, and functional context menu system for Resium/Cesium applications. Fully controlled via React Context without global registries or singletons.
โจ Features
- ๐ฏ Context-first Architecture - Everything is declaratively controlled via React Context
- ๐ง Pure Functional Design - Menu factories as pure functions without side effects
- ๐จ Flexible Configuration - Per-entity overrides, type-based factories
- โก Async Ready - Supports asynchronous menu generation with loading states
- โฟ Fully Accessible - Keyboard navigation, ARIA roles, focus management
- ๐ฆ TypeScript Support - Type-safe throughout
- ๐ Zero Dependencies - Only React as peer dependency
๐ฆ Installation
npm install resium-entity-context-menu
# or
yarn add resium-entity-context-menu
# or
pnpm add resium-entity-context-menu๐ Quick Start
1. Setup Provider
import { EntityContextMenuProvider, EntityContextMenu } from 'resium-entity-context-menu';
function App() {
// Default factory for all entities
const defaultFactory = (ctx) => [
{
id: 'info',
label: 'Show Info',
onClick: () => console.log(ctx),
},
];
// Type-specific factories
const factoriesByType = {
city: (ctx) => [
{
id: 'fly',
label: 'Fly Here',
onClick: () => flyToCity(ctx.worldPosition),
},
],
};
return (
<EntityContextMenuProvider defaultFactory={defaultFactory} factoriesByType={factoriesByType}>
<CesiumMap />
<EntityContextMenu />
</EntityContextMenuProvider>
);
}2. Use in Components
import { useEntityContextMenu } from 'resium-entity-context-menu';
function MyEntity({ entity }) {
const { showMenu } = useEntityContextMenu();
const handleRightClick = (e) => {
e.preventDefault();
showMenu({
entityId: entity.id,
entityType: entity.type,
position: { x: e.clientX, y: e.clientY },
entityData: entity,
clickedAt: new Date().toISOString(),
});
};
return <div onContextMenu={handleRightClick}>{/* Entity content */}</div>;
}3. Per-Entity Override
Entities can provide their own menu factory:
const berlinEntity = {
id: 'berlin',
type: 'city',
name: 'Berlin',
// Highest priority!
menuFactory: (ctx) => [
{
id: 'special',
label: 'Berlin-specific Action',
onClick: () => openBerlinDetails(),
},
],
};๐ฏ Priority System
Menu resolution follows this priority:
- entity.menuFactory - Entity-specific menu (highest priority)
- factoriesByType[entityType] - Type-based menu
- defaultFactory - Default menu (lowest priority)
๐ API Reference
EntityContextMenuProvider
type EntityContextMenuProviderProps = {
children: React.ReactNode;
defaultFactory: (ctx: EntityContext) => MenuItem[] | Promise<MenuItem[]>;
factoriesByType?: Record<string, MenuFactory>;
onOpen?: (ctx: EntityContext) => void;
onClose?: () => void;
closeOnAction?: boolean; // default: true
};useEntityContextMenu Hook
function useEntityContextMenu(): {
showMenu: (ctx: EntityContext) => void;
hideMenu: () => void;
isVisible: boolean;
context?: EntityContext;
menuItems?: MenuItem[];
};MenuItem Type
type MenuItem = {
id: string;
label: string;
type?: 'action' | 'submenu' | 'toggle' | 'separator' | 'custom';
visible?: (ctx: EntityContext) => boolean;
enabled?: (ctx: EntityContext) => boolean;
onClick?: (ctx: EntityContext) => void | Promise<void>;
items?: MenuItem[]; // for submenus
render?: (ctx: EntityContext) => React.ReactNode; // for custom items
checked?: boolean; // for toggle items
};๐ฅ Advanced Features
Asynchronous Menu Generation
const cityFactory = async (ctx) => {
// Load data from server
const cityData = await fetchCityData(ctx.entityId);
return [
{
id: 'population',
label: `Population: ${cityData.population}`,
onClick: () => showDetails(cityData),
},
];
};Conditional Visibility & Enabling
const menuItems = [
{
id: 'edit',
label: 'Edit',
visible: (ctx) => ctx.entityData.editable,
enabled: (ctx) => !ctx.entityData.locked,
onClick: (ctx) => editEntity(ctx.entityId),
},
];Submenus
const menuItems = [
{
id: 'export',
label: 'Export',
type: 'submenu',
items: [
{ id: 'pdf', label: 'As PDF', onClick: exportPDF },
{ id: 'csv', label: 'As CSV', onClick: exportCSV },
],
},
];Custom Rendering
const menuItems = [
{
id: 'color',
type: 'custom',
render: (ctx) => (
<ColorPicker
value={ctx.entityData.color}
onChange={(color) => updateColor(ctx.entityId, color)}
/>
),
},
];โจ๏ธ Keyboard Shortcuts
- โ/โ - Navigate between menu items
- โ - Open submenu
- โ - Close submenu
- Enter/Space - Activate menu item
- Escape - Close menu
๐จ Styling
The menu uses basic CSS classes. For custom styling:
<EntityContextMenu className="my-custom-menu" />.my-custom-menu {
background: #2a2a2a;
border: 1px solid #444;
/* More styles */
}๐งช Testing
import { render, screen, fireEvent } from '@testing-library/react';
import { EntityContextMenuProvider, useEntityContextMenu } from 'resium-entity-context-menu';
test('shows menu on showMenu call', () => {
const TestComponent = () => {
const { showMenu } = useEntityContextMenu();
return (
<button
onClick={() =>
showMenu({
entityId: 'test',
position: { x: 100, y: 100 },
clickedAt: new Date().toISOString(),
})
}
>
Open Menu
</button>
);
};
render(
<EntityContextMenuProvider defaultFactory={() => [{ id: 'test', label: 'Test Item' }]}>
<TestComponent />
<EntityContextMenu />
</EntityContextMenuProvider>,
);
fireEvent.click(screen.getByText('Open Menu'));
expect(screen.getByText('Test Item')).toBeInTheDocument();
});๐ง Configuration for Cesium/Resium
import { Viewer, Entity } from 'resium';
import { useEntityContextMenu } from 'resium-entity-context-menu';
function CesiumEntity({ position, name }) {
const { showMenu } = useEntityContextMenu();
const handleClick = (movement, target) => {
if (!target) return;
showMenu({
entityId: target.id.id,
entityType: 'cesium-entity',
position: {
x: movement.position.x,
y: movement.position.y,
},
worldPosition: target.id.position,
entityData: target.id,
clickedAt: new Date().toISOString(),
});
};
return <Entity position={position} name={name} onClick={handleClick} />;
}๐ Requirements
- React 16.8+ (Hooks support)
- TypeScript 4.0+ (optional but recommended)
๐ค Contributing
Contributions are welcome! Please create an issue or pull request.
๐ License
MIT
๐ Credits
Built with โค๏ธ for the React/Cesium community