Showing preview only (1,351K chars total). Download the full file or copy to clipboard to get everything.
Repository: medialize/jQuery-contextMenu
Branch: master
Commit: 39805525db8d
Files: 150
Total size: 1.3 MB
Directory structure:
gitextract_y3pnf_da/
├── .editorconfig
├── .gitignore
├── .jscsrc
├── .sauce.yml
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── bower.json
├── contextMenu.jquery.json
├── dist/
│ ├── jquery.contextMenu.css
│ ├── jquery.contextMenu.js
│ └── jquery.ui.position.js
├── documentation/
│ ├── CONTRIBUTE.md
│ ├── couscous.yml
│ ├── demo/
│ │ ├── accesskeys.md
│ │ ├── accesskeys_test.md
│ │ ├── async-create.md
│ │ ├── async-promise.md
│ │ ├── callback.md
│ │ ├── callback_test.md
│ │ ├── custom-command.md
│ │ ├── custom-command_test.md
│ │ ├── disabled-callback.md
│ │ ├── disabled-callback_test.md
│ │ ├── disabled-changing.md
│ │ ├── disabled-changing_test.md
│ │ ├── disabled-menu.md
│ │ ├── disabled.md
│ │ ├── disabled_test.md
│ │ ├── dynamic-create.md
│ │ ├── dynamic.md
│ │ ├── fontawesome-icons.md
│ │ ├── html5-import.md
│ │ ├── html5-polyfill-firefox8.md
│ │ ├── html5-polyfill.md
│ │ ├── input.md
│ │ ├── keeping-contextmenu-open.md
│ │ ├── menu-promise.md
│ │ ├── menu-title.md
│ │ ├── on-dom-element.md
│ │ ├── sub-menus-promise.md
│ │ ├── sub-menus.md
│ │ ├── sub-menus_test.md
│ │ ├── trigger-custom.md
│ │ ├── trigger-hover-autohide.md
│ │ ├── trigger-hover.md
│ │ ├── trigger-left-click.md
│ │ └── trigger-swipe.md
│ ├── demo.md
│ ├── docs/
│ │ ├── custom-command-types.md
│ │ ├── customize.md
│ │ ├── events.md
│ │ ├── font-awesome.md
│ │ ├── html5-polyfill.md
│ │ ├── input-helpers.md
│ │ ├── items.md
│ │ ├── plugin-commands.md
│ │ └── runtime-options.md
│ ├── docs.md
│ ├── index.md
│ └── website/
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── couscous.yml
│ ├── css/
│ │ ├── screen.css
│ │ ├── theme-fixes.css
│ │ └── theme.css
│ ├── default.twig
│ ├── fonts/
│ │ └── FontAwesome.otf
│ ├── js/
│ │ ├── main.js
│ │ └── theme.js
│ └── original-theme/
│ ├── bower.json
│ ├── css/
│ │ └── badge_only.css
│ └── sass/
│ ├── _theme_badge.sass
│ ├── _theme_badge_fa.sass
│ ├── _theme_breadcrumbs.sass
│ ├── _theme_font_awesome_compatibility.sass
│ ├── _theme_layout.sass
│ ├── _theme_mathjax.sass
│ ├── _theme_rst.sass
│ ├── _theme_variables.sass
│ ├── badge_only.sass
│ └── theme.sass
├── gulpfile.js
├── karma-saucelabs.conf.js
├── karma.conf.js
├── package.json
├── src/
│ ├── .csscomb.json
│ ├── .csslintrc
│ ├── .jshintrc
│ ├── jquery.contextMenu.js
│ ├── jquery.ui.position.js
│ └── sass/
│ ├── _icons.scss
│ ├── _variables.scss
│ ├── icons/
│ │ ├── _icon_classes.scss.tpl
│ │ ├── _mixins.scss
│ │ ├── _variables.scss
│ │ └── _variables.scss.tpl
│ └── jquery.contextMenu.scss
├── test/
│ ├── index.html
│ ├── integration/
│ │ ├── custom-command.js
│ │ ├── disabled-callback.js
│ │ ├── disabled-changing.js
│ │ ├── disabled-menu.js
│ │ ├── disabled.js
│ │ ├── dynamic-create.js
│ │ ├── dynamic.js
│ │ ├── html/
│ │ │ ├── accesskeys.html
│ │ │ ├── accesskeys_test.html
│ │ │ ├── async-create.html
│ │ │ ├── callback.html
│ │ │ ├── callback_test.html
│ │ │ ├── custom-command.html
│ │ │ ├── custom-command_test.html
│ │ │ ├── disabled-callback.html
│ │ │ ├── disabled-callback_test.html
│ │ │ ├── disabled-changing.html
│ │ │ ├── disabled-changing_test.html
│ │ │ ├── disabled-menu.html
│ │ │ ├── disabled.html
│ │ │ ├── disabled_test.html
│ │ │ ├── dynamic-create.html
│ │ │ ├── dynamic.html
│ │ │ ├── html5-import.html
│ │ │ ├── html5-polyfill-firefox8.html
│ │ │ ├── html5-polyfill.html
│ │ │ ├── input.html
│ │ │ ├── keeping-contextmenu-open.html
│ │ │ ├── menu-title.html
│ │ │ ├── on-dom-element.html
│ │ │ ├── sub-menus.html
│ │ │ ├── sub-menus_test.html
│ │ │ ├── trigger-custom.html
│ │ │ ├── trigger-hover-autohide.html
│ │ │ ├── trigger-hover.html
│ │ │ ├── trigger-left-click.html
│ │ │ └── trigger-swipe.html
│ │ ├── input.js
│ │ ├── keeping-contextmenu-open.js
│ │ ├── on-dom-element.js
│ │ ├── trigger-custom.js
│ │ ├── trigger-left-click.js
│ │ └── trigger-right-click.js
│ ├── specs/
│ │ ├── accesskeys.js
│ │ ├── aync-create.js
│ │ ├── callback.js
│ │ └── submenu.js
│ └── unit/
│ └── contextmenu.test.js
└── wdio.conf.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
root = true
[**]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
================================================
FILE: .gitignore
================================================
.DS_Store
node_modules/
bower_components/
### Node template
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
node_modules
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
*.iml
## Directory-based project format:
.idea/
# if you remove the above rule, at least ignore the following:
# User-specific stuff:
# .idea/workspace.xml
# .idea/tasks.xml
# .idea/dictionaries
# Sensitive or high-churn files:
# .idea/dataSources.ids
# .idea/dataSources.xml
# .idea/sqlDataSources.xml
# .idea/dynamic.xml
# .idea/uiDesigner.xml
# Gradle:
# .idea/gradle.xml
# .idea/libraries
# Mongo Explorer plugin:
# .idea/mongoSettings.xml
## File-based project format:
*.ipr
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
================================================
FILE: .jscsrc
================================================
{
"requireCurlyBraces": [
"if",
"else",
"for",
"while",
"do",
"try",
"catch"
],
"requireOperatorBeforeLineBreak": true,
"requireParenthesesAroundIIFE": true,
"requireCommaBeforeLineBreak": true,
"requireCamelCaseOrUpperCaseIdentifiers": true,
"requireDotNotation": true,
"requireSpacesInForStatement": true,
"requireSpaceBetweenArguments": true,
"maximumLineLength": {
"value": 100,
"tabSize": 4,
"allExcept": ["urlComments", "regex"]
},
"validateQuoteMarks": { "mark": "\"", "escape": true },
"disallowMixedSpacesAndTabs": "smart",
"disallowTrailingWhitespace": true,
"disallowMultipleLineStrings": true,
"disallowTrailingComma": true,
"disallowSpaceBeforeComma": true,
"requireSpaceAfterComma": true,
"requireSpaceBeforeBlockStatements": true,
"requireSpacesInFunctionExpression": {
"beforeOpeningCurlyBrace": true
},
"requireSpaceAfterKeywords": [
"if",
"else",
"for",
"while",
"do",
"switch",
"return",
"try",
"catch"
],
"requireSpacesInsideObjectBrackets": "all",
"requireSemicolons": true,
"requireSpaceAfterBinaryOperators": true,
"requireLineFeedAtFileEnd": true,
"requireSpaceBeforeBinaryOperators": [
"=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=",
"&=", "|=", "^=", "+=",
"+", "-", "*", "/", "%", "<<", ">>", ">>>", "&",
"|", "^", "&&", "||", "===", "==", ">=",
"<=", "<", ">", "!=", "!=="
],
"requireSpacesInAnonymousFunctionExpression": {
"beforeOpeningCurlyBrace": true
},
"requireSpacesInNamedFunctionExpression": {
"beforeOpeningCurlyBrace": true
},
"requirePaddingNewLinesBeforeLineComments": true,
"validateLineBreaks": "LF",
"disallowKeywords": [ "with" ],
"disallowKeywordsOnNewLine": [ "else" ],
"disallowSpacesInFunctionExpression": {
"beforeOpeningRoundBrace": true
},
"disallowSpacesInNamedFunctionExpression": {
"beforeOpeningRoundBrace": true
},
"disallowSpacesInAnonymousFunctionExpression": {
"beforeOpeningRoundBrace": true
},
"disallowSpacesInCallExpression": true,
"disallowSpaceAfterObjectKeys": true,
"disallowSpaceAfterPrefixUnaryOperators": true,
"disallowSpaceBeforePostfixUnaryOperators": true,
"disallowSpaceBeforeBinaryOperators": [ "," ],
"disallowMultipleLineBreaks": true
}
================================================
FILE: .sauce.yml
================================================
---
language: "javascript"
framework: "webdriverio"
configPath: "wdio.conf.js"
================================================
FILE: .travis.yml
================================================
sudo: false
cache:
directories:
- node_modules
env:
global:
- GIT_NAME: "'Couscous auto deploy'"
- GIT_EMAIL: couscous@couscous.io
- GH_REF: github.com/swisnl/jQuery-contextMenu
- SAUCE_USERNAME: "bbrala-contextmenu"
# GH_TOKEN
- secure: gWIh9j5sUgVn9DmVDYnmS4OT2GhHmsndLTPKRLgAF8LM4sa65SkSCNB4Xu5drcb1a6e4egOe0bXNgXSl70ApBoQIxPvjvWxLBCjPQBPD6kJjo2ovOsfxLARMSGAqlGN7hiocLi+s8qa7RP3uuJ373z+Ge+bLIV6vFxEjK9U3Iz4=
# SAUCE_ACCESS_KEY
- secure: UOse3txRLxLQKsPVQf6OKZZP3c0nLaPJ+4G2vR/qJqBXCTTCQ84+9qx9ih/40FDFcjVXwabJsdn0EhkqDw4h50OGdc58V1UfSbk7g1RiuvvRakOPTK0J9h7bEkBPb7QQXCvAVfOZ81DN6l5lMjmH1tiC2T/h/MNOLHPXsbzXElg=
matrix:
include:
- language: node_js
node_js: "10"
env: JQUERY=1
- language: node_js
node_js: "10"
env: JQUERY=2
- language: node_js
node_js: "10"
env: JQUERY=3
- language: php
php: 7.4
env: DOCUMENTATION=1
allow_failures:
- env: DOCUMENTATION=1
install:
- if [ "$JQUERY" ] ; then yarn add jquery@$JQUERY ; fi
- if [ "$DOCUMENTATION" ] ; then cd documentation && composer global require couscous/couscous ; fi
script:
- if [ "$JQUERY" ] ; then yarn run test ; else /home/travis/.config/composer/vendor/bin/couscous travis-auto-deploy --php-version=7.4 ; fi
- if [ "$JQUERY" ] && [ "$SAUCE_USERNAME" ] ; then yarn run test-sauce ; fi
deploy:
provider: npm
email: bjorn@swis.nl
api_key:
secure: YvSWphr8aTtwhvzO99jAVl4KoGHFEwwYVf1R7mklO3ZvU4yM1AYQ7m/gwpgkO3vBl0U6C1ixoE5VZzZHHDx3U1UAWeKktVdTvCp3uBDlnRVJdPIQ2gR5hT8X6j6LLTBc1iN/gaf5rT8xTeeeJp/M9gK6f757k88ZLm1DP/mdU3w=
on:
tags: true
repo: swisnl/jQuery-contextMenu
branch: master
condition: $JQUERY = 3
================================================
FILE: CHANGELOG.md
================================================
## Changelog ##
### Unreleased
* Context menu no longer jumps to the top of the screen #749
### 2.9.2
* Fix reflow when adding click layer to page (fixed #721, thanks @Rhain)
### 2.9.1
* Fix error when closing the menu by clicking on the page without any element under that click point. (fixes #717)
* Upgrades dependencies
### 2.9.0
#### Added
* Added `dataAttr` option to add arbitrary data attributes to menu items.
#### Changed
* Updated dev dependencies.
### 2.8.1
#### Fixed
* Added FontAwesome `fab` class to known classes.
#### Documentation
* Updated documentation for `callback` (thanks @arashdalir)
### 2.8.0
#### Added
* Added support for `events.preShow` so you can enable default browser menu if needed (thanks @terwarf)
### 2.7.1
#### Fixed
* A context menu appears outside the screen Under certain conditions (thanks @so-susa)
* No font-awesome icons visible in submenu ([Issue #659](https://github.com/swisnl/jQuery-contextMenu/issues/659)) thanks @betafritz and @klues
### 2.7.0
#### Documentation
* Add `getting started` to the documentation.
* Fixed typo in documentation which breaks the `callback` demo.
* Fixed typo `promis` => `promise` ([Issue #633](https://github.com/swisnl/jQuery-contextMenu/issues/633)).
* Fixed arguments for callback option in documentation ([Issue #571](https://github.com/swisnl/jQuery-contextMenu/issues/571)).
#### Added
* Added support for Font Awesome 5 ([Issue #593](https://github.com/swisnl/jQuery-contextMenu/issues/593)), ([Issue #593](https://github.com/swisnl/jQuery-contextMenu/issues/593))
### 2.6.4
#### Fixed
* `events.activated` is called without `options`as argument ([Issue #580](https://github.com/swisnl/jQuery-contextMenu/issues/580)).
* LayerClick sometimes breaks when the source is not a mouseevent ([Issue #132](https://github.com/swisnl/jQuery-contextMenu/issues/132)).
* The contextmenu now checks `visible` on items once instead of twice. Fixes [issue 612](https://github.com/swisnl/jQuery-contextMenu/issues/612).
* Font awesome li height is now consistent again ([Issue #610](https://github.com/swisnl/jQuery-contextMenu/issues/610)).
### 2.6.3
#### Fixed
* Broke build script after 2.5.0 which ment no updates to dist folder ([Issue #578](https://github.com/swisnl/jQuery-contextMenu/issues/578)).
### 2.6.2
#### Fixed
* Dev dependency ended up in normal dependencies.
### 2.6.1
#### Added
* Ability to define touchstart as trigger (thanks @npuser)
* Extra event `activated` that triggers after the menu is activated (thanks @AliShahrivarian)
* Flag denoting if a second trigger should close the menu (thanks @OliverColeman)
* Added update call to update visibility, disabled, icon and form value stats for items. Fixes issue ([Issue #555](https://github.com/swisnl/jQuery-contextMenu/issues/555)).
```javascript
$('.context-menu-one').contextMenu('update'); // update single menu
$.contextMenu('update') // update all open menus
```
#### Fixed
* Fix for out of bounds problem on window edges (thanks @AliShahrivarian)
### 2.5.0
#### Added
* Callback function now supplies original event ([Issue #211](https://github.com/swisnl/jQuery-contextMenu/issues/211)) thanks @wizzard0
### 2.4.5
#### Fixed
* ContextMenu appears with wrong position ([Issue #502](https://github.com/swisnl/jQuery-contextMenu/issues/502) thanks @apptaro
* Check if given selected value is a 0, if it is a zero so return it as is. Thanks @Falseee
* Events are never trigger when opening a contextMenu right after the other ([Issue #454](https://github.com/swisnl/jQuery-contextMenu/issues/454) thanks @kagant15
* Accesskey jQuery Modal Dialog not working ([Issue #506](https://github.com/swisnl/jQuery-contextMenu/issues/506) thanks @CiTRO33
* Fix submenu hover not always staying active if hovering over a submenu item. ([Issue #523](https://github.com/swisnl/jQuery-contextMenu/issues/523) thanks @tim-nz
* Change $node.click() to $node.get(0).click() to allow native event in HTML5 ([Issue #517](https://github.com/swisnl/jQuery-contextMenu/issues/517)
### 2.4.4
#### Fixed
* trigger is sometimes called on undefined objects because of typecheck on null. thanks @andreasrosdal
### 2.4.3
#### Changed
* The inline style causes a Content Security Policy violation if style-src 'unsafe-inline' is not defined in the policy. [PR 498](https://github.com/swisnl/jQuery-contextMenu/pull/498) thanks @StealthDuck
* Removed GPL license from the comment in the plugin. Was already removed everywhere else. Only MIT applies now.
#### Added
* Added SauceLabs tests for common browsers.
### 2.4.2 ###
### Fixed
* Focus not set on content editable element when right clicking the second time ([Issue #482](https://github.com/swisnl/jQuery-contextMenu/issues/482))
* `selectableSubMenu` broke disabling click menu (fixes ([Issue #493](https://github.com/swisnl/jQuery-contextMenu/issues/493))
### 2.4.1 ###
#### Fixed
* Quick fix for error in visible check ([Issue #484](https://github.com/swisnl/jQuery-contextMenu/issues/484))
#### Updated
* Tweaked positioning of submenu ([Issue #387](https://github.com/swisnl/jQuery-contextMenu/issues/387))
### 2.4.0 ###
#### Added
* Selectable Sub Menus ([Issue #483](https://github.com/swisnl/jQuery-contextMenu/issues/483)) thanks @zyuhel
#### Fixed
* The contextmenu shows even if all items are set to visible:false ([Issue #473](https://github.com/swisnlhttps://github.com/swisnl/jQuery-contextMenu/issues/482/jQuery-contextMenu/issues/473))
#### Documentation
* Update documentation to include demo for async promise fixes ([Issue #470](https://github.com/swisnl/jQuery-contextMenu/issues/470))
### 2.3.0 ###
#### Added
* Asynchronous promise support for submenu's ([Issue #429](https://github.com/swisnl/jQuery-contextMenu/issues/429)) thanks @Ruud-cb for the hard work.
* Include dist and src in package.json to easily use SCSS files ([PR #467](https://github.com/swisnl/jQuery-contextMenu/pull/467)) thanks @RoachMech
#### Fixed
* Font family when using font awesome ([Issue #433](https://github.com/swisnl/jQuery-contextMenu/issues/433))
* Add check for `opt.$menu` is null when handling callbacks. ([Issue #462](https://github.com/swisnl/jQuery-contextMenu/issues/462)) thanks @andreasrosdal
#### Changed
* Make `<input>` and `<select>` tags xhtml compatible ([Issue #451](https://github.com/swisnl/jQuery-contextMenu/issues/451)) thanks @andreasplesch
* Update jQuery UI position to 1.12.1
#### Documentation
* Fix demo for custom-command. ([Issue #294](https://github.com/swisnl/jQuery-contextMenu/issues/294))
* Fix broken link and demo title ([Issue #458](https://github.com/swisnl/jQuery-contextMenu/issues/458))
### 2.2.4 ###
#### Fixed
* Error on try to recreate menu after destroy ([Issue #397](https://github.com/swisnl/jQuery-contextMenu/issues/397))
### 2.2.3 ###
#### Fixed
* Callbacks are now called from the scope of the menu the item is in (like a submenu). For now they overwrite root callbacks only if the item is not in a submenu, this so the callbacks are always correct. Unfortunately this will also mean the callbacks option is still not complete if you use the same key for an item in any place. Cant fix that easily. Issue #413.
### 2.2.1 ###
#### Added
* Alias for 'cm_seperator' type: 'cm_separator' (thanks @nelson6e65)
#### Changed
* Removed old integration tests, framework on which they were built is abandoned.
* Enable jQuery 3 tests in TravisCI
#### Fixed
* jQuery 3 support was fixed again, was a result of jQuery UI (Fixes #407)
* Add checks for null before using opt.$menu and root.$menu. Fixes #352 (thanks @andreasrosdal)
* Small fix for color or ``input`` option on hover
#### Documentation
* Documentation added for cm_seperator (thanks @nelson6e65)
* Fix typo in items options documentation (thanks @nelson6e65)
* Fix typo in animation: fadeOut (thanks @avi-meslati-sp)
* Fix typo in docs code: `show` is used twice (thanks @kgeorgiou)
* Fix in async documentation.
### 2.2.0 ###
#### Added
* Add option to show item title as HTML (thanks @brassard)
* Full Font Awesome support
#### Changed
* Use relative units for css fixes ([Issue #386](https://github.com/swisnl/jQuery-contextMenu/issues/386)) (thanks @RoachMech)
* Change unicode characters in CSS to readable strings.
* Improved item styles (thanks @anseki)
#### Fixed
* Force woff2 font creation for Windows some machines.
* Fix so that disabled items can't get focus anymore (thanks @anseki)
* Fix so menu size is calculated better no items will take up 2 lines again (thanks @anseki)
* Fix bower.json (thanks @nelson6e65)
* Fix typo in documentation for "position" and "build" callback (thanks @mmcev106)
### 2.1.1 ###
* Fixed a problem when using the open function with custom arguments (thanks @RareDevil)
* `width` is increased when repoening menu. Fixed by using outerwidth to calculate width. Fixes #360 (thanks @anseki)
* Submenus are not collapsed when the menu is closed fixes #358 (thanks @anseki)
* Small delay in checking for autohide to fix missing the menu by a pixel or two. Fixes #347 (thanks @Risord)
* Check if an item is not hidden in any way when scrolling through items with the keyboard. Fixes #348
* Change links and base url of documentation to https as mentioned by @OmgImAlexis in PR#345
### 2.1.0 ###
* Added support for providing a function as zIndex value in options object (thanks @eivindga)
* Fixed a switch to use the correct type for separators (thanks @RareDevil)
* Fixed the problem with submenus size wrongly ([Issue #308](https://github.com/swisnl/jQuery-contextMenu/issues/308)) (thanks @RareDevil)
* Incorrect entry on package.json ([Issue #336](https://github.com/swisnl/jQuery-contextMenu/issues/336)) (thanks @Dijir)
* Gray out disabled icons as well as text ([Issue #337](https://github.com/swisnl/jQuery-contextMenu/issues/337)) (thanks @r02b)
* Optimized generated CSS so that ``context-menu-icon`` class can be used to overwrite icon CSS.
* Positioning of contextmenu when using appendTo (thanks @mrMarco)
* Check to see if target have a higher zIndex than the contextmenu in the key event handler (thanks @RareDevil)
### 2.0.1 (December 3rd 2015) ###
* Remove executable bit from jquery.contextMenu.js (thanks @jacknagel)
* Fixed a problem there was when using a function for icons (thanks @RareDevil)
* Fixed a problem where submenus resized wrong (thanks @RareDevil)
* Fixed a problem where the contextmenu would open another menu (thanks @RareDevil) - ([Issue #252](https://github.com/swisnl/jQuery-contextMenu/issues/252) and [Issue #293](https://github.com/swisnl/jQuery-contextMenu/issues/293))
* Fixed regression of node name's not being appended to the label of input elements. (thanks @RareDevil)
* Add check that root.$layer exists, to prevent calling hide() on an defined object. (thanks @andreasrosdal)
### 2.0.0 (October 28th 2015) ###
* __This version changes the default names of the icon classes in order to stop CSS conflicts with frameworks which define the class 'icon'.__ In order to keep the icon names the same as before this change you can change the defaults on the classnames for the icons ([docs on classNames option](http://swisnl.github.io/jQuery-contextMenu/docs.html#options-classNames)). The classnames will probably be "context-menu-icon-*" as proposed earlier by @rodneyrehm.
* You can not use SASS to customize your contextmenu. The gulp command build-icons takes all the SVG icons from src/icons and builds them into a font. In order to this we needed to break backwards compatibility. This does mean the new CSS does not have the old .icon class defined which makes it a lot more stable within CSS frameworks. The first revision of the documentation is found [here](documentation/docs/customize.md).
* The 1.x branch will be maintained for a while with bugfixes. But support for 1.x will be dropped in the coming months.
* Reverted the change from 1.7.0: .html() changed back to .text() since it is an security issue (thanks @arai-a)
### 1.10.1 (October 25th 2015) ###
* Added gulp command (integration-test-paths) to change the paths in the integration tests to the correct path after they are overwritten by the documentation generator.
* Make sure the contextmenu is not outside the client area by (thanks to @arai-a)
* Update jQuery dependecy so that it will not result in double installation of jQuery when using npm (thanks to @fredericlb)
### 1.9.1 (October 11th 2015) ###
* Fixed a bug where the classNames options would fail on a submenu.
* New documentation site and generation using [couscous](https://github.com/CouscousPHP/Couscous)
### 1.9.0 (October 1st 2015) ###
* Make classes configurable for those that can easily conflict. See the [docs on classNames option](http://swisnl.github.io/jQuery-contextMenu/docs.html#options-classNames). This also prepares to change classnames to non conflicting defaults so the hassle with frameworks as bootstrap will stop.
* Fix for handling of seperator string. It threw an error on the protected property of String.$node
* Fix for opening the contextmenu at coordinate 0,0 (by [Andreme](https://github.com/andreme))
* Fixed check for jQuery UI ([Issue #182](https://github.com/swisnl/jQuery-contextMenu/issues/182))
* Updated doc for function argument for icon
### 1.8.1 (September 14th 2015) ###
* Updated readme.
* Updated dist files
### 1.8.0 (September 14th 2015) - dist files not updated! ###
* Added dist folder with compiled JS and CSS, added these files to package and bower configuration.
* Fixed doc link for jQuery UI position ([Issue #274](https://github.com/swisnl/jQuery-contextMenu/issues/274))
* Item icon can now be a callback to dynamically decide on icon class. - ([Issue #158](https://github.com/swisnl/jQuery-contextMenu/issues/158), [Issue #129](https://github.com/swisnl/jQuery-contextMenu/issues/129), [Issue #151](https://github.com/swisnl/jQuery-contextMenu/issues/151), [Issue #249](https://github.com/swisnl/jQuery-contextMenu/issues/249))
* Small fix to calculating width and height on screen edges when padding is present.
### 1.7.0 (August 29th 2015) ###
* Touch support optimisations (by kccarter76)
* changed .text to .html so there are no extra span's fixed - ([Issue #252](https://github.com/swisnl/jQuery-contextMenu/issues/252))
* added visibility callback to item definition
* copy the HTML5 icon attribute when creating from HTML5 elements
* growing menu when opening multiple times fixed - ([Issue #197](https://github.com/swisnl/jQuery-contextMenu/issues/197))
* fixed failure to run tests
### 1.6.8 (August 18th 2015) ###
* changes for new maintainer
### 1.6.7 (May 21st 2015) ###
* looking for maintainer note
* publish to npm
### 1.6.6 (July 12th 2014) ###
* fixing bower manifest
### 1.6.5 (January 20th 2013) ###
* fixing "opening a second menu can break the layer" - ([Issue #105](https://github.com/swisnl/jQuery-contextMenu/issues/105))
### 1.6.4 (January 19th 2013) ###
* fixing [jQuery plugin manifest](https://github.com/swisnl/jQuery-contextMenu/commit/413b1ecaba0aeb4e50f97cee35f7c367435e7830#commitcomment-2465216), again. yep. I'm that kind of a guy. :(
### 1.6.3 (January 19th 2013) ###
* fixing [jQuery plugin manifest](https://github.com/swisnl/jQuery-contextMenu/commit/413b1ecaba0aeb4e50f97cee35f7c367435e7830#commitcomment-2465216)
### 1.6.2 (January 19th 2013) ###
* fixing "menu won't close" regression introduced by 1.6.1
### 1.6.1 (January 19th 2013) ###
* fixing potential html parsing problem
* upgrading to jQuery UI position v1.10.0
* replaced `CRLF` by `LF` (no idea how this happened in the first place...)
* adding `options.reposition` to dis/allow simply relocating a menu instead of rebuilding it ([Issue #104](https://github.com/swisnl/jQuery-contextMenu/issues/104))
### 1.6.0 (December 29th 2012) ###
* adding [DOM Element bound context menus](http://swisnl.github.io/jQuery-contextMenu/demo/on-dom-element.html) - ([Issue 88](https://github.com/swisnl/jQuery-contextMenu/issues/88))
* adding class `context-menu-active` to define state on active trigger element - ([Issue 92](https://github.com/swisnl/jQuery-contextMenu/issues/92))
* adding [demo for TouchSwipe](http://swisnl.github.io/jQuery-contextMenu/demo/trigger-swipe.html) activation
* adding export of internal functions and event handlers - ([Issue 101](https://github.com/swisnl/jQuery-contextMenu/issues/101))
* fixing key "watch" might translate to Object.prototype.watch in callbacks map - ([Issue 93](https://github.com/swisnl/jQuery-contextMenu/issues/93))
* fixing menu and submenu width calculation - ([Issue 18](https://github.com/swisnl/jQuery-contextMenu/issues/18))
* fixing unused variables - ([Issue 100](https://github.com/swisnl/jQuery-contextMenu/issues/100))
* fixing iOS "click" compatibility problem - ([Issue 83](https://github.com/swisnl/jQuery-contextMenu/issues/83))
* fixing separators to not be clickable - ([Issue 85](https://github.com/swisnl/jQuery-contextMenu/issues/85))
* fixing issues with fixed positioned triggers ([Issue 95](https://github.com/swisnl/jQuery-contextMenu/issues/95))
* fixing word break problem - ([Issue 80](https://github.com/swisnl/jQuery-contextMenu/issues/80))
### 1.5.25 (October 8th 2012) ###
* upgrading to jQuery 1.8.2 ([Issue 78](https://github.com/swisnl/jQuery-contextMenu/issues/78))
* upgrading to jQuery UI position 1.9.0 RC1 ([Issue 78](https://github.com/swisnl/jQuery-contextMenu/issues/78))
### 1.5.24 (August 30th 2012) ###
* adding context menu options to input command events ([Issue 72](https://github.com/swisnl/jQuery-contextMenu/issues/72), dtex)
* code cosmetics for JSLint
### 1.5.23 (August 22nd 2012) ###
* fixing reposition/close issue on scrolled documents ([Issue 69](https://github.com/swisnl/jQuery-contextMenu/issues/69))
* fixing jQuery reference ([Issue 68](https://github.com/swisnl/jQuery-contextMenu/issues/68))
### 1.5.22 (July 16th 2012) ###
* fixing issue with animation and remove on hide (Issue #64)
### 1.5.21 (July 14th 2012) ###
* fixing backdrop would not remove on destroy (Issue #63)
### 1.5.20 (June 26th 2012) ###
Note: git tag of version is `v1.6.20`?!
* fixing backdrop would not position properly in IE6 (Issue #59)
* fixing nested input elements not accessible in Chrome / Safari (Issue #58)
### 1.5.19 ###
Note: git tag of version is missing...?!
* fixing sub-menu positioning when `$.ui.position` is not available (Issue #56)
### 1.5.18 ###
Note: git tag of version is missing...?!
* fixing html5 `<menu>` import (Issue #53)
### 1.5.17 (June 4th 2012) ###
* fixing `options` to default to `options.trigger = "right"`
* fixing variable name typo (Within Issue #51)
* fixing menu not closing while opening other menu (Within Issue #51)
* adding workaround for `contextmenu`-bug in Firefox 12 (Within Issue #51)
### 1.5.16 (May 29th 2012) ###
* added vendor-prefixed user-select to CSS
* fixed issue with z-indexing when `<body>` is used as a trigger (Issue #49)
### 1.5.15 (May 26th 2012) ###
* allowing to directly open another element's menu while a menu is shown (Issue #48)
* fixing autohide option that would not properly hide the menu
### 1.5.14 (May 22nd 2012) ###
* options.build() would break default options (Issue #47)
* $.contextMenu('destroy') would not remove backdrop
### 1.5.13 (May 4th 2012) ###
* exposing $trigger to dynamically built custom menu-item types (Issue #42)
* fixing repositioning of open menu (formerly accidental re-open)
* adding asynchronous example
* dropping ignoreRightClick in favor of proper event-type detection
### 1.5.12 (May 2nd 2012) ###
* prevent invoking callback of first item of a sub-menu when clicking on the sub-menu-item (Issue #41)
### 1.5.11 (April 27th 2012) ###
* providing `opt.$trigger` to show event (Issue #39)
### 1.5.10 (April 21st 2012) ###
* ignoreRightClick would not prevent right click when menu is already open (Issue #38)
### 1.5.9 (March 10th 2012) ###
* If build() did not return any items, an empty menu was shown (Issue #33)
### 1.5.8 (January 28th 2012) ###
* Capturing Page Up and Page Down keys to ignore like space (Issue #30)
* Added Home / End keys to jump to first / last command of menu (Issue #29)
* Bug hitting enter in an <input> would yield an error (Issue #28)
### 1.5.7 (January 21st 2012) ###
* Non-ASCII character in jquery.contextMenu.js caused compatibility issues in Rails (Issue #27)
### 1.5.6 (January 8th 2012) ###
* Bug contextmenu event was not passed to build() callback (Issue #24)
* Bug sub-menu markers would not display properly in Safari and Chrome (Issue #25)
### 1.5.5 (January 6th 2012) ###
* Bug Internet Explorer would not close menu when giving input elements focus (Issue #23)
### 1.5.4 (January 5th 2012) ##
* Bug not set z-index of sub-menus might not overlap the main menu correctly (Issue #22)
### 1.5.3 (January 1st 2012) ###
* Bug `console.log is undefined`
### 1.5.2 (December 25th 2012) ###
* Bug sub-menus would not properly update their disabled states (Issue #16) [again…]
* Bug sub-menus would not properly adjust width accoring to min-width and max-width (Issue #18)
### 1.5.1 (December 18th 2011) ###
* Bug sub-menus would not properly update their disabled states (Issue #16)
### 1.5 (December 13th 2011) ###
* Added [dynamic menu creation](http://swisnl.github.io/jQuery-contextMenu/demo/dynamic-create.html) (Issue #15)
### 1.4.4 (December 12th 2011) ###
* Bug positioning <menu> when trigger element is `position:fixed` (Issue #14)
### 1.4.3 (December 11th 2011) ###
* Bug key handler would caputure all key strokes while menu was visible (essentially disabling F5 and co.)
### 1.4.2 (December 6th 2011) ###
* Bug opt.$trigger was not available to disabled callbacks
* jQuery bumped to 1.7.1
### 1.4.1 (November 9th 2011) ###
* Bug where <menu> imports would not pass action (click event) properly
### 1.4 (November 7th 2011) ###
* Upgraded to jQuery 1.7 (changed dependecy!)
* Added internal events `contextmenu:focus`, `contextmenu:blur` and `contextmenu:hide`
* Added custom <command> types
* Bug where `className` wasn't properly set on <menu>
### 1.3 (September 5th 2011) ###
* Added support for accesskeys
* Bug where two sub-menus could be open simultaneously
### 1.2.2 (August 24th 2011) ###
* Bug in HTML5 import
### 1.2.1 (August 24th 2011) ###
* Bug in HTML5 detection
### 1.2 (August 24th 2011) ###
* Added compatibility to <menuitem> for Firefox 8
* Upgraded to jQuery 1.6.2
### 1.1 (August 11th 2011) ###
* Bug #1 TypeError on HTML5 action passthru
* Bug #2 disbaled callback not invoked properly
* Feature #3 auto-hide option for hover trigger
* Feature #4 option to use a single callback for all commands, rather than registering the same function for each item
* Option to ignore right-click (original "contextmenu" event trigger) for non-right-click triggers
### 1.0 (July 7th 2011) ###
* Initial $.contextMenu handler
================================================
FILE: LICENSE
================================================
The MIT License
Copyright (c) 2010-2016 SWIS BV
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: README.md
================================================
# jQuery contextMenu plugin & polyfill #
[](https://greenkeeper.io/)
[](https://app.travis-ci.com/github/swisnl/jQuery-contextMenu) [](https://www.npmjs.com/package/jquery-contextmenu) [](https://www.npmjs.com/package/jquery-contextmenu) [](https://cdnjs.com/libraries/jquery-contextmenu) []() [](https://plant.treeware.earth/swisnl/jQuery-contextMenu)
`$.contextMenu` is a management facility for - you guessed it - context menus. It was designed for an application where there are hundreds of elements that may show a context menu - so intialization speed and memory usage are kept fairly small. It also allows to register context menus without providing actual markup, as `$.contextMenu` generates DOMElements as needed.
[features](http://swisnl.github.io/jQuery-contextMenu/index.html) -
[demo](http://swisnl.github.io/jQuery-contextMenu/demo.html) -
[documentation](http://swisnl.github.io/jQuery-contextMenu/docs.html)
[](https://saucelabs.com/u/bbrala-contextmenu)
## Dependencies ##
* jQuery >=1.8.2
* jQuery UI position (optional but recommended)
## Usage ##
register contextMenu from javascript:
```javascript
$.contextMenu({
// define which elements trigger this menu
selector: ".with-cool-menu",
// define the elements of the menu
items: {
foo: {name: "Foo", callback: function(key, opt){ alert("Foo!"); }},
bar: {name: "Bar", callback: function(key, opt){ alert("Bar!") }}
}
// there's more, have a look at the demos and docs...
});
```
have a look at the [demos](http://swisnl.github.io/jQuery-contextMenu/demo.html).
## Version 3.0 beta
Version 3.0 is a restructure of the javascript into something more sane written in ES6. It consolidates all API's so callbacks are better documented and more concise. The basics are still the same, but all callbacks are structured differently.
The goal of this refactor was mostly to make the ContextMenu easier to maintain, and make the API's more consise. It also adds JSdoc comments so the API documentation is generated from the code and it enables code completion.
Check out the [3.x branch](https://github.com/swisnl/jQuery-contextMenu/tree/3.x), or install with `npm install jquery-contextmenu@next`.
## HTML5 Compatibility ##
Firefox 8 implemented contextmenu using the <menuitem> tags for menu-structure. The specs however state that <command> tags should be used for this purpose. $.contextMenu accepts both.
Firefox 8 does not yet fully implement the contextmenu specification ([Ticket #617528](https://bugzilla.mozilla.org/show_bug.cgi?id=617528)). The elements
[a](http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-a-element-to-define-a-command),
[button](http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-button-element-to-define-a-command),
[input](http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-input-element-to-define-a-command) and
[option](http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-option-element-to-define-a-command)
usable as commands are being ignored altogether. It also doesn't (optically) distinguish between checkbox/radio and regular commands ([Bug #705292](https://bugzilla.mozilla.org/show_bug.cgi?id=705292)).
* [contextmenu specs](http://www.w3.org/TR/html5/interactive-elements.html#context-menus)
* [command specs](http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html)
* [Browser support according to caniuse.com](http://caniuse.com/#search=context%20menu)
Note: While the specs note <option>s to be rendered as regular commands, $.contextMenu will render an actual <select>. import contextMenu from HTML5 <menu>:
```javascript
$.contextMenu("html5");
```
## Interaction Principles
You're (obviously) able to use the context menu with your mouse. Once it is opened, you can also use the keyboard to (fully) navigate it.
* ↑ (up) previous item in list, will skip disabled elements and wrap around
* ↓ (down) next item in, will skip disabled elements and wrap around
* → (right) dive into sub-menu
* ← (left) rise from sub-menu
* ↵ (return) invoke command
* ⇥ (tab) next item or input element, will skip disabled elements and wrap around
* ⇪ ⇥ (shift tab) previous item or input element, will skip disabled elements and wrap around
* ⎋ (escape) close menu
* ⌴ (space) captured and ignore to avoid page scrolling (for consistency with native menus)
* ⇞ (page up) captured and ignore to avoid page scrolling (for consistency with native menus)
* ⇟ (page down) captured and ignore to avoid page scrolling (for consistency with native menus)
* ↖ (home) first item in list, will skip disabled elements
* ↘ (end) last item in list, will skip disabled elements
Besides the obvious, browser also react to alphanumeric key strokes. Hitting <code>r</code> in a context menu will make Firefox (8) reload the page immediately. Chrome selects the option to see infos on the page, Safari selects the option to print the document. Awesome, right? Until trying the same on Windows I did not realize that the browsers were using the access-key for this. I would've preferred typing the first character of something, say "s" for "save" and then iterate through all the commands beginning with s. But that's me - what do I know about UX? Anyways, $.contextMenu now also supports accesskey handling.
## Authors
* [Björn Brala](https://github.com/swisnl)
* [Rodney Rehm](https://github.com/rodneyrehm) (original creator)
* [Christiaan Baartse](https://github.com/christiaan) (single callback per menu)
* [Addy Osmani](https://github.com/addyosmani) (compatibility with native context menu in Firefox 8)
## License
`$.contextMenu` is published under the [MIT license](http://www.opensource.org/licenses/mit-license)
This package is [Treeware](https://treeware.earth). If you use it in production, then we ask that you [**buy the world a tree**](https://plant.treeware.earth/swisnl/jQuery-contextMenu) to thank us for our work. By contributing to the Treeware forest you’ll be creating employment for local families and restoring wildlife habitats.
## Special thanks
Font-Awesome icons used from [encharm/Font-Awesome-SVG-PNG](https://github.com/encharm/Font-Awesome-SVG-PNG).
## SWIS :heart: Open Source
[SWIS][link-swis] is a web agency from Leiden, the Netherlands. We love working with open source software.
[link-swis]: https://www.swis.nl
================================================
FILE: bower.json
================================================
{
"name": "jQuery-contextMenu",
"main": [
"dist/jquery.contextMenu.js",
"dist/jquery.contextMenu.css"
],
"homepage": "http://swisnl.github.io/jQuery-contextMenu/",
"authors": [
"Björn Brala <bjorn@swis.nl> (http://www.swis.nl)",
"Rodney Rehm <rodney.rehm@medialize.de> (http://rodneyrehm.de/en)"
],
"description": "Full featured context menu handler capable of handling thousands of elements",
"keywords": ["contextmenu", "context-menu", "right-click-menu", "right-click", "navigation", "menu"],
"license": "MIT",
"dependencies": {
"jquery": ">=1.8.2"
},
"ignore": [
"demo/",
"prettify/",
"screenshots/",
"**/.*",
"*.md",
"*.html",
"/*.css",
"/*.js"
]
}
================================================
FILE: contextMenu.jquery.json
================================================
{
"name": "contextMenu",
"title": "jQuery contextMenu",
"description": "full featured context menu handler capable of handling thousands of elements",
"keywords": [
"contextmenu",
"context-menu",
"right-click-menu",
"right-click",
"navigation",
"menu"
],
"version": "git-master",
"author": {
"name": "Björn Brala (SWIS.nl)",
"url": "http://www.swis.nl"
},
"licenses": [
{
"type": "MIT",
"url": "https://github.com/jquery/jquery-color/blob/2.1.2/MIT-LICENSE.txt"
}
],
"bugs": "https://github.com/swisnl/jQuery-contextMenu/issues",
"homepage": "http://swisnl.github.io/jQuery-contextMenu/",
"docs": "http://swisnl.github.io/jQuery-contextMenu/docs.html",
"dependencies": {
"jquery": ">=1.8.2"
}
}
================================================
FILE: dist/jquery.contextMenu.css
================================================
@charset "UTF-8";
/*!
* jQuery contextMenu - Plugin for simple contextMenu handling
*
* Version: v2.9.2
*
* Authors: Björn Brala (SWIS.nl), Rodney Rehm, Addy Osmani (patches for FF)
* Web: http://swisnl.github.io/jQuery-contextMenu/
*
* Copyright (c) 2011-2025 SWIS BV and contributors
*
* Licensed under
* MIT License http://www.opensource.org/licenses/mit-license
*
* Date: 2025-11-04T11:31:40.817Z
*/
@-webkit-keyframes cm-spin {
0% {
-webkit-transform: translateY(-50%) rotate(0deg);
transform: translateY(-50%) rotate(0deg);
}
100% {
-webkit-transform: translateY(-50%) rotate(359deg);
transform: translateY(-50%) rotate(359deg);
}
}
@-o-keyframes cm-spin {
0% {
-webkit-transform: translateY(-50%) rotate(0deg);
-o-transform: translateY(-50%) rotate(0deg);
transform: translateY(-50%) rotate(0deg);
}
100% {
-webkit-transform: translateY(-50%) rotate(359deg);
-o-transform: translateY(-50%) rotate(359deg);
transform: translateY(-50%) rotate(359deg);
}
}
@keyframes cm-spin {
0% {
-webkit-transform: translateY(-50%) rotate(0deg);
-o-transform: translateY(-50%) rotate(0deg);
transform: translateY(-50%) rotate(0deg);
}
100% {
-webkit-transform: translateY(-50%) rotate(359deg);
-o-transform: translateY(-50%) rotate(359deg);
transform: translateY(-50%) rotate(359deg);
}
}
@font-face {
font-family: "context-menu-icons";
font-style: normal;
font-weight: normal;
src: url("font/context-menu-icons.eot?2dq4x");
src: url("font/context-menu-icons.eot?2dq4x#iefix") format("embedded-opentype"), url("font/context-menu-icons.woff2?2dq4x") format("woff2"), url("font/context-menu-icons.woff?2dq4x") format("woff"), url("font/context-menu-icons.ttf?2dq4x") format("truetype");
}
.context-menu-icon-add:before {
content: "\EA01";
}
.context-menu-icon-copy:before {
content: "\EA02";
}
.context-menu-icon-cut:before {
content: "\EA03";
}
.context-menu-icon-delete:before {
content: "\EA04";
}
.context-menu-icon-edit:before {
content: "\EA05";
}
.context-menu-icon-loading:before {
content: "\EA06";
}
.context-menu-icon-paste:before {
content: "\EA07";
}
.context-menu-icon-quit:before {
content: "\EA08";
}
.context-menu-icon::before {
position: absolute;
top: 50%;
left: 0;
width: 2em;
font-family: "context-menu-icons";
font-size: 1em;
font-style: normal;
font-weight: normal;
line-height: 1;
color: #2980b9;
text-align: center;
-webkit-transform: translateY(-50%);
-ms-transform: translateY(-50%);
-o-transform: translateY(-50%);
transform: translateY(-50%);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.context-menu-icon.context-menu-hover:before {
color: #fff;
}
.context-menu-icon.context-menu-disabled::before {
color: #bbb;
}
.context-menu-icon.context-menu-icon-loading:before {
-webkit-animation: cm-spin 2s infinite;
-o-animation: cm-spin 2s infinite;
animation: cm-spin 2s infinite;
}
.context-menu-icon.context-menu-icon--fa {
display: list-item;
font-family: inherit;
line-height: inherit;
}
.context-menu-icon.context-menu-icon--fa::before {
position: absolute;
top: 50%;
left: 0;
width: 2em;
font-family: FontAwesome;
font-size: 1em;
font-style: normal;
font-weight: normal;
line-height: 1;
color: #2980b9;
text-align: center;
-webkit-transform: translateY(-50%);
-ms-transform: translateY(-50%);
-o-transform: translateY(-50%);
transform: translateY(-50%);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.context-menu-icon.context-menu-icon--fa.context-menu-hover:before {
color: #fff;
}
.context-menu-icon.context-menu-icon--fa.context-menu-disabled::before {
color: #bbb;
}
.context-menu-icon.context-menu-icon--fa5 {
display: list-item;
font-family: inherit;
line-height: inherit;
}
.context-menu-icon.context-menu-icon--fa5 i, .context-menu-icon.context-menu-icon--fa5 svg {
position: absolute;
top: .3em;
left: .5em;
color: #2980b9;
}
.context-menu-icon.context-menu-icon--fa5.context-menu-hover > i, .context-menu-icon.context-menu-icon--fa5.context-menu-hover > svg {
color: #fff;
}
.context-menu-icon.context-menu-icon--fa5.context-menu-disabled i, .context-menu-icon.context-menu-icon--fa5.context-menu-disabled svg {
color: #bbb;
}
.context-menu-list {
position: absolute;
display: inline-block;
min-width: 13em;
max-width: 26em;
padding: .25em 0;
margin: .3em;
font-family: inherit;
font-size: inherit;
list-style-type: none;
background: #fff;
border: 1px solid #bebebe;
border-radius: .2em;
-webkit-box-shadow: 0 2px 5px rgba(0, 0, 0, .5);
box-shadow: 0 2px 5px rgba(0, 0, 0, .5);
}
.context-menu-item {
position: relative;
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
padding: .2em 2em;
color: #2f2f2f;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
background-color: #fff;
}
.context-menu-separator {
padding: 0;
margin: .35em 0;
border-bottom: 1px solid #e6e6e6;
}
.context-menu-item > label > input,
.context-menu-item > label > textarea {
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
}
.context-menu-item.context-menu-hover {
color: #fff;
cursor: pointer;
background-color: #2980b9;
}
.context-menu-item.context-menu-disabled {
color: #bbb;
cursor: default;
background-color: #fff;
}
.context-menu-input.context-menu-hover {
color: #2f2f2f;
cursor: default;
}
.context-menu-submenu:after {
position: absolute;
top: 50%;
right: .5em;
z-index: 1;
width: 0;
height: 0;
content: '';
border-color: transparent transparent transparent #2f2f2f;
border-style: solid;
border-width: .25em 0 .25em .25em;
-webkit-transform: translateY(-50%);
-ms-transform: translateY(-50%);
-o-transform: translateY(-50%);
transform: translateY(-50%);
}
/**
* Inputs
*/
.context-menu-item.context-menu-input {
padding: .3em .6em;
}
/* vertically align inside labels */
.context-menu-input > label > * {
vertical-align: top;
}
/* position checkboxes and radios as icons */
.context-menu-input > label > input[type="checkbox"],
.context-menu-input > label > input[type="radio"] {
position: relative;
top: .12em;
margin-right: .4em;
}
.context-menu-input > label {
margin: 0;
}
.context-menu-input > label,
.context-menu-input > label > input[type="text"],
.context-menu-input > label > textarea,
.context-menu-input > label > select {
display: block;
width: 100%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.context-menu-input > label > textarea {
height: 7em;
}
.context-menu-item > .context-menu-list {
top: .3em;
/* re-positioned by js */
right: -.3em;
display: none;
}
.context-menu-item.context-menu-visible > .context-menu-list {
display: block;
}
.context-menu-accesskey {
text-decoration: underline;
}
================================================
FILE: dist/jquery.contextMenu.js
================================================
/**
* jQuery contextMenu v2.9.2 - Plugin for simple contextMenu handling
*
* Version: v2.9.2
*
* Authors: Björn Brala (SWIS.nl), Rodney Rehm, Addy Osmani (patches for FF)
* Web: http://swisnl.github.io/jQuery-contextMenu/
*
* Copyright (c) 2011-2025 SWIS BV and contributors
*
* Licensed under
* MIT License http://www.opensource.org/licenses/mit-license
*
* Date: 2025-11-04T11:31:41.320Z
*/
// jscs:disable
/* jshint ignore:start */
(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as anonymous module.
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// Node / CommonJS
factory(require('jquery'));
} else {
// Browser globals.
factory(jQuery);
}
})(function ($) {
'use strict';
// helper function to check for rapid interactions after menu display
var isInteractionTooFast = function($element) {
if (!('ontouchstart' in window
|| navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0)) {
return false;
}
var interactionTime = Date.now();
var $liItem = $element.is('input, textarea, select') ? $element.closest('.context-menu-item') : $element;
if (!$liItem || !$liItem.length) {
return false;
}
var $parentMenu = $liItem.parent();
if (!$parentMenu || !$parentMenu.length) {
return false;
}
// only apply the check for items within submenus
if ($parentMenu.hasClass('context-menu-root')) {
return false;
}
var showTimestamp = $parentMenu.data('_showTimestamp');
var timeDifference = showTimestamp ? interactionTime - showTimestamp : Infinity;
// threshold for fast interaction (e.g., mobile tap)
var threshold = 50; // ms
return timeDifference < threshold;
};
// TODO: -
// ARIA stuff: menuitem, menuitemcheckbox und menuitemradio
// create <menu> structure if $.support[htmlCommand || htmlMenuitem] and !opt.disableNative
// determine html5 compatibility
$.support.htmlMenuitem = ('HTMLMenuItemElement' in window);
$.support.htmlCommand = ('HTMLCommandElement' in window);
$.support.eventSelectstart = ('onselectstart' in document.documentElement);
/* // should the need arise, test for css user-select
$.support.cssUserSelect = (function(){
var t = false,
e = document.createElement('div');
$.each('Moz|Webkit|Khtml|O|ms|Icab|'.split('|'), function(i, prefix) {
var propCC = prefix + (prefix ? 'U' : 'u') + 'serSelect',
prop = (prefix ? ('-' + prefix.toLowerCase() + '-') : '') + 'user-select';
e.style.cssText = prop + ': text;';
if (e.style[propCC] == 'text') {
t = true;
return false;
}
return true;
});
return t;
})();
*/
if (!$.ui || !$.widget) {
// duck punch $.cleanData like jQueryUI does to get that remove event
$.cleanData = (function (orig) {
return function (elems) {
var events, elem, i;
for (i = 0; elems[i] != null; i++) {
elem = elems[i];
try {
// Only trigger remove when necessary to save time
events = $._data(elem, 'events');
if (events && events.remove) {
$(elem).triggerHandler('remove');
}
// Http://bugs.jquery.com/ticket/8235
} catch (e) {
}
}
orig(elems);
};
})($.cleanData);
}
/* jshint ignore:end */
// jscs:enable
var // currently active contextMenu trigger
$currentTrigger = null,
// is contextMenu initialized with at least one menu?
initialized = false,
// window handle
$win = $(window),
// number of registered menus
counter = 0,
// mapping selector to namespace
namespaces = {},
// mapping namespace to options
menus = {},
// custom command type handlers
types = {},
// default values
defaults = {
// selector of contextMenu trigger
selector: null,
// where to append the menu to
appendTo: null,
// method to trigger context menu ["right", "left", "hover"]
trigger: 'right',
// hide menu when mouse leaves trigger / menu elements
autoHide: false,
// ms to wait before showing a hover-triggered context menu
delay: 200,
// flag denoting if a second trigger should simply move (true) or rebuild (false) an open menu
// as long as the trigger happened on one of the trigger-element's child nodes
reposition: true,
// Flag denoting if a second trigger should close the menu, as long as
// the trigger happened on one of the trigger-element's child nodes.
// This overrides the reposition option.
hideOnSecondTrigger: false,
// use a modal layer for closing the menu rather than a captured event on document
useModal: true,
//ability to select submenu
selectableSubMenu: false,
// Default classname configuration to be able avoid conflicts in frameworks
classNames: {
hover: 'context-menu-hover', // Item hover
disabled: 'context-menu-disabled', // Item disabled
visible: 'context-menu-visible', // Item visible
notSelectable: 'context-menu-not-selectable', // Item not selectable
icon: 'context-menu-icon',
iconEdit: 'context-menu-icon-edit',
iconCut: 'context-menu-icon-cut',
iconCopy: 'context-menu-icon-copy',
iconPaste: 'context-menu-icon-paste',
iconDelete: 'context-menu-icon-delete',
iconAdd: 'context-menu-icon-add',
iconQuit: 'context-menu-icon-quit',
iconLoadingClass: 'context-menu-icon-loading'
},
// determine position to show menu at
determinePosition: function ($menu) {
// position to the lower middle of the trigger element
if ($.ui && $.ui.position) {
// .position() is provided as a jQuery UI utility
// (...and it won't work on hidden elements)
$menu.css('display', 'block').position({
my: 'center top',
at: 'center bottom',
of: this,
offset: '0 5',
collision: 'fit'
}).css('display', 'none');
} else {
// determine contextMenu position
var offset = this.offset();
offset.top += this.outerHeight();
offset.left += this.outerWidth() / 2 - $menu.outerWidth() / 2;
$menu.css(offset);
}
},
// position menu
position: function (opt, x, y) {
var offset;
// determine contextMenu position
if (!x && !y) {
opt.determinePosition.call(this, opt.$menu);
return;
} else if (x === 'maintain' && y === 'maintain') {
// x and y must not be changed (after re-show on command click)
offset = opt.$menu.position();
} else {
// x and y are given (by mouse event)
var offsetParentOffset = opt.$menu.offsetParent().offset();
offset = {top: y - offsetParentOffset.top, left: x -offsetParentOffset.left};
}
// correct offset if viewport demands it
var bottom = $win.scrollTop() + $win.height(),
right = $win.scrollLeft() + $win.width(),
height = opt.$menu.outerHeight(),
width = opt.$menu.outerWidth();
if (offset.top + height > bottom) {
offset.top -= height;
}
if (offset.top < 0) {
offset.top = 0;
}
if (offset.left + width > right) {
offset.left -= width;
}
if (offset.left < 0) {
offset.left = 0;
}
opt.$menu.css(offset);
},
// position the sub-menu
positionSubmenu: function ($menu) {
if (typeof $menu === 'undefined') {
// When user hovers over item (which has sub items) handle.focusItem will call this.
// but the submenu does not exist yet if opt.items is a promise. just return, will
// call positionSubmenu after promise is completed.
return;
}
if ($.ui && $.ui.position) {
// .position() is provided as a jQuery UI utility
// (...and it won't work on hidden elements)
$menu.css('display', 'block').position({
my: 'left top-5',
at: 'right top',
of: this,
collision: 'flipfit fit'
}).css('display', '');
} else {
// determine contextMenu position
var offset = {
top: -9,
left: this.outerWidth() - 5
};
$menu.css(offset);
}
},
// offset to add to zIndex
zIndex: 1,
// show hide animation settings
animation: {
duration: 50,
show: 'slideDown',
hide: 'slideUp'
},
// events
events: {
preShow: $.noop,
show: $.noop,
hide: $.noop,
activated: $.noop
},
// default callback
callback: null,
// list of contextMenu items
items: {}
},
// mouse position for hover activation
hoveract = {
timer: null,
pageX: null,
pageY: null
},
// determine zIndex
zindex = function ($t) {
var zin = 0,
$tt = $t;
while (true) {
zin = Math.max(zin, parseInt($tt.css('z-index'), 10) || 0);
$tt = $tt.parent();
if (!$tt || !$tt.length || 'html body'.indexOf($tt.prop('nodeName').toLowerCase()) > -1) {
break;
}
}
return zin;
},
// event handlers
handle = {
// abort anything
abortevent: function (e) {
e.preventDefault();
e.stopImmediatePropagation();
},
// contextmenu show dispatcher
contextmenu: function (e) {
var $this = $(this);
//Show browser context-menu when preShow returns false
if (e.data.events.preShow($this,e) === false) {
return;
}
// disable actual context-menu if we are using the right mouse button as the trigger
if (e.data.trigger === 'right') {
e.preventDefault();
e.stopImmediatePropagation();
}
// abort native-triggered events unless we're triggering on right click
if ((e.data.trigger !== 'right' && e.data.trigger !== 'demand') && e.originalEvent) {
return;
}
// Let the current contextmenu decide if it should show or not based on its own trigger settings
if (typeof e.mouseButton !== 'undefined' && e.data) {
if (!(e.data.trigger === 'left' && e.mouseButton === 0) && !(e.data.trigger === 'right' && e.mouseButton === 2)) {
// Mouse click is not valid.
return;
}
}
// abort event if menu is visible for this trigger
if ($this.hasClass('context-menu-active')) {
return;
}
if (!$this.hasClass('context-menu-disabled')) {
// theoretically need to fire a show event at <menu>
// http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#context-menus
// var evt = jQuery.Event("show", { data: data, pageX: e.pageX, pageY: e.pageY, relatedTarget: this });
// e.data.$menu.trigger(evt);
$currentTrigger = $this;
if (e.data.build) {
var built = e.data.build($currentTrigger, e);
// abort if build() returned false
if (built === false) {
return;
}
// dynamically build menu on invocation
e.data = $.extend(true, {}, defaults, e.data, built || {});
// abort if there are no items to display
if (!e.data.items || $.isEmptyObject(e.data.items)) {
// Note: jQuery captures and ignores errors from event handlers
if (window.console) {
(console.error || console.log).call(console, 'No items specified to show in contextMenu');
}
throw new Error('No Items specified');
}
// backreference for custom command type creation
e.data.$trigger = $currentTrigger;
op.create(e.data);
}
op.show.call($this, e.data, e.pageX, e.pageY);
}
},
// contextMenu left-click trigger
click: function (e) {
e.preventDefault();
e.stopImmediatePropagation();
$(this).trigger($.Event('contextmenu', {data: e.data, pageX: e.pageX, pageY: e.pageY}));
},
// contextMenu right-click trigger
mousedown: function (e) {
// register mouse down
var $this = $(this);
// hide any previous menus
if ($currentTrigger && $currentTrigger.length && !$currentTrigger.is($this)) {
$currentTrigger.data('contextMenu').$menu.trigger('contextmenu:hide');
}
// activate on right click
if (e.button === 2) {
$currentTrigger = $this.data('contextMenuActive', true);
}
},
// contextMenu right-click trigger
mouseup: function (e) {
// show menu
var $this = $(this);
if ($this.data('contextMenuActive') && $currentTrigger && $currentTrigger.length && $currentTrigger.is($this) && !$this.hasClass('context-menu-disabled')) {
e.preventDefault();
e.stopImmediatePropagation();
$currentTrigger = $this;
$this.trigger($.Event('contextmenu', {data: e.data, pageX: e.pageX, pageY: e.pageY}));
}
$this.removeData('contextMenuActive');
},
// contextMenu hover trigger
mouseenter: function (e) {
var $this = $(this),
$related = $(e.relatedTarget),
$document = $(document);
// abort if we're coming from a menu
if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) {
return;
}
// abort if a menu is shown
if ($currentTrigger && $currentTrigger.length) {
return;
}
hoveract.pageX = e.pageX;
hoveract.pageY = e.pageY;
hoveract.data = e.data;
$document.on('mousemove.contextMenuShow', handle.mousemove);
hoveract.timer = setTimeout(function () {
hoveract.timer = null;
$document.off('mousemove.contextMenuShow');
$currentTrigger = $this;
$this.trigger($.Event('contextmenu', {
data: hoveract.data,
pageX: hoveract.pageX,
pageY: hoveract.pageY
}));
}, e.data.delay);
},
// contextMenu hover trigger
mousemove: function (e) {
hoveract.pageX = e.pageX;
hoveract.pageY = e.pageY;
},
// contextMenu hover trigger
mouseleave: function (e) {
// abort if we're leaving for a menu
var $related = $(e.relatedTarget);
if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) {
return;
}
try {
clearTimeout(hoveract.timer);
} catch (e) {
}
hoveract.timer = null;
},
// click on layer to hide contextMenu
layerClick: function (e, opt, onhide) {
var $this = $(this),
root = (opt !== undefined) ? opt : $this.data('contextMenuRoot'),
button = e.button,
x = e.pageX,
y = e.pageY,
fakeClick = x === undefined,
target,
offset;
// If the click is not real, things break: https://github.com/swisnl/jQuery-contextMenu/issues/132
if(fakeClick){
if (root !== null && typeof root !== 'undefined' && root.$menu !== null && typeof root.$menu !== 'undefined') {
root.$menu.trigger('contextmenu:hide');
}
return;
}
// if the click closing is done through windwow event listener rather than a transparent layer
if (!root.$layer) {
target = document.elementFromPoint(x - $win.scrollLeft(), y - $win.scrollTop());
if (root.$menu === null || typeof root.$menu === 'undefined' || !root.$menu[0].contains(target)) {
root.$menu.trigger('contextmenu:hide');
if (typeof onhide !== 'undefined')
onhide();
}
return;
}
e.preventDefault();
setTimeout(function () {
var $window;
var triggerAction = ((root.trigger === 'left' && button === 0) || (root.trigger === 'right' && button === 2));
// find the element that would've been clicked, wasn't the layer in the way
if (document.elementFromPoint && root.$layer) {
root.$layer.hide();
target = document.elementFromPoint(x - $win.scrollLeft(), y - $win.scrollTop());
// also need to try and focus this element if we're in a contenteditable area,
// as the layer will prevent the browser mouse action we want
if (target !== null && target.isContentEditable) {
var range = document.createRange(),
sel = window.getSelection();
range.selectNode(target);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
$(target).trigger(e);
root.$layer.show();
}
if (root.hideOnSecondTrigger && triggerAction && root.$menu !== null && typeof root.$menu !== 'undefined') {
root.$menu.trigger('contextmenu:hide');
return;
}
if (root.reposition && triggerAction) {
if (document.elementFromPoint) {
if (root.$trigger.is(target)) {
root.position.call(root.$trigger, root, x, y);
return;
}
} else {
offset = root.$trigger.offset();
$window = $(window);
// while this looks kinda awful, it's the best way to avoid
// unnecessarily calculating any positions
offset.top += $window.scrollTop();
if (offset.top <= e.pageY) {
offset.left += $window.scrollLeft();
if (offset.left <= e.pageX) {
offset.bottom = offset.top + root.$trigger.outerHeight();
if (offset.bottom >= e.pageY) {
offset.right = offset.left + root.$trigger.outerWidth();
if (offset.right >= e.pageX) {
// reposition
root.position.call(root.$trigger, root, x, y);
return;
}
}
}
}
}
}
if (target && triggerAction) {
root.$trigger.one('contextmenu:hidden', function () {
$(target).contextMenu({x: x, y: y, button: button});
});
}
if (root !== null && typeof root !== 'undefined' && root.$menu !== null && typeof root.$menu !== 'undefined') {
root.$menu.trigger('contextmenu:hide');
}
}, 50);
},
// key handled :hover
keyStop: function (e, opt) {
if (!opt.isInput) {
e.preventDefault();
}
e.stopPropagation();
},
key: function (e) {
var opt = {};
// Only get the data from $currentTrigger if it exists
if ($currentTrigger) {
opt = $currentTrigger.data('contextMenu') || {};
}
// If the trigger happen on a element that are above the contextmenu do this
if (typeof opt.zIndex === 'undefined') {
opt.zIndex = 0;
}
var targetZIndex = 0;
var getZIndexOfTriggerTarget = function (target) {
if (target.style.zIndex !== '') {
targetZIndex = target.style.zIndex;
} else {
if (target.offsetParent !== null && typeof target.offsetParent !== 'undefined') {
getZIndexOfTriggerTarget(target.offsetParent);
}
else if (target.parentElement !== null && typeof target.parentElement !== 'undefined') {
getZIndexOfTriggerTarget(target.parentElement);
}
}
};
getZIndexOfTriggerTarget(e.target);
// If targetZIndex is heigher then opt.zIndex dont progress any futher.
// This is used to make sure that if you are using a dialog with a input / textarea / contenteditable div
// and its above the contextmenu it wont steal keys events
if (opt.$menu && parseInt(targetZIndex,10) > parseInt(opt.$menu.css("zIndex"),10)) {
return;
}
switch (e.keyCode) {
case 9:
case 38: // up
handle.keyStop(e, opt);
// if keyCode is [38 (up)] or [9 (tab) with shift]
if (opt.isInput) {
if (e.keyCode === 9 && e.shiftKey) {
e.preventDefault();
if (opt.$selected) {
opt.$selected.find('input, textarea, select').blur();
}
if (opt.$menu !== null && typeof opt.$menu !== 'undefined') {
opt.$menu.trigger('prevcommand');
}
return;
} else if (e.keyCode === 38 && opt.$selected.find('input, textarea, select').prop('type') === 'checkbox') {
// checkboxes don't capture this key
e.preventDefault();
return;
}
} else if (e.keyCode !== 9 || e.shiftKey) {
if (opt.$menu !== null && typeof opt.$menu !== 'undefined') {
opt.$menu.trigger('prevcommand');
}
return;
}
break;
// omitting break;
// case 9: // tab - reached through omitted break;
case 40: // down
handle.keyStop(e, opt);
if (opt.isInput) {
if (e.keyCode === 9) {
e.preventDefault();
if (opt.$selected) {
opt.$selected.find('input, textarea, select').blur();
}
if (opt.$menu !== null && typeof opt.$menu !== 'undefined') {
opt.$menu.trigger('nextcommand');
}
return;
} else if (e.keyCode === 40 && opt.$selected.find('input, textarea, select').prop('type') === 'checkbox') {
// checkboxes don't capture this key
e.preventDefault();
return;
}
} else {
if (opt.$menu !== null && typeof opt.$menu !== 'undefined') {
opt.$menu.trigger('nextcommand');
}
return;
}
break;
case 37: // left
handle.keyStop(e, opt);
if (opt.isInput || !opt.$selected || !opt.$selected.length) {
break;
}
if (!opt.$selected.parent().hasClass('context-menu-root')) {
var $parent = opt.$selected.parent().parent();
opt.$selected.trigger('contextmenu:blur');
opt.$selected = $parent;
return;
}
break;
case 39: // right
handle.keyStop(e, opt);
if (opt.isInput || !opt.$selected || !opt.$selected.length) {
break;
}
var itemdata = opt.$selected.data('contextMenu') || {};
if (itemdata.$menu && opt.$selected.hasClass('context-menu-submenu')) {
opt.$selected = null;
itemdata.$selected = null;
itemdata.$menu.trigger('nextcommand');
return;
}
break;
case 35: // end
case 36: // home
if (opt.$selected && opt.$selected.find('input, textarea, select').length) {
return;
} else {
(opt.$selected && opt.$selected.parent() || opt.$menu)
.children(':not(.' + opt.classNames.disabled + ', .' + opt.classNames.notSelectable + ')')[e.keyCode === 36 ? 'first' : 'last']()
.trigger('contextmenu:focus');
e.preventDefault();
return;
}
break;
case 13: // enter
handle.keyStop(e, opt);
if (opt.isInput) {
if (opt.$selected && !opt.$selected.is('textarea, select')) {
e.preventDefault();
return;
}
break;
}
if (typeof opt.$selected !== 'undefined' && opt.$selected !== null) {
opt.$selected.trigger('mouseup');
}
return;
case 32: // space
case 33: // page up
case 34: // page down
// prevent browser from scrolling down while menu is visible
handle.keyStop(e, opt);
return;
case 27: // esc
handle.keyStop(e, opt);
if (opt.$menu !== null && typeof opt.$menu !== 'undefined') {
opt.$menu.trigger('contextmenu:hide');
}
return;
default: // 0-9, a-z
var k = (String.fromCharCode(e.keyCode)).toUpperCase();
if (opt.accesskeys && opt.accesskeys[k]) {
// according to the specs accesskeys must be invoked immediately
opt.accesskeys[k].$node.trigger(opt.accesskeys[k].$menu ? 'contextmenu:focus' : 'mouseup');
return;
}
break;
}
// pass event to selected item,
// stop propagation to avoid endless recursion
e.stopPropagation();
if (typeof opt.$selected !== 'undefined' && opt.$selected !== null) {
opt.$selected.trigger(e);
}
},
// select previous possible command in menu
prevItem: function (e) {
e.stopPropagation();
var opt = $(this).data('contextMenu') || {};
var root = $(this).data('contextMenuRoot') || {};
// obtain currently selected menu
if (opt.$selected) {
var $s = opt.$selected;
opt = opt.$selected.parent().data('contextMenu') || {};
opt.$selected = $s;
}
var $children = opt.$menu.children(),
$prev = !opt.$selected || !opt.$selected.prev().length ? $children.last() : opt.$selected.prev(),
$round = $prev;
// skip disabled or hidden elements
while ($prev.hasClass(root.classNames.disabled) || $prev.hasClass(root.classNames.notSelectable) || $prev.is(':hidden')) {
if ($prev.prev().length) {
$prev = $prev.prev();
} else {
$prev = $children.last();
}
if ($prev.is($round)) {
// break endless loop
return;
}
}
// leave current
if (opt.$selected) {
handle.itemMouseleave.call(opt.$selected.get(0), e);
}
// activate next
handle.itemMouseenter.call($prev.get(0), e);
// focus input
var $input = $prev.find('input, textarea, select');
if ($input.length) {
$input.focus();
}
},
// select next possible command in menu
nextItem: function (e) {
e.stopPropagation();
var opt = $(this).data('contextMenu') || {};
var root = $(this).data('contextMenuRoot') || {};
// obtain currently selected menu
if (opt.$selected) {
var $s = opt.$selected;
opt = opt.$selected.parent().data('contextMenu') || {};
opt.$selected = $s;
}
var $children = opt.$menu.children(),
$next = !opt.$selected || !opt.$selected.next().length ? $children.first() : opt.$selected.next(),
$round = $next;
// skip disabled
while ($next.hasClass(root.classNames.disabled) || $next.hasClass(root.classNames.notSelectable) || $next.is(':hidden')) {
if ($next.next().length) {
$next = $next.next();
} else {
$next = $children.first();
}
if ($next.is($round)) {
// break endless loop
return;
}
}
// leave current
if (opt.$selected) {
handle.itemMouseleave.call(opt.$selected.get(0), e);
}
// activate next
handle.itemMouseenter.call($next.get(0), e);
// focus input
var $input = $next.find('input, textarea, select');
if ($input.length) {
$input.focus();
}
},
// flag that we're inside an input so the key handler can act accordingly
focusInput: function () {
var $this = $(this).closest('.context-menu-item'),
data = $this.data(),
opt = data.contextMenu,
root = data.contextMenuRoot;
root.$selected = opt.$selected = $this;
root.isInput = opt.isInput = true;
},
// flag that we're inside an input so the key handler can act accordingly
blurInput: function () {
var $this = $(this).closest('.context-menu-item'),
data = $this.data(),
opt = data.contextMenu,
root = data.contextMenuRoot;
root.isInput = opt.isInput = false;
},
// :hover on menu
menuMouseenter: function () {
var root = $(this).data().contextMenuRoot;
root.hovering = true;
},
// :hover on menu
menuMouseleave: function (e) {
var root = $(this).data().contextMenuRoot;
if (root.$layer && root.$layer.is(e.relatedTarget)) {
root.hovering = false;
}
},
// :hover done manually so key handling is possible
itemMouseenter: function (e) {
var $this = $(this),
data = $this.data(),
opt = data.contextMenu,
root = data.contextMenuRoot;
// prevent fast hover on mobile tap-through
if (isInteractionTooFast($this)) {
return;
}
root.hovering = true;
// abort if we're re-entering
if (e && root.$layer && root.$layer.is(e.relatedTarget)) {
e.preventDefault();
e.stopImmediatePropagation();
}
// make sure only one item is selected
(opt.$menu ? opt : root).$menu
.children('.' + root.classNames.hover).trigger('contextmenu:blur')
.children('.hover').trigger('contextmenu:blur');
if ($this.hasClass(root.classNames.disabled) || $this.hasClass(root.classNames.notSelectable)) {
opt.$selected = null;
return;
}
$this.trigger('contextmenu:focus');
},
// :hover done manually so key handling is possible
itemMouseleave: function (e) {
var $this = $(this),
data = $this.data(),
opt = data.contextMenu,
root = data.contextMenuRoot;
if (root !== opt && root.$layer && root.$layer.is(e.relatedTarget)) {
if (typeof root.$selected !== 'undefined' && root.$selected !== null) {
root.$selected.trigger('contextmenu:blur');
}
e.preventDefault();
e.stopImmediatePropagation();
root.$selected = opt.$selected = opt.$node;
return;
}
if(opt && opt.$menu && opt.$menu.hasClass('context-menu-visible')){
return;
}
$this.trigger('contextmenu:blur');
},
// contextMenu item click
itemClick: function (e) {
var $this = $(this),
data = $this.data(),
opt = data.contextMenu,
root = data.contextMenuRoot,
key = data.contextMenuKey,
callback;
// prevent fast click-through on mobile taps
if (isInteractionTooFast($this)) {
e.preventDefault();
e.stopImmediatePropagation();
return;
}
// abort if the key is unknown or disabled or is a menu
// explicitly handle non-selectable submenu clicks first to stop propagation
if ($this.is('.context-menu-submenu') && root.selectableSubMenu === false) {
e.preventDefault();
e.stopImmediatePropagation(); // Stop event here for non-selectable submenus
return;
}
// original check for other non-clickable/disabled items
if (!opt.items[key] || $this.is('.' + root.classNames.disabled + ', .context-menu-separator, .' + root.classNames.notSelectable)) {
return;
}
// if it wasn't a non-selectable submenu or other disabled item, prevent default and stop propagation before callback
e.preventDefault();
e.stopImmediatePropagation();
if ($.isFunction(opt.callbacks[key]) && Object.prototype.hasOwnProperty.call(opt.callbacks, key)) {
// item-specific callback
callback = opt.callbacks[key];
} else if ($.isFunction(root.callback)) {
// default callback
callback = root.callback;
} else {
// no callback, no action
return;
}
// hide menu if callback doesn't stop that
if (callback.call(root.$trigger, key, root, e) !== false) {
root.$menu.trigger('contextmenu:hide');
} else if (root.$menu.parent().length) {
op.update.call(root.$trigger, root);
}
},
// ignore click events on input elements
inputClick: function (e) {
e.stopImmediatePropagation();
},
// hide <menu>
hideMenu: function (e, data) {
var root = $(this).data('contextMenuRoot');
op.hide.call(root.$trigger, root, data && data.force);
},
// focus <command>
focusItem: function (e) {
e.stopPropagation();
var $this = $(this),
data = $this.data(),
opt = data.contextMenu,
root = data.contextMenuRoot;
if ($this.hasClass(root.classNames.disabled) || $this.hasClass(root.classNames.notSelectable)) {
return;
}
$this
.addClass([root.classNames.hover, root.classNames.visible].join(' '))
// select other items and included items
.parent().find('.context-menu-item').not($this)
.removeClass(root.classNames.visible)
.filter('.' + root.classNames.hover)
.trigger('contextmenu:blur');
// remember selected
opt.$selected = root.$selected = $this;
if(opt && opt.$node && opt.$node.hasClass('context-menu-submenu')){
opt.$node.addClass(root.classNames.hover);
}
// position sub-menu - do after show so dumb $.ui.position can keep up
if (opt.$node) {
root.positionSubmenu.call(opt.$node, opt.$menu);
if (opt.$menu) {
var focusShowTimestamp = Date.now();
opt.$menu.data('_showTimestamp', focusShowTimestamp);
}
}
},
// blur <command>
blurItem: function (e) {
e.stopPropagation();
var $this = $(this),
data = $this.data(),
opt = data.contextMenu,
root = data.contextMenuRoot;
if (opt.autoHide) { // for tablets and touch screens this needs to remain
$this.removeClass(root.classNames.visible);
}
$this.removeClass(root.classNames.hover);
opt.$selected = null;
}
},
// operations
op = {
show: function (opt, x, y) {
var $trigger = $(this),
css = {};
// hide any open menus
if ($('#context-menu-layer').length > 0)
$('#context-menu-layer').trigger('mousedown');
else
$(document).trigger('contextmenu:hide');
// backreference for callbacks
opt.$trigger = $trigger;
// show event
if (opt.events.show.call($trigger, opt) === false) {
$currentTrigger = null;
return;
}
// create or update context menu
var hasVisibleItems = op.update.call($trigger, opt);
if (hasVisibleItems === false) {
$currentTrigger = null;
return;
}
// position menu
opt.position.call($trigger, opt, x, y);
// make sure we're in front
if (opt.zIndex) {
var additionalZValue = opt.zIndex;
// If opt.zIndex is a function, call the function to get the right zIndex.
if (typeof opt.zIndex === 'function') {
additionalZValue = opt.zIndex.call($trigger, opt);
}
css.zIndex = zindex($trigger) + additionalZValue;
}
// add layer
op.layer.call(opt.$menu, opt, css.zIndex);
// adjust sub-menu zIndexes
opt.$menu.find('ul').css('zIndex', css.zIndex + 1);
// position and show context menu
opt.$menu.css(css)[opt.animation.show](opt.animation.duration, function () {
$trigger.trigger('contextmenu:visible');
var rootShowTimestamp = Date.now();
opt.$menu.data('_showTimestamp', rootShowTimestamp);
op.activated(opt);
opt.events.activated(opt);
});
// make options available and set state
$trigger
.data('contextMenu', opt)
.addClass('context-menu-active');
// register key handler
$(document).off('keydown.contextMenu').on('keydown.contextMenu', handle.key);
// register autoHide handler
if (opt.autoHide) {
// mouse position handler
$(document).on('mousemove.contextMenuAutoHide', function (e) {
// need to capture the offset on mousemove,
// since the page might've been scrolled since activation
var pos = $trigger.offset();
pos.right = pos.left + $trigger.outerWidth();
pos.bottom = pos.top + $trigger.outerHeight();
if (opt.$layer && !opt.hovering && (!(e.pageX >= pos.left && e.pageX <= pos.right) || !(e.pageY >= pos.top && e.pageY <= pos.bottom))) {
/* Additional hover check after short time, you might just miss the edge of the menu */
setTimeout(function () {
if (!opt.hovering && opt.$menu !== null && typeof opt.$menu !== 'undefined') {
opt.$menu.trigger('contextmenu:hide');
}
}, 50);
}
});
}
},
hide: function (opt, force) {
var $trigger = $(this);
if (!opt) {
opt = $trigger.data('contextMenu') || {};
}
// hide event
if (!force && opt.events && opt.events.hide.call($trigger, opt) === false) {
return;
}
// remove options and revert state
$trigger
.removeData('contextMenu')
.removeClass('context-menu-active');
if (opt.$layer) {
// keep layer for a bit so the contextmenu event can be aborted properly by opera
setTimeout((function ($layer) {
return function () {
$layer.remove();
};
})(opt.$layer), 10);
try {
delete opt.$layer;
} catch (e) {
opt.$layer = null;
}
}
// remove handle
$currentTrigger = null;
// remove selected
opt.$menu.find('.' + opt.classNames.hover).trigger('contextmenu:blur');
opt.$selected = null;
// collapse all submenus
opt.$menu.find('.' + opt.classNames.visible).removeClass(opt.classNames.visible);
// unregister key and mouse handlers
// $(document).off('.contextMenuAutoHide keydown.contextMenu'); // http://bugs.jquery.com/ticket/10705
$(document).off('.contextMenuAutoHide').off('keydown.contextMenu');
// hide menu
if (opt.$menu) {
opt.$menu[opt.animation.hide](opt.animation.duration, function () {
// tear down dynamically built menu after animation is completed.
if (opt.build) {
opt.$menu.remove();
$.each(opt, function (key) {
switch (key) {
case 'ns':
case 'selector':
case 'build':
case 'trigger':
return true;
default:
opt[key] = undefined;
try {
delete opt[key];
} catch (e) {
}
return true;
}
});
}
setTimeout(function () {
$trigger.trigger('contextmenu:hidden');
}, 10);
});
}
},
create: function (opt, root) {
if (typeof root === 'undefined') {
root = opt;
}
// define handler for fast input clicks
var handleFastInputClick = function(e) {
var $inputClicked = $(this);
if (isInteractionTooFast($inputClicked)) {
e.preventDefault();
e.stopImmediatePropagation();
return false;
}
};
// create contextMenu
opt.$menu = $('<ul class="context-menu-list"></ul>').addClass(opt.className || '').data({
'contextMenu': opt,
'contextMenuRoot': root
});
if(opt.dataAttr){
$.each(opt.dataAttr, function (key, item) {
opt.$menu.attr('data-' + opt.key, item);
});
}
$.each(['callbacks', 'commands', 'inputs'], function (i, k) {
opt[k] = {};
if (!root[k]) {
root[k] = {};
}
});
if (!root.accesskeys) {
root.accesskeys = {};
}
function createNameNode(item) {
var $name = $('<span></span>');
if (item._accesskey) {
if (item._beforeAccesskey) {
$name.append(document.createTextNode(item._beforeAccesskey));
}
$('<span></span>')
.addClass('context-menu-accesskey')
.text(item._accesskey)
.appendTo($name);
if (item._afterAccesskey) {
$name.append(document.createTextNode(item._afterAccesskey));
}
} else {
if (item.isHtmlName) {
// restrict use with access keys
if (typeof item.accesskey !== 'undefined') {
throw new Error('accesskeys are not compatible with HTML names and cannot be used together in the same item');
}
$name.html(item.name);
} else {
$name.text(item.name);
}
}
return $name;
}
// create contextMenu items
$.each(opt.items, function (key, item) {
var $t = $('<li class="context-menu-item"></li>').addClass(item.className || ''),
$label = null,
$input = null;
// iOS needs to see a click-event bound to an element to actually
// have the TouchEvents infrastructure trigger the click event
$t.on('click', $.noop);
// Make old school string seperator a real item so checks wont be
// akward later.
// And normalize 'cm_separator' into 'cm_seperator'.
if (typeof item === 'string' || item.type === 'cm_separator') {
item = {type: 'cm_seperator'};
}
item.$node = $t.data({
'contextMenu': opt,
'contextMenuRoot': root,
'contextMenuKey': key
});
// register accesskey
// NOTE: the accesskey attribute should be applicable to any element, but Safari5 and Chrome13 still can't do that
if (typeof item.accesskey !== 'undefined') {
var aks = splitAccesskey(item.accesskey);
for (var i = 0, ak; ak = aks[i]; i++) {
if (!root.accesskeys[ak]) {
root.accesskeys[ak] = item;
var matched = item.name.match(new RegExp('^(.*?)(' + ak + ')(.*)$', 'i'));
if (matched) {
item._beforeAccesskey = matched[1];
item._accesskey = matched[2];
item._afterAccesskey = matched[3];
}
break;
}
}
}
if (item.type && types[item.type]) {
// run custom type handler
types[item.type].call($t, item, opt, root);
// register commands
$.each([opt, root], function (i, k) {
k.commands[key] = item;
// Overwrite only if undefined or the item is appended to the root. This so it
// doesn't overwrite callbacks of root elements if the name is the same.
if ($.isFunction(item.callback) && (typeof k.callbacks[key] === 'undefined' || typeof opt.type === 'undefined')) {
k.callbacks[key] = item.callback;
}
});
} else {
// add label for input
if (item.type === 'cm_seperator') {
$t.addClass('context-menu-separator ' + root.classNames.notSelectable);
} else if (item.type === 'html') {
$t.addClass('context-menu-html ' + root.classNames.notSelectable);
} else if (item.type !== 'sub' && item.type) {
$label = $('<label></label>').appendTo($t);
createNameNode(item).appendTo($label);
$t.addClass('context-menu-input');
opt.hasTypes = true;
$.each([opt, root], function (i, k) {
k.commands[key] = item;
k.inputs[key] = item;
});
} else if (item.items) {
item.type = 'sub';
}
switch (item.type) {
case 'cm_seperator':
break;
case 'text':
$input = $('<input type="text" value="1" name="" />')
.attr('name', 'context-menu-input-' + key)
.val(item.value || '')
.appendTo($label);
break;
case 'textarea':
$input = $('<textarea name=""></textarea>')
.attr('name', 'context-menu-input-' + key)
.val(item.value || '')
.appendTo($label);
if (item.height) {
$input.height(item.height);
}
break;
case 'checkbox':
$input = $('<input type="checkbox" value="1" name="" />')
.attr('name', 'context-menu-input-' + key)
.val(item.value || '')
.prop('checked', !!item.selected)
.prependTo($label);
// prevent checkbox default action on fast click-through
$input.on('click', handleFastInputClick);
break;
case 'radio':
$input = $('<input type="radio" value="1" name="" />')
.attr('name', 'context-menu-input-' + item.radio)
.val(item.value || '')
.prop('checked', !!item.selected)
.prependTo($label);
// prevent radio default action on fast click-through
$input.on('click', handleFastInputClick);
break;
case 'select':
$input = $('<select name=""></select>')
.attr('name', 'context-menu-input-' + key)
.appendTo($label);
if (item.options) {
$.each(item.options, function (value, text) {
$('<option></option>').val(value).text(text).appendTo($input);
});
$input.val(item.selected);
}
break;
case 'sub':
createNameNode(item).appendTo($t);
item.appendTo = item.$node;
$t.data('contextMenu', item).addClass('context-menu-submenu');
item.callback = null;
// If item contains items, and this is a promise, we should create it later
// check if subitems is of type promise. If it is a promise we need to create
// it later, after promise has been resolved.
if ('function' === typeof item.items.then) {
// probably a promise, process it, when completed it will create the sub menu's.
op.processPromises(item, root, item.items);
} else {
// normal submenu.
op.create(item, root);
}
break;
case 'html':
$(item.html).appendTo($t);
break;
default:
$.each([opt, root], function (i, k) {
k.commands[key] = item;
// Overwrite only if undefined or the item is appended to the root. This so it
// doesn't overwrite callbacks of root elements if the name is the same.
if ($.isFunction(item.callback) && (typeof k.callbacks[key] === 'undefined' || typeof opt.type === 'undefined')) {
k.callbacks[key] = item.callback;
}
});
createNameNode(item).appendTo($t);
break;
}
// disable key listener in <input>
if (item.type && item.type !== 'sub' && item.type !== 'html' && item.type !== 'cm_seperator') {
$input
.on('focus', handle.focusInput)
.on('blur', handle.blurInput);
if (item.events) {
$input.on(item.events, opt);
}
}
// add icons
if (item.icon) {
if ($.isFunction(item.icon)) {
item._icon = item.icon.call(this, this, $t, key, item);
} else {
if (typeof(item.icon) === 'string' && (
item.icon.substring(0, 4) === 'fab '
|| item.icon.substring(0, 4) === 'fas '
|| item.icon.substring(0, 4) === 'fad '
|| item.icon.substring(0, 4) === 'far '
|| item.icon.substring(0, 4) === 'fal ')
) {
// to enable font awesome
$t.addClass(root.classNames.icon + ' ' + root.classNames.icon + '--fa5');
item._icon = $('<i class="' + item.icon + '"></i>');
} else if (typeof(item.icon) === 'string' && item.icon.substring(0, 3) === 'fa-') {
item._icon = root.classNames.icon + ' ' + root.classNames.icon + '--fa fa ' + item.icon;
} else {
item._icon = root.classNames.icon + ' ' + root.classNames.icon + '-' + item.icon;
}
}
if(typeof(item._icon) === "string"){
$t.addClass(item._icon);
} else {
$t.prepend(item._icon);
}
}
}
// cache contained elements
item.$input = $input;
item.$label = $label;
// attach item to menu
$t.appendTo(opt.$menu);
// Disable text selection
if (!opt.hasTypes && $.support.eventSelectstart) {
// browsers support user-select: none,
// IE has a special event for text-selection
// browsers supporting neither will not be preventing text-selection
$t.on('selectstart.disableTextSelect', handle.abortevent);
}
});
// attach contextMenu to <body> (to bypass any possible overflow:hidden issues on parents of the trigger element)
if (!opt.$node) {
opt.$menu.css('display', 'none').addClass('context-menu-root');
}
opt.$menu.appendTo(opt.appendTo || document.body);
},
resize: function ($menu, nested) {
var domMenu;
// determine widths of submenus, as CSS won't grow them automatically
// position:absolute within position:absolute; min-width:100; max-width:200; results in width: 100;
// kinda sucks hard...
// determine width of absolutely positioned element
$menu.css({position: 'absolute', display: 'block'});
// don't apply yet, because that would break nested elements' widths
$menu.data('width',
(domMenu = $menu.get(0)).getBoundingClientRect ?
Math.ceil(domMenu.getBoundingClientRect().width) :
$menu.outerWidth() + 1); // outerWidth() returns rounded pixels
// reset styles so they allow nested elements to grow/shrink naturally
$menu.css({
position: 'static',
minWidth: '0px',
maxWidth: '100000px'
});
// identify width of nested menus
$menu.find('> li > ul').each(function () {
op.resize($(this), true);
});
// reset and apply changes in the end because nested
// elements' widths wouldn't be calculatable otherwise
if (!nested) {
$menu.find('ul').addBack().css({
position: '',
display: '',
minWidth: '',
maxWidth: ''
}).outerWidth(function () {
return $(this).data('width');
});
}
},
update: function (opt, root) {
var $trigger = this;
if (typeof root === 'undefined') {
root = opt;
op.resize(opt.$menu);
}
var hasVisibleItems = false;
// re-check disabled for each item
opt.$menu.children().each(function () {
var $item = $(this),
key = $item.data('contextMenuKey'),
item = opt.items[key],
disabled = ($.isFunction(item.disabled) && item.disabled.call($trigger, key, root)) || item.disabled === true,
visible;
if ($.isFunction(item.visible)) {
visible = item.visible.call($trigger, key, root);
} else if (typeof item.visible !== 'undefined') {
visible = item.visible === true;
} else {
visible = true;
}
if (visible) {
hasVisibleItems = true;
}
$item[visible ? 'show' : 'hide']();
// dis- / enable item
$item[disabled ? 'addClass' : 'removeClass'](root.classNames.disabled);
if ($.isFunction(item.icon)) {
$item.removeClass(item._icon);
var iconResult = item.icon.call(this, $trigger, $item, key, item);
if(typeof(iconResult) === "string"){
$item.addClass(iconResult);
} else {
$item.prepend(iconResult);
}
}
if (item.type) {
// dis- / enable input elements
$item.find('input, select, textarea').prop('disabled', disabled);
// update input states
switch (item.type) {
case 'text':
case 'textarea':
item.$input.val(item.value || '');
break;
case 'checkbox':
case 'radio':
item.$input.val(item.value || '').prop('checked', !!item.selected);
break;
case 'select':
item.$input.val((item.selected === 0 ? "0" : item.selected) || '');
break;
}
}
if (item.$menu) {
// update sub-menu
var subMenuHasVisibleItems = op.update.call($trigger, item, root);
if (subMenuHasVisibleItems) {
hasVisibleItems = true;
}
}
});
return hasVisibleItems;
},
layer: function (opt, zIndex) {
if (!opt.useModal) {
var listener = function (ev) {
handle.layerClick(ev, opt, function() {
document.removeEventListener('mousedown', listener, true);
});
};
document.addEventListener('mousedown', listener, true);
return;
}
// add transparent layer for click area
// filter and background for Internet Explorer, Issue #23
var $layer = opt.$layer = $('<div id="context-menu-layer"></div>')
.css({
height: $win.height(),
width: $win.width(),
display: 'block',
position: 'fixed',
'z-index': zIndex - 1,
top: 0,
left: 0,
opacity: 0,
filter: 'alpha(opacity=0)',
'background-color': '#000'
})
.data('contextMenuRoot', opt)
.appendTo(document.body)
.on('contextmenu', handle.abortevent)
.on('mousedown', handle.layerClick);
// IE6 doesn't know position:fixed;
if (typeof document.body.style.maxWidth === 'undefined') { // IE6 doesn't support maxWidth
$layer.css({
'position': 'absolute',
'height': $(document).height()
});
}
return $layer;
},
processPromises: function (opt, root, promise) {
// Start
opt.$node.addClass(root.classNames.iconLoadingClass);
function completedPromise(opt, root, items) {
// Completed promise (dev called promise.resolve). We now have a list of items which can
// be used to create the rest of the context menu.
if (typeof items === 'undefined') {
// Null result, dev should have checked
errorPromise(undefined);//own error object
}
finishPromiseProcess(opt, root, items);
}
function errorPromise(opt, root, errorItem) {
// User called promise.reject() with an error item, if not, provide own error item.
if (typeof errorItem === 'undefined') {
errorItem = {
"error": {
name: "No items and no error item",
icon: "context-menu-icon context-menu-icon-quit"
}
};
if (window.console) {
(console.error || console.log).call(console, 'When you reject a promise, provide an "items" object, equal to normal sub-menu items');
}
} else if (typeof errorItem === 'string') {
errorItem = {"error": {name: errorItem}};
}
finishPromiseProcess(opt, root, errorItem);
}
function finishPromiseProcess(opt, root, items) {
if (typeof root.$menu === 'undefined' || !root.$menu.is(':visible')) {
return;
}
opt.$node.removeClass(root.classNames.iconLoadingClass);
opt.items = items;
op.create(opt, root, true); // Create submenu
op.update(opt, root); // Correctly update position if user is already hovered over menu item
root.positionSubmenu.call(opt.$node, opt.$menu); // positionSubmenu, will only do anything if user already hovered over menu item that just got new subitems.
}
// Wait for promise completion. .then(success, error, notify) (we don't track notify). Bind the opt
// and root to avoid scope problems
promise.then(completedPromise.bind(this, opt, root), errorPromise.bind(this, opt, root));
},
// operation that will run after contextMenu showed on screen
activated: function(opt){
var $menu = opt.$menu;
var $menuOffset = $menu.offset();
var winHeight = $(window).height();
var winWidth = $(window).width();
var winScrollTop = $(window).scrollTop();
var winScrollLeft = $(window).scrollLeft();
var menuHeight = $menu.height();
var outerHeight = $menu.outerHeight();
var outerWidth = $menu.outerWidth();
if(menuHeight > winHeight){
$menu.css({
'height' : winHeight + 'px',
'overflow-x': 'hidden',
'overflow-y': 'auto',
'top': winScrollTop + 'px'
});
} else if($menuOffset.top < winScrollTop){
$menu.css({
'top': winScrollTop + 'px'
});
} else if($menuOffset.top + outerHeight > winScrollTop + winHeight){
$menu.css({
'top': $menuOffset.top - (($menuOffset.top + outerHeight) - (winScrollTop + winHeight)) + "px"
});
}
if($menuOffset.left + outerWidth > winScrollLeft + winWidth){
$menu.css({
'left': $menuOffset.left - (($menuOffset.left + outerWidth) - (winScrollLeft + winWidth)) + "px"
});
}
}
};
// split accesskey according to http://www.whatwg.org/specs/web-apps/current-work/multipage/editing.html#assigned-access-key
function splitAccesskey(val) {
var t = val.split(/\s+/);
var keys = [];
for (var i = 0, k; k = t[i]; i++) {
k = k.charAt(0).toUpperCase(); // first character only
// theoretically non-accessible characters should be ignored, but different systems, different keyboard layouts, ... screw it.
// a map to look up already used access keys would be nice
keys.push(k);
}
return keys;
}
// handle contextMenu triggers
$.fn.contextMenu = function (operation) {
var $t = this, $o = operation;
if (this.length > 0) { // this is not a build on demand menu
if (typeof operation === 'undefined') {
this.first().trigger('contextmenu');
} else if (typeof operation.x !== 'undefined' && typeof operation.y !== 'undefined') {
this.first().trigger($.Event('contextmenu', {
pageX: operation.x,
pageY: operation.y,
mouseButton: operation.button
}));
} else if (operation === 'hide') {
var $menu = this.first().data('contextMenu') ? this.first().data('contextMenu').$menu : null;
if ($menu) {
$menu.trigger('contextmenu:hide');
}
} else if (operation === 'destroy') {
$.contextMenu('destroy', {context: this});
} else if ($.isPlainObject(operation)) {
operation.context = this;
$.contextMenu('create', operation);
} else if (operation) {
this.removeClass('context-menu-disabled');
} else if (!operation) {
this.addClass('context-menu-disabled');
}
} else {
$.each(menus, function () {
if (this.selector === $t.selector) {
$o.data = this;
$.extend($o.data, {trigger: 'demand'});
}
});
handle.contextmenu.call($o.target, $o);
}
return this;
};
// manage contextMenu instances
$.contextMenu = function (operation, options) {
if (typeof operation !== 'string') {
options = operation;
operation = 'create';
}
if (typeof options === 'string') {
options = {selector: options};
} else if (typeof options === 'undefined') {
options = {};
}
// merge with default options
var o = $.extend(true, {}, defaults, options || {});
var $document = $(document);
var $context = $document;
var _hasContext = false;
if (!o.context || !o.context.length) {
o.context = document;
} else {
// you never know what they throw at you...
$context = $(o.context).first();
o.context = $context.get(0);
_hasContext = !$(o.context).is(document);
}
switch (operation) {
case 'update':
// Updates visibility and such
if(_hasContext){
op.update($context);
} else {
for(var menu in menus){
if(menus.hasOwnProperty(menu)){
op.update(menus[menu]);
}
}
}
break;
case 'create':
// no selector no joy
if (!o.selector) {
throw new Error('No selector specified');
}
// make sure internal classes are not bound to
if (o.selector.match(/.context-menu-(list|item|input)($|\s)/)) {
throw new Error('Cannot bind to selector "' + o.selector + '" as it contains a reserved className');
}
if (!o.build && (!o.items || $.isEmptyObject(o.items))) {
throw new Error('No Items specified');
}
counter++;
o.ns = '.contextMenu' + counter;
if (!_hasContext) {
namespaces[o.selector] = o.ns;
}
menus[o.ns] = o;
// default to right click
if (!o.trigger) {
o.trigger = 'right';
}
if (!initialized) {
var itemClick = o.itemClickEvent === 'click' ? 'click.contextMenu' : 'mouseup.contextMenu';
var contextMenuItemObj = {
// 'mouseup.contextMenu': handle.itemClick,
// 'click.contextMenu': handle.itemClick,
'contextmenu:focus.contextMenu': handle.focusItem,
'contextmenu:blur.contextMenu': handle.blurItem,
'contextmenu.contextMenu': handle.abortevent,
'mouseenter.contextMenu': handle.itemMouseenter,
'mouseleave.contextMenu': handle.itemMouseleave
};
contextMenuItemObj[itemClick] = handle.itemClick;
// make sure item click is registered first
$document
.on({
'contextmenu:hide.contextMenu': handle.hideMenu,
'prevcommand.contextMenu': handle.prevItem,
'nextcommand.contextMenu': handle.nextItem,
'contextmenu.contextMenu': handle.abortevent,
'mouseenter.contextMenu': handle.menuMouseenter,
'mouseleave.contextMenu': handle.menuMouseleave
}, '.context-menu-list')
.on('mouseup.contextMenu', '.context-menu-input', handle.inputClick)
.on(contextMenuItemObj, '.context-menu-item');
initialized = true;
}
// engage native contextmenu event
$context
.on('contextmenu' + o.ns, o.selector, o, handle.contextmenu);
if (_hasContext) {
// add remove hook, just in case
$context.on('remove' + o.ns, function () {
$(this).contextMenu('destroy');
});
}
switch (o.trigger) {
case 'hover':
$context
.on('mouseenter' + o.ns, o.selector, o, handle.mouseenter)
.on('mouseleave' + o.ns, o.selector, o, handle.mouseleave);
break;
case 'left':
$context.on('click' + o.ns, o.selector, o, handle.click);
break;
case 'touchstart':
$context.on('touchstart' + o.ns, o.selector, o, handle.click);
break;
/*
default:
// http://www.quirksmode.org/dom/events/contextmenu.html
$document
.on('mousedown' + o.ns, o.selector, o, handle.mousedown)
.on('mouseup' + o.ns, o.selector, o, handle.mouseup);
break;
*/
}
// create menu
if (!o.build) {
op.create(o);
}
break;
case 'destroy':
var $visibleMenu;
if (_hasContext) {
// get proper options
var context = o.context;
$.each(menus, function (ns, o) {
if (!o) {
return true;
}
// Is this menu equest to the context called from
if (!$(context).is(o.selector)) {
return true;
}
$visibleMenu = $('.context-menu-list').filter(':visible');
if ($visibleMenu.length && $visibleMenu.data().contextMenuRoot.$trigger.is($(o.context).find(o.selector))) {
$visibleMenu.trigger('contextmenu:hide', {force: true});
}
try {
if (menus[o.ns].$menu) {
menus[o.ns].$menu.remove();
}
delete menus[o.ns];
} catch (e) {
menus[o.ns] = null;
}
$(o.context).off(o.ns);
return true;
});
} else if (!o.selector) {
$document.off('.contextMenu .contextMenuAutoHide');
$.each(menus, function (ns, o) {
$(o.context).off(o.ns);
});
namespaces = {};
menus = {};
counter = 0;
initialized = false;
$('#context-menu-layer, .context-menu-list').remove();
} else if (namespaces[o.selector]) {
$visibleMenu = $('.context-menu-list').filter(':visible');
if ($visibleMenu.length && $visibleMenu.data().contextMenuRoot.$trigger.is(o.selector)) {
$visibleMenu.trigger('contextmenu:hide', {force: true});
}
try {
if (menus[namespaces[o.selector]].$menu) {
menus[namespaces[o.selector]].$menu.remove();
}
delete menus[namespaces[o.selector]];
} catch (e) {
menus[namespaces[o.selector]] = null;
}
$document.off(namespaces[o.selector]);
}
break;
case 'html5':
// if <command> and <menuitem> are not handled by the browser,
// or options was a bool true,
// initialize $.contextMenu for them
if ((!$.support.htmlCommand && !$.support.htmlMenuitem) || (typeof options === 'boolean' && options)) {
$('menu[type="context"]').each(function () {
if (this.id) {
$.contextMenu({
selector: '[contextmenu=' + this.id + ']',
items: $.contextMenu.fromMenu(this)
});
}
}).css('display', 'none');
}
break;
default:
throw new Error('Unknown operation "' + operation + '"');
}
return this;
};
// import values into <input> commands
$.contextMenu.setInputValues = function (opt, data) {
if (typeof data === 'undefined') {
data = {};
}
$.each(opt.inputs, function (key, item) {
switch (item.type) {
case 'text':
case 'textarea':
item.value = data[key] || '';
break;
case 'checkbox':
item.selected = data[key] ? true : false;
break;
case 'radio':
item.selected = (data[item.radio] || '') === item.value;
break;
case 'select':
item.selected = data[key] || '';
break;
}
});
};
// export values from <input> commands
$.contextMenu.getInputValues = function (opt, data) {
if (typeof data === 'undefined') {
data = {};
}
$.each(opt.inputs, function (key, item) {
switch (item.type) {
case 'text':
case 'textarea':
case 'select':
data[key] = item.$input.val();
break;
case 'checkbox':
data[key] = item.$input.prop('checked');
break;
case 'radio':
if (item.$input.prop('checked')) {
data[item.radio] = item.value;
}
break;
}
});
return data;
};
// find <label for="xyz">
function inputLabel(node) {
return (node.id && $('label[for="' + node.id + '"]').val()) || node.name;
}
// convert <menu> to items object
function menuChildren(items, $children, counter) {
if (!counter) {
counter = 0;
}
$children.each(function () {
var $node = $(this),
node = this,
nodeName = this.nodeName.toLowerCase(),
label,
item;
// extract <label><input>
if (nodeName === 'label' && $node.find('input, textarea, select').length) {
label = $node.text();
$node = $node.children().first();
node = $node.get(0);
nodeName = node.nodeName.toLowerCase();
}
/*
* <menu> accepts flow-content as children. that means <embed>, <canvas> and such are valid menu items.
* Not being the sadistic kind, $.contextMenu only accepts:
* <command>, <menuitem>, <hr>, <span>, <p> <input [text, radio, checkbox]>, <textarea>, <select> and of course <menu>.
* Everything else will be imported as an html node, which is not interfaced with contextMenu.
*/
// http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#concept-command
switch (nodeName) {
// http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#the-menu-element
case 'menu':
item = {name: $node.attr('label'), items: {}};
counter = menuChildren(item.items, $node.children(), counter);
break;
// http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-a-element-to-define-a-command
case 'a':
// http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-button-element-to-define-a-command
case 'button':
item = {
name: $node.text(),
disabled: !!$node.attr('disabled'),
callback: (function () {
return function (itemKey, opt, ev) {
if ($node.get(0).onclick !== null) {
$node.get(0).click();
} else {
opt.callback(itemKey, opt, ev);
}
};
})()
};
break;
// http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-command-element-to-define-a-command
case 'menuitem':
case 'command':
switch ($node.attr('type')) {
case undefined:
case 'command':
case 'menuitem':
item = {
name: $node.attr('label'),
disabled: !!$node.attr('disabled'),
icon: $node.attr('icon'),
callback: (function () {
return function (itemKey, opt, ev) {
if ($node.get(0).onclick !== null) {
$node.get(0).click();
} else {
opt.callback(itemKey, opt, ev);
}
};
})()
};
break;
case 'checkbox':
item = {
type: 'checkbox',
disabled: !!$node.attr('disabled'),
name: $node.attr('label'),
selected: !!$node.attr('checked')
};
break;
case 'radio':
item = {
type: 'radio',
disabled: !!$node.attr('disabled'),
name: $node.attr('label'),
radio: $node.attr('radiogroup'),
value: $node.attr('id'),
selected: !!$node.attr('checked')
};
break;
default:
item = undefined;
}
break;
case 'hr':
item = '-------';
break;
case 'input':
switch ($node.attr('type')) {
case 'text':
item = {
type: 'text',
name: label || inputLabel(node),
disabled: !!$node.attr('disabled'),
value: $node.val()
};
break;
case 'checkbox':
item = {
type: 'checkbox',
name: label || inputLabel(node),
disabled: !!$node.attr('disabled'),
selected: !!$node.attr('checked')
};
break;
case 'radio':
item = {
type: 'radio',
name: label || inputLabel(node),
disabled: !!$node.attr('disabled'),
radio: !!$node.attr('name'),
value: $node.val(),
selected: !!$node.attr('checked')
};
break;
default:
item = undefined;
break;
}
break;
case 'select':
item = {
type: 'select',
name: label || inputLabel(node),
disabled: !!$node.attr('disabled'),
selected: $node.val(),
options: {}
};
$node.children().each(function () {
item.options[this.value] = $(this).text();
});
break;
case 'textarea':
item = {
type: 'textarea',
name: label || inputLabel(node),
disabled: !!$node.attr('disabled'),
value: $node.val()
};
break;
case 'label':
break;
default:
item = {type: 'html', html: $node.clone(true)};
break;
}
if (item) {
counter++;
items['key' + counter] = item;
}
});
return counter;
}
// convert html5 menu
$.contextMenu.fromMenu = function (element) {
var $this = $(element),
items = {};
menuChildren(items, $this.children());
return items;
};
// make defaults accessible
$.contextMenu.defaults = defaults;
$.contextMenu.types = types;
// export internal functions - undocumented, for hacking only!
$.contextMenu.handle = handle;
$.contextMenu.op = op;
$.contextMenu.menus = menus;
});
================================================
FILE: dist/jquery.ui.position.js
================================================
/*! jQuery UI - v1.12.1 - 2016-09-16
* http://jqueryui.com
* Includes: position.js
* Copyright jQuery Foundation and other contributors; Licensed MIT */
(function( factory ) {
if ( typeof define === "function" && define.amd ) {
// AMD. Register as an anonymous module.
define([ "jquery" ], factory );
} else {
// Browser globals
factory( jQuery );
}
}(function( $ ) {
$.ui = $.ui || {};
var version = $.ui.version = "1.12.1";
/*!
* jQuery UI Position 1.12.1
* http://jqueryui.com
*
* Copyright jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*
* http://api.jqueryui.com/position/
*/
//>>label: Position
//>>group: Core
//>>description: Positions elements relative to other elements.
//>>docs: http://api.jqueryui.com/position/
//>>demos: http://jqueryui.com/position/
( function() {
var cachedScrollbarWidth,
max = Math.max,
abs = Math.abs,
rhorizontal = /left|center|right/,
rvertical = /top|center|bottom/,
roffset = /[\+\-]\d+(\.[\d]+)?%?/,
rposition = /^\w+/,
rpercent = /%$/,
_position = $.fn.position;
function getOffsets( offsets, width, height ) {
return [
parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),
parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )
];
}
function parseCss( element, property ) {
return parseInt( $.css( element, property ), 10 ) || 0;
}
function getDimensions( elem ) {
var raw = elem[ 0 ];
if ( raw.nodeType === 9 ) {
return {
width: elem.width(),
height: elem.height(),
offset: { top: 0, left: 0 }
};
}
if ( $.isWindow( raw ) ) {
return {
width: elem.width(),
height: elem.height(),
offset: { top: elem.scrollTop(), left: elem.scrollLeft() }
};
}
if ( raw.preventDefault ) {
return {
width: 0,
height: 0,
offset: { top: raw.pageY, left: raw.pageX }
};
}
return {
width: elem.outerWidth(),
height: elem.outerHeight(),
offset: elem.offset()
};
}
$.position = {
scrollbarWidth: function() {
if ( cachedScrollbarWidth !== undefined ) {
return cachedScrollbarWidth;
}
var w1, w2,
div = $( "<div " +
"style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'>" +
"<div style='height:100px;width:auto;'></div></div>" ),
innerDiv = div.children()[ 0 ];
$( "body" ).append( div );
w1 = innerDiv.offsetWidth;
div.css( "overflow", "scroll" );
w2 = innerDiv.offsetWidth;
if ( w1 === w2 ) {
w2 = div[ 0 ].clientWidth;
}
div.remove();
return ( cachedScrollbarWidth = w1 - w2 );
},
getScrollInfo: function( within ) {
var overflowX = within.isWindow || within.isDocument ? "" :
within.element.css( "overflow-x" ),
overflowY = within.isWindow || within.isDocument ? "" :
within.element.css( "overflow-y" ),
hasOverflowX = overflowX === "scroll" ||
( overflowX === "auto" && within.width < within.element[ 0 ].scrollWidth ),
hasOverflowY = overflowY === "scroll" ||
( overflowY === "auto" && within.height < within.element[ 0 ].scrollHeight );
return {
width: hasOverflowY ? $.position.scrollbarWidth() : 0,
height: hasOverflowX ? $.position.scrollbarWidth() : 0
};
},
getWithinInfo: function( element ) {
var withinElement = $( element || window ),
isWindow = $.isWindow( withinElement[ 0 ] ),
isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9,
hasOffset = !isWindow && !isDocument;
return {
element: withinElement,
isWindow: isWindow,
isDocument: isDocument,
offset: hasOffset ? $( element ).offset() : { left: 0, top: 0 },
scrollLeft: withinElement.scrollLeft(),
scrollTop: withinElement.scrollTop(),
width: withinElement.outerWidth(),
height: withinElement.outerHeight()
};
}
};
$.fn.position = function( options ) {
if ( !options || !options.of ) {
return _position.apply( this, arguments );
}
// Make a copy, we don't want to modify arguments
options = $.extend( {}, options );
var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions,
target = $( options.of ),
within = $.position.getWithinInfo( options.within ),
scrollInfo = $.position.getScrollInfo( within ),
collision = ( options.collision || "flip" ).split( " " ),
offsets = {};
dimensions = getDimensions( target );
if ( target[ 0 ].preventDefault ) {
// Force left top to allow flipping
options.at = "left top";
}
targetWidth = dimensions.width;
targetHeight = dimensions.height;
targetOffset = dimensions.offset;
// Clone to reuse original targetOffset later
basePosition = $.extend( {}, targetOffset );
// Force my and at to have valid horizontal and vertical positions
// if a value is missing or invalid, it will be converted to center
$.each( [ "my", "at" ], function() {
var pos = ( options[ this ] || "" ).split( " " ),
horizontalOffset,
verticalOffset;
if ( pos.length === 1 ) {
pos = rhorizontal.test( pos[ 0 ] ) ?
pos.concat( [ "center" ] ) :
rvertical.test( pos[ 0 ] ) ?
[ "center" ].concat( pos ) :
[ "center", "center" ];
}
pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center";
pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center";
// Calculate offsets
horizontalOffset = roffset.exec( pos[ 0 ] );
verticalOffset = roffset.exec( pos[ 1 ] );
offsets[ this ] = [
horizontalOffset ? horizontalOffset[ 0 ] : 0,
verticalOffset ? verticalOffset[ 0 ] : 0
];
// Reduce to just the positions without the offsets
options[ this ] = [
rposition.exec( pos[ 0 ] )[ 0 ],
rposition.exec( pos[ 1 ] )[ 0 ]
];
} );
// Normalize collision option
if ( collision.length === 1 ) {
collision[ 1 ] = collision[ 0 ];
}
if ( options.at[ 0 ] === "right" ) {
basePosition.left += targetWidth;
} else if ( options.at[ 0 ] === "center" ) {
basePosition.left += targetWidth / 2;
}
if ( options.at[ 1 ] === "bottom" ) {
basePosition.top += targetHeight;
} else if ( options.at[ 1 ] === "center" ) {
basePosition.top += targetHeight / 2;
}
atOffset = getOffsets( offsets.at, targetWidth, targetHeight );
basePosition.left += atOffset[ 0 ];
basePosition.top += atOffset[ 1 ];
return this.each( function() {
var collisionPosition, using,
elem = $( this ),
elemWidth = elem.outerWidth(),
elemHeight = elem.outerHeight(),
marginLeft = parseCss( this, "marginLeft" ),
marginTop = parseCss( this, "marginTop" ),
collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) +
scrollInfo.width,
collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) +
scrollInfo.height,
position = $.extend( {}, basePosition ),
myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() );
if ( options.my[ 0 ] === "right" ) {
position.left -= elemWidth;
} else if ( options.my[ 0 ] === "center" ) {
position.left -= elemWidth / 2;
}
if ( options.my[ 1 ] === "bottom" ) {
position.top -= elemHeight;
} else if ( options.my[ 1 ] === "center" ) {
position.top -= elemHeight / 2;
}
position.left += myOffset[ 0 ];
position.top += myOffset[ 1 ];
collisionPosition = {
marginLeft: marginLeft,
marginTop: marginTop
};
$.each( [ "left", "top" ], function( i, dir ) {
if ( $.ui.position[ collision[ i ] ] ) {
$.ui.position[ collision[ i ] ][ dir ]( position, {
targetWidth: targetWidth,
targetHeight: targetHeight,
elemWidth: elemWidth,
elemHeight: elemHeight,
collisionPosition: collisionPosition,
collisionWidth: collisionWidth,
collisionHeight: collisionHeight,
offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],
my: options.my,
at: options.at,
within: within,
elem: elem
} );
}
} );
if ( options.using ) {
// Adds feedback as second argument to using callback, if present
using = function( props ) {
var left = targetOffset.left - position.left,
right = left + targetWidth - elemWidth,
top = targetOffset.top - position.top,
bottom = top + targetHeight - elemHeight,
feedback = {
target: {
element: target,
left: targetOffset.left,
top: targetOffset.top,
width: targetWidth,
height: targetHeight
},
element: {
element: elem,
left: position.left,
top: position.top,
width: elemWidth,
height: elemHeight
},
horizontal: right < 0 ? "left" : left > 0 ? "right" : "center",
vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle"
};
if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {
feedback.horizontal = "center";
}
if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {
feedback.vertical = "middle";
}
if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {
feedback.important = "horizontal";
} else {
feedback.important = "vertical";
}
options.using.call( this, props, feedback );
};
}
elem.offset( $.extend( position, { using: using } ) );
} );
};
$.ui.position = {
fit: {
left: function( position, data ) {
var within = data.within,
withinOffset = within.isWindow ? within.scrollLeft : within.offset.left,
outerWidth = within.width,
collisionPosLeft = position.left - data.collisionPosition.marginLeft,
overLeft = withinOffset - collisionPosLeft,
overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,
newOverRight;
// Element is wider than within
if ( data.collisionWidth > outerWidth ) {
// Element is initially over the left side of within
if ( overLeft > 0 && overRight <= 0 ) {
newOverRight = position.left + overLeft + data.collisionWidth - outerWidth -
withinOffset;
position.left += overLeft - newOverRight;
// Element is initially over right side of within
} else if ( overRight > 0 && overLeft <= 0 ) {
position.left = withinOffset;
// Element is initially over both left and right sides of within
} else {
if ( overLeft > overRight ) {
position.left = withinOffset + outerWidth - data.collisionWidth;
} else {
position.left = withinOffset;
}
}
// Too far left -> align with left edge
} else if ( overLeft > 0 ) {
position.left += overLeft;
// Too far right -> align with right edge
} else if ( overRight > 0 ) {
position.left -= overRight;
// Adjust based on position and margin
} else {
position.left = max( position.left - collisionPosLeft, position.left );
}
},
top: function( position, data ) {
var within = data.within,
withinOffset = within.isWindow ? within.scrollTop : within.offset.top,
outerHeight = data.within.height,
collisionPosTop = position.top - data.collisionPosition.marginTop,
overTop = withinOffset - collisionPosTop,
overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,
newOverBottom;
// Element is taller than within
if ( data.collisionHeight > outerHeight ) {
// Element is initially over the top of within
if ( overTop > 0 && overBottom <= 0 ) {
newOverBottom = position.top + overTop + data.collisionHeight - outerHeight -
withinOffset;
position.top += overTop - newOverBottom;
// Element is initially over bottom of within
} else if ( overBottom > 0 && overTop <= 0 ) {
position.top = withinOffset;
// Element is initially over both top and bottom of within
} else {
if ( overTop > overBottom ) {
position.top = withinOffset + outerHeight - data.collisionHeight;
} else {
position.top = withinOffset;
}
}
// Too far up -> align with top
} else if ( overTop > 0 ) {
position.top += overTop;
// Too far down -> align with bottom edge
} else if ( overBottom > 0 ) {
position.top -= overBottom;
// Adjust based on position and margin
} else {
position.top = max( position.top - collisionPosTop, position.top );
}
}
},
flip: {
left: function( position, data ) {
var within = data.within,
withinOffset = within.offset.left + within.scrollLeft,
outerWidth = within.width,
offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left,
collisionPosLeft = position.left - data.collisionPosition.marginLeft,
overLeft = collisionPosLeft - offsetLeft,
overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,
myOffset = data.my[ 0 ] === "left" ?
-data.elemWidth :
data.my[ 0 ] === "right" ?
data.elemWidth :
0,
atOffset = data.at[ 0 ] === "left" ?
data.targetWidth :
data.at[ 0 ] === "right" ?
-data.targetWidth :
0,
offset = -2 * data.offset[ 0 ],
newOverRight,
newOverLeft;
if ( overLeft < 0 ) {
newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth -
outerWidth - withinOffset;
if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) {
position.left += myOffset + atOffset + offset;
}
} else if ( overRight > 0 ) {
newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset +
atOffset + offset - offsetLeft;
if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) {
position.left += myOffset + atOffset + offset;
}
}
},
top: function( position, data ) {
var within = data.within,
withinOffset = within.offset.top + within.scrollTop,
outerHeight = within.height,
offsetTop = within.isWindow ? within.scrollTop : within.offset.top,
collisionPosTop = position.top - data.collisionPosition.marginTop,
overTop = collisionPosTop - offsetTop,
overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop,
top = data.my[ 1 ] === "top",
myOffset = top ?
-data.elemHeight :
data.my[ 1 ] === "bottom" ?
data.elemHeight :
0,
atOffset = data.at[ 1 ] === "top" ?
data.targetHeight :
data.at[ 1 ] === "bottom" ?
-data.targetHeight :
0,
offset = -2 * data.offset[ 1 ],
newOverTop,
newOverBottom;
if ( overTop < 0 ) {
newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight -
outerHeight - withinOffset;
if ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) {
position.top += myOffset + atOffset + offset;
}
} else if ( overBottom > 0 ) {
newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset +
offset - offsetTop;
if ( newOverTop > 0 || abs( newOverTop ) < overBottom ) {
position.top += myOffset + atOffset + offset;
}
}
}
},
flipfit: {
left: function() {
$.ui.position.flip.left.apply( this, arguments );
$.ui.position.fit.left.apply( this, arguments );
},
top: function() {
$.ui.position.flip.top.apply( this, arguments );
$.ui.position.fit.top.apply( this, arguments );
}
}
};
} )();
var position = $.ui.position;
}));
================================================
FILE: documentation/CONTRIBUTE.md
================================================
To generate documentation install couscous and run in this folder. Use ```couscous preview``` to run local server with the documentation.
http://couscous.io/docs/getting-started.html
The generated html will also be copied to the tests/integration/html folder for integration tests.
================================================
FILE: documentation/couscous.yml
================================================
title: jQuery contextMenu (2.x)
subTitle: Management facility for context menus. Developed for a large number of triggering objects. HTML5 Polyfill.
#template:
# url: https://github.com/CouscousPHP/Template-ReadTheDocs
exclude:
- website
- website-dark
baseUrl: https://swisnl.github.io/jQuery-contextMenu
scripts:
before:
- mkdir website/dist
- cp -R ../dist/* website/dist/
- git clone -b gh-pages https://github.com/swisnl/jQuery-contextMenu docs-3x
- cp -R docs-3x/3.x website/
after:
- rm -rf website/dist/
- rm -rf docs-3x/
- cp .couscous/generated/demo/* ../test/integration/html
github:
user: swisnl
repo: jQuery-contextMenu
# The left menu bar
menu:
items:
introduction:
text: Introduction
relativeUrl: /
items:
author:
text: Author
absoluteUrl: https://www.swis.nl/over-ons/bjorn-brala
demo:
text: Demo
# You can use relative urls
relativeUrl: demo.html
documentation:
text: Documentation
relativeUrl: docs.html
items:
options:
text: Options
relativeUrl: docs.html
items:
text: Defining menu items
relativeUrl: docs/items.html
plugin-commands:
text: Plugin commands
relativeUrl: docs/plugin-commands.html
custom-icons:
text: Customize icons
relativeUrl: docs/customize.html
fontawesome-icons:
text: FontAwesome icons
relativeUrl: demo/fontawesome-icons.html
font-awesome:
text: Font Awesome support
relativeUrl: docs/font-awesome.html
runtime-options:
text: Runtime options
relativeUrl: docs/runtime-options.html
custom-command-types:
text: Custom command types
relativeUrl: docs/custom-command-types.html
events:
text: Events
relativeUrl: docs/events.html
html5-polyfill-docs:
text: HTML5 polyfill
relativeUrl: docs/html5-polyfill.html
demo-gallery:
text: Demo gallery
relativeUrl: demo.html
items:
simple-context-menu:
text: Simple Context Menu
relativeUrl: demo.html
fontawesome-icons:
text: FontAwesome icons
relativeUrl: demo/fontawesome-icons.html
accesskeys:
text: Accesskeys
relativeUrl: demo/accesskeys.html
async-create:
text: Create Context Menu (asynchronous)
relativeUrl: demo/async-create.html
async-promise:
text: Create Context Menu (promise)
relativeUrl: demo/async-promise.html
callback:
text: Command's action (callbacks)
relativeUrl: demo/callback.html
custom-command:
text: Custom Command Types
relativeUrl: demo/custom-command.html
disabled:
text: Disabled Command
relativeUrl: demo/disabled.html
disabled-callback:
text: Disabled Callback Command
relativeUrl: demo/disabled-callback.html
disabled-changing:
text: Changing Command's disabled status
relativeUrl: demo/disabled-changing.html
disabled-menu:
text: Disabled Menu
relativeUrl: demo/disabled-menu.html
dynamic:
text: Adding new Context Menu Triggers
relativeUrl: demo/dynamic.html
dynamic-create:
text: Create Context Menu on demand
relativeUrl: demo/dynamic-create.html
html5-import:
text: Importing HTML5 menu
relativeUrl: demo/html5-import.html
html5-polyfill:
text: HTML5 polyfill
relativeUrl: demo/html5-polyfill.html
html5-polyfill-firefox8:
text: HTML5 polyfill (Firefox)
relativeUrl: demo/html5-polyfill-firefox8.html
input:
text: Input Commands
relativeUrl: demo/input.html
keeping-contextmenu-open:
text: Keeping the context menu open
relativeUrl: demo/keeping-contextmenu-open.html
menu-title:
text: Menus with titles
relativeUrl: demo/menu-title.html
on-dom-element:
text: Context Menu on DOM Element
relativeUrl: demo/on-dom-element.html
sub-menus:
text: Submenus
relativeUrl: demo/sub-menus.html
trigger-custom:
text: Custom Activated Context Menu
relativeUrl: demo/trigger-custom.html
trigger-hover:
text: Hover Activated Context Menu
relativeUrl: demo/trigger-hover.html
trigger-hover-autohide:
text: Hover Activated Context Menu With Autohide
relativeUrl: demo/trigger-hover-autohide.html
trigger-left-click:
text: Left-Click Trigger
relativeUrl: demo/trigger-left-click.html
trigger-swipe:
text: Swipe Trigger
relativeUrl: demo/trigger-swipe.html
================================================
FILE: documentation/demo/accesskeys.md
================================================
---
currentMenu: accesskeys
---
# Demo: Accesskeys
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Example code](#example-code)
- [Example HTML](#example-html)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<span class="context-menu-one btn btn-neutral">right click me</span>
## Example code
<script type="text/javascript" class="showcase">
$(function(){
$.contextMenu({
selector: '.context-menu-one',
callback: function(key, options) {
var m = "clicked: " + key;
window.console && console.log(m) || alert(m);
},
items: {
"edit": {name: "Edit", icon: "edit", accesskey: "e"},
"cut": {name: "Cut", icon: "cut", accesskey: "c"},
// first unused character is taken (here: o)
"copy": {name: "Copy", icon: "copy", accesskey: "c o p y"},
// words are truncated to their first letter (here: p)
"paste": {name: "Paste", icon: "paste", accesskey: "cool paste"},
"delete": {name: "Delete", icon: "delete"},
"sep1": "---------",
"quit": {name: "Quit", icon: function($element, key, item){ return 'context-menu-icon context-menu-icon-quit'; }}
}
});
});
</script>
## Example HTML
<div style="display:none;" class="showcase" data-showcase-import=".context-menu-one"></div>
================================================
FILE: documentation/demo/accesskeys_test.md
================================================
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Demo: Accesskeys](#demo-accesskeys)
- [Example code](#example-code)
- [Example HTML](#example-html)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
# Demo: Accesskeys
<span class="context-menu-one btn btn-neutral">right click me</span>
## Example code
<script type="text/javascript" class="showcase">
$(function(){
$.contextMenu({
selector: '.context-menu-one',
callback: function(key, options) {
var message = "clicked: " + key;
$('#msg').text(message);
},
items: {
"edit": {name: "Edit", icon: "edit", accesskey: "e"},
"cut": {name: "Cut", icon: "cut", accesskey: "c"},
// first unused character is taken (here: o)
"copy": {name: "Copy", icon: "copy", accesskey: "c o p y"},
// words are truncated to their first letter (here: p)
"paste": {name: "Paste", icon: "paste", accesskey: "cool paste"},
"delete": {name: "Delete", icon: "delete"},
"sep1": "---------",
"quit": {name: "Quit", icon: function($element, key, item){ return 'context-menu-icon context-menu-icon-quit'; }}
}
});
});
</script>
## Example HTML
<div style="display:none;" class="showcase" data-showcase-import=".context-menu-one"></div>
<div id="msg"></div>
================================================
FILE: documentation/demo/async-create.md
================================================
---
currentMenu: async-create
---
# Demo: Create Context Menu (asynchronous)
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Example code](#example-code)
- [Example HTML](#example-html)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<span class="context-menu-one btn btn-neutral">right click me</span>
## Example code
<script type="text/javascript" class="showcase">
$(function(){
// some build handler to call asynchronously
function createSomeMenu() {
return {
callback: function(key, options) {
var m = "clicked: " + key;
window.console && console.log(m) || alert(m);
},
items: {
"edit": {name: "Edit", icon: "edit"},
"cut": {name: "Cut", icon: "cut"},
"copy": {name: "Copy", icon: "copy"}
}
};
}
// some asynchronous click handler
$('.context-menu-one').on('mouseup', function(e){
var $this = $(this);
// store a callback on the trigger
$this.data('runCallbackThingie', createSomeMenu);
var _offset = $this.offset(),
position = {
x: _offset.left + 10,
y: _offset.top + 10
}
// open the contextMenu asynchronously
setTimeout(function(){ $this.contextMenu(position); }, 1000);
});
// setup context menu
$.contextMenu({
selector: '.context-menu-one',
trigger: 'none',
build: function($trigger, e) {
e.preventDefault();
// pull a callback from the trigger
return $trigger.data('runCallbackThingie')();
}
});
});
</script>
## Example HTML
<div style="display:none;" class="showcase" data-showcase-import=".context-menu-one"></div>
================================================
FILE: documentation/demo/async-promise.md
================================================
---
currentMenu: async-promise
---
# Demo: Submenu through promise (asynchronous)
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Example code](#example-code)
- [Example HTML](#example-html)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<span class="context-menu-one btn btn-neutral">right click me</span>
## Example code
<script type="text/javascript" class="showcase">
var $ = jQuery;
$(document).ready(function () {
'use strict';
var errorItems = { "errorItem": { name: "Items Load error" },};
var loadItems = function () {
var dfd = jQuery.Deferred();
setTimeout(function () {
dfd.resolve(subItems);
}, 2000);
//setTimeout(function () {
// dfd.reject(errorItems);
//}, 1000);
return dfd.promise();
};
var subItems = {
"sub1": { name: "Submenu1", icon: "edit" },
"sub2": { name: "Submenu2", icon: "cut" },
};
$.contextMenu({
selector: '.context-menu-one',
build: function ($trigger, e) {
return {
callback: function (key, options) {
var m = "clicked: " + key;
console.log(m);
},
items: {
"edit": { name: "Edit", icon: "edit" },
"cut": { name: "Cut", icon: "cut" },
"status": {
name: "Status",
icon: "delete",
items: loadItems(),
},
"normalSub": {
name: "Normal Sub",
items: {
"normalsub1": { name: "normal Sub 1"},
"normalsub2": { name: "normal Sub 2"},
"normalsub3": { name: "normal Sub 3" },
}
}
}
};
}
});
//normal promise usage example
var completedPromise = function (status) {
console.log("completed promise:", status);
};
var failPromise = function (status) {
console.log("fail promise:", status);
};
var notifyPromise = function (status) {
console.log("notify promise:", status);
};
$.loadItemsAsync = function() {
console.log("loadItemsAsync");
var promise = loadItems();
$.when(promise).then(completedPromise, failPromise, notifyPromise);
};
});
</script>
## Example HTML
<div style="display:none;" class="showcase" data-showcase-import=".context-menu-one"></div>
================================================
FILE: documentation/demo/callback.md
================================================
---
currentMenu: callback
---
# Demo: Callback
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Example code](#example-code)
- [Example HTML](#example-html)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<span class="context-menu-one btn btn-neutral">right click me</span>
## Example code
<script type="text/javascript" class="showcase">
$(function(){
$.contextMenu({
selector: '.context-menu-one',
callback: function(itemKey, opt, e) {
var m = "global: " + itemKey;
window.console && console.log(m) || alert(m);
},
items: {
"edit": {
name: "Edit",
icon: "edit",
// superseeds "global" callback
callback: function(itemKey, opt, e) {
var m = "edit was clicked";
window.console && console.log(m) || alert(m);
}
},
"cut": {name: "Cut", icon: "cut"},
"copy": {name: "Copy", icon: "copy"},
"paste": {name: "Paste", icon: "paste"},
"delete": {name: "Delete", icon: "delete"},
"sep1": "---------",
"quit": {name: "Quit", icon: function($element, key, item){ return 'context-menu-icon context-menu-icon-quit'; }}
}
});
});
</script>
## Example HTML
<div style="display:none;" class="showcase" data-showcase-import=".context-menu-one"></div>
================================================
FILE: documentation/demo/callback_test.md
================================================
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Demo: Callback](#demo-callback)
- [Example code](#example-code)
- [Example HTML](#example-html)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
# Demo: Callback
<span class="context-menu-one btn btn-neutral">right click me</span>
## Example code
<script type="text/javascript" class="showcase">
$(function(){
$.contextMenu({
selector: '.context-menu-one',
callback: function(key, options) {
var message = "global: " + key;
$('#msg').text(message);
},
items: {
"edit": {
name: "Edit",
icon: "edit",
// superseeds "global" callback
callback: function(key, options) {
var m = "edit was clicked";
$('#msg').text(m);
}
},
"cut": {name: "Cut", icon: "cut"},
"copy": {name: "Copy", icon: "copy"},
"paste": {name: "Paste", icon: "paste"},
"delete": {name: "Delete", icon: "delete"},
"sep1": "---------",
"quit": {name: "Quit", icon: function($element, key, item){ return 'context-menu-icon context-menu-icon-quit'; }}
}
});
});
</script>
## Example HTML
<div style="display:none;" class="showcase" data-showcase-import=".context-menu-one"></div>
<div id="msg"></div>
================================================
FILE: documentation/demo/custom-command.md
================================================
---
currentMenu: custom-command
---
# Demo: Custom command
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Example code](#example-code)
- [Example HTML](#example-html)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<span class="context-menu-one btn btn-neutral">right click me</span>
## Example code
<script type="text/javascript" class="showcase">
$(function(){
/**************************************************
* Custom Command Handler
**************************************************/
$.contextMenu.types.label = function(item, opt, root) {
// this === item.$node
$('<span>Label<ul>'
+ '<li class="label1" title="label 1">label 1</li>'
+ '<li class="label2" title="label 2">label 2</li>'
+ '<li class="label3" title="label 3">label 3</li>'
+ '<li class="label4" title="label 4">label 4</li></ul></span>')
.appendTo(this)
.on('click', 'li', function() {
// do some funky stuff
console.log('Clicked on ' + $(this).text());
// hide the menu
root.$menu.trigger('contextmenu:hide');
});
this.addClass('labels').on('contextmenu:focus', function(e) {
// setup some awesome stuff
}).on('contextmenu:blur', function(e) {
// tear down whatever you did
}).on('keydown', function(e) {
// some funky key handling, maybe?
});
};
/**************************************************
* Context-Menu with custom command "label"
**************************************************/
$.contextMenu({
selector: '.context-menu-one',
callback: function(itemKey, opt, rootMenu, originalEvent) {
var m = "clicked: " + key;
window.console && console.log(m) || alert(m);
},
items: {
open: {name: "Open", callback: $.noop},
label: {type: "label", customName: "Label"},
edit: {name: "Edit", callback: $.noop}
}
});
});
</script>
## Example HTML
<div style="display:none;" class="showcase" data-showcase-import=".context-menu-one"></div>
<style type="text/css" class="showcase">
.labels > span > ul {
margin: 0;
padding: 0;
list-style: none;
display: block;
float: none;
}
.labels > span > ul > li {
display: inline-block;
width: 20px;
height: 20px;
border: 1px solid #CCC;
overflow: hidden;
text-indent: -2000px;
}
.labels > span > ul > li.selected,
.labels > span > ul > li:hover { border: 1px solid #000; }
.labels > span > ul > li + li { margin-left: 5px; }
.labels > span > ul > li.label1 { background: red; }
.labels > span > ul > li.label2 { background: green; }
.labels > span > ul > li.label3 { background: blue; }
.labels > span > ul > li.label4 { background: yellow; }
</style>
================================================
FILE: documentation/demo/custom-command_test.md
================================================
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Demo: Custom command](#demo-custom-command)
- [Example code](#example-code)
- [Example HTML](#example-html)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
# Demo: Custom command
<span class="context-menu-one btn btn-neutral">right click me</span>
## Example code
<script type="text/javascript" class="showcase">
$(function(){
/**************************************************
* Custom Command Handler
**************************************************/
$.contextMenu.types.label = function(item, opt, root) {
// this === item.$node
$('<span>Label</span><ul>'
+ '<li class="label1" title="label 1">label 1</li>'
+ '<li class="label2" title="label 2">label 2</li>'
+ '<li class="label3" title="label 3">label 3</li>'
+ '<li class="label4" title="label 4">label 4</li></ul>')
.appendTo(this)
.on('click', 'li', function() {
var message = "text: " + $(this).text();
$('#msg').text($('#msg').text() + ' | ' + message);
// hide the menu
root.$menu.trigger('contextmenu:hide');
});
this.addClass('labels').on('contextmenu:focus', function(e) {
// setup some awesome stuff
}).on('contextmenu:blur', function(e) {
// tear down whatever you did
}).on('keydown', function(e) {
// some funky key handling, maybe?
});
};
/**************************************************
* Context-Menu with custom command "label"
**************************************************/
$.contextMenu({
selector: '.context-menu-one',
callback: function(key, options) {
var message = "clicked: " + key;
$('#msg').text(message);
},
items: {
open: {name: "Open", callback: $.noop},
label: {type: "label", customName: "Label"},
edit: {name: "Edit", callback: $.noop}
}
});
});
</script>
## Example HTML
<div style="display:none;" class="showcase" data-showcase-import=".context-menu-one"></div>
<div id="msg"></div>
<style type="text/css" class="showcase">
.labels > ul {
margin: 0;
padding: 0;
list-style: none;
display: block;
float: none;
}
.labels > ul > li {
display: inline-block;
width: 20px;
height: 20px;
border: 1px solid #CCC;
overflow: hidden;
text-indent: -2000px;
}
.labels > ul > li.selected,
.labels > ul > li:hover { border: 1px solid #000; }
.labels > ul > li + li { margin-left: 5px; }
.labels > ul > li.label1 { background: red; }
.labels > ul > li.label2 { background: green; }
.labels > ul > li.label3 { background: blue; }
.labels > ul > li.label4 { background: yellow; }
</style>
================================================
FILE: documentation/demo/disabled-callback.md
================================================
---
currentMenu: disabled-callback
---
# Demo: Disabled Callback
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Example code](#example-code)
- [Example HTML](#example-html)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<span class="context-menu-one btn btn-neutral">right click me</span>
## Example code
<script type="text/javascript" class="showcase">
$(function(){
$.contextMenu({
selector: '.context-menu-one',
callback: function(key, options) {
var m = "clicked: " + key;
window.console && console.log(m) || alert(m);
},
items: {
"edit": {
name: "Clickable",
icon: "edit",
disabled: function(){ return false; }
},
"cut": {
name: "Disabled",
icon: "cut",
disabled: function(){ return true; }
}
}
});
});
</script>
## Example HTML
<div style="display:none;" class="showcase" data-showcase-import=".context-menu-one"></div>
================================================
FILE: documentation/demo/disabled-callback_test.md
================================================
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Demo: Disabled Callback](#demo-disabled-callback)
- [Example code](#example-code)
- [Example HTML](#example-html)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
# Demo: Disabled Callback
<span class="context-menu-one btn btn-neutral">right click me</span>
## Example code
<script type="text/javascript" class="showcase">
$(function(){
$.contextMenu({
selector: '.context-menu-one',
callback: function(key, options) {
var message = "clicked: " + key;
$('#msg').text(message);
},
items: {
"edit": {
name: "Clickable",
icon: "edit",
disabled: function(){ return false; }
},
"cut": {
name: "Disabled",
icon: "cut",
disabled: function(){ return true; }
}
}
});
});
</script>
## Example HTML
<div style="display:none;" class="showcase" data-showcase-import=".context-menu-one"></div>
<div id="msg"></div>
================================================
FILE: documentation/demo/disabled-changing.md
================================================
---
currentMenu: disabled-changing
---
# Demo: Disabled changing
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Example code](#example-code)
- [Example HTML](#example-html)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<span class="context-menu-one btn btn-neutral">right click me</span>
## Example code
<script type="text/javascript" class="showcase">
$(function(){
$.contextMenu({
selector: '.context-menu-one',
callback: function(key, options) {
var m = "clicked: " + key;
window.console && console.log(m) || alert(m);
},
items: {
"edit": {name: "Clickable", icon: "edit"},
"cut": {
name: "Disabled",
icon: "cut",
disabled: function(key, opt) {
// this references the trigger element
return !this.data('cutDisabled');
}
},
"toggle": {
name: "Toggle",
callback: function() {
// this references the trigger element
this.data('cutDisabled', !this.data('cutDisabled'));
return false;
}
}
}
});
});
</script>
## Example HTML
<div style="display:none;" class="showcase" data-showcase-import=".context-menu-one"></div>
================================================
FILE: documentation/demo/disabled-changing_test.md
================================================
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Demo: Disabled changing](#demo-disabled-changing)
- [Example code](#example-code)
- [Example HTML](#example-html)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
# Demo: Disabled changing
<span class="context-menu-one btn btn-neutral">right click me</span>
## Example code
<script type="text/javascript" class="showcase">
$(function(){
$.contextMenu({
selector: '.context-menu-one',
callback: function(key, options) {
var message = "clicked: " + key;
$('#msg').text(message);
},
items: {
"edit": {name: "Clickable", icon: "edit"},
"cut": {
name: "Disabled",
icon: "cut",
disabled: function(key, opt) {
// this references the trigger element
return !this.data('cutDisabled');
}
},
"toggle": {
name: "Toggle",
callback: function() {
// this references the trigger element
this.data('cutDisabled', !this.data('cutDisabled'));
return false;
}
}
}
});
});
</script>
## Example HTML
<div style="display:none;" class="showcase" data-showcase-import=".context-menu-one"></div>
<div id="msg"></div>
================================================
FILE: documentation/demo/disabled-menu.md
================================================
---
currentMenu: disabled-menu
---
# Demo: Disabled menu
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Example code](#example-code)
- [Example HTML](#example-html)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<span class="context-menu-one btn btn-neutral context-menu-disabled">right click me</span>
<button type="button btn btn-neutral" id="toggle-disabled">Enable Menu</button>
## Example code
<script type="text/javascript" class="showcase">
$(function(){
$.contextMenu({
selector: '.context-menu-one',
callback: function(key, options) {
var m = "clicked: " + key;
window.console && console.log(m) || alert(m);
},
items: {
"edit": {name: "Edit", icon: "edit"},
"cut": {name: "Cut", icon: "cut"},
"copy": {name: "Copy", icon: "copy"},
"paste": {name: "Paste", icon: "paste"},
"delete": {name: "Delete", icon: "delete"},
"sep1": "---------",
"quit": {name: "Quit", icon: function($element, key, item){ return 'context-menu-icon context-menu-icon-quit'; }}
}
});
$('#toggle-disabled').on('click', function(e) {
e.preventDefault();
var $this = $(this),
$trigger = $('.context-menu-one');
if ($trigger.hasClass('context-menu-disabled')) {
$this.text("Disable Menu");
$trigger.contextMenu(true);
} else {
$this.text("Enable Menu");
$trigger.contextMenu(false);
}
});
});
</script>
## Example HTML
<div style="display:none;" class="showcase" data-showcase-import=".context-menu-one"></div>
========================
gitextract_y3pnf_da/ ├── .editorconfig ├── .gitignore ├── .jscsrc ├── .sauce.yml ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bower.json ├── contextMenu.jquery.json ├── dist/ │ ├── jquery.contextMenu.css │ ├── jquery.contextMenu.js │ └── jquery.ui.position.js ├── documentation/ │ ├── CONTRIBUTE.md │ ├── couscous.yml │ ├── demo/ │ │ ├── accesskeys.md │ │ ├── accesskeys_test.md │ │ ├── async-create.md │ │ ├── async-promise.md │ │ ├── callback.md │ │ ├── callback_test.md │ │ ├── custom-command.md │ │ ├── custom-command_test.md │ │ ├── disabled-callback.md │ │ ├── disabled-callback_test.md │ │ ├── disabled-changing.md │ │ ├── disabled-changing_test.md │ │ ├── disabled-menu.md │ │ ├── disabled.md │ │ ├── disabled_test.md │ │ ├── dynamic-create.md │ │ ├── dynamic.md │ │ ├── fontawesome-icons.md │ │ ├── html5-import.md │ │ ├── html5-polyfill-firefox8.md │ │ ├── html5-polyfill.md │ │ ├── input.md │ │ ├── keeping-contextmenu-open.md │ │ ├── menu-promise.md │ │ ├── menu-title.md │ │ ├── on-dom-element.md │ │ ├── sub-menus-promise.md │ │ ├── sub-menus.md │ │ ├── sub-menus_test.md │ │ ├── trigger-custom.md │ │ ├── trigger-hover-autohide.md │ │ ├── trigger-hover.md │ │ ├── trigger-left-click.md │ │ └── trigger-swipe.md │ ├── demo.md │ ├── docs/ │ │ ├── custom-command-types.md │ │ ├── customize.md │ │ ├── events.md │ │ ├── font-awesome.md │ │ ├── html5-polyfill.md │ │ ├── input-helpers.md │ │ ├── items.md │ │ ├── plugin-commands.md │ │ └── runtime-options.md │ ├── docs.md │ ├── index.md │ └── website/ │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── couscous.yml │ ├── css/ │ │ ├── screen.css │ │ ├── theme-fixes.css │ │ └── theme.css │ ├── default.twig │ ├── fonts/ │ │ └── FontAwesome.otf │ ├── js/ │ │ ├── main.js │ │ └── theme.js │ └── original-theme/ │ ├── bower.json │ ├── css/ │ │ └── badge_only.css │ └── sass/ │ ├── _theme_badge.sass │ ├── _theme_badge_fa.sass │ ├── _theme_breadcrumbs.sass │ ├── _theme_font_awesome_compatibility.sass │ ├── _theme_layout.sass │ ├── _theme_mathjax.sass │ ├── _theme_rst.sass │ ├── _theme_variables.sass │ ├── badge_only.sass │ └── theme.sass ├── gulpfile.js ├── karma-saucelabs.conf.js ├── karma.conf.js ├── package.json ├── src/ │ ├── .csscomb.json │ ├── .csslintrc │ ├── .jshintrc │ ├── jquery.contextMenu.js │ ├── jquery.ui.position.js │ └── sass/ │ ├── _icons.scss │ ├── _variables.scss │ ├── icons/ │ │ ├── _icon_classes.scss.tpl │ │ ├── _mixins.scss │ │ ├── _variables.scss │ │ └── _variables.scss.tpl │ └── jquery.contextMenu.scss ├── test/ │ ├── index.html │ ├── integration/ │ │ ├── custom-command.js │ │ ├── disabled-callback.js │ │ ├── disabled-changing.js │ │ ├── disabled-menu.js │ │ ├── disabled.js │ │ ├── dynamic-create.js │ │ ├── dynamic.js │ │ ├── html/ │ │ │ ├── accesskeys.html │ │ │ ├── accesskeys_test.html │ │ │ ├── async-create.html │ │ │ ├── callback.html │ │ │ ├── callback_test.html │ │ │ ├── custom-command.html │ │ │ ├── custom-command_test.html │ │ │ ├── disabled-callback.html │ │ │ ├── disabled-callback_test.html │ │ │ ├── disabled-changing.html │ │ │ ├── disabled-changing_test.html │ │ │ ├── disabled-menu.html │ │ │ ├── disabled.html │ │ │ ├── disabled_test.html │ │ │ ├── dynamic-create.html │ │ │ ├── dynamic.html │ │ │ ├── html5-import.html │ │ │ ├── html5-polyfill-firefox8.html │ │ │ ├── html5-polyfill.html │ │ │ ├── input.html │ │ │ ├── keeping-contextmenu-open.html │ │ │ ├── menu-title.html │ │ │ ├── on-dom-element.html │ │ │ ├── sub-menus.html │ │ │ ├── sub-menus_test.html │ │ │ ├── trigger-custom.html │ │ │ ├── trigger-hover-autohide.html │ │ │ ├── trigger-hover.html │ │ │ ├── trigger-left-click.html │ │ │ └── trigger-swipe.html │ │ ├── input.js │ │ ├── keeping-contextmenu-open.js │ │ ├── on-dom-element.js │ │ ├── trigger-custom.js │ │ ├── trigger-left-click.js │ │ └── trigger-right-click.js │ ├── specs/ │ │ ├── accesskeys.js │ │ ├── aync-create.js │ │ ├── callback.js │ │ └── submenu.js │ └── unit/ │ └── contextmenu.test.js └── wdio.conf.js
SYMBOL INDEX (22 symbols across 6 files)
FILE: dist/jquery.contextMenu.js
function createNameNode (line 1227) | function createNameNode(item) {
function completedPromise (line 1634) | function completedPromise(opt, root, items) {
function errorPromise (line 1644) | function errorPromise(opt, root, errorItem) {
function finishPromiseProcess (line 1662) | function finishPromiseProcess(opt, root, items) {
function splitAccesskey (line 1714) | function splitAccesskey(val) {
function inputLabel (line 2053) | function inputLabel(node) {
function menuChildren (line 2058) | function menuChildren(items, $children, counter) {
FILE: dist/jquery.ui.position.js
function getOffsets (line 52) | function getOffsets( offsets, width, height ) {
function parseCss (line 59) | function parseCss( element, property ) {
function getDimensions (line 63) | function getDimensions( elem ) {
FILE: src/jquery.contextMenu.js
function createNameNode (line 1227) | function createNameNode(item) {
function completedPromise (line 1634) | function completedPromise(opt, root, items) {
function errorPromise (line 1644) | function errorPromise(opt, root, errorItem) {
function finishPromiseProcess (line 1662) | function finishPromiseProcess(opt, root, items) {
function splitAccesskey (line 1714) | function splitAccesskey(val) {
function inputLabel (line 2053) | function inputLabel(node) {
function menuChildren (line 2058) | function menuChildren(items, $children, counter) {
FILE: src/jquery.ui.position.js
function getOffsets (line 73) | function getOffsets( offsets, width, height ) {
function parseCss (line 80) | function parseCss( element, property ) {
function getDimensions (line 84) | function getDimensions( elem ) {
FILE: test/specs/callback.js
function openCallbackMenu (line 5) | function openCallbackMenu() {
FILE: test/unit/contextmenu.test.js
function testQUnit (line 7) | function testQUnit(name, itemClickEvent, triggerEvent) {
Condensed preview — 150 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,385K chars).
[
{
"path": ".editorconfig",
"chars": 148,
"preview": "root = true\n\n[**]\ncharset = utf-8\nend_of_line = lf\nindent_size = 4\nindent_style = space\ninsert_final_newline = true\ntrim"
},
{
"path": ".gitignore",
"chars": 1474,
"preview": ".DS_Store\nnode_modules/\nbower_components/\n\n### Node template\n# Logs\nlogs\n*.log\nnpm-debug.log*\n\n# Runtime data\npids\n*.pid"
},
{
"path": ".jscsrc",
"chars": 2347,
"preview": "{\n \"requireCurlyBraces\": [\n \"if\",\n \"else\",\n \"for\",\n \"while\",\n \"do\",\n \"try\",\n \"catch\"\n ],\n \"requi"
},
{
"path": ".sauce.yml",
"chars": 84,
"preview": "---\n language: \"javascript\"\n framework: \"webdriverio\"\n configPath: \"wdio.conf.js\""
},
{
"path": ".travis.yml",
"chars": 1711,
"preview": "sudo: false\n\ncache:\n directories:\n - node_modules\n\nenv:\n global:\n - GIT_NAME: \"'Couscous auto deploy'\"\n - GIT"
},
{
"path": "CHANGELOG.md",
"chars": 23010,
"preview": "## Changelog ##\n\n### Unreleased\n\n* Context menu no longer jumps to the top of the screen #749\n\n### 2.9.2\n\n* Fix reflow w"
},
{
"path": "LICENSE",
"chars": 1073,
"preview": "The MIT License\n\nCopyright (c) 2010-2016 SWIS BV\n\nPermission is hereby granted, free of charge, to any person obtaining "
},
{
"path": "README.md",
"chars": 7202,
"preview": "# jQuery contextMenu plugin & polyfill #\n\n[\nsubTitle: Management facility for context menus. Developed for a large number of trigger"
},
{
"path": "documentation/demo/accesskeys.md",
"chars": 1486,
"preview": "---\ncurrentMenu: accesskeys\n---\n\n# Demo: Accesskeys\n\n<!-- START doctoc generated TOC please keep comment here to allow a"
},
{
"path": "documentation/demo/accesskeys_test.md",
"chars": 1502,
"preview": "<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<!-- DON'T EDIT THIS SECTION, INSTEAD "
},
{
"path": "documentation/demo/async-create.md",
"chars": 1939,
"preview": "---\ncurrentMenu: async-create \n---\n\n# Demo: Create Context Menu (asynchronous)\n\n<!-- START doctoc generated TOC please k"
},
{
"path": "documentation/demo/async-promise.md",
"chars": 2982,
"preview": "---\ncurrentMenu: async-promise \n---\n\n# Demo: Submenu through promise (asynchronous)\n\n<!-- START doctoc generated TOC ple"
},
{
"path": "documentation/demo/callback.md",
"chars": 1570,
"preview": "---\ncurrentMenu: callback \n---\n\n# Demo: Callback\n<!-- START doctoc generated TOC please keep comment here to allow auto "
},
{
"path": "documentation/demo/callback_test.md",
"chars": 1546,
"preview": "<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<!-- DON'T EDIT THIS SECTION, INSTEAD "
},
{
"path": "documentation/demo/custom-command.md",
"chars": 3141,
"preview": "---\ncurrentMenu: custom-command \n---\n\n# Demo: Custom command\n\n<!-- START doctoc generated TOC please keep comment here t"
},
{
"path": "documentation/demo/custom-command_test.md",
"chars": 3119,
"preview": "<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<!-- DON'T EDIT THIS SECTION, INSTEAD "
},
{
"path": "documentation/demo/disabled-callback.md",
"chars": 1195,
"preview": "---\ncurrentMenu: disabled-callback \n---\n\n# Demo: Disabled Callback\n\n<!-- START doctoc generated TOC please keep comment "
},
{
"path": "documentation/demo/disabled-callback_test.md",
"chars": 1217,
"preview": "<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<!-- DON'T EDIT THIS SECTION, INSTEAD "
},
{
"path": "documentation/demo/disabled-changing.md",
"chars": 1513,
"preview": "---\ncurrentMenu: disabled-changing \n---\n\n# Demo: Disabled changing\n\n<!-- START doctoc generated TOC please keep comment "
},
{
"path": "documentation/demo/disabled-changing_test.md",
"chars": 1535,
"preview": "<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<!-- DON'T EDIT THIS SECTION, INSTEAD "
},
{
"path": "documentation/demo/disabled-menu.md",
"chars": 1808,
"preview": "---\ncurrentMenu: disabled-menu \n---\n\n# Demo: Disabled menu\n\n\n<!-- START doctoc generated TOC please keep comment here to"
},
{
"path": "documentation/demo/disabled.md",
"chars": 1003,
"preview": "---\ncurrentMenu: disabled \n---\n\n# Demo: Disabled\n\n<!-- START doctoc generated TOC please keep comment here to allow auto"
},
{
"path": "documentation/demo/disabled_test.md",
"chars": 1016,
"preview": "<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<!-- DON'T EDIT THIS SECTION, INSTEAD "
},
{
"path": "documentation/demo/dynamic-create.md",
"chars": 1748,
"preview": "---\ncurrentMenu: dynamic-create \n---\n\n# Demo: Create Context Menu on Demand\n\n<!-- START doctoc generated TOC please keep"
},
{
"path": "documentation/demo/dynamic.md",
"chars": 1759,
"preview": "---\ncurrentMenu: dynamic \n---\n\n# Demo: Adding new Context Menu Triggers\n\n<!-- START doctoc generated TOC please keep com"
},
{
"path": "documentation/demo/fontawesome-icons.md",
"chars": 1404,
"preview": "---\ncurrentMenu: fontawesome-icons\n---\n\n# Demo: FontAwesome icons\n\n<!-- START doctoc generated TOC please keep comment h"
},
{
"path": "documentation/demo/html5-import.md",
"chars": 1533,
"preview": "---\ncurrentMenu: html5-import \n---\n\n# Demo: Importing HTML5 `<menu type=\"context\">`\n\n<!-- START doctoc generated TOC ple"
},
{
"path": "documentation/demo/html5-polyfill-firefox8.md",
"chars": 2425,
"preview": "---\ncurrentMenu: html5-polyfill-firefox8 \n---\n\n# Demo: HTML5 Polyfill (Firefox)\n\n<!-- START doctoc generated TOC please "
},
{
"path": "documentation/demo/html5-polyfill.md",
"chars": 1888,
"preview": "---\ncurrentMenu: html5-polyfill \n---\n\n# Demo: HTML5 Polyfill\n\n<!-- START doctoc generated TOC please keep comment here t"
},
{
"path": "documentation/demo/input.md",
"chars": 3663,
"preview": "---\ncurrentMenu: input \n---\n\n# Demo: Input Commands\n\n<!-- START doctoc generated TOC please keep comment here to allow a"
},
{
"path": "documentation/demo/keeping-contextmenu-open.md",
"chars": 1223,
"preview": "---\ncurrentMenu: keeping-contextmenu-open \n---\n\n# Demo: Keeping the Menu visible\n\n<!-- START doctoc generated TOC please"
},
{
"path": "documentation/demo/menu-promise.md",
"chars": 3308,
"preview": "---\ncurrentMenu: menu-promise \n---\n\n# Demo: Submenu through promise (asynchronous)\n\n<!-- START doctoc generated TOC plea"
},
{
"path": "documentation/demo/menu-title.md",
"chars": 4143,
"preview": "---\ncurrentMenu: menu-title \n---\n\n# Demo: Menu Title\n\n\n<!-- START doctoc generated TOC please keep comment here to allo"
},
{
"path": "documentation/demo/on-dom-element.md",
"chars": 1688,
"preview": "---\ncurrentMenu: on-dom-element\n---\n\n# Demo: Context Menu on DOM Element\n\n<!-- START doctoc generated TOC please keep co"
},
{
"path": "documentation/demo/sub-menus-promise.md",
"chars": 2069,
"preview": "---\ncurrentMenu: sub-menus-promise \n---\n\n# Demo: Submenus with promise\n\n<!-- START doctoc generated TOC please keep com"
},
{
"path": "documentation/demo/sub-menus.md",
"chars": 2098,
"preview": "---\ncurrentMenu: sub-menus \n---\n\n# Demo: Submenus\n\n<!-- START doctoc generated TOC please keep comment here to allow au"
},
{
"path": "documentation/demo/sub-menus_test.md",
"chars": 2109,
"preview": "<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<!-- DON'T EDIT THIS SECTION, INSTEAD "
},
{
"path": "documentation/demo/trigger-custom.md",
"chars": 1717,
"preview": "---\ncurrentMenu: trigger-custom \n---\n\n# Demo: Custom Activated Context Menu\n\n<!-- START doctoc generated TOC please kee"
},
{
"path": "documentation/demo/trigger-hover-autohide.md",
"chars": 1398,
"preview": "---\ncurrentMenu: trigger-hover-autohide \n---\n\n# Demo: Hover Activated Context Menu With Autohide\n\n<!-- START doctoc gen"
},
{
"path": "documentation/demo/trigger-hover.md",
"chars": 1352,
"preview": "---\ncurrentMenu: trigger-hover \n---\n\n# Demo: Hover Activated Context Menu\n\n\n<!-- START doctoc generated TOC please keep"
},
{
"path": "documentation/demo/trigger-left-click.md",
"chars": 1325,
"preview": "---\ncurrentMenu: trigger-left-click \n---\n\n# Demo: Left-Click Trigger\n\n<!-- START doctoc generated TOC please keep comme"
},
{
"path": "documentation/demo/trigger-swipe.md",
"chars": 2000,
"preview": "---\ncurrentMenu: trigger-swipe\n---\n\n# Demo: Swipe Trigger\n\n<!-- START doctoc generated TOC please keep comment here to a"
},
{
"path": "documentation/demo.md",
"chars": 3071,
"preview": "---\ncurrentMenu: simple-context-menu\n---\n\n# Demo: Simple Context Menu\n\n<!-- START doctoc generated TOC please keep comme"
},
{
"path": "documentation/docs/custom-command-types.md",
"chars": 1705,
"preview": "---\ncurrentMenu: custom-command-types\n---\n\n# Custom Command Types\n\n<!-- START doctoc generated TOC please keep comment h"
},
{
"path": "documentation/docs/customize.md",
"chars": 1298,
"preview": "---\ncurrentMenu: custom-icons\n---\n\n## Customize icons\n\nYou can add icons to src/icons and run ``gulp build-icons``. This"
},
{
"path": "documentation/docs/events.md",
"chars": 2454,
"preview": "---\ncurrentMenu: events\n---\n\n# Events\n\n<!-- START doctoc generated TOC please keep comment here to allow auto update -->"
},
{
"path": "documentation/docs/font-awesome.md",
"chars": 828,
"preview": "---\ncurrentMenu: font-awesome\n-------------------------\n\n## Customize icons\n\nIt is possible to use font-awesome icons if"
},
{
"path": "documentation/docs/html5-polyfill.md",
"chars": 1845,
"preview": "---\ncurrentMenu: html5-polyfill\n---\n\n# HTML5 `<menu>` shiv/polyfill\n\n<!-- START doctoc generated TOC please keep comment"
},
{
"path": "documentation/docs/input-helpers.md",
"chars": 1111,
"preview": "---\ncurrentMenu: input-helpers\n---\n\n# Helpers\n\n<!-- START doctoc generated TOC please keep comment here to allow auto up"
},
{
"path": "documentation/docs/items.md",
"chars": 13622,
"preview": "---\ncurrentMenu: items\n---\n\n# Items\n\n<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<"
},
{
"path": "documentation/docs/plugin-commands.md",
"chars": 1615,
"preview": "---\ncurrentMenu: plugin-commands\n---\n\n# Plugin commands\n\n<!-- START doctoc generated TOC please keep comment here to all"
},
{
"path": "documentation/docs/runtime-options.md",
"chars": 2755,
"preview": "---\ncurrentMenu: runtime-options\n---\n\n# Runtime options (opt)\n\n<!-- START doctoc generated TOC please keep comment here "
},
{
"path": "documentation/docs.md",
"chars": 13579,
"preview": "---\ncurrentMenu: options\n---\n# Documentation\n\n<!-- START doctoc generated TOC please keep comment here to allow auto upd"
},
{
"path": "documentation/index.md",
"chars": 4578,
"preview": "---\ncurrentMenu: introduction\n---\n\n# [jQuery contextMenu](https://github.com/swisnl/jQuery-contextMenu)\n\n## Contextmenu "
},
{
"path": "documentation/website/.gitignore",
"chars": 31,
"preview": "/.couscous/\n/bower_components/\n"
},
{
"path": "documentation/website/LICENSE",
"chars": 1210,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2013 Dave Snider\nCopyright (c) 2015 Matthieu Napoli\n\nThis project was forked of the"
},
{
"path": "documentation/website/README.md",
"chars": 2508,
"preview": "<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<!-- DON'T EDIT THIS SECTION, INSTEAD "
},
{
"path": "documentation/website/couscous.yml",
"chars": 576,
"preview": "template:\n directory: .\n\ngithub:\n user: CouscousPHP\n repo: Template-ReadTheDocs\n\ntitle: ReadTheDocs template\nsu"
},
{
"path": "documentation/website/css/screen.css",
"chars": 4220,
"preview": "html .icon {\n display:block;\n}\n\n.rst-content ul {\n margin-bottom:24px;\n}\n.rst-content ul li {\n line-height:24p"
},
{
"path": "documentation/website/css/theme-fixes.css",
"chars": 451,
"preview": ".wy-side-nav-search>a, .wy-side-nav-search .wy-dropdown>a {\n margin-bottom: 0;\n}\n\n/* Code blocks */\npre > code {\n "
},
{
"path": "documentation/website/css/theme.css",
"chars": 104658,
"preview": "* {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box\n}\n\narticle, aside, "
},
{
"path": "documentation/website/default.twig",
"chars": 5460,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-"
},
{
"path": "documentation/website/js/main.js",
"chars": 968,
"preview": "(function($, undefined){\n $(function() {\n\n $('.document table').addClass('docutils');\n\n $('.showcase')."
},
{
"path": "documentation/website/js/theme.js",
"chars": 1675,
"preview": "$( document ).ready(function() {\n // Shift nav in mobile when clicking the menu.\n $(document).on('click', \"[data-t"
},
{
"path": "documentation/website/original-theme/bower.json",
"chars": 102,
"preview": "{\n \"name\": \"couscous-readthedocs-template\",\n \"dependencies\": {\n \"wyrm\": \"~0.0.x\"\n }\n}\n"
},
{
"path": "documentation/website/original-theme/css/badge_only.css",
"chars": 3381,
"preview": ".fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:"
},
{
"path": "documentation/website/original-theme/sass/_theme_badge.sass",
"chars": 1991,
"preview": ".rst-versions\n position: fixed\n bottom: 0\n left: 0\n width: $nav-desktop-width\n color: $section-background-color\n b"
},
{
"path": "documentation/website/original-theme/sass/_theme_badge_fa.sass",
"chars": 1195,
"preview": "// Slimmer version of FA for use on the badge_only.sass file.\n\n+font-face(FontAwesome, '#{$font-awesome-dir}fontawesome_"
},
{
"path": "documentation/website/original-theme/sass/_theme_breadcrumbs.sass",
"chars": 469,
"preview": ".wy-breadcrumbs li\n display: inline-block\n &.wy-breadcrumbs-aside\n float: right\n a\n display: inline-block\n p"
},
{
"path": "documentation/website/original-theme/sass/_theme_font_awesome_compatibility.sass",
"chars": 404,
"preview": ".icon\n @extend .fa\n.icon-home\n @extend .fa-home\n.icon-search\n @extend .fa-search\n.icon-book\n @extend .fa-book\n.icon-"
},
{
"path": "documentation/website/original-theme/sass/_theme_layout.sass",
"chars": 7473,
"preview": ".wy-affix\n position: fixed\n top: $gutter\n\n.wy-menu\n a:hover\n text-decoration: none\n\n.wy-menu-horiz\n +clearfix\n u"
},
{
"path": "documentation/website/original-theme/sass/_theme_mathjax.sass",
"chars": 77,
"preview": "span[id*='MathJax-Span']\n color: $mathjax-color\n\n.math\n text-align: center\n"
},
{
"path": "documentation/website/original-theme/sass/_theme_rst.sass",
"chars": 9481,
"preview": "// -------------------------------------------------------------------------------------------------------------------\n/"
},
{
"path": "documentation/website/original-theme/sass/_theme_variables.sass",
"chars": 875,
"preview": "// In here are varibles used for sphinx_rtd_theme, they either add to or overwrite the default ones\n// that are set in w"
},
{
"path": "documentation/website/original-theme/sass/badge_only.sass",
"chars": 577,
"preview": "// ------------------------------------------------------------\n// CONTRIBUTORS, PLEASE READ THIS!\n// ------------------"
},
{
"path": "documentation/website/original-theme/sass/theme.sass",
"chars": 1500,
"preview": "// ------------------------------------------------------------\n// CONTRIBUTORS, PLEASE READ THIS!\n// ------------------"
},
{
"path": "gulpfile.js",
"chars": 5294,
"preview": "'use strict';\n\nvar gulp = require('gulp');\nvar plugins = require('gulp-load-plugins')();\nvar sass = require('gulp-sass')"
},
{
"path": "karma-saucelabs.conf.js",
"chars": 2691,
"preview": "\nmodule.exports = function (config) {\n if (!process.env.SAUCE_USERNAME || !process.env.SAUCE_ACCESS_KEY) {\n co"
},
{
"path": "karma.conf.js",
"chars": 1982,
"preview": "// Karma configuration\n// Generated on Wed Oct 29 2014 01:56:16 GMT+0100 (CET)\n\nmodule.exports = function(config) {\n co"
},
{
"path": "package.json",
"chars": 2732,
"preview": "{\n \"name\": \"jquery-contextmenu\",\n \"title\": \"jQuery.contextMenu()\",\n \"version\": \"2.10.1\",\n \"author\": {\n \"name\": \"B"
},
{
"path": "src/.csscomb.json",
"chars": 8099,
"preview": "{\n \"always-semicolon\": true,\n \"block-indent\": 2,\n \"color-case\": \"lower\",\n \"color-shorthand\": true,\n \"element-case\":"
},
{
"path": "src/.csslintrc",
"chars": 479,
"preview": "{\n \"adjoining-classes\": false,\n \"box-sizing\": false,\n \"box-model\": false,\n \"compatible-vendor-prefixes\": false,\n \"f"
},
{
"path": "src/.jshintrc",
"chars": 406,
"preview": "{\n \"bitwise\": true,\n \"curly\": true,\n \"eqeqeq\": true,\n \"forin\": true,\n \"freeze\": true,\n \"funcscope\": true,\n \"itera"
},
{
"path": "src/jquery.contextMenu.js",
"chars": 97030,
"preview": "/**\n * jQuery contextMenu v@VERSION - Plugin for simple contextMenu handling\n *\n * Version: v@VERSION\n *\n * Authors: Bjö"
},
{
"path": "src/jquery.ui.position.js",
"chars": 16584,
"preview": "/*! jQuery UI - v1.12.0 - 2016-07-15\n * http://jqueryui.com\n * Includes: position.js\n * Copyright jQuery Foundation and "
},
{
"path": "src/sass/_icons.scss",
"chars": 560,
"preview": "\n.context-menu-icon-add {\n @include context-menu-item-icon(add);\n}\n.context-menu-icon-copy {\n @include context-menu-it"
},
{
"path": "src/sass/_variables.scss",
"chars": 2114,
"preview": "// Container Sizing\n$context-menu-min-width: 13em !default;\n$context-menu-max-width: $context-menu-min-width * 2 !defaul"
},
{
"path": "src/sass/icons/_icon_classes.scss.tpl",
"chars": 135,
"preview": "<% _.each(glyphs, function(glyph) { %>\n.<%= className %>-<%= glyph.name %> {\n @include <%= mixinName %>(<%= glyph.name "
},
{
"path": "src/sass/icons/_mixins.scss",
"chars": 3019,
"preview": "@import \"variables\";\n\n$context-menu-icons: () !default;\n\n@function context-menu-font-url($url) {\n @if function-exists(a"
},
{
"path": "src/sass/icons/_variables.scss",
"chars": 247,
"preview": "// DON'T MANUALLY EDIT THIS FILE; run `gulp build-icons` instead.\n$context-menu-icons-cachebust: \"2dq4x\";\n$context-menu-"
},
{
"path": "src/sass/icons/_variables.scss.tpl",
"chars": 301,
"preview": "// DON'T MANUALLY EDIT THIS FILE; run `gulp build-icons` instead.\n$context-menu-icons-cachebust: \"<%= (0|Math.random()*9"
},
{
"path": "src/sass/jquery.contextMenu.scss",
"chars": 4330,
"preview": "/*!\n * jQuery contextMenu - Plugin for simple contextMenu handling\n *\n * Version: v@VERSION\n *\n * Authors: Björn Brala ("
},
{
"path": "test/index.html",
"chars": 915,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <title>QUnit Test Suite</title>\n <link rel=\"stylesheet\" href=\"../node_modules/qunit"
},
{
"path": "test/integration/custom-command.js",
"chars": 1721,
"preview": "var pwd = process.cwd();\nvar helper = require('../integration_test_helper.js');\n\n// this test uses custom HTML because P"
},
{
"path": "test/integration/disabled-callback.js",
"chars": 1184,
"preview": "var pwd = process.cwd();\nvar helper = require('../integration_test_helper.js');\n\n// this test uses custom HTML because P"
},
{
"path": "test/integration/disabled-changing.js",
"chars": 2051,
"preview": "var pwd = process.cwd();\nvar helper = require('../integration_test_helper.js');\n\n// this test uses custom HTML because P"
},
{
"path": "test/integration/disabled-menu.js",
"chars": 2187,
"preview": "var pwd = process.cwd();\nvar helper = require('../integration_test_helper.js');\n\nmodule.exports = {\n 'Disabled trigger "
},
{
"path": "test/integration/disabled.js",
"chars": 1175,
"preview": "var pwd = process.cwd();\nvar helper = require('../integration_test_helper.js');\n\n// this test uses custom HTML because P"
},
{
"path": "test/integration/dynamic-create.js",
"chars": 608,
"preview": "var pwd = process.cwd();\nvar helper = require('../integration_test_helper.js');\n\nmodule.exports = {\n 'Dynamically creat"
},
{
"path": "test/integration/dynamic.js",
"chars": 1506,
"preview": "var pwd = process.cwd();\nvar helper = require('../integration_test_helper.js');\n\nmodule.exports = {\n 'Dynamically creat"
},
{
"path": "test/integration/html/accesskeys.html",
"chars": 23069,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-"
},
{
"path": "test/integration/html/accesskeys_test.html",
"chars": 23122,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-"
},
{
"path": "test/integration/html/async-create.html",
"chars": 23533,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-"
},
{
"path": "test/integration/html/callback.html",
"chars": 23142,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-"
},
{
"path": "test/integration/html/callback_test.html",
"chars": 23164,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-"
},
{
"path": "test/integration/html/custom-command.html",
"chars": 24585,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-"
},
{
"path": "test/integration/html/custom-command_test.html",
"chars": 24677,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-"
},
{
"path": "test/integration/html/disabled-callback.html",
"chars": 22807,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-"
},
{
"path": "test/integration/html/disabled-callback_test.html",
"chars": 22843,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-"
},
{
"path": "test/integration/html/disabled-changing.html",
"chars": 23095,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-"
},
{
"path": "test/integration/html/disabled-changing_test.html",
"chars": 23162,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-"
},
{
"path": "test/integration/html/disabled-menu.html",
"chars": 23384,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-"
},
{
"path": "test/integration/html/disabled.html",
"chars": 22586,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-"
},
{
"path": "test/integration/html/disabled_test.html",
"chars": 22634,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-"
},
{
"path": "test/integration/html/dynamic-create.html",
"chars": 23350,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-"
},
{
"path": "test/integration/html/dynamic.html",
"chars": 23360,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-"
},
{
"path": "test/integration/html/html5-import.html",
"chars": 23269,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-"
},
{
"path": "test/integration/html/html5-polyfill-firefox8.html",
"chars": 24091,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-"
},
{
"path": "test/integration/html/html5-polyfill.html",
"chars": 23574,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-"
},
{
"path": "test/integration/html/input.html",
"chars": 25268,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-"
},
{
"path": "test/integration/html/keeping-contextmenu-open.html",
"chars": 22805,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-"
},
{
"path": "test/integration/html/menu-title.html",
"chars": 25800,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-"
},
{
"path": "test/integration/html/on-dom-element.html",
"chars": 23388,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-"
},
{
"path": "test/integration/html/sub-menus.html",
"chars": 23969,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-"
},
{
"path": "test/integration/html/sub-menus_test.html",
"chars": 23726,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-"
},
{
"path": "test/integration/html/trigger-custom.html",
"chars": 23308,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-"
},
{
"path": "test/integration/html/trigger-hover-autohide.html",
"chars": 22999,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-"
},
{
"path": "test/integration/html/trigger-hover.html",
"chars": 22947,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-"
},
{
"path": "test/integration/html/trigger-left-click.html",
"chars": 22906,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-"
},
{
"path": "test/integration/html/trigger-swipe.html",
"chars": 23594,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-"
},
{
"path": "test/integration/input.js",
"chars": 1802,
"preview": "var pwd = process.cwd();\nvar helper = require('../integration_test_helper.js');\nvar text1 = '.context-menu-root input[na"
},
{
"path": "test/integration/keeping-contextmenu-open.js",
"chars": 1570,
"preview": "var pwd = process.cwd();\nvar helper = require('../integration_test_helper.js');\n\nmodule.exports = {\n 'Ensure context me"
},
{
"path": "test/integration/on-dom-element.js",
"chars": 1072,
"preview": "var pwd = process.cwd();\nvar helper = require('../integration_test_helper.js');\n\nmodule.exports = {\n 'Right-click on mu"
},
{
"path": "test/integration/trigger-custom.js",
"chars": 712,
"preview": "var pwd = process.cwd();\nvar helper = require('../integration_test_helper.js');\n\nmodule.exports = {\n 'Mouse hover opens"
},
{
"path": "test/integration/trigger-left-click.js",
"chars": 633,
"preview": "var pwd = process.cwd();\nvar helper = require('../integration_test_helper.js');\n\nmodule.exports = {\n 'Left-click opens "
},
{
"path": "test/integration/trigger-right-click.js",
"chars": 618,
"preview": "var pwd = process.cwd();\nvar helper = require('../integration_test_helper.js');\n\nmodule.exports = {\n 'Right-click opens"
},
{
"path": "test/specs/accesskeys.js",
"chars": 1925,
"preview": "var assert = require('assert');\nvar pwd = process.cwd();\ndescribe('Test accesskeys', function() {\n it('should navigat"
},
{
"path": "test/specs/aync-create.js",
"chars": 443,
"preview": "var assert = require('assert');\nvar pwd = process.cwd();\ndescribe('Test async create', function() {\n it('should rende"
},
{
"path": "test/specs/callback.js",
"chars": 1041,
"preview": "var assert = require('assert');\nvar pwd = process.cwd();\n\ndescribe('Test callback', function() {\n function openCallback"
},
{
"path": "test/specs/submenu.js",
"chars": 1011,
"preview": "var assert = require('assert');\nvar pwd = process.cwd();\ndescribe('Test submenus', function() {\n it('should navigate "
},
{
"path": "test/unit/contextmenu.test.js",
"chars": 7246,
"preview": "var menuOpenCounter = 0;\nvar menuCloseCounter = 0;\nvar itemSelectedCounter = 0;\nvar itemSelectedStack = [];\nvar menuRunt"
},
{
"path": "wdio.conf.js",
"chars": 7876,
"preview": "exports.config = {\n \n //\n // =================\n // Service Providers\n // =================\n // Webdriv"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the medialize/jQuery-contextMenu GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 150 files (1.3 MB), approximately 257.4k tokens, and a symbol index with 22 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.