summaryrefslogtreecommitdiffstatshomepage
path: root/core/modules/contextual/js
diff options
context:
space:
mode:
Diffstat (limited to 'core/modules/contextual/js')
-rw-r--r--core/modules/contextual/js/contextual.js99
-rw-r--r--core/modules/contextual/js/contextual.toolbar.js35
-rw-r--r--core/modules/contextual/js/contextualModelView.js254
-rw-r--r--core/modules/contextual/js/models/StateModel.js130
-rw-r--r--core/modules/contextual/js/toolbar/contextualToolbarModelView.js175
-rw-r--r--core/modules/contextual/js/toolbar/models/StateModel.js126
-rw-r--r--core/modules/contextual/js/toolbar/views/AuralView.js122
-rw-r--r--core/modules/contextual/js/toolbar/views/VisualView.js85
-rw-r--r--core/modules/contextual/js/views/AuralView.js59
-rw-r--r--core/modules/contextual/js/views/KeyboardView.js62
-rw-r--r--core/modules/contextual/js/views/RegionView.js75
-rw-r--r--core/modules/contextual/js/views/VisualView.js109
12 files changed, 469 insertions, 862 deletions
diff --git a/core/modules/contextual/js/contextual.js b/core/modules/contextual/js/contextual.js
index 87ccaa52dffe..f1008eabe07b 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.
@@ -14,22 +14,19 @@
},
},
);
-
// Clear the cached contextual links whenever the current user's set of
// permissions changes.
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.startsWith('Drupal.contextual.')) {
- storage.removeItem(key);
- }
- });
+ Object.keys(storage).forEach((key) => {
+ if (key.startsWith('Drupal.contextual.')) {
+ storage.removeItem(key);
+ }
+ });
}
storage.setItem('Drupal.contextual.permissionsHash', permissionsHash);
}
@@ -87,7 +84,7 @@
*/
function initContextual($contextual, html) {
const $region = $contextual.closest('.contextual-region');
- const contextual = Drupal.contextual;
+ const { contextual } = Drupal;
$contextual
// Update the placeholder to contain its rendered contextual links.
@@ -107,46 +104,18 @@
const glue = url.includes('?') ? '&' : '?';
this.setAttribute('href', url + glue + destination);
});
-
let title = '';
const $regionHeading = $region.find('h2');
if ($regionHeading.length) {
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)),
- );
-
- // 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);
-
- // Let other JavaScript react to the adding of a new contextual link.
- $(document).trigger(
- 'drupalContextualLinkAdded',
- Drupal.deprecatedProperty({
- target: {
- $el: $contextual,
- $region,
- model,
- },
- deprecatedProperty: 'model',
- message:
- 'The model property is deprecated in drupal:9.4.0 and is removed from drupal:12.0.0. There is no replacement.',
- }),
+ options.title = title;
+ const contextualModelView = new Drupal.contextual.ContextualModelView(
+ $contextual,
+ $region,
+ options,
);
-
+ contextual.instances.push(contextualModelView);
// Fix visual collisions between contextual link triggers.
adjustIfNestedAndOverlapping($contextual);
}
@@ -192,7 +161,7 @@
// 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
+ // the chance to set up an event listener on the collection
// Drupal.contextual.collection.
window.setTimeout(() => {
initContextual(
@@ -217,7 +186,7 @@
data: { 'ids[]': uncachedIDs, 'tokens[]': uncachedTokens },
dataType: 'json',
success(results) {
- _.each(results, (html, contextualID) => {
+ Object.entries(results).forEach(([contextualID, html]) => {
// Store the metadata.
storage.setItem(`Drupal.contextual.${contextualID}`, html);
// If the rendered contextual links are empty, then the current
@@ -274,21 +243,23 @@
* replacement.
*/
regionViews: [],
+ instances: new Proxy([], {
+ set: function set(obj, prop, value) {
+ obj[prop] = value;
+ window.dispatchEvent(new Event('contextual-instances-added'));
+ return true;
+ },
+ deleteProperty(target, prop) {
+ if (prop in target) {
+ delete target[prop];
+ window.dispatchEvent(new Event('contextual-instances-removed'));
+ }
+ },
+ }),
+ ContextualModelView: {},
};
/**
- * A Backbone.Collection of {@link Drupal.contextual.StateModel} instances.
- *
- * @type {Backbone.Collection}
- *
- * @deprecated in drupal:9.4.0 and is removed from drupal:12.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}
@@ -311,12 +282,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 8fc206cc2c3b..c94d0df414c9 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.',
@@ -21,33 +21,19 @@
* A contextual links DOM element as rendered by the server.
*/
function initContextualToolbar(context) {
- if (!Drupal.contextual || !Drupal.contextual.collection) {
+ if (!Drupal.contextual || !Drupal.contextual.instances) {
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:
- document.querySelector('body .contextual-region') === null ||
- localStorage.getItem('Drupal.contextualToolbar.isViewing') !==
- 'false',
- },
- {
- contextualCollection: Drupal.contextual.collection,
- },
- );
+ const { contextualToolbar } = Drupal;
const viewOptions = {
el: $('.toolbar .toolbar-bar .contextual-toolbar-tab'),
- model: contextualToolbar.model,
strings,
};
- new contextualToolbar.VisualView(viewOptions);
- new contextualToolbar.AuralView(viewOptions);
+ contextualToolbar.model = new Drupal.contextual.ContextualToolbarModelView(
+ viewOptions,
+ );
}
/**
@@ -75,13 +61,10 @@
*/
Drupal.contextualToolbar = {
/**
- * The {@link Drupal.contextualToolbar.StateModel} instance.
- *
- * @type {?Drupal.contextualToolbar.StateModel}
+ * The {@link Drupal.contextual.ContextualToolbarModelView} instance.
*
- * @deprecated in drupal:9.4.0 and is removed from drupal:12.0.0. There is
- * no replacement.
+ * @type {?Drupal.contextual.ContextualToolbarModelView}
*/
model: null,
};
-})(jQuery, Drupal, Backbone);
+})(jQuery, Drupal);
diff --git a/core/modules/contextual/js/contextualModelView.js b/core/modules/contextual/js/contextualModelView.js
new file mode 100644
index 000000000000..4488045e2236
--- /dev/null
+++ b/core/modules/contextual/js/contextualModelView.js
@@ -0,0 +1,254 @@
+(($, Drupal) => {
+ /**
+ * Models the state of a contextual link's trigger, list & region.
+ */
+ Drupal.contextual.ContextualModelView = class {
+ constructor($contextual, $region, options) {
+ this.title = options.title || '';
+ this.regionIsHovered = false;
+ this._hasFocus = false;
+ this._isOpen = false;
+ this._isLocked = false;
+ this.strings = options.strings;
+ this.timer = NaN;
+ this.modelId = btoa(Math.random()).substring(0, 12);
+ this.$region = $region;
+ this.$contextual = $contextual;
+
+ if (!document.body.classList.contains('touchevents')) {
+ this.$region.on({
+ mouseenter: () => {
+ this.regionIsHovered = true;
+ },
+ mouseleave: () => {
+ this.close().blur();
+ this.regionIsHovered = false;
+ },
+ 'mouseleave mouseenter': () => this.render(),
+ });
+ this.$contextual.on('mouseenter', () => {
+ this.focus();
+ this.render();
+ });
+ }
+
+ this.$contextual.on(
+ {
+ click: () => {
+ this.toggleOpen();
+ },
+ touchend: () => {
+ Drupal.contextual.ContextualModelView.touchEndToClick();
+ },
+ focus: () => {
+ this.focus();
+ },
+ blur: () => {
+ this.blur();
+ },
+ 'click blur touchend focus': () => this.render(),
+ },
+ '.trigger',
+ );
+
+ this.$contextual.on(
+ {
+ click: () => {
+ this.close().blur();
+ },
+ touchend: (event) => {
+ Drupal.contextual.ContextualModelView.touchEndToClick(event);
+ },
+ focus: () => {
+ this.focus();
+ },
+ blur: () => {
+ this.waitCloseThenBlur();
+ },
+ 'click blur touchend focus': () => this.render(),
+ },
+ '.contextual-links a',
+ );
+
+ this.render();
+
+ // Let other JavaScript react to the adding of a new contextual link.
+ $(document).trigger('drupalContextualLinkAdded', {
+ $el: $contextual,
+ $region,
+ model: this,
+ });
+ }
+
+ /**
+ * Updates the rendered representation of the current contextual links.
+ */
+ render() {
+ const { isOpen } = this;
+ const isVisible = this.isLocked || this.regionIsHovered || isOpen;
+ this.$region.toggleClass('focus', this.hasFocus);
+ this.$contextual
+ .toggleClass('open', isOpen)
+ // Update the visibility of the trigger.
+ .find('.trigger')
+ .toggleClass('visually-hidden', !isVisible);
+
+ this.$contextual.find('.contextual-links').prop('hidden', !isOpen);
+ const trigger = this.$contextual.find('.trigger').get(0);
+ trigger.textContent = Drupal.t('@action @title configuration options', {
+ '@action': !isOpen ? this.strings.open : this.strings.close,
+ '@title': this.title,
+ });
+ trigger.setAttribute('aria-pressed', isOpen);
+ }
+
+ /**
+ * Prevents delay and simulated mouse events.
+ *
+ * @param {jQuery.Event} event the touch end event.
+ */
+ static touchEndToClick(event) {
+ event.preventDefault();
+ event.target.click();
+ }
+
+ /**
+ * Set up a timeout to allow a user to tab between the trigger and the
+ * contextual links without the menu dismissing.
+ */
+ waitCloseThenBlur() {
+ this.timer = window.setTimeout(() => {
+ this.isOpen = false;
+ this.hasFocus = false;
+ this.render();
+ }, 150);
+ }
+
+ /**
+ * Opens or closes the contextual link.
+ *
+ * If it is opened, then also give focus.
+ *
+ * @return {Drupal.contextual.ContextualModelView}
+ * The current contextual model view.
+ */
+ toggleOpen() {
+ const newIsOpen = !this.isOpen;
+ this.isOpen = newIsOpen;
+ if (newIsOpen) {
+ this.focus();
+ }
+ return this;
+ }
+
+ /**
+ * Gives focus to this contextual link.
+ *
+ * Also closes + removes focus from every other contextual link.
+ *
+ * @return {Drupal.contextual.ContextualModelView}
+ * The current contextual model view.
+ */
+ focus() {
+ const { modelId } = this;
+ Drupal.contextual.instances.forEach((model) => {
+ if (model.modelId !== modelId) {
+ model.close().blur();
+ }
+ });
+ window.clearTimeout(this.timer);
+ this.hasFocus = true;
+ return this;
+ }
+
+ /**
+ * Removes focus from this contextual link, unless it is open.
+ *
+ * @return {Drupal.contextual.ContextualModelView}
+ * The current contextual model view.
+ */
+ blur() {
+ if (!this.isOpen) {
+ this.hasFocus = false;
+ }
+ 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.ContextualModelView}
+ * The current contextual model view.
+ */
+ close() {
+ this.isOpen = false;
+ return this;
+ }
+
+ /**
+ * Gets the current focus state.
+ *
+ * @return {boolean} the focus state.
+ */
+ get hasFocus() {
+ return this._hasFocus;
+ }
+
+ /**
+ * Sets the current focus state.
+ *
+ * @param {boolean} value - new focus state
+ */
+ set hasFocus(value) {
+ this._hasFocus = value;
+ this.$region.toggleClass('focus', this._hasFocus);
+ }
+
+ /**
+ * Gets the current open state.
+ *
+ * @return {boolean} the open state.
+ */
+ get isOpen() {
+ return this._isOpen;
+ }
+
+ /**
+ * Sets the current open state.
+ *
+ * @param {boolean} value - new open state
+ */
+ set isOpen(value) {
+ this._isOpen = value;
+ // Nested contextual region handling: hide any nested contextual triggers.
+ this.$region
+ .closest('.contextual-region')
+ .find('.contextual .trigger:not(:first)')
+ .toggle(!this.isOpen);
+ }
+
+ /**
+ * Gets the current locked state.
+ *
+ * @return {boolean} the locked state.
+ */
+ get isLocked() {
+ return this._isLocked;
+ }
+
+ /**
+ * Sets the current locked state.
+ *
+ * @param {boolean} value - new locked state
+ */
+ set isLocked(value) {
+ if (value !== this._isLocked) {
+ this._isLocked = value;
+ this.render();
+ }
+ }
+ };
+})(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 622f897917f5..000000000000
--- 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:12.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/contextualToolbarModelView.js b/core/modules/contextual/js/toolbar/contextualToolbarModelView.js
new file mode 100644
index 000000000000..6c6db5fe70cd
--- /dev/null
+++ b/core/modules/contextual/js/toolbar/contextualToolbarModelView.js
@@ -0,0 +1,175 @@
+(($, Drupal) => {
+ Drupal.contextual.ContextualToolbarModelView = class {
+ constructor(options) {
+ this.strings = options.strings;
+ this.isVisible = false;
+ this._contextualCount = Drupal.contextual.instances.count;
+ this.tabbingContext = null;
+ this._isViewing =
+ localStorage.getItem('Drupal.contextualToolbar.isViewing') !== 'false';
+ this.$el = options.el;
+
+ window.addEventListener('contextual-instances-added', () =>
+ this.lockNewContextualLinks(),
+ );
+ window.addEventListener('contextual-instances-removed', () => {
+ this.contextualCount = Drupal.contextual.instances.count;
+ });
+
+ this.$el.on({
+ click: () => {
+ this.isViewing = !this.isViewing;
+ },
+ touchend: (event) => {
+ event.preventDefault();
+ event.target.click();
+ },
+ 'click touchend': () => this.render(),
+ });
+
+ $(document).on('keyup', (event) => this.onKeypress(event));
+ this.manageTabbing(true);
+ this.render();
+ }
+
+ /**
+ * 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.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.isViewing = true;
+ }
+ }
+
+ /**
+ * Updates the rendered representation of the current toolbar model view.
+ */
+ render() {
+ this.$el[0].classList.toggle('hidden', this.isVisible);
+ const button = this.$el[0].querySelector('button');
+ button.classList.toggle('is-active', !this.isViewing);
+ button.setAttribute('aria-pressed', !this.isViewing);
+ this.contextualCount = Drupal.contextual.instances.count;
+ }
+
+ /**
+ * Automatically updates visibility of the view/edit mode toggle.
+ */
+ updateVisibility() {
+ this.isVisible = this.get('contextualCount') > 0;
+ }
+
+ /**
+ * Lock newly added contextual links if edit mode is enabled.
+ */
+ lockNewContextualLinks() {
+ Drupal.contextual.instances.forEach((model) => {
+ model.isLocked = !this.isViewing;
+ });
+ this.contextualCount = Drupal.contextual.instances.count;
+ }
+
+ /**
+ * Limits tabbing to the contextual links and edit mode toolbar tab.
+ *
+ * @param {boolean} init - true to initialize tabbing.
+ */
+ manageTabbing(init = false) {
+ let { tabbingContext } = this;
+ // Always release an existing tabbing context.
+ if (tabbingContext && !init) {
+ // Only announce release when the context was active.
+ if (tabbingContext.active) {
+ Drupal.announce(this.strings.tabbingReleased);
+ }
+ tabbingContext.release();
+ this.tabbingContext = null;
+ }
+ // Create a new tabbing context when edit mode is enabled.
+ if (!this.isViewing) {
+ tabbingContext = Drupal.tabbingManager.constrain(
+ $('.contextual-toolbar-tab, .contextual'),
+ );
+ this.tabbingContext = tabbingContext;
+ this.announceTabbingConstraint();
+ this.announcedOnce = true;
+ }
+ }
+
+ /**
+ * Announces the current tabbing constraint.
+ */
+ announceTabbingConstraint() {
+ const { strings } = this;
+ Drupal.announce(
+ Drupal.formatString(strings.tabbingConstrained, {
+ '@contextualsCount': Drupal.formatPlural(
+ Drupal.contextual.instances.length,
+ '@count contextual link',
+ '@count contextual links',
+ ),
+ }) + strings.pressEsc,
+ );
+ }
+
+ /**
+ * Gets the current viewing state.
+ *
+ * @return {boolean} the viewing state.
+ */
+ get isViewing() {
+ return this._isViewing;
+ }
+
+ /**
+ * Sets the current viewing state.
+ *
+ * @param {boolean} value - new viewing state
+ */
+ set isViewing(value) {
+ this._isViewing = value;
+ localStorage[!value ? 'setItem' : 'removeItem'](
+ 'Drupal.contextualToolbar.isViewing',
+ 'false',
+ );
+
+ Drupal.contextual.instances.forEach((model) => {
+ model.isLocked = !this.isViewing;
+ });
+ this.manageTabbing();
+ }
+
+ /**
+ * Gets the current contextual links count.
+ *
+ * @return {integer} the current contextual links count.
+ */
+ get contextualCount() {
+ return this._contextualCount;
+ }
+
+ /**
+ * Sets the current contextual links count.
+ *
+ * @param {integer} value - new contextual links count.
+ */
+ set contextualCount(value) {
+ if (value !== this._contextualCount) {
+ this._contextualCount = value;
+ this.updateVisibility();
+ }
+ }
+ };
+})(jQuery, Drupal);
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 88f66193f9f3..000000000000
--- 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:12.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 2bcf9cdcca0f..000000000000
--- 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:12.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 10d8dff2deaa..000000000000
--- 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:12.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 62287c1bf118..000000000000
--- 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:12.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 2a3d144bea07..000000000000
--- 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:12.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 349428301d81..000000000000
--- 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:12.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 fcd932b1faf4..000000000000
--- 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:12.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);