Commit 2f0ce4e6 authored by jrockowitz's avatar jrockowitz Committed by jrockowitz
Browse files

Issue #3170593 by jrockowitz: Replace jQuery UI tooltips with TippyJS (and PopperJS)

parent 38eb6c96
......@@ -43,8 +43,7 @@
"require": {
"drupal/core": "^8.8 || ^9",
"drupal/jquery_ui": "~1.0",
"drupal/jquery_ui_tabs": "~1.0",
"drupal/jquery_ui_tooltip": "~1.0"
"drupal/jquery_ui_tabs": "~1.0"
},
"require-dev": {
"drupal/address": "~1.0",
......
......@@ -51,7 +51,13 @@
"jquery/toggles": "*",
"progress-tracker/progress-tracker": "*",
"signature_pad/signature_pad": "*",
"svg-pan-zoom/svg-pan-zoom": "*"
"svg-pan-zoom/svg-pan-zoom": "*",
"tippyjs/5.x": "*",
"tippyjs/6.x": "*"
},
"suggest": {
"drupal/jquery_ui_checkboxradio": "Provides jQuery UI Checkboxradio library. Required by the Webform jQueryUI Buttons module. The Webform jQueryUI Buttons module is deprecated because jQueryUI is no longer maintained.",
"drupal/jquery_ui_datepicker": "Provides jQuery UI Datepicker library. Required to support datepickers. The Webform jQueryUI Datepicker module is deprecated because jQueryUI is no longer maintained."
},
"repositories": {
"algolia.places": {
......@@ -467,6 +473,42 @@
"composer/installers": "~1.0"
}
}
},
"tippyjs/5.x": {
"type": "package",
"package": {
"name": "tippyjs/5.x",
"version": "5.2.1",
"type": "drupal-library",
"extra": {
"installer-name": "tippyjs/5.x"
},
"dist": {
"url": "https://unpkg.com/tippy.js@5.2.1/dist/tippy-bundle.iife.min.js",
"type": "file"
},
"require": {
"composer/installers": "~1.0"
}
}
},
"tippyjs/6.x": {
"type": "package",
"package": {
"name": "tippyjs/6.x",
"version": "6.2.6",
"type": "drupal-library",
"extra": {
"installer-name": "tippyjs/6.x"
},
"dist": {
"url": "https://unpkg.com/tippy.js@6.2.6/dist/tippy-bundle.umd.min.js",
"type": "file"
},
"require": {
"composer/installers": "~1.0"
}
}
}
}
}
......@@ -30,17 +30,18 @@
}
.webform-element-help--title {
margin: 0 0 0.2em 0;
margin-bottom: 0.2em;
font-size: 1.1em;
font-weight: bold;
}
.ui-tooltip.webform-element-help--tooltip {
max-width: 400px;
.webform-element-help--content {
font-weight: normal;
}
@media only screen and (max-width: 400px) {
.ui-tooltip.webform-element-help--tooltip {
max-width: 300px;
}
.webform-element-help--content a:link,
.webform-element-help--content a:visited,
.webform-element-help--content a:hover,
.webform-element-help--content a:active {
color: #9cf;
}
......@@ -113,6 +113,7 @@ html.js .webform-message--close .webform-message__link:active {
table th {
height: auto;
position: inherit;
}
/**
......@@ -278,37 +279,6 @@ div.webform-form-filter input.webform-form-filter-text {
margin-bottom: 0;
}
/***************************************************************************** */
/* jQuery UI */
/***************************************************************************** */
/**
* jQuery UI tooltip.
*
* Used for webform element help (tooltips).
*
* @see core/assets/vendor/jquery.ui/themes/base/tabs.css
*/
.ui-tooltip.ui-widget {
border: 1px solid #ed5;
border-radius: 2px;
background: #fe6;
}
.ui-tooltip hr {
height: 1px;
margin: 0.3em 0;
background: #666;
}
.ui-tooltip code {
padding: 0.2em;
white-space: nowrap;
background-color: #f5f5f2;
font-size: 0.9em;
font-weight: bold;
}
/**
* jQuery UI tabs.
*/
......
......@@ -46,14 +46,6 @@ code {
font-weight: bold;
}
/* Add yellow background to tooltips and tabs */
/* @see core/assets/vendor/jquery.ui/themes/base/tabs.css */
.ui-tooltip.ui-widget {
border: 1px solid #ed5;
border-radius: 2px;
background: #fe6;
}
.webform-tabs .ui-tabs-nav .ui-tabs-anchor {
text-decoration: inherit;
}
......@@ -185,21 +177,6 @@ pre.webform-codemirror-runmode {
background: #fe6;
}
/* jQuery UI tooltip */
.ui-tooltip hr {
height: 1px;
margin: 0.3em 0;
background: #666;
}
.ui-tooltip code {
padding: 0.2em;
white-space: nowrap;
background-color: #f5f5f2;
font-size: 0.9em;
font-weight: bold;
}
/* jQuery UI tabs */
.ui-tabs .ui-tabs-panel {
padding: 0;
......
......@@ -296,7 +296,7 @@ Standard and custom properties allow for:
- Input masks (using [jquery.inputmask](https://github.com/RobinHerbots/jquery.inputmask))
- [Select2](https://select2.github.io/) or [Chosen](https://harvesthq.github.io/chosen/) replacement of select boxes
- Word and character counting for text elements
- Help tooltips (using [jQuery UI Tooltip](https://jqueryui.com/tooltip/))
- Help tooltips
- More information slideouts
- Regular expression pattern validation
- Private elements, visible only to administrators
......
......@@ -56,6 +56,16 @@ function webform_library_info_alter(&$libraries, $extension) {
return;
}
// Use Tippy.js 6.x which is compatible with Drupal 9.x.
if (isset($libraries['libraries.tippyjs']) && floatval(\Drupal::VERSION) >= 9) {
/** @var \Drupal\webform\WebformLibrariesManagerInterface $libraries_manager */
$libraries_manager = \Drupal::service('webform.libraries_manager');
$tippyjs_library = $libraries_manager->getLibrary('tippyjs/6.x');
$libraries['libraries.tippyjs']['version'] = $tippyjs_library['version'];
$libraries['libraries.tippyjs']['cdn'] = ['/libraries/tippyjs/6.x/' => 'https://unpkg.com/tippy.js@' . $tippyjs_library['version'] . '/dist/'];
$libraries['libraries.tippyjs']['js'] = ['/libraries/tippyjs/6.x/' . basename($tippyjs_library['download_url']->toString()) => []];
}
// If webform date element in D8 use the old dependency on 'core/drupal.date'.
if (isset($libraries['webform.element.date']) && floatval(\Drupal::VERSION) < 9) {
$libraries['webform.element.date']['dependencies'] = ['core/drupal.date', 'core/drupalSettings'];
......
......@@ -7,26 +7,11 @@
'use strict';
// @see http://api.jqueryui.com/tooltip/
// @see https://atomiks.github.io/tippyjs/v5/all-props/
// @see https://atomiks.github.io/tippyjs/v6/all-props/
Drupal.webform = Drupal.webform || {};
Drupal.webform.elementHelpIcon = Drupal.webform.elementHelpIcon || {};
Drupal.webform.elementHelpIcon.options = Drupal.webform.elementHelpIcon.options || {
position: {my: 'left+5 top+5', at: 'left bottom', collision: 'flipfit'},
tooltipClass: 'webform-element-help--tooltip',
// @see https://stackoverflow.com/questions/18231315/jquery-ui-tooltip-html-with-links
show: {delay: 100},
close: function (event, ui) {
ui.tooltip.hover(
function () {
$(this).stop(true).fadeTo(400, 1);
},
function () {
$(this).fadeOut('400', function () {
$(this).remove();
});
});
}
};
Drupal.webform.elementHelpIcon.options = Drupal.webform.elementHelpIcon.options || {};
/**
* Element help icon.
......@@ -35,42 +20,26 @@
*/
Drupal.behaviors.webformElementHelpIcon = {
attach: function (context) {
if (!window.tippy) {
return;
}
$(context).find('.js-webform-element-help').once('webform-element-help').each(function () {
var $link = $(this);
$link.on('click', function (event) {
// Prevent click from toggling <label>s wrapped around help.
event.preventDefault();
});
var options = $.extend({
// Use 'data-webform-help' attribute which can include HTML markup.
content: $link.attr('data-webform-help'),
items: '[data-webform-help]'
delay: 100,
allowHTML: true,
interactive: true
}, Drupal.webform.elementHelpIcon.options);
$link.tooltip(options)
.on('click', function (event) {
// Prevent click from toggling <label>s wrapped around help.
event.preventDefault();
}).on('keydown', function (event) {
// Prevent ESC from from closing dialogs.
if (event.keyCode === $.ui.keyCode.ESCAPE) {
event.stopPropagation();
}
});
// Help tooltips are generally placed with <label> tags.
// Screen readers are also reading the <label> and the
// 'aria-describedby' attribute.
// To prevent this issue we are removing the <label>'s 'for' attribute
// when the tooltip is focused.
var $label = $(this).parent('label');
var labelFor = $label.attr('for') || '';
if ($label.length && labelFor) {
$link
.on('focus', function () {
$label.removeAttr('for');
})
.on('blur', function () {
$label.attr('for', labelFor);
});
}
tippy(this, options);
});
}
};
......
/**
* @file
* JavaScript behaviors for jQuery UI tooltip integration.
*
* Please Note:
* jQuery UI's tooltip implementation is not very responsive or adaptive.
*
* @see https://www.drupal.org/node/2207383
* JavaScript behaviors for Tippy.js tooltip integration.
*/
(function ($, Drupal) {
......@@ -13,22 +8,11 @@
'use strict';
var tooltipDefaultOptions = {
// @see https://stackoverflow.com/questions/18231315/jquery-ui-tooltip-html-with-links
show: {delay: 100},
close: function (event, ui) {
ui.tooltip.hover(
function () {
$(this).stop(true).fadeTo(400, 1);
},
function () {
$(this).fadeOut('400', function () {
$(this).remove();
});
});
}
delay: 100
};
// @see http://api.jqueryui.com/tooltip/
// @see https://atomiks.github.io/tippyjs/v5/all-props/
// @see https://atomiks.github.io/tippyjs/v6/all-props/
Drupal.webform = Drupal.webform || {};
Drupal.webform.tooltipElement = Drupal.webform.tooltipElement || {};
......@@ -38,17 +22,20 @@
Drupal.webform.tooltipLink.options = Drupal.webform.tooltipLink.options || tooltipDefaultOptions;
/**
* Initialize jQuery UI tooltip element support.
* Initialize tooltip element support.
*
* @type {Drupal~behavior}
*/
Drupal.behaviors.webformTooltipElement = {
attach: function (context) {
$(context).find('.js-webform-tooltip-element').once('webform-tooltip-element').each(function () {
var $element = $(this);
if (!window.tippy) {
return;
}
$(context).find('.js-webform-tooltip-element').once('webform-tooltip-element').each(function () {
// Checkboxes, radios, buttons, toggles, etc… use fieldsets.
// @see \Drupal\webform\Plugin\WebformElement\OptionsBase::prepare
var $element = $(this);
var $description;
if ($element.is('fieldset')) {
$description = $element.find('> .fieldset-wrapper > .description > .webform-element-description.visually-hidden');
......@@ -57,29 +44,12 @@
$description = $element.find('> .description > .webform-element-description.visually-hidden');
}
var isFileButton = $element.find('label.webform-file-button').length;
var hasVisibleInput = $element.find(':input:not([type=hidden])').length;
var hasCheckboxesOrRadios = $element.find(':checkbox, :radio').length;
var isComposite = $element.hasClass('form-composite');
var isCustom = $element.is('.js-form-type-webform-signature, .js-form-type-webform-image-select, .js-form-type-webform-mapping, .js-form-type-webform-rating, .js-form-type-datelist, .js-form-type-datetime');
var items;
if (isFileButton) {
items = 'label.webform-file-button';
}
else if (hasVisibleInput && !hasCheckboxesOrRadios && !isComposite && !isCustom) {
items = ':input';
}
else {
items = $element;
}
var options = $.extend({
items: items,
content: $description.html()
content: $description.html(),
allowHTML: true
}, Drupal.webform.tooltipElement.options);
$element.tooltip(options);
tippy(this, options);
});
}
};
......@@ -91,12 +61,16 @@
*/
Drupal.behaviors.webformTooltipLink = {
attach: function (context) {
$(context).find('.js-webform-tooltip-link').once('webform-tooltip-link').each(function () {
var $link = $(this);
if (!window.tippy) {
return;
}
var options = $.extend({}, Drupal.webform.tooltipLink.options);
$(context).find('.js-webform-tooltip-link').once('webform-tooltip-link').each(function () {
var options = $.extend({
allowHTML: true
}, Drupal.webform.tooltipLink.options);
$link.tooltip(options);
tippy(this, options);
});
}
};
......
/**
* @file
* JavaScript behaviors for Bootstrap element help text (tooltip).
*
* @see js/webform.element.help.js
*/
(function ($, Drupal) {
'use strict';
// @see http://bootstrapdocs.com/v3.0.3/docs/javascript/#tooltips-usage
Drupal.webformBootstrap = Drupal.webformBootstrap || {};
Drupal.webformBootstrap.elementHelpIcon = Drupal.webformBootstrap.elementHelpIcon || {};
Drupal.webformBootstrap.elementHelpIcon.options = Drupal.webformBootstrap.elementHelpIcon.options || {
trigger: 'hover focus click',
placement: 'auto right',
delay: 200
};
/**
* Bootstrap element help icon.
*
* @type {Drupal~behavior}
*/
Drupal.behaviors.webformBootstrapElementHelpIcon = {
attach: function (context) {
$(context).find('.webform-element-help').once('webform-element-help').each(function () {
var $link = $(this);
var options = $.extend({
title: $link.attr('data-webform-help'),
html: true
}, Drupal.webformBootstrap.elementHelpIcon.options);
$link.tooltip(options)
.on('click', function (event) {
// Prevent click from toggling <label>s wrapped around help.
event.preventDefault();
});
});
}
};
})(jQuery, Drupal);
......@@ -46,13 +46,6 @@ function webform_bootstrap_webform_element_alter(array &$element, FormStateInter
return;
}
// Remove jQuery Tooltip and use Bootstrap Tooltip.
// @see \Drupal\webform\Plugin\WebformElementBase::prepare
if (isset($element['#attached']['library'][0]) && $element['#attached']['library'][0] === 'webform/webform.tooltip') {
unset($element['#attached']['library'][0]);
$element['#attached']['library'] = array_values($element['#attached']['library']);
}
// Convert #description are being changed smart descriptions which
// contain render arrays to rendered markup.
// @see \Drupal\bootstrap\Utility\Element::smartDescription
......@@ -101,28 +94,6 @@ function webform_bootstrap_webform_element_alter(array &$element, FormStateInter
}
}
/**
* Implements hook_js_alter().
*/
function webform_bootstrap_js_alter(&$javascript, AttachedAssetsInterface $assets) {
if (!_webform_bootstrap_is_active_theme()) {
return;
}
// Make sure jQuery tooltip is never loaded and use
// bootstrap specific webform.element.help.js.
foreach ($javascript as $javascript_path => $javascript_item) {
if (strpos($javascript_path, 'tooltip-min') !== FALSE) {
unset($javascript[$javascript_path]);
}
}
$webform_help_path = drupal_get_path('module', 'webform') . '/js/webform.element.help.js';
$webform_bootstrap_help_path = drupal_get_path('module', 'webform_bootstrap') . '/js/webform.element.help.js';;
if (isset($javascript[$webform_help_path])) {
$javascript[$webform_help_path]['data'] = $webform_bootstrap_help_path;
}
}
/**
* Implements hook_link_alter().
*/
......
......@@ -61,20 +61,6 @@ html.js .webform-options-custom[data-select-hidden] .js-form-type-select {
fill: #eee;
}
/**
* Tooltip.
*/
.ui-tooltip.webform-options-custom-tooltip {
max-width: 400px;
cursor: pointer;
user-select: none;
}
.webform-options-custom-tooltip--text {
font-weight: bold;
}
@media only screen and (max-width : 400px) {
.ui-tooltip.webform-options-custom-tooltip {
max-width: 300px;
}
}
......@@ -10,30 +10,11 @@
Drupal.webformOptionsCustom = Drupal.webformOptionsCustom || {};
// @see http://api.jqueryui.com/tooltip/
Drupal.webformOptionsCustom.jQueryUiTooltip = Drupal.webformOptionsCustom.jQueryUiTooltip || {};
Drupal.webformOptionsCustom.jQueryUiTooltip.options = Drupal.webformOptionsCustom.jQueryUiTooltip.options || {
tooltipClass: 'webform-options-custom-tooltip',
track: true,
// @see
// https://stackoverflow.com/questions/18231315/jquery-ui-tooltip-html-with-links
show: {delay: 300},
close: function (event, ui) {
ui.tooltip.hover(
function () {
$(this).stop(true).fadeTo(400, 1);
},
function () {
$(this).fadeOut('400', function () {
$(this).remove();
});
});
}
};
// @see http://bootstrapdocs.com/v3.0.3/docs/javascript/#tooltips-usage
Drupal.webformOptionsCustom.bootstrapTooltip = Drupal.webformOptionsCustom.bootstrapTooltip || {};
Drupal.webformOptionsCustom.bootstrapTooltip.options = Drupal.webformOptionsCustom.bootstrapTooltip.options || {
delay: 200
Drupal.webformOptionsCustom.tippy = Drupal.webformOptionsCustom.tippy || {};
Drupal.webformOptionsCustom.tippy.options = Drupal.webformOptionsCustom.tippy.options || {
delay: 300,
allowHTML: true,
followCursor: true
};
// @see https://github.com/ariutta/svg-pan-zoom
......@@ -111,6 +92,7 @@
// Template event handling.
$template
.on('click', setTemplateValue)
.on('click', setTemplateValue)
.on('keydown', function (event) {
var $templateOption = $(event.target);
......@@ -327,7 +309,7 @@
* The select option.
*/
function initializeTemplateTooltip($templateOption, option) {
if (!hasTooltip) {
if (!hasTooltip || !window.tippy) {
return;
}
......@@ -336,36 +318,10 @@
content += '<div class="webform-options-custom-tooltip--description">' + option.description + '</div>';
}
if (typeof $.ui.tooltip !== 'undefined') {
// jQuery UI tooltip support.
var tooltipOptions = $.extend({
content: content,
items: '[data-option-value]',
open: function (event, ui) {
$(ui.tooltip).on('click', function () {
var value = $(this)
.find('[data-tooltip-value]')
.attr('data-tooltip-value');
setValue(value);
});
}
}, Drupal.webformOptionsCustom.jQueryUiTooltip.options);
$templateOption.tooltip(tooltipOptions);
}
else if ((typeof $.fn.tooltip) !== 'undefined') {
// Bootstrap tooltip support.
var options = $.extend({
html: true,
title: content
}, Drupal.webformOptionsCustom.bootstrapTooltip.options);
$templateOption
.tooltip(options)
.on('show.bs.tooltip', function (event) {
$templateOptions.not($templateOption).tooltip('hide');
});
}
var tooltipOptions = $.extend({