mirror of
https://github.com/google/blockly.git
synced 2026-01-04 15:40:08 +01:00
feat: Allow for HTML elements in dropdown field menus. (#8889)
* feat: Allow for HTML elements in dropdown field menus. * refactor: Use dot access.
This commit is contained in:
@@ -332,11 +332,11 @@ export class FieldDropdown extends Field<string> {
|
||||
|
||||
const [label, value] = option;
|
||||
const content = (() => {
|
||||
if (typeof label === 'object') {
|
||||
if (isImageProperties(label)) {
|
||||
// Convert ImageProperties to an HTMLImageElement.
|
||||
const image = new Image(label['width'], label['height']);
|
||||
image.src = label['src'];
|
||||
image.alt = label['alt'] || '';
|
||||
const image = new Image(label.width, label.height);
|
||||
image.src = label.src;
|
||||
image.alt = label.alt;
|
||||
return image;
|
||||
}
|
||||
return label;
|
||||
@@ -497,7 +497,7 @@ export class FieldDropdown extends Field<string> {
|
||||
|
||||
// Show correct element.
|
||||
const option = this.selectedOption && this.selectedOption[0];
|
||||
if (option && typeof option === 'object') {
|
||||
if (isImageProperties(option)) {
|
||||
this.renderSelectedImage(option);
|
||||
} else {
|
||||
this.renderSelectedText();
|
||||
@@ -635,8 +635,10 @@ export class FieldDropdown extends Field<string> {
|
||||
return null;
|
||||
}
|
||||
const option = this.selectedOption[0];
|
||||
if (typeof option === 'object') {
|
||||
return option['alt'];
|
||||
if (isImageProperties(option)) {
|
||||
return option.alt;
|
||||
} else if (option instanceof HTMLElement) {
|
||||
return option.title ?? option.ariaLabel ?? option.innerText;
|
||||
}
|
||||
return option;
|
||||
}
|
||||
@@ -685,10 +687,9 @@ export class FieldDropdown extends Field<string> {
|
||||
hasImages = true;
|
||||
// Copy the image properties so they're not influenced by the original.
|
||||
// NOTE: No need to deep copy since image properties are only 1 level deep.
|
||||
const imageLabel =
|
||||
label.alt !== null
|
||||
? {...label, alt: parsing.replaceMessageReferences(label.alt)}
|
||||
: {...label};
|
||||
const imageLabel = isImageProperties(label)
|
||||
? {...label, alt: parsing.replaceMessageReferences(label.alt)}
|
||||
: {...label};
|
||||
return [imageLabel, value];
|
||||
});
|
||||
|
||||
@@ -774,12 +775,13 @@ export class FieldDropdown extends Field<string> {
|
||||
} else if (
|
||||
option[0] &&
|
||||
typeof option[0] !== 'string' &&
|
||||
typeof option[0].src !== 'string'
|
||||
!isImageProperties(option[0]) &&
|
||||
!(option[0] instanceof HTMLElement)
|
||||
) {
|
||||
foundError = true;
|
||||
console.error(
|
||||
`Invalid option[${i}]: Each FieldDropdown option must have a string
|
||||
label or image description. Found ${option[0]} in: ${option}`,
|
||||
label, image description, or HTML element. Found ${option[0]} in: ${option}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -789,6 +791,27 @@ export class FieldDropdown extends Field<string> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not an object conforms to the ImageProperties interface.
|
||||
*
|
||||
* @param obj The object to test.
|
||||
* @returns True if the object conforms to ImageProperties, otherwise false.
|
||||
*/
|
||||
function isImageProperties(obj: any): obj is ImageProperties {
|
||||
return (
|
||||
obj &&
|
||||
typeof obj === 'object' &&
|
||||
'src' in obj &&
|
||||
typeof obj.src === 'string' &&
|
||||
'alt' in obj &&
|
||||
typeof obj.alt === 'string' &&
|
||||
'width' in obj &&
|
||||
typeof obj.width === 'number' &&
|
||||
'height' in obj &&
|
||||
typeof obj.height === 'number'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Definition of a human-readable image dropdown option.
|
||||
*/
|
||||
@@ -803,9 +826,12 @@ export interface ImageProperties {
|
||||
* An individual option in the dropdown menu. Can be either the string literal
|
||||
* `separator` for a menu separator item, or an array for normal action menu
|
||||
* items. In the latter case, the first element is the human-readable value
|
||||
* (text or image), and the second element is the language-neutral value.
|
||||
* (text, ImageProperties object, or HTML element), and the second element is
|
||||
* the language-neutral value.
|
||||
*/
|
||||
export type MenuOption = [string | ImageProperties, string] | 'separator';
|
||||
export type MenuOption =
|
||||
| [string | ImageProperties | HTMLElement, string]
|
||||
| 'separator';
|
||||
|
||||
/**
|
||||
* A function that generates an array of menu options for FieldDropdown
|
||||
|
||||
@@ -92,9 +92,9 @@ suite('Dropdown Fields', function () {
|
||||
expectedText: 'a',
|
||||
args: [
|
||||
[
|
||||
[{src: 'scrA', alt: 'a'}, 'A'],
|
||||
[{src: 'scrB', alt: 'b'}, 'B'],
|
||||
[{src: 'scrC', alt: 'c'}, 'C'],
|
||||
[{src: 'scrA', alt: 'a', width: 10, height: 10}, 'A'],
|
||||
[{src: 'scrB', alt: 'b', width: 10, height: 10}, 'B'],
|
||||
[{src: 'scrC', alt: 'c', width: 10, height: 10}, 'C'],
|
||||
],
|
||||
],
|
||||
},
|
||||
@@ -121,9 +121,9 @@ suite('Dropdown Fields', function () {
|
||||
args: [
|
||||
() => {
|
||||
return [
|
||||
[{src: 'scrA', alt: 'a'}, 'A'],
|
||||
[{src: 'scrB', alt: 'b'}, 'B'],
|
||||
[{src: 'scrC', alt: 'c'}, 'C'],
|
||||
[{src: 'scrA', alt: 'a', width: 10, height: 10}, 'A'],
|
||||
[{src: 'scrB', alt: 'b', width: 10, height: 10}, 'B'],
|
||||
[{src: 'scrC', alt: 'c', width: 10, height: 10}, 'C'],
|
||||
];
|
||||
},
|
||||
],
|
||||
|
||||
@@ -256,12 +256,6 @@ suite('JSON Block Definitions', function () {
|
||||
'alt': '%{BKY_ALT_TEXT}',
|
||||
};
|
||||
const VALUE1 = 'VALUE1';
|
||||
const IMAGE2 = {
|
||||
'width': 90,
|
||||
'height': 123,
|
||||
'src': 'http://image2.src',
|
||||
};
|
||||
const VALUE2 = 'VALUE2';
|
||||
|
||||
Blockly.defineBlocksWithJsonArray([
|
||||
{
|
||||
@@ -274,7 +268,6 @@ suite('JSON Block Definitions', function () {
|
||||
'options': [
|
||||
[IMAGE0, VALUE0],
|
||||
[IMAGE1, VALUE1],
|
||||
[IMAGE2, VALUE2],
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -305,11 +298,6 @@ suite('JSON Block Definitions', function () {
|
||||
assertImageEquals(IMAGE1, image1);
|
||||
assert.equal(image1.alt, IMAGE1_ALT_TEXT); // Via Msg reference
|
||||
assert.equal(VALUE1, options[1][1]);
|
||||
|
||||
const image2 = options[2][0];
|
||||
assertImageEquals(IMAGE1, image1);
|
||||
assert.notExists(image2.alt); // No alt specified.
|
||||
assert.equal(VALUE2, options[2][1]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user