-
Notifications
You must be signed in to change notification settings - Fork 721
fix: keep Font Awesome icons centered in a menu with a title #803
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| --- | ||
| currentMenu: menu-title-fontawesome | ||
| --- | ||
|
|
||
| # Demo: Menu Title with Font Awesome icons | ||
|
|
||
|
|
||
| <!-- START doctoc generated TOC please keep comment here to allow auto update --> | ||
| <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> | ||
|
|
||
|
|
||
| - [Example CSS](#example-css) | ||
| - [Example code](#example-code) | ||
| - [Example HTML](#example-html) | ||
|
|
||
| <!-- END doctoc generated TOC please keep comment here to allow auto update --> | ||
|
|
||
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css"> | ||
|
|
||
| Combining a [menu title](menu-title.html) with [Font Awesome icons](fontawesome-icons.html). | ||
| The icons stay vertically centered in their item, whether the menu has a title or not. | ||
|
|
||
| <span class="context-menu-fa-title btn btn-neutral">right click me (with title)</span> | ||
| <span class="context-menu-fa-plain btn btn-neutral">right click me (no title)</span> | ||
|
|
||
| ## Example CSS | ||
|
|
||
| Note the child combinator in `> :first-child`: the top margin is only meant for | ||
| the first menu item, so that the title has room. Without it the rule also | ||
| matches the `<i>` element that the plugin creates for a Font Awesome icon, | ||
| and the label of an input item, since those are the first child of their own | ||
| menu item. | ||
|
|
||
| <style type="text/css" class="showcase"> | ||
| /* menu header */ | ||
| .menu-title-fa:before { | ||
| content: "Font Awesome title"; | ||
| display: block; | ||
| position: absolute; | ||
| top: 0; | ||
| right: 0; | ||
| left: 0; | ||
| background: #DDD; | ||
| padding: 2px; | ||
|
|
||
| font-family: Verdana, Arial, Helvetica, sans-serif; | ||
| font-size: 11px; | ||
| font-weight: bold; | ||
| } | ||
| .menu-title-fa > :first-child { | ||
| margin-top: 20px; | ||
| } | ||
| </style> | ||
|
|
||
| ## Example code | ||
|
|
||
| <script type="text/javascript" class="showcase"> | ||
| $(function(){ | ||
| var items = { | ||
| "edit": {name: "Edit", icon: "fas fa-edit"}, | ||
| "cut": {name: "Beer", icon: "fas fa-beer"}, | ||
| "copy": {name: "Cloud download", icon: "fas fa-cloud-download-alt"}, | ||
| "paste": {name: "Certificate", icon: "fas fa-certificate"} | ||
| }; | ||
|
|
||
| // menu with a title provided by CSS | ||
| $.contextMenu({ | ||
| selector: '.context-menu-fa-title', | ||
| className: 'menu-title-fa', | ||
| callback: function(key, options) { | ||
| var m = "clicked: " + key; | ||
| window.console && console.log(m) || alert(m); | ||
| }, | ||
| items: items | ||
| }); | ||
|
|
||
| // the same menu without a title, for comparison | ||
| $.contextMenu({ | ||
| selector: '.context-menu-fa-plain', | ||
| callback: function(key, options) { | ||
| var m = "clicked: " + key; | ||
| window.console && console.log(m) || alert(m); | ||
| }, | ||
| items: items | ||
| }); | ||
| }); | ||
| </script> | ||
|
|
||
| ## Example HTML | ||
|
|
||
| ```html | ||
| <span class="context-menu-fa-title btn btn-neutral">right click me (with title)</span> | ||
|
|
||
| <span class="context-menu-fa-plain btn btn-neutral">right click me (no title)</span> | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| const { test, expect } = require('@playwright/test'); | ||
| const { fixture } = require('../support/helpers'); | ||
|
|
||
| // Regression test for https://github.com/swisnl/jQuery-contextMenu/issues/738 | ||
| // | ||
| // The documented way to give a menu a title is a `:before` pseudo element on | ||
| // the menu plus a top margin on its first child, to make room for that title. | ||
| // A Font Awesome icon is rendered as an <i> element inside the menu item, so | ||
| // it is the first child of its own <li>. A descendant `:first-child` selector | ||
| // therefore matched every icon as well and pushed them all down by the title's | ||
| // height, leaving the icons hanging below their label. | ||
| // | ||
| // The plugin now pins the icon element's own box (including its margin), so | ||
| // the icon stays vertically centered in its item regardless of what the page | ||
| // does to first children. | ||
|
|
||
| // The icon and its item may legitimately differ by a sub-pixel amount because | ||
| // of fractional em based paddings. | ||
| const TOLERANCE = 1.5; | ||
|
|
||
| // Reads the vertical center of every Font Awesome icon and of the menu item it | ||
| // belongs to, from the menu that is currently visible. | ||
| function readIconOffsets(page) { | ||
| return page.evaluate(() => { | ||
| const menu = Array.from(document.querySelectorAll('.context-menu-list')) | ||
| .find((el) => window.getComputedStyle(el).display !== 'none'); | ||
|
|
||
| return Array.from(menu.children) | ||
| .filter((item) => item.querySelector('i')) | ||
| .map((item) => { | ||
| const itemBox = item.getBoundingClientRect(); | ||
| const iconBox = item.querySelector('i').getBoundingClientRect(); | ||
|
|
||
| return { | ||
| label: item.textContent.trim(), | ||
| offset: (iconBox.top + iconBox.height / 2) - (itemBox.top + itemBox.height / 2), | ||
| }; | ||
| }); | ||
| }); | ||
| } | ||
|
|
||
| // The built-in icon font is drawn in a `::before` pseudo element, which has no | ||
| // box to measure directly. It is centered with `top: 50%` plus a translate of | ||
| // half its own height, so its center sits exactly at the used value of `top`, | ||
| // which must be half the item's height. | ||
| function readBuiltInIconOffsets(page) { | ||
| return page.evaluate(() => { | ||
| const menu = Array.from(document.querySelectorAll('.context-menu-list')) | ||
| .find((el) => window.getComputedStyle(el).display !== 'none'); | ||
|
|
||
| return Array.from(menu.children) | ||
| .filter((item) => item.classList.contains('context-menu-icon')) | ||
| .map((item) => ({ | ||
| label: item.textContent.trim(), | ||
| offset: parseFloat(window.getComputedStyle(item, '::before').top) - | ||
| item.getBoundingClientRect().height / 2, | ||
| })); | ||
| }); | ||
| } | ||
|
|
||
| function expectCentered(offsets) { | ||
| expect(offsets.length).toBeGreaterThan(0); | ||
| for (const { label, offset } of offsets) { | ||
| expect(Math.abs(offset), 'icon of item "' + label + '" is off center by ' + offset + 'px') | ||
| .toBeLessThanOrEqual(TOLERANCE); | ||
| } | ||
| } | ||
|
|
||
| test.describe('Test icon alignment in a menu with a title (#738)', () => { | ||
| test('Font Awesome icons are centered in a menu with a title', async ({ page }) => { | ||
| await page.goto(fixture('menu-title-fontawesome.html')); | ||
| await page.click('.context-menu-fa-title', { button: 'right' }); | ||
|
|
||
| const menu = page.locator('.context-menu-list.menu-title-fa'); | ||
| await expect(menu).toBeVisible(); | ||
|
|
||
| expectCentered(await readIconOffsets(page)); | ||
| }); | ||
|
|
||
| test('Font Awesome icons are centered in a menu without a title', async ({ page }) => { | ||
| await page.goto(fixture('menu-title-fontawesome.html')); | ||
| await page.click('.context-menu-fa-plain', { button: 'right' }); | ||
|
|
||
| await expect(page.locator('.context-menu-list:visible')).toBeVisible(); | ||
|
|
||
| expectCentered(await readIconOffsets(page)); | ||
| }); | ||
|
|
||
| test('Font Awesome icons survive a descendant :first-child title rule', async ({ page }) => { | ||
| await page.goto(fixture('menu-title-fontawesome.html')); | ||
|
|
||
| // The title recipe as it was documented before #738: a descendant | ||
| // selector rather than a child combinator. Plenty of pages in the wild | ||
| // still use it, so the plugin's own icon styling has to win. | ||
| await page.addStyleTag({ content: '.menu-title-fa :first-child { margin-top: 20px; }' }); | ||
|
|
||
| await page.click('.context-menu-fa-title', { button: 'right' }); | ||
|
|
||
| const menu = page.locator('.context-menu-list.menu-title-fa'); | ||
| await expect(menu).toBeVisible(); | ||
| // sanity check: the rule under test really is applied to the menu's first | ||
| // item, so the title still has its room | ||
| await expect(menu.locator('li').first()).toHaveCSS('margin-top', '20px'); | ||
|
|
||
| expectCentered(await readIconOffsets(page)); | ||
| }); | ||
|
|
||
| test('built-in icon font is centered in a menu with a title', async ({ page }) => { | ||
| await page.goto(fixture('menu-title.html')); | ||
| await page.click('.context-menu-two', { button: 'right' }); | ||
|
|
||
| const menu = page.locator('.context-menu-list.css-title'); | ||
| await expect(menu).toBeVisible(); | ||
|
|
||
| expectCentered(await readBuiltInIconOffsets(page)); | ||
| }); | ||
|
|
||
| test('built-in icon font is centered in a menu without a title', async ({ page }) => { | ||
| await page.goto(fixture('menu-title.html')); | ||
| await page.click('.context-menu-one', { button: 'right' }); | ||
|
|
||
| await expect(page.locator('.context-menu-list:visible')).toBeVisible(); | ||
|
|
||
| expectCentered(await readBuiltInIconOffsets(page)); | ||
| }); | ||
| }); | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In every acceptance job,
npm run test:fixturesgenerates pages whose stylesheet link points to the checked-indist/jquery.contextMenu.css, and the workflow proceeds directly to Playwright without runningnpm run build. Because this commit changes only the Sass source, the loaded CSS still hastop: 0.3emand no margin reset; therefore this injected descendant rule gives each icon a 20px top margin and the subsequent centering assertion fails across the acceptance matrix. Update the tracked distribution CSS or make the fixture/workflow compile and load the changed stylesheet.Useful? React with 👍 / 👎.