diff --git a/core/modules/contextual/contextual.libraries.yml b/core/modules/contextual/contextual.libraries.yml
index bfc1c996c9..8fa2ac3e25 100644
--- a/core/modules/contextual/contextual.libraries.yml
+++ b/core/modules/contextual/contextual.libraries.yml
@@ -2,15 +2,8 @@ drupal.contextual-links:
version: VERSION
js:
# Ensure to run before contextual/drupal.context-toolbar.
- # Core.
js/contextual.js: { weight: -2 }
- # Models.
- js/models/StateModel.js: { weight: -2 }
- # Views.
- js/views/AuralView.js: { weight: -2 }
- js/views/KeyboardView.js: { weight: -2 }
- js/views/RegionView.js: { weight: -2 }
- js/views/VisualView.js: { weight: -2 }
+ js/contextualModelView.js: { weight: -2 }
css:
component:
css/contextual.module.css: {}
@@ -22,8 +15,6 @@ drupal.contextual-links:
- core/drupal
- core/drupal.ajax
- core/drupalSettings
- # @todo Remove this in https://www.drupal.org/project/drupal/issues/3203920
- - core/internal.backbone
- core/once
- core/drupal.touchevents-test
@@ -31,19 +22,13 @@ drupal.contextual-toolbar:
version: VERSION
js:
js/contextual.toolbar.js: {}
- # Models.
- js/toolbar/models/StateModel.js: {}
- # Views.
- js/toolbar/views/AuralView.js: {}
- js/toolbar/views/VisualView.js: {}
+ js/toolbar/contextualToolbarModelView.js: {}
css:
component:
css/contextual.toolbar.css: {}
dependencies:
- core/jquery
- core/drupal
- # @todo Remove this in https://www.drupal.org/project/drupal/issues/3203920
- - core/internal.backbone
- core/once
- core/drupal.tabbingmanager
- core/drupal.announce
diff --git a/core/modules/contextual/css/contextual.theme.css b/core/modules/contextual/css/contextual.theme.css
index d84444d18d..39c0b34424 100644
--- a/core/modules/contextual/css/contextual.theme.css
+++ b/core/modules/contextual/css/contextual.theme.css
@@ -17,6 +17,10 @@
left: 0;
}
+.contextual.open {
+ z-index: 501;
+}
+
/**
* Contextual region.
*/
diff --git a/core/modules/contextual/js/contextual.js b/core/modules/contextual/js/contextual.js
index b5fe7d094f..1087095ef9 100644
--- a/core/modules/contextual/js/contextual.js
+++ b/core/modules/contextual/js/contextual.js
@@ -3,7 +3,7 @@
* Attaches behaviors for the Contextual module.
*/
-(function ($, Drupal, drupalSettings, _, Backbone, JSON, storage) {
+(function ($, Drupal, drupalSettings, JSON, storage) {
const options = $.extend(
drupalSettings.contextual,
// Merge strings on top of drupalSettings so that they are not mutable.
@@ -20,16 +20,14 @@
const cachedPermissionsHash = storage.getItem(
'Drupal.contextual.permissionsHash',
);
- const permissionsHash = drupalSettings.user.permissionsHash;
+ const { permissionsHash } = drupalSettings.user;
if (cachedPermissionsHash !== permissionsHash) {
if (typeof permissionsHash === 'string') {
- _.chain(storage)
- .keys()
- .each((key) => {
- if (key.substring(0, 18) === 'Drupal.contextual.') {
- storage.removeItem(key);
- }
- });
+ Object.keys(storage).forEach((key) => {
+ if (key.substring(0, 18) === 'Drupal.contextual.') {
+ storage.removeItem(key);
+ }
+ });
}
storage.setItem('Drupal.contextual.permissionsHash', permissionsHash);
}
@@ -45,7 +43,8 @@
*/
function adjustIfNestedAndOverlapping($contextual) {
const $contextuals = $contextual
- // @todo confirm that .closest() is not sufficient
+ // Parents is used instead of closest because we need the outer-most
+ // element, not the closest one.
.parents('.contextual-region')
.eq(-1)
.find('.contextual');
@@ -86,8 +85,6 @@
*/
function initContextual($contextual, html) {
const $region = $contextual.closest('.contextual-region');
- const contextual = Drupal.contextual;
-
$contextual
// Update the placeholder to contain its rendered contextual links.
.html(html)
@@ -113,23 +110,17 @@
title = $regionHeading[0].textContent.trim();
}
// Create a model and the appropriate views.
- const model = new contextual.StateModel({
- title,
- });
- const viewOptions = $.extend({ el: $contextual, model }, options);
- contextual.views.push({
- visual: new contextual.VisualView(viewOptions),
- aural: new contextual.AuralView(viewOptions),
- keyboard: new contextual.KeyboardView(viewOptions),
- });
- contextual.regionViews.push(
- new contextual.RegionView($.extend({ el: $region, model }, options)),
- );
+ options.title = title;
// Add the model to the collection. This must happen after the views have
// been associated with it, otherwise collection change event handlers can't
// trigger the model change event handler in its views.
- contextual.collection.add(model);
+ const contextualModelView = new Drupal.contextual.ContextualModelView(
+ $contextual,
+ $region,
+ options,
+ );
+ Drupal.contextual.instances.push(contextualModelView);
// Let other JavaScript react to the adding of a new contextual link.
$(document).trigger(
@@ -138,7 +129,8 @@
target: {
$el: $contextual,
$region,
- model,
+ // Send the new object.
+ contextualModelView,
},
deprecatedProperty: 'model',
message:
@@ -164,59 +156,57 @@
*/
Drupal.behaviors.contextual = {
attach(context) {
- const $context = $(context);
-
// Find all contextual links placeholders, if any.
- let $placeholders = $(
- once('contextual-render', '[data-contextual-id]', context),
+ const placeholders = once(
+ 'contextual-render',
+ '[data-contextual-id]',
+ context,
);
- if ($placeholders.length === 0) {
+ if (placeholders.length === 0) {
return;
}
+ const $context = $(context);
+ const uncached = {
+ ids: [],
+ tokens: [],
+ };
+
// Collect the IDs for all contextual links placeholders.
- const ids = [];
- $placeholders.each(function () {
- ids.push({
- id: $(this).attr('data-contextual-id'),
- token: $(this).attr('data-contextual-token'),
+ placeholders
+ .map((placeholder) => ({
+ id: placeholder.dataset.contextualId,
+ token: placeholder.dataset.contextualToken,
+ }))
+ .forEach(({ id, token }) => {
+ const html = storage.getItem(`Drupal.contextual.${id}`);
+ if (html && html.length) {
+ // Initialize after the current execution cycle, to make the AJAX
+ // request for retrieving the uncached contextual links as soon as
+ // possible.
+ window.setTimeout(() => {
+ initContextual(
+ $context.find(`[data-contextual-id="${id}"]:empty`).eq(0),
+ html,
+ );
+ });
+ return;
+ }
+ uncached.ids.push(id);
+ uncached.tokens.push(token);
});
- });
-
- const uncachedIDs = [];
- const uncachedTokens = [];
- ids.forEach((contextualID) => {
- const html = storage.getItem(`Drupal.contextual.${contextualID.id}`);
- if (html && html.length) {
- // Initialize after the current execution cycle, to make the AJAX
- // request for retrieving the uncached contextual links as soon as
- // possible, but also to ensure that other Drupal behaviors have had
- // the chance to set up an event listener on the Backbone collection
- // Drupal.contextual.collection.
- window.setTimeout(() => {
- initContextual(
- $context
- .find(`[data-contextual-id="${contextualID.id}"]:empty`)
- .eq(0),
- html,
- );
- });
- return;
- }
- uncachedIDs.push(contextualID.id);
- uncachedTokens.push(contextualID.token);
- });
// Perform an AJAX request to let the server render the contextual links
// for each of the placeholders.
- if (uncachedIDs.length > 0) {
+ if (uncached.ids.length > 0) {
$.ajax({
url: Drupal.url('contextual/render'),
type: 'POST',
- data: { 'ids[]': uncachedIDs, 'tokens[]': uncachedTokens },
+ data: uncached,
dataType: 'json',
success(results) {
- _.each(results, (html, contextualID) => {
+ Object.keys(results).forEach((contextualID) => {
+ const html = results[contextualID];
// Store the metadata.
storage.setItem(`Drupal.contextual.${contextualID}`, html);
// If the rendered contextual links are empty, then the current
@@ -228,14 +218,18 @@
// possible for multiple identical placeholders exist on the
// page (probably because the same content appears more than
// once).
- $placeholders = $context.find(
- `[data-contextual-id="${contextualID}"]`,
- );
-
- // Initialize the contextual links.
- for (let i = 0; i < $placeholders.length; i++) {
- initContextual($placeholders.eq(i), html);
- }
+ once
+ .filter(
+ 'contextual-render',
+ `[data-contextual-id="${contextualID}"]`,
+ context,
+ )
+ // Transform DOM elements to jQuery objects.
+ .map($)
+ // Initialize the contextual links.
+ .forEach(($placeholder) => {
+ initContextual($placeholder, html);
+ });
}
});
},
@@ -253,49 +247,23 @@
*/
Drupal.contextual = {
/**
- * The {@link Drupal.contextual.View} instances associated with each list
- * element of contextual links.
- *
- * @type {Array}
- *
- * @deprecated in drupal:9.4.0 and is removed from drupal:10.0.0. There is no
- * replacement.
- */
- views: [],
-
- /**
- * The {@link Drupal.contextual.RegionView} instances associated with each
- * contextual region element.
+ * Instances is instantiated as a proxy so an event can be triggered when
+ * items are added.
*
- * @type {Array}
+ * @private
*
- * @deprecated in drupal:9.4.0 and is removed from drupal:10.0.0. There is no
- * replacement.
*/
- regionViews: [],
+ instances: [],
};
- /**
- * A Backbone.Collection of {@link Drupal.contextual.StateModel} instances.
- *
- * @type {Backbone.Collection}
- *
- * @deprecated in drupal:9.4.0 and is removed from drupal:10.0.0. There is no
- * replacement.
- */
- Drupal.contextual.collection = new Backbone.Collection([], {
- model: Drupal.contextual.StateModel,
- });
-
/**
* A trigger is an interactive element often bound to a click handler.
*
* @return {string}
* A string representing a DOM fragment.
*/
- Drupal.theme.contextualTrigger = function () {
- return '';
- };
+ Drupal.theme.contextualTrigger = () =>
+ '';
/**
* Bind Ajax contextual links when added.
@@ -310,12 +278,4 @@
$(document).on('drupalContextualLinkAdded', (event, data) => {
Drupal.ajax.bindAjaxLinks(data.$el[0]);
});
-})(
- jQuery,
- Drupal,
- drupalSettings,
- _,
- Backbone,
- window.JSON,
- window.sessionStorage,
-);
+})(jQuery, Drupal, drupalSettings, window.JSON, window.sessionStorage);
diff --git a/core/modules/contextual/js/contextual.toolbar.js b/core/modules/contextual/js/contextual.toolbar.js
index 2cbac3e139..71178bcd56 100644
--- a/core/modules/contextual/js/contextual.toolbar.js
+++ b/core/modules/contextual/js/contextual.toolbar.js
@@ -3,7 +3,7 @@
* Attaches behaviors for the Contextual module's edit toolbar tab.
*/
-(function ($, Drupal, Backbone) {
+(function ($, Drupal) {
const strings = {
tabbingReleased: Drupal.t(
'Tabbing is no longer constrained by the Contextual module.',
@@ -15,39 +15,20 @@
};
/**
- * Initializes a contextual link: updates its DOM, sets up model and views.
+ * Namespace for the contextual toolbar.
*
- * @param {HTMLElement} context
- * A contextual links DOM element as rendered by the server.
+ * @namespace
*/
- function initContextualToolbar(context) {
- if (!Drupal.contextual || !Drupal.contextual.collection) {
- return;
- }
-
- const contextualToolbar = Drupal.contextualToolbar;
- contextualToolbar.model = new contextualToolbar.StateModel(
- {
- // Checks whether localStorage indicates we should start in edit mode
- // rather than view mode.
- // @see Drupal.contextualToolbar.VisualView.persist
- isViewing:
- localStorage.getItem('Drupal.contextualToolbar.isViewing') !==
- 'false',
- },
- {
- contextualCollection: Drupal.contextual.collection,
- },
- );
-
- const viewOptions = {
- el: $('.toolbar .toolbar-bar .contextual-toolbar-tab'),
- model: contextualToolbar.model,
- strings,
- };
- new contextualToolbar.VisualView(viewOptions);
- new contextualToolbar.AuralView(viewOptions);
- }
+ Drupal.contextualToolbar = {
+ /**
+ * The {@link Drupal.contextualToolbar.ContextualToolbarModelView} instance.
+ *
+ * @type {?Drupal.contextualToolbar.ContextualToolbarModelView}
+ *
+ * @private
+ */
+ model: null,
+ };
/**
* Attaches contextual's edit toolbar tab behavior.
@@ -58,29 +39,21 @@
* Attaches contextual toolbar behavior on a contextualToolbar-init event.
*/
Drupal.behaviors.contextualToolbar = {
- attach(context) {
- if (once('contextualToolbar-init', 'body').length) {
- initContextualToolbar(context);
+ attach() {
+ if (!once('contextualToolbar-init', 'body').length) {
+ return;
+ }
+ if (!Drupal.contextual || !Drupal.contextual.instances) {
+ return;
}
+ Drupal.contextualToolbar.model =
+ new Drupal.contextualToolbar.ContextualToolbarModelView({
+ el: $('.toolbar .toolbar-bar .contextual-toolbar-tab'),
+ strings,
+ isViewing:
+ localStorage.getItem('Drupal.contextualToolbar.isViewing') !==
+ 'false',
+ });
},
};
-
- /**
- * Namespace for the contextual toolbar.
- *
- * @namespace
- *
- * @private
- */
- Drupal.contextualToolbar = {
- /**
- * The {@link Drupal.contextualToolbar.StateModel} instance.
- *
- * @type {?Drupal.contextualToolbar.StateModel}
- *
- * @deprecated in drupal:9.4.0 and is removed from drupal:10.0.0. There is
- * no replacement.
- */
- model: null,
- };
-})(jQuery, Drupal, Backbone);
+})(jQuery, Drupal);
diff --git a/core/modules/contextual/js/models/StateModel.js b/core/modules/contextual/js/models/StateModel.js
deleted file mode 100644
index 383037a7f2..0000000000
--- a/core/modules/contextual/js/models/StateModel.js
+++ /dev/null
@@ -1,130 +0,0 @@
-/**
- * @file
- * A Backbone Model for the state of a contextual link's trigger, list & region.
- */
-
-(function (Drupal, Backbone) {
- /**
- * Models the state of a contextual link's trigger, list & region.
- *
- * @constructor
- *
- * @augments Backbone.Model
- *
- * @deprecated in drupal:9.4.0 and is removed from drupal:10.0.0. There is no
- * replacement.
- */
- Drupal.contextual.StateModel = Backbone.Model.extend(
- /** @lends Drupal.contextual.StateModel# */ {
- /**
- * @type {object}
- *
- * @prop {string} title
- * @prop {boolean} regionIsHovered
- * @prop {boolean} hasFocus
- * @prop {boolean} isOpen
- * @prop {boolean} isLocked
- */
- defaults: /** @lends Drupal.contextual.StateModel# */ {
- /**
- * The title of the entity to which these contextual links apply.
- *
- * @type {string}
- */
- title: '',
-
- /**
- * Represents if the contextual region is being hovered.
- *
- * @type {boolean}
- */
- regionIsHovered: false,
-
- /**
- * Represents if the contextual trigger or options have focus.
- *
- * @type {boolean}
- */
- hasFocus: false,
-
- /**
- * Represents if the contextual options for an entity are available to
- * be selected (i.e. whether the list of options is visible).
- *
- * @type {boolean}
- */
- isOpen: false,
-
- /**
- * When the model is locked, the trigger remains active.
- *
- * @type {boolean}
- */
- isLocked: false,
- },
-
- /**
- * Opens or closes the contextual link.
- *
- * If it is opened, then also give focus.
- *
- * @return {Drupal.contextual.StateModel}
- * The current contextual state model.
- */
- toggleOpen() {
- const newIsOpen = !this.get('isOpen');
- this.set('isOpen', newIsOpen);
- if (newIsOpen) {
- this.focus();
- }
- return this;
- },
-
- /**
- * Closes this contextual link.
- *
- * Does not call blur() because we want to allow a contextual link to have
- * focus, yet be closed for example when hovering.
- *
- * @return {Drupal.contextual.StateModel}
- * The current contextual state model.
- */
- close() {
- this.set('isOpen', false);
- return this;
- },
-
- /**
- * Gives focus to this contextual link.
- *
- * Also closes + removes focus from every other contextual link.
- *
- * @return {Drupal.contextual.StateModel}
- * The current contextual state model.
- */
- focus() {
- this.set('hasFocus', true);
- const cid = this.cid;
- this.collection.each((model) => {
- if (model.cid !== cid) {
- model.close().blur();
- }
- });
- return this;
- },
-
- /**
- * Removes focus from this contextual link, unless it is open.
- *
- * @return {Drupal.contextual.StateModel}
- * The current contextual state model.
- */
- blur() {
- if (!this.get('isOpen')) {
- this.set('hasFocus', false);
- }
- return this;
- },
- },
- );
-})(Drupal, Backbone);
diff --git a/core/modules/contextual/js/toolbar/models/StateModel.js b/core/modules/contextual/js/toolbar/models/StateModel.js
deleted file mode 100644
index f1d6e71aa6..0000000000
--- a/core/modules/contextual/js/toolbar/models/StateModel.js
+++ /dev/null
@@ -1,126 +0,0 @@
-/**
- * @file
- * A Backbone Model for the state of Contextual module's edit toolbar tab.
- */
-
-(function (Drupal, Backbone) {
- /**
- * @deprecated in drupal:9.4.0 and is removed from drupal:10.0.0. There is no
- * replacement.
- */
- Drupal.contextualToolbar.StateModel = Backbone.Model.extend(
- /** @lends Drupal.contextualToolbar.StateModel# */ {
- /**
- * @type {object}
- *
- * @prop {boolean} isViewing
- * @prop {boolean} isVisible
- * @prop {number} contextualCount
- * @prop {Drupal~TabbingContext} tabbingContext
- */
- defaults: /** @lends Drupal.contextualToolbar.StateModel# */ {
- /**
- * Indicates whether the toggle is currently in "view" or "edit" mode.
- *
- * @type {boolean}
- */
- isViewing: true,
-
- /**
- * Indicates whether the toggle should be visible or hidden. Automatically
- * calculated, depends on contextualCount.
- *
- * @type {boolean}
- */
- isVisible: false,
-
- /**
- * Tracks how many contextual links exist on the page.
- *
- * @type {number}
- */
- contextualCount: 0,
-
- /**
- * A TabbingContext object as returned by {@link Drupal~TabbingManager}:
- * the set of tabbable elements when edit mode is enabled.
- *
- * @type {?Drupal~TabbingContext}
- */
- tabbingContext: null,
- },
-
- /**
- * Models the state of the edit mode toggle.
- *
- * @constructs
- *
- * @augments Backbone.Model
- *
- * @param {object} attrs
- * Attributes for the backbone model.
- * @param {object} options
- * An object with the following option:
- * @param {Backbone.collection} options.contextualCollection
- * The collection of {@link Drupal.contextual.StateModel} models that
- * represent the contextual links on the page.
- */
- initialize(attrs, options) {
- // Respond to new/removed contextual links.
- this.listenTo(
- options.contextualCollection,
- 'reset remove add',
- this.countContextualLinks,
- );
- this.listenTo(
- options.contextualCollection,
- 'add',
- this.lockNewContextualLinks,
- );
-
- // Automatically determine visibility.
- this.listenTo(this, 'change:contextualCount', this.updateVisibility);
-
- // Whenever edit mode is toggled, lock all contextual links.
- this.listenTo(this, 'change:isViewing', (model, isViewing) => {
- options.contextualCollection.each((contextualModel) => {
- contextualModel.set('isLocked', !isViewing);
- });
- });
- },
-
- /**
- * Tracks the number of contextual link models in the collection.
- *
- * @param {Drupal.contextual.StateModel} contextualModel
- * The contextual links model that was added or removed.
- * @param {Backbone.Collection} contextualCollection
- * The collection of contextual link models.
- */
- countContextualLinks(contextualModel, contextualCollection) {
- this.set('contextualCount', contextualCollection.length);
- },
-
- /**
- * Lock newly added contextual links if edit mode is enabled.
- *
- * @param {Drupal.contextual.StateModel} contextualModel
- * The contextual links model that was added.
- * @param {Backbone.Collection} [contextualCollection]
- * The collection of contextual link models.
- */
- lockNewContextualLinks(contextualModel, contextualCollection) {
- if (!this.get('isViewing')) {
- contextualModel.set('isLocked', true);
- }
- },
-
- /**
- * Automatically updates visibility of the view/edit mode toggle.
- */
- updateVisibility() {
- this.set('isVisible', this.get('contextualCount') > 0);
- },
- },
- );
-})(Drupal, Backbone);
diff --git a/core/modules/contextual/js/toolbar/views/AuralView.js b/core/modules/contextual/js/toolbar/views/AuralView.js
deleted file mode 100644
index 8f1076edd0..0000000000
--- a/core/modules/contextual/js/toolbar/views/AuralView.js
+++ /dev/null
@@ -1,122 +0,0 @@
-/**
- * @file
- * A Backbone View that provides the aural view of the edit mode toggle.
- */
-
-(function ($, Drupal, Backbone, _) {
- /**
- * @deprecated in drupal:9.4.0 and is removed from drupal:10.0.0. There is no
- * replacement.
- */
- Drupal.contextualToolbar.AuralView = Backbone.View.extend(
- /** @lends Drupal.contextualToolbar.AuralView# */ {
- /**
- * Tracks whether the tabbing constraint announcement has been read once.
- *
- * @type {boolean}
- */
- announcedOnce: false,
-
- /**
- * Renders the aural view of the edit mode toggle (screen reader support).
- *
- * @constructs
- *
- * @augments Backbone.View
- *
- * @param {object} options
- * Options for the view.
- */
- initialize(options) {
- this.options = options;
-
- this.listenTo(this.model, 'change', this.render);
- this.listenTo(this.model, 'change:isViewing', this.manageTabbing);
-
- $(document).on('keyup', _.bind(this.onKeypress, this));
- this.manageTabbing();
- },
-
- /**
- * {@inheritdoc}
- *
- * @return {Drupal.contextualToolbar.AuralView}
- * The current contextual toolbar aural view.
- */
- render() {
- // Render the state.
- this.$el
- .find('button')
- .attr('aria-pressed', !this.model.get('isViewing'));
-
- return this;
- },
-
- /**
- * Limits tabbing to the contextual links and edit mode toolbar tab.
- */
- manageTabbing() {
- let tabbingContext = this.model.get('tabbingContext');
- // Always release an existing tabbing context.
- if (tabbingContext) {
- // Only announce release when the context was active.
- if (tabbingContext.active) {
- Drupal.announce(this.options.strings.tabbingReleased);
- }
- tabbingContext.release();
- }
- // Create a new tabbing context when edit mode is enabled.
- if (!this.model.get('isViewing')) {
- tabbingContext = Drupal.tabbingManager.constrain(
- $('.contextual-toolbar-tab, .contextual'),
- );
- this.model.set('tabbingContext', tabbingContext);
- this.announceTabbingConstraint();
- this.announcedOnce = true;
- }
- },
-
- /**
- * Announces the current tabbing constraint.
- */
- announceTabbingConstraint() {
- const strings = this.options.strings;
- Drupal.announce(
- Drupal.formatString(strings.tabbingConstrained, {
- '@contextualsCount': Drupal.formatPlural(
- Drupal.contextual.collection.length,
- '@count contextual link',
- '@count contextual links',
- ),
- }),
- );
- Drupal.announce(strings.pressEsc);
- },
-
- /**
- * Responds to esc and tab key press events.
- *
- * @param {jQuery.Event} event
- * The keypress event.
- */
- onKeypress(event) {
- // The first tab key press is tracked so that an announcement about
- // tabbing constraints can be raised if edit mode is enabled when the page
- // is loaded.
- if (
- !this.announcedOnce &&
- event.keyCode === 9 &&
- !this.model.get('isViewing')
- ) {
- this.announceTabbingConstraint();
- // Set announce to true so that this conditional block won't run again.
- this.announcedOnce = true;
- }
- // Respond to the ESC key. Exit out of edit mode.
- if (event.keyCode === 27) {
- this.model.set('isViewing', true);
- }
- },
- },
- );
-})(jQuery, Drupal, Backbone, _);
diff --git a/core/modules/contextual/js/toolbar/views/VisualView.js b/core/modules/contextual/js/toolbar/views/VisualView.js
deleted file mode 100644
index 8e452e0d8a..0000000000
--- a/core/modules/contextual/js/toolbar/views/VisualView.js
+++ /dev/null
@@ -1,85 +0,0 @@
-/**
- * @file
- * A Backbone View that provides the visual view of the edit mode toggle.
- */
-
-(function (Drupal, Backbone) {
- /**
- * @deprecated in drupal:9.4.0 and is removed from drupal:10.0.0. There is no
- * replacement.
- */
- Drupal.contextualToolbar.VisualView = Backbone.View.extend(
- /** @lends Drupal.contextualToolbar.VisualView# */ {
- /**
- * Events for the Backbone view.
- *
- * @return {object}
- * A mapping of events to be used in the view.
- */
- events() {
- // Prevents delay and simulated mouse events.
- const touchEndToClick = function (event) {
- event.preventDefault();
- event.target.click();
- };
-
- return {
- click() {
- this.model.set('isViewing', !this.model.get('isViewing'));
- },
- touchend: touchEndToClick,
- };
- },
-
- /**
- * Renders the visual view of the edit mode toggle.
- *
- * Listens to mouse & touch and handles edit mode toggle interactions.
- *
- * @constructs
- *
- * @augments Backbone.View
- */
- initialize() {
- this.listenTo(this.model, 'change', this.render);
- this.listenTo(this.model, 'change:isViewing', this.persist);
- },
-
- /**
- * {@inheritdoc}
- *
- * @return {Drupal.contextualToolbar.VisualView}
- * The current contextual toolbar visual view.
- */
- render() {
- // Render the visibility.
- this.$el.toggleClass('hidden', !this.model.get('isVisible'));
- // Render the state.
- this.$el
- .find('button')
- .toggleClass('is-active', !this.model.get('isViewing'));
-
- return this;
- },
-
- /**
- * Model change handler; persists the isViewing value to localStorage.
- *
- * `isViewing === true` is the default, so only stores in localStorage when
- * it's not the default value (i.e. false).
- *
- * @param {Drupal.contextualToolbar.StateModel} model
- * A {@link Drupal.contextualToolbar.StateModel} model.
- * @param {boolean} isViewing
- * The value of the isViewing attribute in the model.
- */
- persist(model, isViewing) {
- if (!isViewing) {
- localStorage.setItem('Drupal.contextualToolbar.isViewing', 'false');
- } else {
- localStorage.removeItem('Drupal.contextualToolbar.isViewing');
- }
- },
- },
- );
-})(Drupal, Backbone);
diff --git a/core/modules/contextual/js/views/AuralView.js b/core/modules/contextual/js/views/AuralView.js
deleted file mode 100644
index 1e317242c1..0000000000
--- a/core/modules/contextual/js/views/AuralView.js
+++ /dev/null
@@ -1,59 +0,0 @@
-/**
- * @file
- * A Backbone View that provides the aural view of a contextual link.
- */
-
-(function (Drupal, Backbone) {
- /**
- * @deprecated in drupal:9.4.0 and is removed from drupal:10.0.0. There is no
- * replacement.
- */
- Drupal.contextual.AuralView = Backbone.View.extend(
- /** @lends Drupal.contextual.AuralView# */ {
- /**
- * Renders the aural view of a contextual link (i.e. screen reader support).
- *
- * @constructs
- *
- * @augments Backbone.View
- *
- * @param {object} options
- * Options for the view.
- */
- initialize(options) {
- this.options = options;
-
- this.listenTo(this.model, 'change', this.render);
-
- // Initial render.
- this.render();
- },
-
- /**
- * {@inheritdoc}
- */
- render() {
- const isOpen = this.model.get('isOpen');
-
- // Set the hidden property of the links.
- this.$el.find('.contextual-links').prop('hidden', !isOpen);
-
- // Update the view of the trigger.
- const $trigger = this.$el.find('.trigger');
- $trigger
- .each((index, element) => {
- element.textContent = Drupal.t(
- '@action @title configuration options',
- {
- '@action': !isOpen
- ? this.options.strings.open
- : this.options.strings.close,
- '@title': this.model.get('title'),
- },
- );
- })
- .attr('aria-pressed', isOpen);
- },
- },
- );
-})(Drupal, Backbone);
diff --git a/core/modules/contextual/js/views/KeyboardView.js b/core/modules/contextual/js/views/KeyboardView.js
deleted file mode 100644
index d7bb101835..0000000000
--- a/core/modules/contextual/js/views/KeyboardView.js
+++ /dev/null
@@ -1,62 +0,0 @@
-/**
- * @file
- * A Backbone View that provides keyboard interaction for a contextual link.
- */
-
-(function (Drupal, Backbone) {
- /**
- * @deprecated in drupal:9.4.0 and is removed from drupal:10.0.0. There is no
- * replacement.
- */
- Drupal.contextual.KeyboardView = Backbone.View.extend(
- /** @lends Drupal.contextual.KeyboardView# */ {
- /**
- * @type {object}
- */
- events: {
- 'focus .trigger': 'focus',
- 'focus .contextual-links a': 'focus',
- 'blur .trigger': function () {
- this.model.blur();
- },
- 'blur .contextual-links a': function () {
- // Set up a timeout to allow a user to tab between the trigger and the
- // contextual links without the menu dismissing.
- const that = this;
- this.timer = window.setTimeout(() => {
- that.model.close().blur();
- }, 150);
- },
- },
-
- /**
- * Provides keyboard interaction for a contextual link.
- *
- * @constructs
- *
- * @augments Backbone.View
- */
- initialize() {
- /**
- * The timer is used to create a delay before dismissing the contextual
- * links on blur. This is only necessary when keyboard users tab into
- * contextual links without edit mode (i.e. without TabbingManager).
- * That means that if we decide to disable tabbing of contextual links
- * without edit mode, all this timer logic can go away.
- *
- * @type {NaN|number}
- */
- this.timer = NaN;
- },
-
- /**
- * Sets focus on the model; Clears the timer that dismisses the links.
- */
- focus() {
- // Clear the timeout that might have been set by blurring a link.
- window.clearTimeout(this.timer);
- this.model.focus();
- },
- },
- );
-})(Drupal, Backbone);
diff --git a/core/modules/contextual/js/views/RegionView.js b/core/modules/contextual/js/views/RegionView.js
deleted file mode 100644
index 08ec5880be..0000000000
--- a/core/modules/contextual/js/views/RegionView.js
+++ /dev/null
@@ -1,75 +0,0 @@
-/**
- * @file
- * A Backbone View that renders the visual view of a contextual region element.
- */
-
-(function (Drupal, Backbone) {
- /**
- * @deprecated in drupal:9.4.0 and is removed from drupal:10.0.0. There is no
- * replacement.
- */
- Drupal.contextual.RegionView = Backbone.View.extend(
- /** @lends Drupal.contextual.RegionView# */ {
- /**
- * Events for the Backbone view.
- *
- * @return {object}
- * A mapping of events to be used in the view.
- */
- events() {
- // Used for tracking the presence of touch events. When true, the
- // mousemove and mouseenter event handlers are effectively disabled.
- // This is used instead of preventDefault() on touchstart as some
- // touchstart events are not cancelable.
- let touchStart = false;
- return {
- touchstart() {
- // Set to true so the mouseenter and mouseleave events that follow
- // know to not execute any hover related logic.
- touchStart = true;
- },
- mouseenter() {
- if (!touchStart) {
- this.model.set('regionIsHovered', true);
- }
- },
- mouseleave() {
- if (!touchStart) {
- this.model.close().blur().set('regionIsHovered', false);
- }
- },
- mousemove() {
- // Because there are scenarios where there are both touchscreens
- // and pointer devices, the touchStart flag should be set back to
- // false after mouseenter and mouseleave complete. It will be set to
- // true if another touchstart event occurs.
- touchStart = false;
- },
- };
- },
-
- /**
- * Renders the visual view of a contextual region element.
- *
- * @constructs
- *
- * @augments Backbone.View
- */
- initialize() {
- this.listenTo(this.model, 'change:hasFocus', this.render);
- },
-
- /**
- * {@inheritdoc}
- *
- * @return {Drupal.contextual.RegionView}
- * The current contextual region view.
- */
- render() {
- this.$el.toggleClass('focus', this.model.get('hasFocus'));
-
- return this;
- },
- },
- );
-})(Drupal, Backbone);
diff --git a/core/modules/contextual/js/views/VisualView.js b/core/modules/contextual/js/views/VisualView.js
deleted file mode 100644
index 7f984cc817..0000000000
--- a/core/modules/contextual/js/views/VisualView.js
+++ /dev/null
@@ -1,109 +0,0 @@
-/**
- * @file
- * A Backbone View that provides the visual view of a contextual link.
- */
-
-(function (Drupal, Backbone) {
- /**
- * @deprecated in drupal:9.4.0 and is removed from drupal:10.0.0. There is no
- * replacement.
- */
- Drupal.contextual.VisualView = Backbone.View.extend(
- /** @lends Drupal.contextual.VisualView# */ {
- /**
- * Events for the Backbone view.
- *
- * @return {object}
- * A mapping of events to be used in the view.
- */
- events() {
- // Prevents delay and simulated mouse events.
- const touchEndToClick = function (event) {
- event.preventDefault();
- event.target.click();
- };
-
- // Used for tracking the presence of touch events. When true, the
- // mousemove and mouseenter event handlers are effectively disabled.
- // This is used instead of preventDefault() on touchstart as some
- // touchstart events are not cancelable.
- let touchStart = false;
-
- return {
- touchstart() {
- // Set to true so the mouseenter events that follows knows to not
- // execute any hover related logic.
- touchStart = true;
- },
- mouseenter() {
- // We only want mouse hover events on non-touch.
- if (!touchStart) {
- this.model.focus();
- }
- },
- mousemove() {
- // Because there are scenarios where there are both touchscreens
- // and pointer devices, the touchStart flag should be set back to
- // false after mouseenter and mouseleave complete. It will be set to
- // true if another touchstart event occurs.
- touchStart = false;
- },
- 'click .trigger': function () {
- this.model.toggleOpen();
- },
- 'touchend .trigger': touchEndToClick,
- 'click .contextual-links a': function () {
- this.model.close().blur();
- },
- 'touchend .contextual-links a': touchEndToClick,
- };
- },
-
- /**
- * Renders the visual view of a contextual link. Listens to mouse & touch.
- *
- * @constructs
- *
- * @augments Backbone.View
- */
- initialize() {
- this.listenTo(this.model, 'change', this.render);
- },
-
- /**
- * {@inheritdoc}
- *
- * @return {Drupal.contextual.VisualView}
- * The current contextual visual view.
- */
- render() {
- const isOpen = this.model.get('isOpen');
- // The trigger should be visible when:
- // - the mouse hovered over the region,
- // - the trigger is locked,
- // - and for as long as the contextual menu is open.
- const isVisible =
- this.model.get('isLocked') ||
- this.model.get('regionIsHovered') ||
- isOpen;
-
- this.$el
- // The open state determines if the links are visible.
- .toggleClass('open', isOpen)
- // Update the visibility of the trigger.
- .find('.trigger')
- .toggleClass('visually-hidden', !isVisible);
-
- // Nested contextual region handling: hide any nested contextual triggers.
- if ('isOpen' in this.model.changed) {
- this.$el
- .closest('.contextual-region')
- .find('.contextual .trigger:not(:first)')
- .toggle(!isOpen);
- }
-
- return this;
- },
- },
- );
-})(Drupal, Backbone);
diff --git a/core/modules/contextual/tests/src/FunctionalJavascript/EditModeTest.php b/core/modules/contextual/tests/src/FunctionalJavascript/EditModeTest.php
index 6b44315ac1..b7536b29b6 100644
--- a/core/modules/contextual/tests/src/FunctionalJavascript/EditModeTest.php
+++ b/core/modules/contextual/tests/src/FunctionalJavascript/EditModeTest.php
@@ -50,6 +50,7 @@ protected function setUp(): void {
/**
* Tests enabling and disabling edit mode.
+ * @var int $expected_restricted_tab_count
*/
public function testEditModeEnableDisable() {
$web_assert = $this->assertSession();