mirror of
https://github.com/google/blockly.git
synced 2026-01-10 02:17:09 +01:00
fix: do not hide all chaff when resizing (#6916)
This commit is contained in:
committed by
GitHub
parent
2fa7280ce1
commit
2bbb3aa1fc
@@ -26,22 +26,24 @@ import type {WorkspaceSvg} from './workspace_svg.js';
|
||||
* Bumps the given object that has passed out of bounds.
|
||||
*
|
||||
* @param workspace The workspace containing the object.
|
||||
* @param scrollMetrics Scroll metrics
|
||||
* in workspace coordinates.
|
||||
* @param bounds The region to bump an object into. For example, pass
|
||||
* ScrollMetrics to bump a block into the scrollable region of the
|
||||
* workspace, or pass ViewMetrics to bump a block into the visible region of
|
||||
* the workspace. This should be specified in workspace coordinates.
|
||||
* @param object The object to bump.
|
||||
* @returns True if block was bumped.
|
||||
* @returns True if object was bumped.
|
||||
*/
|
||||
function bumpObjectIntoBounds(
|
||||
workspace: WorkspaceSvg, scrollMetrics: ContainerRegion,
|
||||
workspace: WorkspaceSvg, bounds: ContainerRegion,
|
||||
object: IBoundedElement): boolean {
|
||||
// Compute new top/left position for object.
|
||||
const objectMetrics = object.getBoundingRectangle();
|
||||
const height = objectMetrics.bottom - objectMetrics.top;
|
||||
const width = objectMetrics.right - objectMetrics.left;
|
||||
|
||||
const topClamp = scrollMetrics.top;
|
||||
const scrollMetricsBottom = scrollMetrics.top + scrollMetrics.height;
|
||||
const bottomClamp = scrollMetricsBottom - height;
|
||||
const topClamp = bounds.top;
|
||||
const boundsBottom = bounds.top + bounds.height;
|
||||
const bottomClamp = boundsBottom - height;
|
||||
// If the object is taller than the workspace we want to
|
||||
// top-align the block
|
||||
const newYPosition =
|
||||
@@ -50,9 +52,9 @@ function bumpObjectIntoBounds(
|
||||
|
||||
// Note: Even in RTL mode the "anchor" of the object is the
|
||||
// top-left corner of the object.
|
||||
let leftClamp = scrollMetrics.left;
|
||||
const scrollMetricsRight = scrollMetrics.left + scrollMetrics.width;
|
||||
let rightClamp = scrollMetricsRight - width;
|
||||
let leftClamp = bounds.left;
|
||||
const boundsRight = bounds.left + bounds.width;
|
||||
let rightClamp = boundsRight - width;
|
||||
if (workspace.RTL) {
|
||||
// If the object is wider than the workspace and we're in RTL
|
||||
// mode we want to right-align the block, which means setting
|
||||
|
||||
@@ -729,6 +729,28 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
|
||||
protected showEditor_(_e?: Event): void {}
|
||||
// NOP
|
||||
|
||||
/**
|
||||
* A developer hook to reposition the WidgetDiv during a window resize. You
|
||||
* need to define this hook if your field has a WidgetDiv that needs to
|
||||
* reposition itself when the window is resized. For example, text input
|
||||
* fields define this hook so that the input WidgetDiv can reposition itself
|
||||
* on a window resize event. This is especially important when modal inputs
|
||||
* have been disabled, as Android devices will fire a window resize event when
|
||||
* the soft keyboard opens.
|
||||
*
|
||||
* If you want the WidgetDiv to hide itself instead of repositioning, return
|
||||
* false. This is the default behavior.
|
||||
*
|
||||
* DropdownDivs already handle their own positioning logic, so you do not need
|
||||
* to override this function if your field only has a DropdownDiv.
|
||||
*
|
||||
* @returns True if the field should be repositioned,
|
||||
* false if the WidgetDiv should hide itself instead.
|
||||
*/
|
||||
repositionForWindowResize(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the size of the field based on the text.
|
||||
*
|
||||
|
||||
@@ -15,7 +15,8 @@ goog.declareModuleId('Blockly.FieldInput');
|
||||
// Unused import preserved for side-effects. Remove if unneeded.
|
||||
import './events/events_block_change.js';
|
||||
|
||||
import type {BlockSvg} from './block_svg.js';
|
||||
import {BlockSvg} from './block_svg.js';
|
||||
import * as bumpObjects from './bump_objects.js';
|
||||
import * as browserEvents from './browser_events.js';
|
||||
import * as dialog from './dialog.js';
|
||||
import * as dom from './utils/dom.js';
|
||||
@@ -505,6 +506,29 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
|
||||
div!.style.top = xy.y + 'px';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles repositioning the WidgetDiv used for input fields when the
|
||||
* workspace is resized. Will bump the block into the viewport and update the
|
||||
* position of the field if necessary.
|
||||
*
|
||||
* @returns True for rendered workspaces, as we never want to hide the widget
|
||||
* div.
|
||||
*/
|
||||
override repositionForWindowResize(): boolean {
|
||||
const block = this.getSourceBlock();
|
||||
// This shouldn't be possible. We should never have a WidgetDiv if not using
|
||||
// rendered blocks.
|
||||
if (!(block instanceof BlockSvg)) return false;
|
||||
|
||||
bumpObjects.bumpIntoBounds(
|
||||
this.workspace_!,
|
||||
this.workspace_!.getMetricsManager().getViewMetrics(true), block);
|
||||
|
||||
this.resizeEditor_();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the field is tab navigable.
|
||||
*
|
||||
|
||||
@@ -213,7 +213,12 @@ function init(mainWorkspace: WorkspaceSvg) {
|
||||
|
||||
const workspaceResizeHandler =
|
||||
browserEvents.conditionalBind(window, 'resize', null, function() {
|
||||
mainWorkspace.hideChaff(true);
|
||||
// Don't hide all the chaff. Leave the dropdown and widget divs open if
|
||||
// possible.
|
||||
Tooltip.hide();
|
||||
mainWorkspace.hideComponents(true);
|
||||
dropDownDiv.repositionForWindowResize();
|
||||
WidgetDiv.repositionForWindowResize();
|
||||
common.svgResize(mainWorkspace);
|
||||
bumpObjects.bumpTopObjectsIntoBounds(mainWorkspace);
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ goog.declareModuleId('Blockly.WidgetDiv');
|
||||
|
||||
import * as common from './common.js';
|
||||
import * as dom from './utils/dom.js';
|
||||
import type {Field} from './field.js';
|
||||
import type {Rect} from './utils/rect.js';
|
||||
import type {Size} from './utils/size.js';
|
||||
import type {WorkspaceSvg} from './workspace_svg.js';
|
||||
@@ -237,3 +238,25 @@ function calculateY(
|
||||
return anchorBBox.bottom;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the owner is a field for purposes of repositioning.
|
||||
* We can't simply check `instanceof Field` as that would introduce a circular
|
||||
* dependency.
|
||||
*/
|
||||
function isRepositionable(item: any): item is Field {
|
||||
return !!item?.repositionForWindowResize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reposition the widget div if the owner of it says to.
|
||||
* If the owner isn't a field, just give up and hide it.
|
||||
*/
|
||||
export function repositionForWindowResize(): void {
|
||||
if (!isRepositionable(owner) || !owner.repositionForWindowResize()) {
|
||||
// If the owner is not a Field, or if the owner returns false from the
|
||||
// reposition method, we should hide the widget div. Otherwise, we'll assume
|
||||
// the owner handled any needed resize.
|
||||
hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2511,14 +2511,25 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg {
|
||||
/**
|
||||
* Close tooltips, context menus, dropdown selections, etc.
|
||||
*
|
||||
* @param opt_onlyClosePopups Whether only popups should be closed.
|
||||
* @param onlyClosePopups Whether only popups should be closed. Defaults to
|
||||
* false.
|
||||
*/
|
||||
hideChaff(opt_onlyClosePopups?: boolean) {
|
||||
hideChaff(onlyClosePopups = false) {
|
||||
Tooltip.hide();
|
||||
WidgetDiv.hide();
|
||||
dropDownDiv.hideWithoutAnimation();
|
||||
|
||||
const onlyClosePopups = !!opt_onlyClosePopups;
|
||||
this.hideComponents(onlyClosePopups);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide any autohideable components (like flyout, trashcan, and any
|
||||
* user-registered components).
|
||||
*
|
||||
* @param onlyClosePopups Whether only popups should be closed. Defaults to
|
||||
* false.
|
||||
*/
|
||||
hideComponents(onlyClosePopups = false) {
|
||||
const autoHideables = this.getComponentManager().getComponents(
|
||||
ComponentManager.Capability.AUTOHIDEABLE, true);
|
||||
autoHideables.forEach(
|
||||
|
||||
Reference in New Issue
Block a user