From 07bb1d067fdddbe04c229453fe3a07416833422d Mon Sep 17 00:00:00 2001 From: Jonatas Walker Date: Mon, 5 Jan 2026 19:20:03 -0300 Subject: [PATCH] feat: improve docs --- .github/ISSUE_TEMPLATE/bug_report.yml | 131 ++++++ .github/ISSUE_TEMPLATE/config.yml | 11 + .github/ISSUE_TEMPLATE/feature_request.yml | 74 ++++ .github/ISSUE_TEMPLATE/question.yml | 53 +++ .github/PULL_REQUEST_TEMPLATE.md | 85 ++++ .gitignore | 1 + CODE_OF_CONDUCT.md | 133 ++++++ CONTRIBUTING.md | 328 ++++++++++++++ README.md | 310 ++++++------- SECURITY.md | 96 +++++ docs/api-reference.md | 479 +++++++++++++++++++++ docs/examples.md | 465 ++++++++++++++++++++ docs/getting-started.md | 233 ++++++++++ docs/troubleshooting.md | 425 ++++++++++++++++++ docs/typescript.md | 440 +++++++++++++++++++ package.json | 21 +- vite.config.ts | 2 +- 17 files changed, 3114 insertions(+), 173 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/ISSUE_TEMPLATE/question.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 SECURITY.md create mode 100644 docs/api-reference.md create mode 100644 docs/examples.md create mode 100644 docs/getting-started.md create mode 100644 docs/troubleshooting.md create mode 100644 docs/typescript.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..499c7fe --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,131 @@ +name: ๐Ÿ› Bug Report +description: Report a bug or unexpected behavior +title: "[Bug]: " +labels: ["bug", "needs-triage"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to report a bug! Please fill out this form to help us understand and fix the issue. + + - type: textarea + id: description + attributes: + label: Description + description: A clear and concise description of what the bug is. + placeholder: The context menu doesn't appear when... + validations: + required: true + + - type: textarea + id: steps + attributes: + label: Steps to Reproduce + description: Steps to reproduce the behavior + placeholder: | + 1. Create a map with '...' + 2. Right-click on '...' + 3. See error + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected Behavior + description: What you expected to happen + placeholder: The context menu should appear... + validations: + required: true + + - type: textarea + id: actual + attributes: + label: Actual Behavior + description: What actually happened + placeholder: Instead, nothing happens... + validations: + required: true + + - type: input + id: version-contextmenu + attributes: + label: ol-contextmenu Version + description: Which version of ol-contextmenu are you using? + placeholder: "5.5.0" + validations: + required: true + + - type: input + id: version-openlayers + attributes: + label: OpenLayers Version + description: Which version of OpenLayers are you using? + placeholder: "10.2.1" + validations: + required: true + + - type: dropdown + id: browser + attributes: + label: Browser + description: Which browser are you using? + options: + - Chrome + - Firefox + - Safari + - Edge + - Other + validations: + required: true + + - type: input + id: browser-version + attributes: + label: Browser Version + placeholder: "120.0" + + - type: dropdown + id: os + attributes: + label: Operating System + options: + - Windows + - macOS + - Linux + - iOS + - Android + - Other + + - type: textarea + id: reproduction + attributes: + label: Minimal Reproduction + description: | + Please provide a link to a minimal reproduction (CodeSandbox, JSFiddle, or GitHub repository). + This helps us identify and fix the issue faster. + placeholder: https://codesandbox.io/s/... + + - type: textarea + id: logs + attributes: + label: Console Errors + description: Any errors from the browser console + render: shell + + - type: textarea + id: additional + attributes: + label: Additional Context + description: Any other context, screenshots, or information about the problem + + - type: checkboxes + id: checklist + attributes: + label: Checklist + options: + - label: I have searched existing issues to avoid duplicates + required: true + - label: I have provided a minimal reproduction (if applicable) + - label: I am using a supported version of OpenLayers (7.x - 10.x) + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..d1d5cf1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: ๐Ÿ“– Documentation + url: https://github.com/jonataswalker/ol-contextmenu#readme + about: Read the documentation and guides + - name: ๐Ÿ’ฌ Discussions + url: https://github.com/jonataswalker/ol-contextmenu/discussions + about: Ask questions and discuss ideas with the community + - name: ๐ŸŽฎ Examples + url: https://github.com/jonataswalker/ol-contextmenu/tree/master/examples + about: Check out working examples diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..f197c08 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,74 @@ +name: ๐Ÿ’ก Feature Request +description: Suggest a new feature or enhancement +title: "[Feature]: " +labels: ["enhancement", "needs-triage"] +body: + - type: markdown + attributes: + value: | + Thanks for suggesting a new feature! Please provide as much detail as possible. + + - type: textarea + id: problem + attributes: + label: Problem Statement + description: What problem does this feature solve? + placeholder: I'm always frustrated when... + validations: + required: true + + - type: textarea + id: solution + attributes: + label: Proposed Solution + description: How would you like this feature to work? + placeholder: The context menu should... + validations: + required: true + + - type: textarea + id: example + attributes: + label: Usage Example + description: Show how you would use this feature + placeholder: | + ```javascript + const contextmenu = new ContextMenu({ + newFeature: true, + // ... + }); + ``` + render: javascript + + - type: textarea + id: alternatives + attributes: + label: Alternatives Considered + description: What other solutions or workarounds have you considered? + + - type: dropdown + id: breaking + attributes: + label: Breaking Change? + description: Would this be a breaking change? + options: + - "No" + - "Yes" + - "Not sure" + validations: + required: true + + - type: textarea + id: additional + attributes: + label: Additional Context + description: Any mockups, screenshots, or references that would help + + - type: checkboxes + id: checklist + attributes: + label: Checklist + options: + - label: I have searched existing issues/PRs for similar features + required: true + - label: I am willing to help implement this feature diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml new file mode 100644 index 0000000..8483a45 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -0,0 +1,53 @@ +name: โ“ Question +description: Ask a question about using ol-contextmenu +title: "[Question]: " +labels: ["question"] +body: + - type: markdown + attributes: + value: | + Have a question? We're here to help! + + **Before asking:** + - Check the [documentation](https://github.com/jonataswalker/ol-contextmenu#readme) + - Search [existing issues](https://github.com/jonataswalker/ol-contextmenu/issues) + - Review the [examples](https://github.com/jonataswalker/ol-contextmenu/tree/master/examples) + + - type: textarea + id: question + attributes: + label: Your Question + description: What would you like to know? + placeholder: How do I...? + validations: + required: true + + - type: textarea + id: context + attributes: + label: What You've Tried + description: What have you already attempted? + placeholder: | + I tried... + I looked at... + + - type: textarea + id: code + attributes: + label: Code Example + description: Share relevant code (if applicable) + render: javascript + + - type: input + id: version + attributes: + label: ol-contextmenu Version + placeholder: "5.5.0" + + - type: checkboxes + id: checklist + attributes: + label: Checklist + options: + - label: I have read the documentation + - label: I have searched existing issues diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..25ccb2b --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,85 @@ +# Description + + + +## Type of Change + + + +- [ ] ๐Ÿ› Bug fix (non-breaking change that fixes an issue) +- [ ] โœจ New feature (non-breaking change that adds functionality) +- [ ] ๐Ÿ’ฅ Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] ๐Ÿ“ Documentation update +- [ ] ๐ŸŽจ Code style update (formatting, renaming) +- [ ] โ™ป๏ธ Code refactoring (no functional changes) +- [ ] โšก Performance improvement +- [ ] โœ… Test update +- [ ] ๐Ÿ”ง Build configuration change +- [ ] ๐Ÿ”จ Other (please describe) + +## Related Issues + + + +Fixes # +Closes # +Related to # + +## Changes Made + + + +- +- +- + +## Testing + + + +- [ ] Tested locally +- [ ] Added/updated unit tests +- [ ] Added/updated browser tests +- [ ] All tests pass (`npm test`) +- [ ] Linter passes (`npm run lint`) + +### Test Environment + +- **ol-contextmenu version:** +- **OpenLayers version:** +- **Node version:** +- **Browser(s):** + +## Screenshots + + + +## Breaking Changes + + + +- [ ] No breaking changes +- [ ] Breaking changes (described below) + + + +## Checklist + + + +- [ ] My code follows the project's code style +- [ ] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new warnings +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## Additional Notes + + diff --git a/.gitignore b/.gitignore index c1c01b1..4c09ced 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ dist .eslintcache coverage .idea +.claude diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..62d7d1b --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,133 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Project maintainers are responsible for clarifying and enforcing our standards +of acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Project maintainers have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the project maintainers responsible for enforcement at +[jonataswalker@gmail.com](mailto:jonataswalker@gmail.com). + +All complaints will be reviewed and investigated promptly and fairly. + +All project maintainers are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Project maintainers will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from project maintainers, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ce85764 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,328 @@ +# Contributing to ol-contextmenu + +First off, thank you for considering contributing to ol-contextmenu! ๐ŸŽ‰ + +We love contributions from the community. Whether it's a bug report, feature request, documentation improvement, or code contribution, every contribution is valuable. + +## Table of Contents + +- [Code of Conduct](#code-of-conduct) +- [How Can I Contribute?](#how-can-i-contribute) +- [Development Setup](#development-setup) +- [Pull Request Process](#pull-request-process) +- [Coding Guidelines](#coding-guidelines) +- [Testing](#testing) +- [Documentation](#documentation) + +## Code of Conduct + +This project adheres to a [Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to the project maintainers. + +## How Can I Contribute? + +### Reporting Bugs + +Before creating bug reports, please check existing issues to avoid duplicates. + +**When submitting a bug report, include:** + +- **Clear title and description** +- **Steps to reproduce** the issue +- **Expected behavior** vs **actual behavior** +- **Screenshots** if applicable +- **Environment details:** + - ol-contextmenu version + - OpenLayers version + - Browser and version + - Operating system +- **Minimal reproduction example** (CodeSandbox, JSFiddle, or repository) + +### Suggesting Enhancements + +Enhancement suggestions are welcome! Please provide: + +- **Clear use case** - Why is this needed? +- **Detailed description** - What should it do? +- **Examples** - How would it be used? +- **Alternatives considered** - What other solutions did you consider? + +### Pull Requests + +We actively welcome pull requests! + +## Development Setup + +### Prerequisites + +- **Node.js**: 16.x or higher +- **npm**: 8.x or higher +- **Git** + +### Fork and Clone + +1. Fork the repository on GitHub +2. Clone your fork locally: + +```bash +git clone https://github.com/YOUR_USERNAME/ol-contextmenu.git +cd ol-contextmenu +``` + +3. Add upstream remote: + +```bash +git remote add upstream https://github.com/jonataswalker/ol-contextmenu.git +``` + +### Install Dependencies + +```bash +npm install +``` + +### Development Commands + +```bash +# Start development server +npm run dev + +# Run tests +npm test + +# Run tests in watch mode +npm run test:ui + +# Run linter +npm run lint + +# Fix linting issues +npm run lint:fix + +# Build for production +npm run build +``` + +### Project Structure + +``` +ol-contextmenu/ +โ”œโ”€โ”€ src/ +โ”‚ โ”œโ”€โ”€ main.ts # Main entry point +โ”‚ โ”œโ”€โ”€ constants.ts # Constants and defaults +โ”‚ โ”œโ”€โ”€ types.ts # TypeScript type definitions +โ”‚ โ”œโ”€โ”€ helpers/ # Helper functions +โ”‚ โ””โ”€โ”€ sass/ # Styles +โ”œโ”€โ”€ tests/ # Test files +โ”‚ โ”œโ”€โ”€ *.unit.spec.ts # Unit tests +โ”‚ โ””โ”€โ”€ *.browser.spec.ts # Browser tests +โ”œโ”€โ”€ examples/ # Example implementations +โ”œโ”€โ”€ docs/ # Documentation +โ””โ”€โ”€ dist/ # Build output (generated) +``` + +## Pull Request Process + +### 1. Create a Feature Branch + +```bash +git checkout -b feature/my-new-feature +# or +git checkout -b fix/issue-description +``` + +### 2. Make Your Changes + +- Write clean, readable code +- Follow the [coding guidelines](#coding-guidelines) +- Add tests for new functionality +- Update documentation as needed + +### 3. Test Your Changes + +```bash +# Run all tests +npm test + +# Run linter +npm run lint + +# Build to ensure no errors +npm run build +``` + +**All tests must pass before submitting a PR.** + +### 4. Commit Your Changes + +Write clear, descriptive commit messages: + +```bash +git commit -m "feat: add support for custom menu positioning + +- Add new positioning API +- Update documentation +- Add tests for new feature + +Fixes #123" +``` + +**Commit message format:** +- `feat:` - New feature +- `fix:` - Bug fix +- `docs:` - Documentation changes +- `test:` - Adding or updating tests +- `refactor:` - Code refactoring +- `style:` - Code style changes (formatting) +- `chore:` - Maintenance tasks + +### 5. Push to Your Fork + +```bash +git push origin feature/my-new-feature +``` + +### 6. Create Pull Request + +1. Go to the [original repository](https://github.com/jonataswalker/ol-contextmenu) +2. Click "New Pull Request" +3. Select your fork and branch +4. Fill out the PR template: + - Describe your changes + - Reference related issues + - Add screenshots if applicable + - Check all boxes in the PR checklist + +### 7. Code Review + +- Maintainers will review your PR +- Address any requested changes +- Keep the PR up to date with the main branch + +### 8. Merge + +Once approved, a maintainer will merge your PR. Congratulations! ๐ŸŽ‰ + +## Coding Guidelines + +### TypeScript + +- **Use TypeScript** for all new code +- **Export types** for public APIs +- **Avoid `any`** - use proper types +- **Document complex types** with comments + +```typescript +// โœ… Good +interface MenuOptions { + /** Width of the menu in pixels */ + width?: number; + /** Default menu items */ + items?: Item[]; +} + +// โŒ Avoid +function doSomething(data: any) { ... } +``` + +### Code Style + +We use ESLint for code style. Run `npm run lint:fix` to auto-fix issues. + +**Key points:** +- **Indentation**: 4 spaces +- **Semicolons**: Required +- **Quotes**: Single quotes for strings +- **Line length**: 100 characters max +- **Naming**: + - `camelCase` for variables and functions + - `PascalCase` for classes and types + - `UPPER_CASE` for constants + +### Best Practices + +- **Keep functions small** and focused +- **Avoid side effects** where possible +- **Use descriptive names** for variables and functions +- **Comment complex logic** but avoid obvious comments +- **Handle errors gracefully** +- **Validate inputs** for public APIs + +## Testing + +We use Vitest for testing with both unit and browser tests. + +### Writing Tests + +1. **Place tests** in the `tests/` directory +2. **Name test files**: `*.spec.ts` or `*.test.ts` +3. **Use descriptive test names** + +```typescript +describe('ContextMenu', () => { + it('should open menu on right-click', async () => { + // Arrange + const contextmenu = new ContextMenu(); + map.addControl(contextmenu); + + // Act + dispatchContextMenu(viewport); + await new Promise(resolve => setTimeout(resolve, 150)); + + // Assert + expect(contextmenu.isOpen()).toBe(true); + }); +}); +``` + +### Test Coverage + +- **Aim for high coverage** (>90%) +- **Test edge cases** and error conditions +- **Test TypeScript types** where applicable + +### Running Specific Tests + +```bash +# Run specific file +npx vitest run tests/instance.unit.spec.ts + +# Run tests matching pattern +npx vitest run --grep "should open menu" +``` + +## Documentation + +### Updating Docs + +When adding features or changing behavior: + +1. **Update README.md** if it affects basic usage +2. **Update API docs** in `docs/api-reference.md` +3. **Add examples** to `docs/examples.md` +4. **Update TypeScript docs** if types changed +5. **Add to CHANGELOG.md** + +### Writing Documentation + +- **Be clear and concise** +- **Include code examples** +- **Explain the "why" not just the "how"** +- **Use proper markdown formatting** +- **Keep it up to date** + +## Getting Help + +Need help with your contribution? + +- ๐Ÿ’ฌ **Ask questions** in your PR or issue +- ๐Ÿ“– **Read the docs** in the `/docs` directory +- ๐Ÿ” **Search existing issues** and PRs +- ๐Ÿ‘ฅ **Reach out to maintainers** + +## Recognition + +Contributors are recognized in: +- [Contributors Graph](https://github.com/jonataswalker/ol-contextmenu/graphs/contributors) +- Release notes (for significant contributions) + +Thank you for contributing to ol-contextmenu! โค๏ธ diff --git a/README.md b/README.md index c0a6d5d..8a164f2 100644 --- a/README.md +++ b/README.md @@ -1,251 +1,223 @@ -# OpenLayers Custom Context Menu +# ๐Ÿ—บ๏ธ OpenLayers Context Menu + +> A customizable, feature-rich context menu extension for OpenLayers maps

- - Build Status + + Build Status npm version - npm + npm downloads - license + MIT License

-A `contextmenu` extension for [OpenLayers](http://openlayers.org/). **Requires** OpenLayers **v7.0.0** or higher. - -![contextmenu anim](https://raw.githubusercontent.com/jonataswalker/ol-contextmenu/screenshot/images/anim.gif) +

+ Context Menu Demo +

-## Demo +**Quick Links:** [Demo](#-demo) โ€ข [Installation](#-installation) โ€ข [Quick Start](#-quick-start) โ€ข [API](#-api) โ€ข [TypeScript](#-typescript) โ€ข [Examples](#-examples) -[JSFiddle](https://jsfiddle.net/jonataswalker/ooxs1w5d/) -[CodeSandbox](https://codesandbox.io/s/openlayers-custom-context-menu-5s99kb?file=/src/index.js) +--- -## How to use it? +## โœจ Features -##### NPM +- ๐ŸŽฏ **Easy Integration** - Works seamlessly with OpenLayers 7.x - 10.x +- ๐ŸŽจ **Fully Customizable** - Control width, icons, styles, and appearance +- ๐Ÿ”— **Nested Submenus** - Unlimited nesting levels for complex menu structures +- ๐Ÿ“ฆ **Zero CSS Dependencies** - Styles are bundled inline (since v6.0) +- ๐ŸŽช **Event-Driven** - React to `beforeopen`, `open`, and `close` events +- ๐ŸŒ **Multiple Trigger Types** - Context menu, click, or double-click +- ๐Ÿ“ฑ **Viewport-Aware** - Automatically repositions to stay visible at screen edges +- ๐Ÿ’ช **TypeScript Support** - Full type definitions included out of the box +- โšก **Lightweight** - Minimal footprint with only one dependency (`tiny-emitter`) +- โœ… **Well Tested** - 191 tests covering all functionality -`npm install ol-contextmenu` +## ๐ŸŽช Demo -##### CDN Hosted - [jsDelivr](https://www.jsdelivr.com/package/npm/ol-contextmenu) +**Try it live:** +- ๐ŸŽฎ [CodeSandbox](https://codesandbox.io/s/openlayers-custom-context-menu-5s99kb?file=/src/index.js) - Interactive demo with full source +- ๐Ÿ“ [JSFiddle](https://jsfiddle.net/jonataswalker/ooxs1w5d/) - Quick playground +- ๐Ÿ’ป [Local Examples](./examples) - Webpack, Vite, and CDN examples -Load Javascript (CSS is bundled): +## ๐Ÿ“ฆ Installation -```HTML - +**npm (Recommended):** +```bash +npm install ol ol-contextmenu ``` -##### CDN Hosted - UNPKG - -Load Javascript (CSS is bundled): - -```HTML - +**CDN:** +```html + ``` -##### Self hosted +**Requires:** OpenLayers 7.0.0 or higher -Download [latest release](https://github.com/jonataswalker/ol-contextmenu/releases/latest) and load Javascript (CSS is bundled). +๐Ÿ“– See [Getting Started Guide](docs/getting-started.md) for detailed installation instructions and setup. -##### Instantiate with some options and add the Control +## ๐Ÿš€ Quick Start ```javascript +import ContextMenu from 'ol-contextmenu'; + const contextmenu = new ContextMenu({ width: 170, - defaultItems: true, // defaultItems are (for now) Zoom In/Zoom Out + defaultItems: true, // Includes Zoom In/Zoom Out items: [ { text: 'Center map here', - classname: 'some-style-class', // add some CSS rules - callback: center, // `center` is your callback function + callback: (obj, map) => { + map.getView().setCenter(obj.coordinate); + }, }, { text: 'Add a Marker', - classname: 'some-style-class', // you can add this icon with a CSS class - // instead of `icon` property (see next line) - icon: 'img/marker.png', // this can be relative or absolute - callback: marker, + icon: 'img/marker.png', + callback: addMarker, }, - '-', // this is a separator + '-', // Separator ], }); + map.addControl(contextmenu); ``` -##### You can add a (nested) submenu like this: - -If you provide `items {Array}` a submenu will be created as a child of the current item. +๐Ÿ“– See [Getting Started Guide](docs/getting-started.md) for complete setup instructions and [Examples](docs/examples.md) for advanced patterns. -```javascript -const all_items = [ - { - text: 'Some Actions', - items: [ - // <== this is a submenu - { - text: 'Action 1', - callback: action, - }, - { - text: 'Other action', - callback: action2, - }, - ], - }, - { - text: 'Add a Marker', - icon: 'img/marker.png', - callback: marker, - }, - '-', // this is a separator -]; -``` +## ๐Ÿ“– Documentation -##### Would you like to propagate custom data to the callback handler? +- **[Getting Started](docs/getting-started.md)** - Installation and basic setup +- **[API Reference](docs/api-reference.md)** - Complete API documentation +- **[TypeScript Guide](docs/typescript.md)** - TypeScript usage and type definitions +- **[Examples & Recipes](docs/examples.md)** - Common patterns and code examples +- **[Troubleshooting](docs/troubleshooting.md)** - Common issues and solutions -```javascript -const removeMarker = function (obj) { - vectorLayer.getSource().removeFeature(obj.data.marker); -}; -const removeMarkerItem = { - text: 'Remove this Marker', - icon: 'img/marker.png', - callback: removeMarker, -}; - -let restore = false; -contextmenu.on('open', function (evt) { - const feature = map.forEachFeatureAtPixel(evt.pixel, function (ft, l) { - return ft; - }); - if (feature) { - contextmenu.clear(); - removeMarkerItem.data = { marker: feature }; - contextmenu.push(removeMarkerItem); - restore = true; - } else if (restore) { - contextmenu.clear(); - contextmenu.extend(contextmenu_items); - contextmenu.extend(contextmenu.getDefaultItems()); - restore = false; - } -}); -``` +## ๐Ÿ“˜ TypeScript -# API +Full TypeScript support with comprehensive type definitions included: -## Constructor +```typescript +import ContextMenu, { type Item } from 'ol-contextmenu'; -#### `new ContextMenu(options)` +const items: Item[] = [ + { + text: 'Center map here', + callback: (obj, map) => { + map.getView().setCenter(obj.coordinate); + }, + }, +]; -###### `options` is an object with the following possible properties: +const contextmenu = new ContextMenu({ width: 170, items }); +``` -- `eventType`: `contextmenu`; The listening event type (You could use `'click'`, `'dblclick'`) -- `defaultItems`: `true`; Whether the default items (which are: Zoom In/Out) are enabled -- `width`: `150`; The menu's width -- `items`: `[]`; An array of object|string +๐Ÿ“– See the [TypeScript Guide](docs/typescript.md) for complete type definitions and usage patterns. -## Methods +## ๐ŸŽฏ API -#### contextmenu.clear() +### Constructor -Remove all elements from the menu. +```javascript +new ContextMenu(options) +``` -#### contextmenu.closeMenu() +**Options:** +- `width` (number, default: `150`) - Menu width in pixels +- `defaultItems` (boolean, default: `true`) - Include default Zoom In/Out items +- `items` (Item[], default: `[]`) - Array of menu items +- `eventType` (string, default: `'contextmenu'`) - Event type to trigger menu -Close the menu programmatically. +### Methods -#### contextmenu.extend(arr) +- `clear()` - Remove all items +- `close()` - Close menu programmatically +- `extend(items)` - Add multiple items +- `push(item)` - Add single item +- `pop()` / `shift()` - Remove items +- `getDefaultItems()` - Get default items array +- `isOpen()` - Check if menu is open +- `enable()` / `disable()` - Control menu state -`@param {Array} arr` +### Events -Add items to the menu. This pushes each item in the provided array to the end of the menu. +- `beforeopen` - Fired before menu opens +- `open` - Fired when menu opens +- `close` - Fired when menu closes -Example: +๐Ÿ“– See [API Reference](docs/api-reference.md) for complete documentation with examples. -```js -const contextmenu = new ContextMenu(); -map.addControl(contextmenu); +## ๐ŸŽจ Examples -const add_later = [ - '-', // this is a separator - { - text: 'Add a Marker', - icon: 'img/marker.png', - callback: marker, - }, -]; -contextmenu.extend(add_later); -``` +- **[Examples & Recipes](docs/examples.md)** - Common patterns and code examples +- **[Local Examples](./examples)** - Complete working projects: + - [CDN Example](./examples/contextmenu.html) - No build tools required + - [Webpack Example](./examples/my-project-with-webpack) - Integration with Webpack + - [Vite Example](./examples/my-project-with-vite) - Modern setup with Vite + TypeScript -#### contextmenu.push(item) +## ๐Ÿ”ง Troubleshooting -`@param {Object|String} item` +Common issues: +- **Menu doesn't appear:** Ensure `map.addControl(contextmenu)` is called +- **Menu cut off:** Update to v5.5.0+ for automatic viewport positioning +- **TypeScript errors:** Use `import ContextMenu, { type Item } from 'ol-contextmenu'` -Insert the provided item at the end of the menu. +๐Ÿ“– See the [Troubleshooting Guide](docs/troubleshooting.md) for detailed solutions and common issues. -#### contextmenu.shift() +## ๐ŸŒ Browser Support -Remove the first item of the menu. +Modern browsers supporting ES6+: +- Chrome/Edge (latest) +- Firefox (latest) +- Safari (latest) -#### contextmenu.pop() +## ๐Ÿค Contributing -Remove the last item of the menu. +Contributions are welcome! We appreciate: +- ๐Ÿ› Bug reports +- ๐Ÿ’ก Feature requests +- ๐Ÿ“ Documentation improvements +- โœจ Code contributions -#### contextmenu.getDefaultItems() +Please read our [Contributing Guidelines](CONTRIBUTING.md) before submitting a PR. -Get an array of default items. +### Development Setup -#### contextmenu.isOpen() +```bash +# Clone the repository +git clone https://github.com/jonataswalker/ol-contextmenu.git +cd ol-contextmenu -Whether the menu is open. +# Install dependencies +npm install -#### contextmenu.updatePosition(pixel) +# Run tests +npm test -`@param {Array} pixel` +# Start development server +npm run dev -Update menu's position. +# Build for production +npm run build +``` -## Events +## ๐Ÿ“„ License -#### If you want to disable this plugin under certain circumstances, listen to `beforeopen` +MIT ยฉ [Jonatas Walker](https://github.com/jonataswalker) -```javascript -contextmenu.on('beforeopen', function (evt) { - const feature = map.forEachFeatureAtPixel(evt.pixel, function (ft, l) { - return ft; - }); - - if (feature) { - // open only on features - contextmenu.enable(); - } else { - contextmenu.disable(); - } -}); -``` +## ๐Ÿ™ Acknowledgments -#### Listen and make some changes when context menu opens +Built with โค๏ธ for the [OpenLayers](https://openlayers.org/) community. -```javascript -contextmenu.on('open', function (evt) { - const feature = map.forEachFeatureAtPixel(evt.pixel, function (ft, l) { - return ft; - }); - - if (feature) { - // add some other items to the menu - } -}); -``` +Special thanks to all [contributors](https://github.com/jonataswalker/ol-contextmenu/graphs/contributors) who have helped improve this project. -#### Any action when context menu gets closed? +--- -```javascript -contextmenu.on('close', function (evt) { - // it's upon you -}); -``` +

+ Made with โค๏ธ by the community +

diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..dbf1759 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,96 @@ +# Security Policy + +## What to Include in Your Report + +Help us understand the issue by including: + +1. **Type of vulnerability** (XSS, injection, etc.) +2. **Affected version(s)** +3. **Step-by-step instructions** to reproduce +4. **Proof of concept** (if applicable) +6. **Suggested fix** (if you have one) + +## Security Best Practices + +When using ol-contextmenu: + +### Input Validation + +Always validate and sanitize user input before displaying in menu items: + +```javascript +// โŒ Unsafe - XSS vulnerability +{ + text: userInput, // Could contain malicious HTML + callback: fn +} + +// โœ… Safe - Sanitized +{ + text: sanitizeHTML(userInput), + callback: fn +} +``` + +### Custom Callbacks + +Be cautious with callbacks that execute user-controlled code: + +```javascript +// โŒ Unsafe - eval is dangerous +{ + text: 'Execute', + callback: (obj) => { + eval(obj.data.code); // Never do this! + } +} + +// โœ… Safe - Validated actions +{ + text: 'Execute', + callback: (obj) => { + if (allowedActions.includes(obj.data.action)) { + performAction(obj.data.action); + } + } +} +``` + +### Icon URLs + +Validate icon URLs to prevent XSS: + +```javascript +// โŒ Unsafe +{ + text: 'Item', + icon: userProvidedURL // Could be javascript: or data: URL +} + +// โœ… Safe +{ + text: 'Item', + icon: isValidImageURL(userProvidedURL) ? userProvidedURL : defaultIcon +} +``` + +### Content Security Policy + +Consider using CSP headers to mitigate XSS risks: + +```html + +``` + +## Known Security Considerations + +### DOM-based XSS + +ol-contextmenu renders user-provided text and icons in the DOM. Always sanitize: + +- Menu item text +- Icon URLs +- Custom CSS classes +- Data passed to callbacks + diff --git a/docs/api-reference.md b/docs/api-reference.md new file mode 100644 index 0000000..4dbff3d --- /dev/null +++ b/docs/api-reference.md @@ -0,0 +1,479 @@ +# API Reference + +Complete API documentation for `ol-contextmenu`. + +## Constructor + +### `new ContextMenu(options)` + +Creates a new context menu instance. + +**Parameters:** + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `options` | `Partial` | `{}` | Configuration options | +| `options.width` | `number` | `150` | Width of the menu in pixels | +| `options.defaultItems` | `boolean` | `true` | Include default zoom items | +| `options.items` | `Item[]` | `[]` | Array of custom menu items | +| `options.eventType` | `EventTypes` | `'contextmenu'` | Event that triggers the menu | +| `options.scrollAt` | `number` | `4` | Number of items before menu becomes scrollable | + +**Example:** + +```javascript +const contextmenu = new ContextMenu({ + width: 200, + defaultItems: false, + eventType: 'click', + items: [ + { text: 'Item 1', callback: fn1 }, + { text: 'Item 2', callback: fn2 }, + ], +}); +``` + +## Methods + +### `clear()` + +Removes all items from the menu. + +**Returns:** `void` + +**Example:** + +```javascript +contextmenu.clear(); +``` + +--- + +### `closeMenu()` + +Closes the context menu programmatically. + +**Returns:** `void` + +**Example:** + +```javascript +contextmenu.closeMenu(); +``` + +--- + +### `extend(items)` + +Adds multiple items to the end of the menu. + +**Parameters:** + +| Name | Type | Description | +|------|------|-------------| +| `items` | `Item[]` | Array of items to add | + +**Returns:** `void` + +**Example:** + +```javascript +contextmenu.extend([ + { text: 'New Item 1', callback: fn1 }, + '-', + { text: 'New Item 2', callback: fn2 }, +]); +``` + +--- + +### `push(item)` + +Adds a single item to the end of the menu. + +**Parameters:** + +| Name | Type | Description | +|------|------|-------------| +| `item` | `Item \| '-'` | Item to add (or separator) | + +**Returns:** `void` + +**Example:** + +```javascript +contextmenu.push({ text: 'New Item', callback: fn }); +contextmenu.push('-'); // Add separator +``` + +--- + +### `pop()` + +Removes the last item from the menu. + +**Returns:** `void` + +**Example:** + +```javascript +contextmenu.pop(); +``` + +--- + +### `shift()` + +Removes the first item from the menu. + +**Returns:** `void` + +**Example:** + +```javascript +contextmenu.shift(); +``` + +--- + +### `getDefaultItems()` + +Returns the array of default menu items (Zoom In/Zoom Out). + +**Returns:** `SingleItem[]` + +**Example:** + +```javascript +const defaults = contextmenu.getDefaultItems(); +console.log(defaults); +// [ +// { text: 'Zoom In', callback: fn, ... }, +// { text: 'Zoom Out', callback: fn, ... } +// ] +``` + +--- + +### `isOpen()` + +Checks whether the menu is currently open. + +**Returns:** `boolean` + +**Example:** + +```javascript +if (contextmenu.isOpen()) { + console.log('Menu is open'); +} +``` + +--- + +### `updatePosition(pixel)` + +Updates the position of the menu. + +**Parameters:** + +| Name | Type | Description | +|------|------|-------------| +| `pixel` | `[number, number]` | New pixel coordinates | + +**Returns:** `void` + +**Example:** + +```javascript +contextmenu.updatePosition([100, 200]); +``` + +--- + +### `enable()` + +Enables the context menu (allows it to open). + +**Returns:** `void` + +**Example:** + +```javascript +contextmenu.enable(); +``` + +--- + +### `disable()` + +Disables the context menu (prevents it from opening). + +**Returns:** `void` + +**Example:** + +```javascript +contextmenu.disable(); +``` + +--- + +### `countItems()` + +Returns the number of items in the menu (excluding separators and submenu items). + +**Returns:** `number` + +**Example:** + +```javascript +const count = contextmenu.countItems(); +console.log(`Menu has ${count} items`); +``` + +## Events + +The context menu extends OpenLayers' `Control` class and emits custom events. + +### `beforeopen` + +Fired before the menu opens. Can be used to conditionally prevent opening. + +**Event Data:** + +```typescript +{ + map: Map, + pixel: [number, number], + coordinate: Coordinate, + originalEvent: MouseEvent, + type: 'beforeopen' +} +``` + +**Example:** + +```javascript +contextmenu.on('beforeopen', (evt) => { + const feature = map.forEachFeatureAtPixel(evt.pixel, (ft) => ft); + + if (!feature) { + contextmenu.disable(); // Don't open if no feature + } else { + contextmenu.enable(); + } +}); +``` + +--- + +### `open` + +Fired when the menu opens. + +**Event Data:** + +```typescript +{ + map: Map, + pixel: [number, number], + coordinate: Coordinate, + originalEvent: MouseEvent, + type: 'open' +} +``` + +**Example:** + +```javascript +contextmenu.on('open', (evt) => { + console.log('Menu opened at:', evt.coordinate); + + // Modify menu based on context + const feature = map.forEachFeatureAtPixel(evt.pixel, (ft) => ft); + if (feature) { + // Add feature-specific items + } +}); +``` + +--- + +### `close` + +Fired when the menu closes. + +**Event Data:** `BaseEvent` + +**Example:** + +```javascript +contextmenu.on('close', () => { + console.log('Menu closed'); +}); +``` + +## Types + +### `Item` + +Union type for menu items: + +```typescript +type Item = SingleItem | ItemWithNested | ItemSeparator; +``` + +### `SingleItem` + +A standard menu item with a callback: + +```typescript +interface SingleItem { + text: string; + callback: (obj: CallbackObject, map: Map) => void; + icon?: string; + classname?: string; + data?: any; +} +``` + +### `ItemWithNested` + +A menu item with nested subitems: + +```typescript +interface ItemWithNested { + text: string; + items: Item[]; + icon?: string; + classname?: string; +} +``` + +### `ItemSeparator` + +A menu separator: + +```typescript +type ItemSeparator = '-'; +``` + +### `CallbackObject` + +Data passed to item callbacks: + +```typescript +interface CallbackObject { + coordinate: Coordinate; + data: unknown; +} +``` + +### `Options` + +Configuration options for the constructor: + +```typescript +interface Options { + width: number; + defaultItems: boolean; + items: Item[]; + eventType: EventTypes; + scrollAt: number; +} +``` + +**Note:** The constructor accepts `Partial`, so all fields are optional when creating an instance. The type definition above shows the complete options object with defaults applied. + +### `EventTypes` + +Possible event types: + +```typescript +enum EventTypes { + CONTEXTMENU = 'contextmenu', + CLICK = 'click', + DBLCLICK = 'dblclick', +} +``` + +## Examples + +### Dynamic Menu Based on Feature + +```javascript +let currentFeature = null; + +contextmenu.on('beforeopen', (evt) => { + currentFeature = map.forEachFeatureAtPixel(evt.pixel, (ft) => ft); + + contextmenu.clear(); + + if (currentFeature) { + contextmenu.extend([ + { + text: 'Delete Feature', + callback: () => { + vectorSource.removeFeature(currentFeature); + }, + }, + { + text: 'Feature Properties', + callback: () => { + console.log(currentFeature.getProperties()); + }, + }, + ]); + } else { + contextmenu.extend([ + { + text: 'Add Marker Here', + callback: (obj) => { + const marker = new Feature({ + geometry: new Point(obj.coordinate), + }); + vectorSource.addFeature(marker); + }, + }, + ]); + } +}); +``` + +### Using Different Event Types + +```javascript +// Trigger on single click instead of right-click +const contextmenu = new ContextMenu({ + eventType: 'click', + items: [/* ... */], +}); + +// Trigger on double-click +const contextmenu2 = new ContextMenu({ + eventType: 'dblclick', + items: [/* ... */], +}); +``` + +### Custom Data in Callbacks + +```javascript +const items = [ + { + text: 'Process Data', + data: { id: 123, type: 'custom' }, + callback: (obj) => { + console.log('Custom data:', obj.data); + // Output: { id: 123, type: 'custom' } + }, + }, +]; +``` + +## See Also + +- [Getting Started](./getting-started.md) +- [TypeScript Guide](./typescript.md) +- [Examples & Recipes](./examples.md) diff --git a/docs/examples.md b/docs/examples.md new file mode 100644 index 0000000..39a8204 --- /dev/null +++ b/docs/examples.md @@ -0,0 +1,465 @@ +# Examples & Recipes + +Common patterns and code examples for `ol-contextmenu`. + +## Table of Contents + +- [Basic Examples](#basic-examples) +- [Dynamic Menus](#dynamic-menus) +- [Feature Detection](#feature-detection) +- [Custom Styling](#custom-styling) +- [Advanced Patterns](#advanced-patterns) + +## Basic Examples + +### Simple Menu + +```javascript +import ContextMenu from 'ol-contextmenu'; + +const contextmenu = new ContextMenu({ + width: 170, + items: [ + { + text: 'Center map here', + callback: (obj, map) => { + map.getView().animate({ + center: obj.coordinate, + duration: 700, + }); + }, + }, + ], +}); + +map.addControl(contextmenu); +``` + +### Menu with Separators + +```javascript +const items = [ + { text: 'Zoom In', callback: zoomIn }, + { text: 'Zoom Out', callback: zoomOut }, + '-', // Separator + { text: 'Center Map', callback: center }, + '-', + { text: 'Export View', callback: exportView }, +]; + +const contextmenu = new ContextMenu({ items }); +``` + +### Menu with Icons + +```javascript +const items = [ + { + text: 'Add Marker', + icon: 'data:image/svg+xml;base64,...', // SVG data URI + callback: addMarker, + }, + { + text: 'Delete', + icon: './icons/trash.png', // Relative path + callback: deleteFeature, + }, +]; +``` + +### Multi-Level Submenus + +```javascript +const items = [ + { + text: 'Measure', + items: [ + { + text: 'Distance', + callback: measureDistance, + }, + { + text: 'Area', + callback: measureArea, + }, + ], + }, + { + text: 'Export', + items: [ + { + text: 'PNG', + callback: exportPNG, + }, + { + text: 'JPEG', + callback: exportJPEG, + }, + { + text: 'PDF', + callback: exportPDF, + }, + ], + }, +]; +``` + +## Dynamic Menus + +### Context-Aware Menu + +Change menu items based on what's clicked: + +```javascript +let currentFeature = null; + +contextmenu.on('beforeopen', (evt) => { + currentFeature = map.forEachFeatureAtPixel(evt.pixel, (ft) => ft); + + contextmenu.clear(); + + if (currentFeature) { + // Menu for features + contextmenu.extend([ + { + text: 'Delete Feature', + callback: () => vectorSource.removeFeature(currentFeature), + }, + { + text: 'Edit Properties', + callback: () => editProperties(currentFeature), + }, + '-', + { + text: 'Zoom to Feature', + callback: () => { + const extent = currentFeature.getGeometry().getExtent(); + map.getView().fit(extent, { duration: 700 }); + }, + }, + ]); + } else { + // Menu for empty map + contextmenu.extend([ + { + text: 'Add Marker', + callback: (obj) => addMarkerAt(obj.coordinate), + }, + { + text: 'Center Here', + callback: (obj) => { + map.getView().animate({ + center: obj.coordinate, + duration: 700, + }); + }, + }, + ]); + } +}); +``` + +### Conditional Menu Items + +```javascript +contextmenu.on('beforeopen', (evt) => { + const view = map.getView(); + const zoom = view.getZoom(); + const maxZoom = view.getMaxZoom(); + + // Prevent menu from opening at max zoom + if (zoom >= maxZoom) { + contextmenu.disable(); + } else { + contextmenu.enable(); + } +}); + +contextmenu.on('open', (evt) => { + const view = map.getView(); + const zoom = view.getZoom(); + + // Add contextual items based on zoom level + if (zoom < 10) { + contextmenu.push({ + text: 'Switch to Satellite', + callback: () => switchLayer('satellite'), + }); + } +}); +``` + +## Feature Detection + +### Detect Feature Type + +```javascript +contextmenu.on('beforeopen', (evt) => { + const feature = map.forEachFeatureAtPixel(evt.pixel, (ft) => ft); + + if (feature) { + const geomType = feature.getGeometry().getType(); + + contextmenu.clear(); + + switch (geomType) { + case 'Point': + contextmenu.extend([ + { text: 'Move Marker', callback: moveMarker }, + { text: 'Delete Marker', callback: deleteMarker }, + ]); + break; + + case 'LineString': + contextmenu.extend([ + { text: 'Measure Length', callback: measureLength }, + { text: 'Delete Line', callback: deleteLine }, + ]); + break; + + case 'Polygon': + contextmenu.extend([ + { text: 'Measure Area', callback: measureArea }, + { text: 'Delete Polygon', callback: deletePolygon }, + ]); + break; + } + } +}); +``` + +### Multiple Feature Layers + +```javascript +contextmenu.on('beforeopen', (evt) => { + const features = []; + + map.forEachFeatureAtPixel(evt.pixel, (feature, layer) => { + features.push({ feature, layer }); + }); + + contextmenu.clear(); + + if (features.length === 0) { + contextmenu.extend(defaultItems); + } else if (features.length === 1) { + contextmenu.extend(getSingleFeatureItems(features[0])); + } else { + // Multiple features - show selection submenu + contextmenu.push({ + text: `${features.length} features`, + items: features.map((f, i) => ({ + text: `Feature ${i + 1}`, + callback: () => selectFeature(f.feature), + })), + }); + } +}); +``` + +## Custom Styling + +### Custom CSS Classes + +```javascript +const items = [ + { + text: 'Danger Action', + classname: 'danger-item', + callback: dangerAction, + }, + { + text: 'Success Action', + classname: 'success-item', + callback: successAction, + }, +]; +``` + +```css +.danger-item { + color: #dc3545; + font-weight: bold; +} + +.success-item { + color: #28a745; +} +``` + +### Font Awesome Icons + +```javascript +const items = [ + { + text: 'Delete', + classname: 'fa fa-trash', + callback: deleteItem, + }, + { + text: 'Edit', + classname: 'fa fa-edit', + callback: editItem, + }, +]; +``` + +## Advanced Patterns + +### Restore Previous Menu + +```javascript +// Store menu items separately since there's no getItems() method +const defaultMenuItems = [ + { text: 'Add Marker', callback: addMarker }, + { text: 'Center Here', callback: centerMap }, +]; + +const featureMenuItems = [ + { text: 'Delete Feature', callback: deleteFeature }, + { text: 'Edit Properties', callback: editProperties }, +]; + +let isFeatureMenu = false; + +contextmenu.on('beforeopen', (evt) => { + const feature = map.forEachFeatureAtPixel(evt.pixel, (ft) => ft); + + if (feature && !isFeatureMenu) { + // Switch to feature menu + contextmenu.clear(); + contextmenu.extend(featureMenuItems); + isFeatureMenu = true; + } else if (!feature && isFeatureMenu) { + // Restore default menu + contextmenu.clear(); + contextmenu.extend(defaultMenuItems); + isFeatureMenu = false; + } +}); +``` + +### Async Data Loading + +```javascript +contextmenu.on('open', async (evt) => { + const feature = map.forEachFeatureAtPixel(evt.pixel, (ft) => ft); + + if (feature) { + const featureId = feature.getId(); + + contextmenu.clear(); + contextmenu.push({ text: 'Loading...', callback: () => {} }); + + try { + const data = await fetch(`/api/features/${featureId}`).then(r => r.json()); + + contextmenu.clear(); + contextmenu.extend([ + { + text: `Edit ${data.name}`, + callback: () => edit(data), + }, + { + text: 'View Details', + callback: () => showDetails(data), + }, + ]); + } catch (error) { + contextmenu.clear(); + contextmenu.push({ + text: 'Error loading data', + callback: () => {}, + }); + } + } +}); +``` + +### Coordinate Formatting + +```javascript +import { toLonLat } from 'ol/proj'; +import { toStringHDMS } from 'ol/coordinate'; + +const showCoordinates = (obj) => { + const lonLat = toLonLat(obj.coordinate); + const hdms = toStringHDMS(lonLat); + + alert(` + Decimal: ${lonLat[1].toFixed(5)}, ${lonLat[0].toFixed(5)} + DMS: ${hdms} + `); +}; + +const items = [ + { + text: 'Show Coordinates', + callback: showCoordinates, + }, +]; +``` + +### Integration with Drawing + +```javascript +import Draw from 'ol/interaction/Draw'; + +let draw; +const source = new VectorSource(); + +contextmenu.on('open', (evt) => { + const items = [ + { + text: 'Start Drawing', + items: [ + { + text: 'Point', + callback: () => startDrawing('Point'), + }, + { + text: 'LineString', + callback: () => startDrawing('LineString'), + }, + { + text: 'Polygon', + callback: () => startDrawing('Polygon'), + }, + ], + }, + ]; + + if (draw) { + items.push({ + text: 'Stop Drawing', + callback: () => { + map.removeInteraction(draw); + draw = null; + }, + }); + } + + contextmenu.clear(); + contextmenu.extend(items); +}); + +function startDrawing(type) { + if (draw) { + map.removeInteraction(draw); + } + + draw = new Draw({ + source, + type, + }); + + map.addInteraction(draw); +} +``` + +## See Also + +- [API Reference](./api-reference.md) +- [TypeScript Guide](./typescript.md) +- [Getting Started](./getting-started.md) +- [Local Examples](../examples) diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..9d51bba --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,233 @@ +# Getting Started with ol-contextmenu + +This guide will help you get up and running with `ol-contextmenu` in your OpenLayers project. + +## Prerequisites + +- **OpenLayers**: Version 7.0.0 or higher +- **Modern Browser**: Chrome, Firefox, Safari, or Edge with ES6 support +- **Build Tool** (optional): Webpack, Vite, Rollup, or similar + +## Installation + +### npm (Recommended) + +```bash +npm install ol-contextmenu +``` + +If you don't have OpenLayers installed: + +```bash +npm install ol ol-contextmenu +``` + +### CDN + +For quick prototyping or simple projects: + +#### jsDelivr + +```html + + + +``` + +#### UNPKG + +```html + + + +``` + +## Basic Setup + +### 1. Import the Library + +**ES6 Modules:** + +```javascript +import Map from 'ol/Map'; +import View from 'ol/View'; +import TileLayer from 'ol/layer/Tile'; +import OSM from 'ol/source/OSM'; +import ContextMenu from 'ol-contextmenu'; +``` + +**CommonJS:** + +```javascript +const ContextMenu = require('ol-contextmenu'); +``` + +**CDN (Global):** + +```javascript +// Available as global: ContextMenu +const contextmenu = new ContextMenu(); +``` + +### 2. Create Your Map + +```javascript +const map = new Map({ + target: 'map', + layers: [ + new TileLayer({ + source: new OSM(), + }), + ], + view: new View({ + center: [0, 0], + zoom: 2, + }), +}); +``` + +### 3. Initialize Context Menu + +```javascript +const contextmenu = new ContextMenu({ + width: 170, + defaultItems: true, + items: [ + { + text: 'Center map here', + callback: (obj) => { + map.getView().animate({ + center: obj.coordinate, + duration: 700, + }); + }, + }, + '-', // Separator + { + text: 'Some Action', + callback: () => { + alert('Action clicked!'); + }, + }, + ], +}); +``` + +### 4. Add to Map + +```javascript +map.addControl(contextmenu); +``` + +## Complete Example + +Here's a complete working example: + +```html + + + + ol-contextmenu Example + + + + +
+ + + + + + +``` + +## Next Steps + +- **[API Reference](./api-reference.md)** - Learn about all available methods and options +- **[Examples & Recipes](./examples.md)** - See common patterns and use cases +- **[TypeScript Guide](./typescript.md)** - Add type safety to your project + +## Common Patterns + +### Adding Icons to Menu Items + +```javascript +{ + text: 'Add Marker', + icon: 'path/to/icon.png', // URL or data URI + callback: addMarker, +} +``` + +### Creating Submenus + +```javascript +{ + text: 'Actions', + items: [ + { + text: 'Action 1', + callback: action1, + }, + { + text: 'Action 2', + callback: action2, + }, + ], +} +``` + +### Using Separators + +```javascript +const items = [ + { text: 'Item 1', callback: fn1 }, + '-', // Separator + { text: 'Item 2', callback: fn2 }, +]; +``` + +## Troubleshooting + +**Menu doesn't appear:** Make sure you've added the control to the map with `map.addControl(contextmenu)`. + +**Styles look wrong:** The CSS is bundled automatically since v6.0. No need to import separately. + +**TypeScript errors:** Make sure you're using the correct import syntax (see [TypeScript Guide](./typescript.md)). + +For more help, check the [Troubleshooting Guide](./troubleshooting.md). diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 0000000..cf8128b --- /dev/null +++ b/docs/troubleshooting.md @@ -0,0 +1,425 @@ +# Troubleshooting + +Common issues and solutions for `ol-contextmenu`. + +## Installation Issues + +### Cannot find module 'ol-contextmenu' + +**Symptoms:** +``` +Error: Cannot find module 'ol-contextmenu' +``` + +**Solutions:** + +1. **Verify installation:** + ```bash + npm list ol-contextmenu + ``` + +2. **Reinstall the package:** + ```bash + npm install ol-contextmenu + ``` + +3. **Clear cache and reinstall:** + ```bash + rm -rf node_modules package-lock.json + npm install + ``` + +--- + +### TypeScript cannot find types + +**Symptoms:** +``` +Could not find a declaration file for module 'ol-contextmenu' +``` + +**Solutions:** + +Type definitions are included since v6.0. Make sure you're on the latest version: + +```bash +npm install ol-contextmenu@latest +``` + +If the issue persists, check your `tsconfig.json`: + +```json +{ + "compilerOptions": { + "moduleResolution": "node", + "esModuleInterop": true + } +} +``` + +## Display Issues + +### Menu doesn't appear + +**Symptoms:** +- Right-clicking does nothing +- No context menu visible + +**Solutions:** + +1. **Verify the control is added to the map:** + ```javascript + map.addControl(contextmenu); + ``` + +2. **Check if menu is disabled:** + ```javascript + contextmenu.enable(); // Re-enable if needed + ``` + +3. **Verify event type:** + ```javascript + const contextmenu = new ContextMenu({ + eventType: 'contextmenu', // Default + }); + ``` + +4. **Check browser console for errors** + +5. **Ensure OpenLayers version compatibility:** + ```bash + npm list ol + # Should be 7.0.0 or higher + ``` + +--- + +### Menu appears cut off at viewport edges + +**Symptoms:** +- Menu is partially hidden at screen edges +- Bottom or right side of menu is cut off + +**Solution:** + +Update to v5.5.0 or higher (automatic viewport positioning): + +```bash +npm install ol-contextmenu@latest +``` + +The menu now automatically repositions to stay within viewport bounds. + +--- + +### Styles are missing or incorrect + +**Symptoms:** +- Menu appears unstyled +- CSS classes not applied + +**Solutions:** + +1. **Verify you're on v6.0+** (CSS is bundled): + ```bash + npm list ol-contextmenu + ``` + +2. **Remove old CSS imports:** + ```diff + - import 'ol-contextmenu/dist/ol-contextmenu.css'; // Remove this + ``` + +3. **Clear build cache:** + ```bash + rm -rf dist/ + npm run build + ``` + +4. **Check for CSS conflicts** in browser DevTools + +## Functional Issues + +### Callbacks not firing + +**Symptoms:** +- Menu items don't respond to clicks +- Callback functions never execute + +**Solutions:** + +1. **Verify callback syntax:** + ```javascript + // โœ… Correct + { + text: 'Test', + callback: (obj, map) => { + console.log('Clicked!'); + } + } + + // โŒ Wrong - missing parameters + { + text: 'Test', + callback: () => { + console.log('Clicked!'); + } + } + ``` + +2. **Check for JavaScript errors** in console + +3. **Ensure item has callback:** + ```javascript + // Separators don't have callbacks + '-' // This is correct + + // Items need callbacks + { text: 'Test', callback: fn } // Needs callback + ``` + +--- + +### Menu closes immediately + +**Symptoms:** +- Menu opens and closes instantly +- Can't click menu items + +**Solutions:** + +1. **Check for conflicting click handlers:** + ```javascript + // Avoid stopPropagation on map clicks + map.on('click', (evt) => { + // Don't stopPropagation here + }); + ``` + +2. **Verify beforeopen handler:** + ```javascript + contextmenu.on('beforeopen', (evt) => { + // Don't call closeMenu() here + // Don't disable() immediately + }); + ``` + +--- + +### Dynamic menus not updating + +**Symptoms:** +- Menu items don't change based on context +- Same items always show + +**Solutions:** + +1. **Clear menu before updating:** + ```javascript + contextmenu.on('beforeopen', (evt) => { + contextmenu.clear(); // Important! + contextmenu.extend(newItems); + }); + ``` + +2. **Use correct event:** + ```javascript + // โœ… Use beforeopen for menu changes + contextmenu.on('beforeopen', updateMenu); + + // โŒ Don't use open (too late) + // contextmenu.on('open', updateMenu); + ``` + +## TypeScript Issues + +### Import errors + +**Symptoms:** +```typescript +Module '"ol-contextmenu"' has no exported member 'ContextMenu' +``` + +**Solution:** + +Use correct import syntax: + +```typescript +// โœ… Correct - default import +import ContextMenu from 'ol-contextmenu'; + +// โŒ Wrong - named import +// import { ContextMenu } from 'ol-contextmenu'; +``` + +--- + +### Type errors with items + +**Symptoms:** +```typescript +Type '{ text: string; }' is missing the following properties: callback +``` + +**Solution:** + +Provide type annotations: + +```typescript +import { type Item, type SingleItem } from 'ol-contextmenu'; + +// Option 1: Type the array +const items: Item[] = [ + { text: 'Test', callback: (obj, map) => {} } +]; + +// Option 2: Type individual items +const item: SingleItem = { + text: 'Test', + callback: (obj, map) => {} +}; +``` + +## Performance Issues + +### Menu opens slowly + +**Symptoms:** +- Delay before menu appears +- Laggy interaction + +**Solutions:** + +1. **Optimize beforeopen handler:** + ```javascript + // โŒ Slow - recalculating every time + contextmenu.on('beforeopen', (evt) => { + const items = expensiveCalculation(); + contextmenu.extend(items); + }); + + // โœ… Fast - calculate once + const items = expensiveCalculation(); + contextmenu.on('beforeopen', (evt) => { + contextmenu.extend(items); + }); + ``` + +2. **Reduce number of items** (use submenus for large lists) + +3. **Avoid DOM operations in callbacks:** + ```javascript + // โŒ Slow + callback: () => { + document.querySelectorAll('.heavy-selector').forEach(/* ... */); + } + + // โœ… Fast + callback: () => { + cachedElements.forEach(/* ... */); + } + ``` + +## Browser-Specific Issues + +### Menu doesn't work in Safari + +**Symptoms:** +- Works in Chrome/Firefox but not Safari + +**Solutions:** + +1. **Check Safari version** (modern versions only) +2. **Verify ES6 support** (Safari 10+) +3. **Check for polyfills needed** + +--- + +### Browser context menu still appears + +**Symptoms:** +- Both custom and browser context menus show + +**Solution:** + +This is expected behavior. The library prevents default for `contextmenu` events, but some browsers may still show their menu. This is handled correctly by the library. + +## Build Issues + +### Webpack build errors + +**Symptoms:** +``` +Module parse failed: Unexpected token +``` + +**Solution:** + +Update Webpack config to handle ES modules: + +```javascript +module.exports = { + module: { + rules: [ + { + test: /\.js$/, + include: /node_modules\/ol-contextmenu/, + use: { + loader: 'babel-loader', + options: { + presets: ['@babel/preset-env'] + } + } + } + ] + } +}; +``` + +--- + +### Vite build errors + +**Solution:** + +Vite should work out of the box. If you encounter issues: + +```javascript +// vite.config.js +export default { + optimizeDeps: { + include: ['ol-contextmenu'] + } +}; +``` + +## Getting More Help + +If your issue isn't covered here: + +1. **Search existing issues:** + [GitHub Issues](https://github.com/jonataswalker/ol-contextmenu/issues) + +2. **Check the examples:** + [Examples Directory](../examples) + +3. **Review the API documentation:** + [API Reference](./api-reference.md) + +4. **Create a new issue:** + + When creating an issue, please include: + - ol-contextmenu version + - OpenLayers version + - Browser and version + - Minimal reproduction example + - Error messages + - What you've tried + + [Create Issue](https://github.com/jonataswalker/ol-contextmenu/issues/new) + +## See Also + +- [Getting Started](./getting-started.md) +- [API Reference](./api-reference.md) +- [Migration Guide](./migration.md) diff --git a/docs/typescript.md b/docs/typescript.md new file mode 100644 index 0000000..803a4f8 --- /dev/null +++ b/docs/typescript.md @@ -0,0 +1,440 @@ +# TypeScript Guide + +`ol-contextmenu` includes full TypeScript support with comprehensive type definitions. + +## Installation + +```bash +npm install ol ol-contextmenu +``` + +Type definitions are included automaticallyโ€”no need for `@types` packages. + +๐Ÿ“– For detailed installation instructions, see [Getting Started](./getting-started.md). + +## Basic Usage + +### Importing + +```typescript +import ContextMenu, { + type Item, + type Options, + type SingleItem, + type CallbackObject, + type ContextMenuEvent, +} from 'ol-contextmenu'; +import type Map from 'ol/Map'; +import type { Coordinate } from 'ol/coordinate'; +``` + +**Note:** Use `type` imports for better tree-shaking and to avoid runtime imports. + +### Creating a Context Menu + +```typescript +import ContextMenu, { type Item } from 'ol-contextmenu'; +import type Map from 'ol/Map'; + +const items: Item[] = [ + { + text: 'Center map here', + callback: (obj, map: Map) => { + map.getView().animate({ + center: obj.coordinate, + duration: 700, + }); + }, + }, + '-', // Separator (type-safe) + { + text: 'Zoom In', + callback: (obj, map: Map) => { + const view = map.getView(); + view.animate({ + zoom: view.getZoom()! + 1, + duration: 500, + }); + }, + }, +]; + +const contextmenu = new ContextMenu({ + width: 170, + defaultItems: true, + items, +}); +``` + +## Available Types + +### `Item` + +Union type representing any menu item: + +```typescript +type Item = SingleItem | ItemWithNested | ItemSeparator; +``` + +### `SingleItem` + +A standard menu item with a callback: + +```typescript +interface SingleItem { + text: string; + callback: (obj: CallbackObject, map: Map) => void; + icon?: string; + classname?: string; + data?: any; +} +``` + +### `ItemWithNested` + +A menu item containing nested subitems: + +```typescript +interface ItemWithNested { + text: string; + items: Item[]; + icon?: string; + classname?: string; +} +``` + +### `ItemSeparator` + +Menu separator (literal type): + +```typescript +type ItemSeparator = '-'; +``` + +### `Options` + +Constructor options: + +```typescript +interface Options { + width?: number; + defaultItems?: boolean; + items?: Item[]; + eventType?: EventTypes; +} +``` + +### `CallbackObject` + +Data passed to callbacks: + +```typescript +interface CallbackObject { + coordinate: Coordinate; + data?: any; +} +``` + +### `ContextMenuEvent` + +Event data for `beforeopen` and `open` events: + +```typescript +class ContextMenuEvent extends BaseEvent { + map: Map; + coordinate: Coordinate; + pixel: Pixel; + originalEvent: MouseEvent; +} +``` + +### `EventTypes` + +Enum for event trigger types: + +```typescript +enum EventTypes { + CONTEXTMENU = 'contextmenu', + CLICK = 'click', + DBLCLICK = 'dblclick', +} +``` + +### `CustomEventTypes` + +Enum for menu events: + +```typescript +enum CustomEventTypes { + BEFOREOPEN = 'beforeopen', + OPEN = 'open', + CLOSE = 'close', +} +``` + +## Type-Safe Patterns + +### Typed Callback Functions + +```typescript +import type { CallbackObject } from 'ol-contextmenu'; +import type Map from 'ol/Map'; + +const handleCenter = (obj: CallbackObject, map: Map): void => { + map.getView().setCenter(obj.coordinate); +}; + +const handleZoom = (obj: CallbackObject, map: Map): void => { + const view = map.getView(); + const currentZoom = view.getZoom(); + + if (currentZoom !== undefined) { + view.setZoom(currentZoom + 1); + } +}; + +const items: Item[] = [ + { text: 'Center', callback: handleCenter }, + { text: 'Zoom In', callback: handleZoom }, +]; +``` + +### Custom Data with Type Safety + +```typescript +interface MarkerData { + id: number; + name: string; + type: 'marker' | 'poi'; +} + +const markerItem: SingleItem = { + text: 'Delete Marker', + data: { + id: 1, + name: 'My Marker', + type: 'marker', + } as MarkerData, + callback: (obj) => { + const data = obj.data as MarkerData; + console.log(`Deleting ${data.type}: ${data.name}`); + }, +}; +``` + +### Event Handlers + +```typescript +import { CustomEventTypes, type ContextMenuEvent } from 'ol-contextmenu'; + +contextmenu.on(CustomEventTypes.BEFOREOPEN, (evt: ContextMenuEvent) => { + console.log('Clicked at:', evt.coordinate); + console.log('Pixel:', evt.pixel); + console.log('Map:', evt.map); +}); + +contextmenu.on(CustomEventTypes.OPEN, (evt: ContextMenuEvent) => { + const feature = evt.map.forEachFeatureAtPixel( + evt.pixel, + (ft) => ft, + ); + + if (feature) { + console.log('Clicked on feature:', feature.getId()); + } +}); +``` + +### Nested Submenus + +```typescript +const nestedItems: Item[] = [ + { + text: 'Tools', + items: [ + { + text: 'Measure', + items: [ + { + text: 'Distance', + callback: measureDistance, + }, + { + text: 'Area', + callback: measureArea, + }, + ], + }, + '-', + { + text: 'Export', + callback: exportMap, + }, + ], + }, +]; +``` + +## Complete TypeScript Example + +```typescript +import Map from 'ol/Map'; +import View from 'ol/View'; +import TileLayer from 'ol/layer/Tile'; +import OSM from 'ol/source/OSM'; +import VectorLayer from 'ol/layer/Vector'; +import VectorSource from 'ol/source/Vector'; +import Feature from 'ol/Feature'; +import Point from 'ol/geom/Point'; +import ContextMenu, { + type Item, + type CallbackObject, + CustomEventTypes, +} from 'ol-contextmenu'; + +// Initialize map +const map = new Map({ + target: 'map', + layers: [ + new TileLayer({ + source: new OSM(), + }), + ], + view: new View({ + center: [0, 0], + zoom: 2, + }), +}); + +// Vector layer for markers +const vectorSource = new VectorSource(); +const vectorLayer = new VectorLayer({ + source: vectorSource, +}); +map.addLayer(vectorLayer); + +// Type-safe callback functions +const addMarker = (obj: CallbackObject, map: Map): void => { + const marker = new Feature({ + geometry: new Point(obj.coordinate), + }); + vectorSource.addFeature(marker); + console.log('Marker added at', obj.coordinate); +}; + +const centerMap = (obj: CallbackObject, map: Map): void => { + map.getView().animate({ + center: obj.coordinate, + duration: 700, + }); +}; + +// Define menu items +const items: Item[] = [ + { + text: 'Add Marker', + icon: './marker.png', + callback: addMarker, + }, + '-', + { + text: 'Center Here', + callback: centerMap, + }, +]; + +// Create context menu +const contextmenu = new ContextMenu({ + width: 180, + defaultItems: true, + items, +}); + +// Add to map +map.addControl(contextmenu); + +// Event handlers +contextmenu.on(CustomEventTypes.BEFOREOPEN, (evt) => { + const feature = map.forEachFeatureAtPixel(evt.pixel, (ft) => ft); + + if (feature) { + contextmenu.clear(); + contextmenu.push({ + text: 'Delete Marker', + callback: () => { + vectorSource.removeFeature(feature); + }, + }); + } else { + contextmenu.clear(); + contextmenu.extend(items); + contextmenu.extend(contextmenu.getDefaultItems()); + } +}); +``` + +## Strict Type Checking + +Enable strict mode in `tsconfig.json` for maximum type safety: + +```json +{ + "compilerOptions": { + "strict": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "noImplicitAny": true, + "noImplicitThis": true + } +} +``` + +## Common Type Issues + +### Issue: Cannot find module 'ol-contextmenu' + +**Solution:** Make sure the package is installed: + +```bash +npm install ol-contextmenu +``` + +### Issue: Type errors with `callback` + +**Problem:** +```typescript +// โŒ Error: Type '() => void' is not assignable... +const item = { + text: 'Test', + callback: () => console.log('test'), +}; +``` + +**Solution:** +```typescript +// โœ… Correct: Include parameters +const item: SingleItem = { + text: 'Test', + callback: (obj, map) => console.log('test'), +}; +``` + +### Issue: Cannot import types + +**Problem:** +```typescript +// โŒ Wrong +import { ContextMenu, Item } from 'ol-contextmenu'; +``` + +**Solution:** +```typescript +// โœ… Correct +import ContextMenu, { type Item } from 'ol-contextmenu'; +``` + +## See Also + +- [API Reference](./api-reference.md) +- [Getting Started](./getting-started.md) +- [Examples & Recipes](./examples.md) diff --git a/package.json b/package.json index 5b5f0a0..1a80199 100644 --- a/package.json +++ b/package.json @@ -2,14 +2,29 @@ "name": "ol-contextmenu", "version": "5.5.0", "description": "Custom Context Menu for Openlayers", + "keywords": [ + "openlayers", + "ol", + "contextmenu", + "context-menu", + "right-click", + "menu", + "map", + "gis", + "geospatial" + ], "type": "module", "main": "./dist/ol-contextmenu.umd.cjs", "jsdelivr": "./dist/ol-contextmenu.iife.js", "module": "./dist/ol-contextmenu.js", + "browser": "./dist/ol-contextmenu.js", + "sideEffects": ["./dist/ol-contextmenu.js", "./dist/ol-contextmenu.umd.cjs", "./dist/ol-contextmenu.iife.js"], "exports": { ".": { "types": "./dist/ol-contextmenu.d.ts", - "import": "./dist/ol-contextmenu.js" + "import": "./dist/ol-contextmenu.js", + "require": "./dist/ol-contextmenu.umd.cjs", + "default": "./dist/ol-contextmenu.js" } }, "files": [ @@ -27,8 +42,8 @@ "url": "https://github.com/jonataswalker/ol-contextmenu/issues" }, "engines": { - "node": ">=16", - "npm": ">=8" + "node": ">=24", + "npm": ">=11" }, "scripts": { "dev": "vite", diff --git a/vite.config.ts b/vite.config.ts index 9edbdf9..f723ac7 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -62,8 +62,8 @@ export default defineConfig(({ command }) => dts({ exclude: ['src/**/*.spec.ts', 'src/**/*.test.ts', 'tests/**', '**/*.config.ts'], include: ['src/**/*.ts'], - insertTypesEntry: true, outDir: 'dist', + rollupTypes: true, }), bannerPlugin(banner), cssInjectedByJsPlugin(),