diff --git a/main/blockhandling.js b/main/blockhandling.js index 7254ae4a..b5269aa6 100644 --- a/main/blockhandling.js +++ b/main/blockhandling.js @@ -591,7 +591,7 @@ function observeBlocklyInputs() { // Observe only the Blockly container to avoid scanning the entire document const blocklyContainer = - workspace.getParentSvg()?.closest("#blocklyDiv") ?? + workspace?.getParentSvg()?.closest("#blocklyDiv") ?? document.getElementById("blocklyDiv") ?? document.body; observer.observe(blocklyContainer, { childList: true, subtree: true }); diff --git a/main/blocklyinit.js b/main/blocklyinit.js index a749293d..e9fc88e9 100644 --- a/main/blocklyinit.js +++ b/main/blocklyinit.js @@ -647,6 +647,55 @@ export function initializeWorkspace() { } workspaceSearch.init(); + // Fade non-matching blocks during search + const blocklyDiv = document.getElementById("blocklyDiv"); + const originalOpen = workspaceSearch.open.bind(workspaceSearch); + const originalClose = workspaceSearch.close.bind(workspaceSearch); + workspaceSearch.open = function () { + originalOpen(); + blocklyDiv?.classList.add("blockly-search-active"); + }; + workspaceSearch.close = function () { + originalClose(); + blocklyDiv?.classList.remove("blockly-search-active"); + }; + + // Override highlight methods to work at block-group level so the plugin's + // injected fill: #000 rule never applies to matched block paths. + workspaceSearch.highlightSearchGroup = function (blocks) { + const matchTopIds = new Set(); + blocks.forEach((block) => { + block.getSvgRoot()?.classList.add("ws-search-match"); + let top = block; + while (top.getSurroundParent()) top = top.getSurroundParent(); + matchTopIds.add(top.id); + }); + workspace.getTopBlocks(false).forEach((block) => { + if (!matchTopIds.has(block.id)) { + block.getSvgRoot()?.classList.add("ws-search-fade"); + } + }); + }; + workspaceSearch.unhighlightSearchGroup = function (blocks) { + blocks.forEach((block) => block.getSvgRoot()?.classList.remove("ws-search-match")); + workspace.getTopBlocks(false).forEach((block) => { + block.getSvgRoot()?.classList.remove("ws-search-fade"); + }); + }; + workspaceSearch.highlightCurrentSelection = function (block) { + const svg = block.getSvgRoot(); + if (svg) { + svg.classList.add("ws-search-current"); + let top = block; + while (top.getSurroundParent()) top = top.getSurroundParent(); + const topSvg = top.getSvgRoot(); + if (topSvg) topSvg.parentNode?.appendChild(topSvg); + } + }; + workspaceSearch.unhighlightCurrentSelection = function (block) { + block.getSvgRoot()?.classList.remove("ws-search-current"); + }; + // Override the workspace centering for workspace search as it jumps all over the place by default! const originalCenter = workspace.centerOnBlock.bind(workspace); diff --git a/style/blockly.css b/style/blockly.css index 1ff35190..1f60c75f 100644 --- a/style/blockly.css +++ b/style/blockly.css @@ -198,9 +198,6 @@ textarea.blocklyCommentText.blocklyTextarea.blocklyText { } body[data-theme="contrast"] { - .blocklyWidgetDiv .blocklyMenuItemContent { - color: black !important; - } .blocklyField text.blocklyText.blocklyFieldText { fill: black !important; } @@ -241,9 +238,6 @@ body[data-theme="low-vision"] { } } -textarea.blocklyCommentText.blocklyTextarea.blocklyText { - color: black; -} /* Search Highlight Styles */ .blockly-ws-search { background: var(--color-bg); @@ -256,12 +250,36 @@ textarea.blocklyCommentText.blocklyTextarea.blocklyText { z-index: 70; } -path.blocklyPath.blockly-ws-search-highlight { - fill: var(--color-search-highlight); +/* Fade top-level blocks that contain no search matches */ +#blocklyDiv.blockly-search-active .ws-search-fade { + opacity: 0.4; + transition: opacity 0.15s ease; } -path.blocklyPath.blockly-ws-search-highlight.blockly-ws-search-current { - fill: var(--color-border-highlight); +/* Non-current matches get a pale yellow background */ +#blocklyDiv.blockly-search-active + .ws-search-match:not(.ws-search-current) + > path.blocklyPath { + fill: rgba(255, 242, 0, 0.3) !important; +} + +/* Low-vision: replace fill with a dashed white stroke for better contrast */ +[data-theme="low-vision"] + #blocklyDiv.blockly-search-active + .ws-search-match:not(.ws-search-current) + > path.blocklyPath { + fill: revert !important; + stroke: #ffffff !important; + stroke-width: 10px !important; + stroke-dasharray: 8 4; + paint-order: stroke fill; +} + +/* Current match gets a yellow outline */ +#blocklyDiv.blockly-search-active .ws-search-current > path.blocklyPath { + stroke: var(--block-outline-focus) !important; + stroke-width: 10px !important; + paint-order: stroke fill; } @media (min-width: 769px) { @@ -418,24 +436,19 @@ path.blocklyPath.blockly-ws-search-highlight.blockly-ws-search-current { cursor: pointer !important; } -/* Search Highlight Styles */ -.blockly-ws-search { - background: var(--color-bg); - margin-top: 5px; - border: solid var(--color-border-highlight) 4px; - box-shadow: 0px 10px 20px var(--color-shadow); - justify-content: center; - padding: 0.25em; - position: absolute; - z-index: 70; -} - -path.blocklyPath.blockly-ws-search-highlight { - fill: var(--color-search-highlight); +[data-theme="dark"] .blockly-ws-search button, +[data-theme="dark-contrast"] .blockly-ws-search button, +[data-theme="low-vision"] .blockly-ws-search button, +[data-theme="contrast"] .blockly-ws-search button { + filter: invert(1); } -path.blocklyPath.blockly-ws-search-highlight.blockly-ws-search-current { - fill: var(--color-border-highlight); +[data-theme="dark"] .blockly-ws-search button:focus, +[data-theme="dark-contrast"] .blockly-ws-search button:focus, +[data-theme="low-vision"] .blockly-ws-search button:focus, +[data-theme="contrast"] .blockly-ws-search button:focus { + outline: 3px solid #0033cc; /* inverts to #ffcc33 (~#fc3) after filter: invert(1) */ + outline-offset: 2px; } /* Responsive Styles */ @@ -471,16 +484,6 @@ path.blocklyPath.blockly-ws-search-highlight.blockly-ws-search-current { stroke-width: 2.5px !important; } -/* Toolbox category selection outline */ -.blocklyToolboxCategoryContainer[aria-selected="true"][aria-level="1"] - > .blocklyToolboxCategory, -.blocklyToolboxCategoryContainer[aria-selected="true"][aria-level="2"] - > .blocklyToolboxCategory { - outline: 2px solid var(--color-outline-focus); - outline-offset: -2px; - border-radius: 4px; -} - /* 2. Default item text color */ .blocklyDropDownDiv .goog-menuitem-content, .blocklyWidgetDiv .goog-menuitem-content, @@ -575,23 +578,6 @@ body[data-theme="dark"] .blocklyTreeLabel { outline: none !important; } -.blocklyToolboxCategoryContainer[aria-selected="true"][aria-level="1"] - > .blocklyToolboxCategory { - outline: 2px solid #fc3; - outline-offset: -2px; - border-radius: 4px; -} - -/* Add custom selection outline for both level 1 and 2 categories */ -.blocklyToolboxCategoryContainer[aria-selected="true"][aria-level="1"] - > .blocklyToolboxCategory, -.blocklyToolboxCategoryContainer[aria-selected="true"][aria-level="2"] - > .blocklyToolboxCategory { - outline: 2px solid #fc3; - outline-offset: -2px; - border-radius: 4px; -} - [data-theme="contrast"] .blocklyText { fill: white !important; } @@ -784,22 +770,6 @@ body[data-theme="low-vision"] } } -.blocklyToolboxCategoryContainer[aria-selected="true"][aria-level="1"] - > .blocklyToolboxCategory { - outline: 2px solid #fc3; - outline-offset: -2px; - border-radius: 4px; -} - -.blocklyToolboxCategoryContainer[aria-selected="true"][aria-level="1"] - > .blocklyToolboxCategory, -.blocklyToolboxCategoryContainer[aria-selected="true"][aria-level="2"] - > .blocklyToolboxCategory { - outline: 2px solid #fc3; - outline-offset: -2px; - border-radius: 4px; -} - /* Keyboard focus for image BUTTON fields only (e.g. plus/minus/toggle icons) */ .blocklyKeyboardNavigation .blocklyActiveFocus.blocklyImageField { outline: 5px solid var(--block-outline-focus); @@ -956,11 +926,6 @@ body[data-theme="low-vision"] stroke-width: 2px !important; } -/* Wrap long lines inside the workspace-comment editor */ -textarea.blocklyCommentText.blocklyTextarea.blocklyText { - color: black; -} - /* Keep block comment editor constrained to its bubble */ .blocklyCommentForeignObject > body.blocklyMinimalBody, .blocklyCommentForeignObject textarea.blocklyCommentText {