Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 41 additions & 15 deletions core/field_dropdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,11 +334,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;
Expand Down Expand Up @@ -499,7 +499,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();
Expand Down Expand Up @@ -637,8 +637,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;
}
Expand Down Expand Up @@ -687,10 +689,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];
});

Expand Down Expand Up @@ -776,12 +777,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}`,
);
}
}
Expand All @@ -791,6 +793,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.
*/
Expand All @@ -805,9 +828,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
Expand Down
12 changes: 6 additions & 6 deletions tests/mocha/field_dropdown_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
],
],
},
Expand All @@ -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'],
];
},
],
Expand Down
12 changes: 0 additions & 12 deletions tests/mocha/json_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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([
{
Expand All @@ -274,7 +268,6 @@ suite('JSON Block Definitions', function () {
'options': [
[IMAGE0, VALUE0],
[IMAGE1, VALUE1],
[IMAGE2, VALUE2],
],
},
],
Expand Down Expand Up @@ -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]);
});
});
});