From ad1f9a3bad065d78374d8ece2dc2af64935ad08c Mon Sep 17 00:00:00 2001 From: Robert Knight Date: Thu, 23 Jan 2025 14:33:32 +0000 Subject: [PATCH 1/5] Use Blockly grid dropdown plugin --- pxtblocks/fields/field_imagedropdown.ts | 200 ++++++++++++------------ 1 file changed, 101 insertions(+), 99 deletions(-) diff --git a/pxtblocks/fields/field_imagedropdown.ts b/pxtblocks/fields/field_imagedropdown.ts index 6b31f1c5e703..e4ca27b41c8f 100644 --- a/pxtblocks/fields/field_imagedropdown.ts +++ b/pxtblocks/fields/field_imagedropdown.ts @@ -2,7 +2,7 @@ import * as Blockly from "blockly"; import { FieldCustom, FieldCustomDropdownOptions, parseColour } from "./field_utils"; -import { FieldDropdown } from "./field_dropdown"; +import { FieldGridDropdown } from "@blockly/field-grid-dropdown"; export interface FieldImageDropdownOptions extends FieldCustomDropdownOptions { columns?: string; @@ -10,7 +10,7 @@ export interface FieldImageDropdownOptions extends FieldCustomDropdownOptions { width?: string; } -export class FieldImageDropdown extends FieldDropdown implements FieldCustom { +export class FieldImageDropdown extends FieldGridDropdown implements FieldCustom { public isFieldCustom_ = true; // Width in pixels protected width_: number; @@ -27,7 +27,8 @@ export class FieldImageDropdown extends FieldDropdown implements FieldCustom { protected savedPrimary_: string; constructor(text: string, options: FieldImageDropdownOptions, validator?: Function) { - super(options.data); + console.log(options.data) + super(options.data, undefined, {columns: parseInt(options.columns)}); this.columns_ = parseInt(options.columns); this.maxRows_ = parseInt(options.maxRows) || 0; @@ -42,102 +43,103 @@ export class FieldImageDropdown extends FieldDropdown implements FieldCustom { * @private */ public showEditor_() { - // If there is an existing drop-down we own, this is a request to hide the drop-down. - if (Blockly.DropDownDiv.hideIfOwner(this)) { - return; - } - // If there is an existing drop-down someone else owns, hide it immediately and clear it. - Blockly.DropDownDiv.hideWithoutAnimation(); - Blockly.DropDownDiv.clearContent(); - // Populate the drop-down with the icons for this field. - let dropdownDiv = Blockly.DropDownDiv.getContentDiv() as HTMLElement; - let contentDiv = document.createElement('div'); - // Accessibility properties - contentDiv.setAttribute('role', 'menu'); - contentDiv.setAttribute('aria-haspopup', 'true'); - const options = this.getOptions(); - let maxButtonHeight: number = 0; - for (let i = 0; i < options.length; i++) { - let content = (options[i] as any)[0]; // Human-readable text or image. - const value = (options[i] as any)[1]; // Language-neutral value. - // Icons with the type property placeholder take up space but don't have any functionality - // Use for special-case layouts - if (content.type == 'placeholder') { - let placeholder = document.createElement('span'); - placeholder.setAttribute('class', 'blocklyDropDownPlaceholder'); - placeholder.style.width = content.width + 'px'; - placeholder.style.height = content.height + 'px'; - contentDiv.appendChild(placeholder); - continue; - } - let button = document.createElement('button'); - button.setAttribute('id', ':' + i); // For aria-activedescendant - button.setAttribute('role', 'menuitem'); - button.setAttribute('class', 'blocklyDropDownButton'); - button.title = content.alt; - let buttonSize = content.height; - if (this.columns_) { - buttonSize = ((this.width_ / this.columns_) - 8); - button.style.width = buttonSize + 'px'; - button.style.height = buttonSize + 'px'; - } else { - button.style.width = content.width + 'px'; - button.style.height = content.height + 'px'; - } - if (buttonSize > maxButtonHeight) { - maxButtonHeight = buttonSize; - } - let backgroundColor = this.backgroundColour_; - if (value == this.getValue()) { - // This icon is selected, show it in a different colour - backgroundColor = (this.sourceBlock_ as Blockly.BlockSvg).getColourTertiary(); - button.setAttribute('aria-selected', 'true'); - } - button.style.backgroundColor = backgroundColor; - button.style.borderColor = this.borderColour_; - Blockly.browserEvents.bind(button, 'click', this, this.buttonClick_); - Blockly.browserEvents.bind(button, 'mouseover', this, () => { - button.setAttribute('class', 'blocklyDropDownButton blocklyDropDownButtonHover'); - contentDiv.setAttribute('aria-activedescendant', button.id); - }); - Blockly.browserEvents.bind(button, 'mouseout', this, () => { - button.setAttribute('class', 'blocklyDropDownButton'); - contentDiv.removeAttribute('aria-activedescendant'); - }); - let buttonImg = document.createElement('img'); - buttonImg.src = content.src; - //buttonImg.alt = icon.alt; - // Upon click/touch, we will be able to get the clicked element as e.target - // Store a data attribute on all possible click targets so we can match it to the icon. - button.setAttribute('data-value', value); - buttonImg.setAttribute('data-value', value); - button.appendChild(buttonImg); - contentDiv.appendChild(button); - } - contentDiv.style.width = this.width_ + 'px'; - dropdownDiv.appendChild(contentDiv); - if (this.maxRows_) { - // Limit the number of rows shown, but add a partial next row to indicate scrolling - dropdownDiv.style.maxHeight = (this.maxRows_ + 0.4) * (maxButtonHeight + 8) + 'px'; - } - - if (pxt.BrowserUtils.isFirefox()) { - // This is to compensate for the scrollbar that overlays content in Firefox. It - // gets removed in onHide_() - dropdownDiv.style.paddingRight = "20px"; - } - - Blockly.DropDownDiv.setColour(this.backgroundColour_, this.borderColour_); - - Blockly.DropDownDiv.showPositionedByField(this, this.onHide_.bind(this)); - - let source = this.sourceBlock_ as Blockly.BlockSvg; - this.savedPrimary_ = source?.getColour(); - if (source?.isShadow()) { - source.setColour(source.getColourTertiary()); - } else if (this.borderRect_) { - this.borderRect_.setAttribute('fill', source.getColourTertiary()); - } + super.showEditor_(); + // // If there is an existing drop-down we own, this is a request to hide the drop-down. + // if (Blockly.DropDownDiv.hideIfOwner(this)) { + // return; + // } + // // If there is an existing drop-down someone else owns, hide it immediately and clear it. + // Blockly.DropDownDiv.hideWithoutAnimation(); + // Blockly.DropDownDiv.clearContent(); + // // Populate the drop-down with the icons for this field. + // let dropdownDiv = Blockly.DropDownDiv.getContentDiv() as HTMLElement; + // let contentDiv = document.createElement('div'); + // // Accessibility properties + // contentDiv.setAttribute('role', 'menu'); + // contentDiv.setAttribute('aria-haspopup', 'true'); + // const options = this.getOptions(); + // let maxButtonHeight: number = 0; + // for (let i = 0; i < options.length; i++) { + // let content = (options[i] as any)[0]; // Human-readable text or image. + // const value = (options[i] as any)[1]; // Language-neutral value. + // // Icons with the type property placeholder take up space but don't have any functionality + // // Use for special-case layouts + // if (content.type == 'placeholder') { + // let placeholder = document.createElement('span'); + // placeholder.setAttribute('class', 'blocklyDropDownPlaceholder'); + // placeholder.style.width = content.width + 'px'; + // placeholder.style.height = content.height + 'px'; + // contentDiv.appendChild(placeholder); + // continue; + // } + // let button = document.createElement('button'); + // button.setAttribute('id', ':' + i); // For aria-activedescendant + // button.setAttribute('role', 'menuitem'); + // button.setAttribute('class', 'blocklyDropDownButton'); + // button.title = content.alt; + // let buttonSize = content.height; + // if (this.columns_) { + // buttonSize = ((this.width_ / this.columns_) - 8); + // button.style.width = buttonSize + 'px'; + // button.style.height = buttonSize + 'px'; + // } else { + // button.style.width = content.width + 'px'; + // button.style.height = content.height + 'px'; + // } + // if (buttonSize > maxButtonHeight) { + // maxButtonHeight = buttonSize; + // } + // let backgroundColor = this.backgroundColour_; + // if (value == this.getValue()) { + // // This icon is selected, show it in a different colour + // backgroundColor = (this.sourceBlock_ as Blockly.BlockSvg).getColourTertiary(); + // button.setAttribute('aria-selected', 'true'); + // } + // button.style.backgroundColor = backgroundColor; + // button.style.borderColor = this.borderColour_; + // Blockly.browserEvents.bind(button, 'click', this, this.buttonClick_); + // Blockly.browserEvents.bind(button, 'mouseover', this, () => { + // button.setAttribute('class', 'blocklyDropDownButton blocklyDropDownButtonHover'); + // contentDiv.setAttribute('aria-activedescendant', button.id); + // }); + // Blockly.browserEvents.bind(button, 'mouseout', this, () => { + // button.setAttribute('class', 'blocklyDropDownButton'); + // contentDiv.removeAttribute('aria-activedescendant'); + // }); + // let buttonImg = document.createElement('img'); + // buttonImg.src = content.src; + // //buttonImg.alt = icon.alt; + // // Upon click/touch, we will be able to get the clicked element as e.target + // // Store a data attribute on all possible click targets so we can match it to the icon. + // button.setAttribute('data-value', value); + // buttonImg.setAttribute('data-value', value); + // button.appendChild(buttonImg); + // contentDiv.appendChild(button); + // } + // contentDiv.style.width = this.width_ + 'px'; + // dropdownDiv.appendChild(contentDiv); + // if (this.maxRows_) { + // // Limit the number of rows shown, but add a partial next row to indicate scrolling + // dropdownDiv.style.maxHeight = (this.maxRows_ + 0.4) * (maxButtonHeight + 8) + 'px'; + // } + + // if (pxt.BrowserUtils.isFirefox()) { + // // This is to compensate for the scrollbar that overlays content in Firefox. It + // // gets removed in onHide_() + // dropdownDiv.style.paddingRight = "20px"; + // } + + // Blockly.DropDownDiv.setColour(this.backgroundColour_, this.borderColour_); + + // Blockly.DropDownDiv.showPositionedByField(this, this.onHide_.bind(this)); + + // let source = this.sourceBlock_ as Blockly.BlockSvg; + // this.savedPrimary_ = source?.getColour(); + // if (source?.isShadow()) { + // source.setColour(source.getColourTertiary()); + // } else if (this.borderRect_) { + // this.borderRect_.setAttribute('fill', source.getColourTertiary()); + // } } doValueUpdate_(newValue: any): void { From d4e0563b1bcbafb3409f0d59601a54a99a818714 Mon Sep 17 00:00:00 2001 From: Robert Knight Date: Thu, 23 Jan 2025 14:45:29 +0000 Subject: [PATCH 2/5] Needs the package.json this time --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 3b1bdc6f9281..06a602dfa536 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ }, "dependencies": { "@blockly/field-colour": "5.0.5", + "@blockly/field-grid-dropdown": "^5.0.12", "@blockly/keyboard-experiment": "0.0.1", "@blockly/plugin-workspace-search": "9.1.0", "@crowdin/crowdin-api-client": "^1.33.0", From d391c253850ffadde8af6cc257b37d675c4cf48a Mon Sep 17 00:00:00 2001 From: Robert Knight Date: Thu, 23 Jan 2025 18:10:12 +0000 Subject: [PATCH 3/5] An attempt to retain the MakeCode styling --- pxtblocks/fields/field_imagedropdown.ts | 170 +++++++++--------------- 1 file changed, 64 insertions(+), 106 deletions(-) diff --git a/pxtblocks/fields/field_imagedropdown.ts b/pxtblocks/fields/field_imagedropdown.ts index e4ca27b41c8f..e7b6f538371e 100644 --- a/pxtblocks/fields/field_imagedropdown.ts +++ b/pxtblocks/fields/field_imagedropdown.ts @@ -3,6 +3,7 @@ import * as Blockly from "blockly"; import { FieldCustom, FieldCustomDropdownOptions, parseColour } from "./field_utils"; import { FieldGridDropdown } from "@blockly/field-grid-dropdown"; +import { ImageProperties } from "blockly/core/field_dropdown"; export interface FieldImageDropdownOptions extends FieldCustomDropdownOptions { columns?: string; @@ -27,7 +28,6 @@ export class FieldImageDropdown extends FieldGridDropdown implements FieldCustom protected savedPrimary_: string; constructor(text: string, options: FieldImageDropdownOptions, validator?: Function) { - console.log(options.data) super(options.data, undefined, {columns: parseInt(options.columns)}); this.columns_ = parseInt(options.columns); @@ -44,102 +44,57 @@ export class FieldImageDropdown extends FieldGridDropdown implements FieldCustom */ public showEditor_() { super.showEditor_(); - // // If there is an existing drop-down we own, this is a request to hide the drop-down. - // if (Blockly.DropDownDiv.hideIfOwner(this)) { - // return; - // } - // // If there is an existing drop-down someone else owns, hide it immediately and clear it. - // Blockly.DropDownDiv.hideWithoutAnimation(); - // Blockly.DropDownDiv.clearContent(); - // // Populate the drop-down with the icons for this field. - // let dropdownDiv = Blockly.DropDownDiv.getContentDiv() as HTMLElement; - // let contentDiv = document.createElement('div'); - // // Accessibility properties - // contentDiv.setAttribute('role', 'menu'); - // contentDiv.setAttribute('aria-haspopup', 'true'); - // const options = this.getOptions(); - // let maxButtonHeight: number = 0; - // for (let i = 0; i < options.length; i++) { - // let content = (options[i] as any)[0]; // Human-readable text or image. - // const value = (options[i] as any)[1]; // Language-neutral value. - // // Icons with the type property placeholder take up space but don't have any functionality - // // Use for special-case layouts - // if (content.type == 'placeholder') { - // let placeholder = document.createElement('span'); - // placeholder.setAttribute('class', 'blocklyDropDownPlaceholder'); - // placeholder.style.width = content.width + 'px'; - // placeholder.style.height = content.height + 'px'; - // contentDiv.appendChild(placeholder); - // continue; - // } - // let button = document.createElement('button'); - // button.setAttribute('id', ':' + i); // For aria-activedescendant - // button.setAttribute('role', 'menuitem'); - // button.setAttribute('class', 'blocklyDropDownButton'); - // button.title = content.alt; - // let buttonSize = content.height; - // if (this.columns_) { - // buttonSize = ((this.width_ / this.columns_) - 8); - // button.style.width = buttonSize + 'px'; - // button.style.height = buttonSize + 'px'; - // } else { - // button.style.width = content.width + 'px'; - // button.style.height = content.height + 'px'; - // } - // if (buttonSize > maxButtonHeight) { - // maxButtonHeight = buttonSize; - // } - // let backgroundColor = this.backgroundColour_; - // if (value == this.getValue()) { - // // This icon is selected, show it in a different colour - // backgroundColor = (this.sourceBlock_ as Blockly.BlockSvg).getColourTertiary(); - // button.setAttribute('aria-selected', 'true'); - // } - // button.style.backgroundColor = backgroundColor; - // button.style.borderColor = this.borderColour_; - // Blockly.browserEvents.bind(button, 'click', this, this.buttonClick_); - // Blockly.browserEvents.bind(button, 'mouseover', this, () => { - // button.setAttribute('class', 'blocklyDropDownButton blocklyDropDownButtonHover'); - // contentDiv.setAttribute('aria-activedescendant', button.id); - // }); - // Blockly.browserEvents.bind(button, 'mouseout', this, () => { - // button.setAttribute('class', 'blocklyDropDownButton'); - // contentDiv.removeAttribute('aria-activedescendant'); - // }); - // let buttonImg = document.createElement('img'); - // buttonImg.src = content.src; - // //buttonImg.alt = icon.alt; - // // Upon click/touch, we will be able to get the clicked element as e.target - // // Store a data attribute on all possible click targets so we can match it to the icon. - // button.setAttribute('data-value', value); - // buttonImg.setAttribute('data-value', value); - // button.appendChild(buttonImg); - // contentDiv.appendChild(button); - // } - // contentDiv.style.width = this.width_ + 'px'; - // dropdownDiv.appendChild(contentDiv); - // if (this.maxRows_) { - // // Limit the number of rows shown, but add a partial next row to indicate scrolling - // dropdownDiv.style.maxHeight = (this.maxRows_ + 0.4) * (maxButtonHeight + 8) + 'px'; - // } - - // if (pxt.BrowserUtils.isFirefox()) { - // // This is to compensate for the scrollbar that overlays content in Firefox. It - // // gets removed in onHide_() - // dropdownDiv.style.paddingRight = "20px"; - // } - - // Blockly.DropDownDiv.setColour(this.backgroundColour_, this.borderColour_); - - // Blockly.DropDownDiv.showPositionedByField(this, this.onHide_.bind(this)); - - // let source = this.sourceBlock_ as Blockly.BlockSvg; - // this.savedPrimary_ = source?.getColour(); - // if (source?.isShadow()) { - // source.setColour(source.getColourTertiary()); - // } else if (this.borderRect_) { - // this.borderRect_.setAttribute('fill', source.getColourTertiary()); - // } + + const dropdownDiv = Blockly.DropDownDiv.getContentDiv() as HTMLElement; + const contentDiv = dropdownDiv.querySelector("div"); + const options = this.getOptions(); + let maxButtonHeight: number = 0; + + contentDiv.querySelectorAll("img").forEach(el => { + el.removeAttribute("height") + el.removeAttribute("width") + }); + + Array.from(contentDiv.children).forEach((el, i) => { + const content = (options[i] as any)[0]; // Human-readable text or image. + const div = el as HTMLDivElement; + div.classList.add("blocklyDropDownButton"); + + // We need to find where this occurs and handle it. + // if (content.type == 'placeholder') { + // let placeholder = document.createElement('span'); + // placeholder.setAttribute('class', 'blocklyDropDownPlaceholder'); + // placeholder.style.width = content.width + 'px'; + // placeholder.style.height = content.height + 'px'; + // contentDiv.appendChild(placeholder); + // return; + // } + + let buttonSize = content.height; + if (this.columns_) { + buttonSize = ((this.width_ / this.columns_) - 8); + div.style.width = buttonSize + 'px'; + div.style.height = buttonSize + 'px'; + } else { + div.style.width = content.width + 'px'; + div.style.height = content.height + 'px'; + } + if (buttonSize > maxButtonHeight) { + maxButtonHeight = buttonSize; + } + }) + + contentDiv.style.width = this.width_ + 'px'; + if (this.maxRows_) { + // Limit the number of rows shown, but add a partial next row to indicate scrolling + dropdownDiv.style.maxHeight = (this.maxRows_ + 0.4) * (maxButtonHeight + 8) + 'px'; + } + + if (pxt.BrowserUtils.isFirefox()) { + // This is to compensate for the scrollbar that overlays content in Firefox. It + // gets removed in onHide_() + dropdownDiv.style.paddingRight = "20px"; + } } doValueUpdate_(newValue: any): void { @@ -182,10 +137,15 @@ export class FieldImageDropdown extends FieldGridDropdown implements FieldCustom } Blockly.Css.register(` +.fieldGridDropDownContainer.blocklyMenu { + grid-gap: 0px; + margin: 0px; +} + .blocklyDropDownButton { display: inline-block; float: left; - padding: 0; + padding: 0 !important; margin: 4px; border-radius: 4px; outline: none; @@ -194,17 +154,15 @@ Blockly.Css.register(` cursor: pointer; } -.blocklyDropDownButtonHover { - box-shadow: 0px 0px 0px 4px rgba(255, 255, 255, 0.2); -} - -.blocklyDropDownButton:active { - box-shadow: 0px 0px 0px 6px rgba(255, 255, 255, 0.2); +.blocklyDropDownButton > div { + display: flex; + align-items: center; + justify-content: center; + height: 100%; } -.blocklyDropDownButton > img { +.blocklyDropDownButton img { width: 80%; height: 80%; - margin-top: 5% } `) \ No newline at end of file From a16be8c892b3f6cd1147f8bf8c38aed144ccb263 Mon Sep 17 00:00:00 2001 From: Robert Knight Date: Fri, 24 Jan 2025 09:23:02 +0000 Subject: [PATCH 4/5] Remove unused code --- pxtblocks/fields/field_imagedropdown.ts | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/pxtblocks/fields/field_imagedropdown.ts b/pxtblocks/fields/field_imagedropdown.ts index e7b6f538371e..85dca414ecf5 100644 --- a/pxtblocks/fields/field_imagedropdown.ts +++ b/pxtblocks/fields/field_imagedropdown.ts @@ -3,7 +3,6 @@ import * as Blockly from "blockly"; import { FieldCustom, FieldCustomDropdownOptions, parseColour } from "./field_utils"; import { FieldGridDropdown } from "@blockly/field-grid-dropdown"; -import { ImageProperties } from "blockly/core/field_dropdown"; export interface FieldImageDropdownOptions extends FieldCustomDropdownOptions { columns?: string; @@ -84,7 +83,6 @@ export class FieldImageDropdown extends FieldGridDropdown implements FieldCustom } }) - contentDiv.style.width = this.width_ + 'px'; if (this.maxRows_) { // Limit the number of rows shown, but add a partial next row to indicate scrolling dropdownDiv.style.maxHeight = (this.maxRows_ + 0.4) * (maxButtonHeight + 8) + 'px'; @@ -114,26 +112,6 @@ export class FieldImageDropdown extends FieldGridDropdown implements FieldCustom this.setValue(value); Blockly.DropDownDiv.hide(); }; - - /** - * Callback for when the drop-down is hidden. - */ - protected onHide_() { - let content = Blockly.DropDownDiv.getContentDiv() as HTMLElement; - content.removeAttribute('role'); - content.removeAttribute('aria-haspopup'); - content.removeAttribute('aria-activedescendant'); - content.style.width = ''; - content.style.paddingRight = ''; - content.style.maxHeight = ''; - - let source = this.sourceBlock_ as Blockly.BlockSvg; - if (source?.isShadow()) { - this.sourceBlock_.setColour(this.savedPrimary_); - } else if (this.borderRect_) { - this.borderRect_.setAttribute('fill', this.savedPrimary_); - } - }; } Blockly.Css.register(` From 8ae735943421a9e09b42623fdf9dbad26ab3acb1 Mon Sep 17 00:00:00 2001 From: Robert Knight Date: Fri, 24 Jan 2025 10:43:50 +0000 Subject: [PATCH 5/5] Use the Blockly grid plugin for the on gesture block --- pxtblocks/fields/field_imagedropdown.ts | 15 +-- pxtblocks/fields/field_images.ts | 149 +++++++++--------------- 2 files changed, 57 insertions(+), 107 deletions(-) diff --git a/pxtblocks/fields/field_imagedropdown.ts b/pxtblocks/fields/field_imagedropdown.ts index 85dca414ecf5..86c304ac662b 100644 --- a/pxtblocks/fields/field_imagedropdown.ts +++ b/pxtblocks/fields/field_imagedropdown.ts @@ -66,7 +66,7 @@ export class FieldImageDropdown extends FieldGridDropdown implements FieldCustom // placeholder.style.width = content.width + 'px'; // placeholder.style.height = content.height + 'px'; // contentDiv.appendChild(placeholder); - // return; + // continue; // } let buttonSize = content.height; @@ -99,19 +99,6 @@ export class FieldImageDropdown extends FieldGridDropdown implements FieldCustom (this as any).selectedOption_ = undefined; super.doValueUpdate_(newValue); } - - /** - * Callback for when a button is clicked inside the drop-down. - * Should be bound to the FieldIconMenu. - * @param {Event} e DOM event for the click/touch - * @private - */ - protected buttonClick_ = (e: MouseEvent) => { - let value = (e.target as Element).getAttribute('data-value'); - if (!value) return; - this.setValue(value); - Blockly.DropDownDiv.hide(); - }; } Blockly.Css.register(` diff --git a/pxtblocks/fields/field_images.ts b/pxtblocks/fields/field_images.ts index 3558410a0ac8..4ec5087b5911 100644 --- a/pxtblocks/fields/field_images.ts +++ b/pxtblocks/fields/field_images.ts @@ -12,14 +12,14 @@ export interface FieldImagesOptions extends FieldImageDropdownOptions { export class FieldImages extends FieldImageDropdown implements FieldCustom { public isFieldCustom_ = true; - private shouldSort_: boolean; - protected addLabel_: boolean; constructor(text: string, options: FieldImagesOptions, validator?: Function) { - super(text, options, validator); + if (options.sort) { + options.data.sort() + } + super(text, {...options, columns: "4"}, validator); - this.shouldSort_ = options.sort; this.addLabel_ = !!options.addLabel; } @@ -28,104 +28,48 @@ export class FieldImages extends FieldImageDropdown implements FieldCustom { * @private */ public showEditor_() { - // If there is an existing drop-down we own, this is a request to hide the drop-down. - if (Blockly.DropDownDiv.hideIfOwner(this)) { - return; - } - let sourceBlock = this.sourceBlock_ as Blockly.BlockSvg; - // If there is an existing drop-down someone else owns, hide it immediately and clear it. - Blockly.DropDownDiv.hideWithoutAnimation(); - Blockly.DropDownDiv.clearContent(); - // Populate the drop-down with the icons for this field. - let dropdownDiv = Blockly.DropDownDiv.getContentDiv(); - let contentDiv = document.createElement('div'); - // Accessibility properties - contentDiv.setAttribute('role', 'menu'); - contentDiv.setAttribute('aria-haspopup', 'true'); + super.showEditor_(); + + const dropdownDiv = Blockly.DropDownDiv.getContentDiv() as HTMLElement; + const contentDiv = dropdownDiv.querySelector("div"); const options = this.getOptions(); - if (this.shouldSort_) options.sort(); - for (let i = 0; i < options.length; i++) { + + contentDiv.querySelectorAll("img").forEach(el => { + el.removeAttribute("height") + el.removeAttribute("width") + }); + + Array.from(contentDiv.children).forEach((el, i) => { const content = (options[i] as any)[0]; // Human-readable text or image. const value = (options[i] as any)[1]; // Language-neutral value. - // Icons with the type property placeholder take up space but don't have any functionality - // Use for special-case layouts - if (content.type == 'placeholder') { - let placeholder = document.createElement('span'); - placeholder.setAttribute('class', 'blocklyDropDownPlaceholder'); - placeholder.style.width = content.width + 'px'; - placeholder.style.height = content.height + 'px'; - contentDiv.appendChild(placeholder); - continue; - } - let button = document.createElement('button'); - button.setAttribute('id', ':' + i); // For aria-activedescendant - button.setAttribute('role', 'menuitem'); - button.setAttribute('class', 'blocklyDropDownButton'); - button.title = content.alt; + const div = el as HTMLDivElement; + div.classList.add("blocklyDropDownButton", "blocklyDropDownImage") + + // We need to find where this occurs and handle it. + // if (content.type == 'placeholder') { + // let placeholder = document.createElement('span'); + // placeholder.setAttribute('class', 'blocklyDropDownPlaceholder'); + // placeholder.style.width = content.width + 'px'; + // placeholder.style.height = content.height + 'px'; + // contentDiv.appendChild(placeholder); + // continue; + // } + if ((this as any).columns_) { - button.style.width = (((this as any).width_ / (this as any).columns_) - 8) + 'px'; - //button.style.height = ((this.width_ / this.columns_) - 8) + 'px'; + div.style.width = (((this as any).width_ / (this as any).columns_) - 8) + 'px'; + div.style.height = "unset"; } else { - button.style.width = content.width + 'px'; - button.style.height = content.height + 'px'; - } - let backgroundColor = sourceBlock.getColour(); - if (value == this.getValue()) { - // This icon is selected, show it in a different colour - backgroundColor = sourceBlock.getColourTertiary(); - button.setAttribute('aria-selected', 'true'); + div.style.width = content.width + 'px'; + div.style.height = content.height + 'px'; } - button.style.backgroundColor = backgroundColor; - button.style.borderColor = sourceBlock.getColourTertiary(); - Blockly.browserEvents.bind(button, 'click', this, this.buttonClick_); - Blockly.browserEvents.bind(button, 'mouseover', this, () => { - button.setAttribute('class', 'blocklyDropDownButton blocklyDropDownButtonHover'); - contentDiv.setAttribute('aria-activedescendant', button.id); - }); - Blockly.browserEvents.bind(button, 'mouseout', this, () => { - button.setAttribute('class', 'blocklyDropDownButton'); - contentDiv.removeAttribute('aria-activedescendant'); - }); - let buttonImg = document.createElement('img'); - buttonImg.src = content.src; - //buttonImg.alt = icon.alt; - // Upon click/touch, we will be able to get the clicked element as e.target - // Store a data attribute on all possible click targets so we can match it to the icon. - button.setAttribute('data-value', value); - buttonImg.setAttribute('data-value', value); - button.appendChild(buttonImg); + if (this.addLabel_) { const buttonText = this.createTextNode_(content.alt); buttonText.setAttribute('data-value', value); - button.appendChild(buttonText); + // Append to blocklyMenuItemContent div. + div.children[0].appendChild(buttonText); } - contentDiv.appendChild(button); - } - contentDiv.style.width = (this as any).width_ + 'px'; - dropdownDiv.appendChild(contentDiv); - - Blockly.DropDownDiv.setColour(sourceBlock.getColour(), sourceBlock.getColourTertiary()); - - // Position based on the field position. - Blockly.DropDownDiv.showPositionedByField(this, this.onHideCallback.bind(this)); - - // Update colour to look selected. - this.savedPrimary_ = sourceBlock?.getColour(); - if (sourceBlock?.isShadow()) { - sourceBlock.setColour(sourceBlock.style.colourTertiary); - } else if (this.borderRect_) { - this.borderRect_.setAttribute('fill', sourceBlock.style.colourTertiary); - } - } - - // Update color (deselect) on dropdown hide - protected onHideCallback() { - let source = this.sourceBlock_ as Blockly.BlockSvg; - if (source?.isShadow()) { - source.setColour(this.savedPrimary_); - } else if (this.borderRect_) { - this.borderRect_.setAttribute('fill', this.savedPrimary_); - } + }) } protected createTextNode_(text: string) { @@ -134,4 +78,23 @@ export class FieldImages extends FieldImageDropdown implements FieldCustom { textSpan.textContent = text; return textSpan; } -} \ No newline at end of file +} + +Blockly.Css.register(` +.blocklyDropDownImage > div { + flex-direction: column; + gap: 4px; + padding: 5px 0; +} + +.blocklyDropDownButton.blocklyDropDownImage img { + width: 80%; + height: unset; +} + +.blocklyDropDownImage .blocklyDropdownTextLabel { + font-family: sans-serif; + line-height: 1.15; + text-align: center; +} +`) \ No newline at end of file