mirror of
https://github.com/google/blockly.git
synced 2026-06-17 00:25:14 +02:00
fix: Make MenuItem methods toggle classes immediately (#9570)
* fix: Make `MenuItem` methods toggle classes immediately * chore: Add docstring * fix: Clarify name of `toggleHasCheckbox()` * fix: Ensure menu items are enabled before highlighting them
This commit is contained in:
+58
-15
@@ -12,7 +12,6 @@
|
||||
// Former goog.module ID: Blockly.MenuItem
|
||||
|
||||
import * as aria from './utils/aria.js';
|
||||
import * as dom from './utils/dom.js';
|
||||
import * as idGenerator from './utils/idgenerator.js';
|
||||
|
||||
/**
|
||||
@@ -74,12 +73,6 @@ export class MenuItem {
|
||||
|
||||
const content = document.createElement('div');
|
||||
content.className = 'blocklyMenuItemContent';
|
||||
// Add a checkbox for checkable menu items.
|
||||
if (this.checkable) {
|
||||
const checkbox = document.createElement('div');
|
||||
checkbox.className = 'blocklyMenuItemCheckbox ';
|
||||
content.appendChild(checkbox);
|
||||
}
|
||||
|
||||
let contentDom: Node = this.content as HTMLElement;
|
||||
if (typeof this.content === 'string') {
|
||||
@@ -88,6 +81,11 @@ export class MenuItem {
|
||||
content.appendChild(contentDom);
|
||||
element.appendChild(content);
|
||||
|
||||
// Add a checkbox for checkable menu items.
|
||||
if (this.checkable) {
|
||||
this.toggleHasCheckbox(true);
|
||||
}
|
||||
|
||||
// Initialize ARIA role and state.
|
||||
if (this.roleName) {
|
||||
aria.setRole(element, this.roleName);
|
||||
@@ -145,6 +143,7 @@ export class MenuItem {
|
||||
*/
|
||||
setRightToLeft(rtl: boolean) {
|
||||
this.rightToLeft = rtl;
|
||||
this.getElement()?.classList.toggle('blocklyMenuItemRtl', this.rightToLeft);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -166,6 +165,12 @@ export class MenuItem {
|
||||
*/
|
||||
setCheckable(checkable: boolean) {
|
||||
this.checkable = checkable;
|
||||
|
||||
if (!this.checkable) {
|
||||
this.setChecked(false);
|
||||
}
|
||||
|
||||
this.toggleHasCheckbox(checkable);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,7 +180,14 @@ export class MenuItem {
|
||||
* @internal
|
||||
*/
|
||||
setChecked(checked: boolean) {
|
||||
if (checked && !this.checkable) return;
|
||||
|
||||
this.checked = checked;
|
||||
const element = this.getElement();
|
||||
if (element) {
|
||||
element.classList.toggle('blocklyMenuItemSelected', this.checked);
|
||||
aria.setState(element, aria.State.SELECTED, this.checked);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,14 +198,11 @@ export class MenuItem {
|
||||
*/
|
||||
setHighlighted(highlight: boolean) {
|
||||
this.highlight = highlight;
|
||||
const el = this.getElement();
|
||||
if (el && this.isEnabled()) {
|
||||
const name = 'blocklyMenuItemHighlight';
|
||||
if (highlight) {
|
||||
dom.addClass(el, name);
|
||||
} else {
|
||||
dom.removeClass(el, name);
|
||||
}
|
||||
if (this.isEnabled()) {
|
||||
this.getElement()?.classList.toggle(
|
||||
'blocklyMenuItemHighlight',
|
||||
this.highlight,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,6 +224,11 @@ export class MenuItem {
|
||||
*/
|
||||
setEnabled(enabled: boolean) {
|
||||
this.enabled = enabled;
|
||||
const element = this.getElement();
|
||||
if (element) {
|
||||
element.classList.toggle('blocklyMenuItemDisabled', !this.enabled);
|
||||
aria.setState(element, aria.State.DISABLED, !this.enabled);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -243,4 +257,33 @@ export class MenuItem {
|
||||
onAction(fn: (p1: MenuItem, menuSelectEvent: Event) => void, obj: object) {
|
||||
this.actionHandler = fn.bind(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or removes the checkmark indicator on this menu item.
|
||||
* The indicator is present even if this menu item is not checked, as long
|
||||
* as it is checkable; its visibility is controlled with CSS.
|
||||
*
|
||||
* @param add True to add the checkmark indicator, false to remove it.
|
||||
*/
|
||||
private toggleHasCheckbox(add: boolean) {
|
||||
if (add) {
|
||||
if (
|
||||
this.getElement()?.querySelector(
|
||||
'.blocklyMenuItemContent .blocklyMenuItemCheckbox',
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const checkbox = document.createElement('div');
|
||||
checkbox.className = 'blocklyMenuItemCheckbox ';
|
||||
this.getElement()
|
||||
?.querySelector('.blocklyMenuItemContent')
|
||||
?.prepend(checkbox);
|
||||
} else {
|
||||
this.getElement()
|
||||
?.querySelector('.blocklyMenuItemContent .blocklyMenuItemCheckbox')
|
||||
?.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,6 +224,7 @@
|
||||
import './blocks/lists_test.js';
|
||||
import './blocks/logic_ternary_test.js';
|
||||
import './blocks/loops_test.js';
|
||||
import './menu_item_test.js';
|
||||
import './metrics_test.js';
|
||||
import './mutator_test.js';
|
||||
import './names_test.js';
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Raspberry Pi Foundation
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {assert} from '../../node_modules/chai/index.js';
|
||||
import {
|
||||
sharedTestSetup,
|
||||
sharedTestTeardown,
|
||||
} from './test_helpers/setup_teardown.js';
|
||||
|
||||
suite('Menu items', function () {
|
||||
setup(function () {
|
||||
sharedTestSetup.call(this);
|
||||
this.menuItem = new Blockly.MenuItem('Hello World');
|
||||
this.menuItem.createDom();
|
||||
});
|
||||
|
||||
teardown(function () {
|
||||
sharedTestTeardown.call(this);
|
||||
});
|
||||
|
||||
test('can be RTL', function () {
|
||||
this.menuItem.setRightToLeft(true);
|
||||
assert.isTrue(
|
||||
this.menuItem.getElement().classList.contains('blocklyMenuItemRtl'),
|
||||
);
|
||||
});
|
||||
|
||||
test('can be LTR', function () {
|
||||
this.menuItem.setRightToLeft(false);
|
||||
assert.isFalse(
|
||||
this.menuItem.getElement().classList.contains('blocklyMenuItemRtl'),
|
||||
);
|
||||
});
|
||||
|
||||
test('can be checked', function () {
|
||||
this.menuItem.setCheckable(true);
|
||||
this.menuItem.setChecked(true);
|
||||
assert.isTrue(
|
||||
this.menuItem.getElement().classList.contains('blocklyMenuItemSelected'),
|
||||
);
|
||||
assert.equal(
|
||||
this.menuItem.getElement().getAttribute('aria-selected'),
|
||||
'true',
|
||||
);
|
||||
});
|
||||
|
||||
test('cannot be checked when designated as uncheckable', function () {
|
||||
this.menuItem.setCheckable(false);
|
||||
this.menuItem.setChecked(true);
|
||||
assert.isFalse(
|
||||
this.menuItem.getElement().classList.contains('blocklyMenuItemSelected'),
|
||||
);
|
||||
assert.equal(
|
||||
this.menuItem.getElement().getAttribute('aria-selected'),
|
||||
'false',
|
||||
);
|
||||
});
|
||||
|
||||
test('can be unchecked', function () {
|
||||
this.menuItem.setCheckable(true);
|
||||
this.menuItem.setChecked(false);
|
||||
assert.isFalse(
|
||||
this.menuItem.getElement().classList.contains('blocklyMenuItemSelected'),
|
||||
);
|
||||
assert.equal(
|
||||
this.menuItem.getElement().getAttribute('aria-selected'),
|
||||
'false',
|
||||
);
|
||||
});
|
||||
|
||||
test('uncheck themselves when designated as non-checkable', function () {
|
||||
this.menuItem.setChecked(true);
|
||||
this.menuItem.setCheckable(false);
|
||||
|
||||
assert.isFalse(
|
||||
this.menuItem.getElement().classList.contains('blocklyMenuItemSelected'),
|
||||
);
|
||||
assert.equal(
|
||||
this.menuItem.getElement().getAttribute('aria-selected'),
|
||||
'false',
|
||||
);
|
||||
});
|
||||
|
||||
test('do not check themselves when designated as checkable', function () {
|
||||
this.menuItem.setChecked(false);
|
||||
this.menuItem.setCheckable(true);
|
||||
|
||||
assert.isFalse(
|
||||
this.menuItem.getElement().classList.contains('blocklyMenuItemSelected'),
|
||||
);
|
||||
assert.equal(
|
||||
this.menuItem.getElement().getAttribute('aria-selected'),
|
||||
'false',
|
||||
);
|
||||
});
|
||||
|
||||
test('adds a checkbox when designated as checkable', function () {
|
||||
assert.isNull(
|
||||
this.menuItem.getElement().querySelector('.blocklyMenuItemCheckbox'),
|
||||
);
|
||||
this.menuItem.setCheckable(true);
|
||||
assert.isNotNull(
|
||||
this.menuItem.getElement().querySelector('.blocklyMenuItemCheckbox'),
|
||||
);
|
||||
});
|
||||
|
||||
test('removes the checkbox when designated as uncheckable', function () {
|
||||
this.menuItem.setCheckable(true);
|
||||
assert.isNotNull(
|
||||
this.menuItem.getElement().querySelector('.blocklyMenuItemCheckbox'),
|
||||
);
|
||||
this.menuItem.setCheckable(false);
|
||||
assert.isNull(
|
||||
this.menuItem.getElement().querySelector('.blocklyMenuItemCheckbox'),
|
||||
);
|
||||
});
|
||||
|
||||
test('can be highlighted', function () {
|
||||
this.menuItem.setHighlighted(true);
|
||||
assert.isTrue(
|
||||
this.menuItem.getElement().classList.contains('blocklyMenuItemHighlight'),
|
||||
);
|
||||
});
|
||||
|
||||
test('can be unhighlighted', function () {
|
||||
this.menuItem.setHighlighted(false);
|
||||
assert.isFalse(
|
||||
this.menuItem.getElement().classList.contains('blocklyMenuItemHighlight'),
|
||||
);
|
||||
});
|
||||
|
||||
test('cannot be highlighted if not enabled', function () {
|
||||
this.menuItem.setEnabled(false);
|
||||
this.menuItem.setHighlighted(true);
|
||||
assert.isFalse(
|
||||
this.menuItem.getElement().classList.contains('blocklyMenuItemHighlight'),
|
||||
);
|
||||
});
|
||||
|
||||
test('can be enabled', function () {
|
||||
this.menuItem.setEnabled(true);
|
||||
assert.isTrue(this.menuItem.isEnabled());
|
||||
assert.isFalse(
|
||||
this.menuItem.getElement().classList.contains('blocklyMenuItemDisabled'),
|
||||
);
|
||||
assert.equal(
|
||||
this.menuItem.getElement().getAttribute('aria-disabled'),
|
||||
'false',
|
||||
);
|
||||
});
|
||||
|
||||
test('can be disabled', function () {
|
||||
this.menuItem.setEnabled(false);
|
||||
assert.isFalse(this.menuItem.isEnabled());
|
||||
assert.isTrue(
|
||||
this.menuItem.getElement().classList.contains('blocklyMenuItemDisabled'),
|
||||
);
|
||||
assert.equal(
|
||||
this.menuItem.getElement().getAttribute('aria-disabled'),
|
||||
'true',
|
||||
);
|
||||
});
|
||||
|
||||
test('invokes its action callback', function () {
|
||||
let called = false;
|
||||
const callback = () => {
|
||||
called = true;
|
||||
};
|
||||
this.menuItem.onAction(callback, this);
|
||||
this.menuItem.performAction(new Event('click'));
|
||||
assert.isTrue(called);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user