This is an automated email from the ASF dual-hosted git repository.
rusackas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new 435fb8babcf fix(sqllab): make dark-theme occurrence highlight readable
(#42403)
435fb8babcf is described below
commit 435fb8babcfc77ce46116882fada901cbe153825
Author: Joe Li <[email protected]>
AuthorDate: Wed Jul 29 10:22:58 2026 -0700
fix(sqllab): make dark-theme occurrence highlight readable (#42403)
Co-authored-by: Claude Opus 4.8 <[email protected]>
---
.../AsyncAceEditor/AsyncAceEditor.test.tsx | 82 +++++++++++++++++++++-
.../src/components/AsyncAceEditor/index.tsx | 28 +++++++-
2 files changed, 106 insertions(+), 4 deletions(-)
diff --git
a/superset-frontend/packages/superset-ui-core/src/components/AsyncAceEditor/AsyncAceEditor.test.tsx
b/superset-frontend/packages/superset-ui-core/src/components/AsyncAceEditor/AsyncAceEditor.test.tsx
index 646c1f3bd8a..db5ac8da798 100644
---
a/superset-frontend/packages/superset-ui-core/src/components/AsyncAceEditor/AsyncAceEditor.test.tsx
+++
b/superset-frontend/packages/superset-ui-core/src/components/AsyncAceEditor/AsyncAceEditor.test.tsx
@@ -18,7 +18,11 @@
*/
import { createRef } from 'react';
import { render, screen, waitFor } from '@superset-ui/core/spec';
-import { supersetTheme } from '@apache-superset/core/theme';
+import {
+ supersetTheme,
+ ThemeProvider,
+ type SupersetTheme,
+} from '@apache-superset/core/theme';
import type AceEditor from 'react-ace';
import {
AsyncAceEditor,
@@ -30,6 +34,7 @@ import {
JsonEditor,
ConfigEditor,
aceCompletionHighlightStyles,
+ aceSelectedWordStyles,
} from '.';
import type { AceModule, AsyncAceEditorOptions } from './types';
@@ -55,6 +60,81 @@ test('themes the autocomplete completion highlight from the
theme', () => {
expect(styles).toContain(supersetTheme.colorPrimaryText);
});
+test('themes the selected-word occurrence markers from the theme', () => {
+ // The light `github` theme hardcodes a near-white box for the markers Ace
+ // paints on every other occurrence of the selected token, which makes the
+ // recolored token glyphs unreadable in dark mode. The shared editor
overrides
+ // the marker to reuse the dark-aware selection color so it stays legible.
+ const { styles } = aceSelectedWordStyles(supersetTheme);
+
+ expect(styles).toContain('.ace_selected-word');
+ // The default theme leaves `colorEditorSelection` unset, so the marker uses
+ // the documented `colorPrimaryBgHover` fallback.
+ expect(styles).toContain(supersetTheme.colorPrimaryBgHover);
+ // The override also restyles the marker border from the theme...
+ expect(styles).toContain(supersetTheme.colorBorder);
+ // ...and carries `!important`, which is what lets it win over Ace's bundled
+ // non-important `.ace-github .ace_marker-layer .ace_selected-word`
near-white
+ // rule regardless of stylesheet order (the effective-cascade guarantee that
+ // jsdom cannot verify by computed style).
+ expect(styles).toContain('!important');
+});
+
+// Collect every CSS rule the render injected: emotion's Global styles land in
+// <style> tags (non-speedy in test) while Ace's bundled theme uses the CSSOM,
+// so read both to reliably observe what actually reached the document.
+function getInjectedCss(): string {
+ const fromTags = Array.from(document.querySelectorAll('style'))
+ .map(tag => tag.textContent ?? '')
+ .join('\n');
+ const fromSheets = Array.from(document.styleSheets)
+ .map(sheet => {
+ try {
+ return Array.from(sheet.cssRules)
+ .map(rule => rule.cssText)
+ .join('\n');
+ } catch {
+ return '';
+ }
+ })
+ .join('\n');
+ return `${fromTags}\n${fromSheets}`;
+}
+
+test('wires the dark-aware selected-word marker into the editor Global
styles', async () => {
+ // Guards the actual regression: Ace's bundled `github` theme already injects
+ // a near-white `.ace-github ... .ace_selected-word` rule, so the fix is only
+ // effective if the editor's own Global block also emits a
`.ace_selected-word`
+ // override driven by the theme. Render with a distinctive
`colorEditorSelection`
+ // and assert that value reaches the injected selected-word rule — this
fails if
+ // the `${aceSelectedWordStyles(token)}` interpolation is dropped from
<Global>.
+ const markerColor = '#010203';
+ const darkTheme: SupersetTheme = {
+ ...supersetTheme,
+ colorEditorSelection: markerColor,
+ };
+
+ // The shared `render` helper injects its own default
`SupersetThemeProvider`,
+ // so nest the dark theme around the editor itself: the innermost emotion
+ // `ThemeProvider` is what the editor's `useTheme()` resolves.
+ const { container } = render(
+ <ThemeProvider theme={darkTheme}>
+ <SQLEditor />
+ </ThemeProvider>,
+ );
+
+ await waitFor(() => {
+ expect(container.querySelector('[id="ace-editor"]')).toBeInTheDocument();
+ });
+
+ const css = getInjectedCss();
+ // The dark-aware color is tied specifically to the selected-word marker
+ // (not just the plain `.ace_selection`), so the occurrence markers inherit
it.
+ expect(css).toMatch(
+ new RegExp(`\\.ace_selected-word[^}]*${markerColor}`, 'i'),
+ );
+});
+
test('SQLEditor uses fontFamilyCode from theme', async () => {
const ref = createRef<AceEditor>();
const { container } = render(<SQLEditor ref={ref as React.Ref<never>} />);
diff --git
a/superset-frontend/packages/superset-ui-core/src/components/AsyncAceEditor/index.tsx
b/superset-frontend/packages/superset-ui-core/src/components/AsyncAceEditor/index.tsx
index 425db253f89..30d5ce01c2a 100644
---
a/superset-frontend/packages/superset-ui-core/src/components/AsyncAceEditor/index.tsx
+++
b/superset-frontend/packages/superset-ui-core/src/components/AsyncAceEditor/index.tsx
@@ -113,6 +113,14 @@ export type AsyncAceEditorOptions = {
> | null;
};
+/**
+ * Dark-aware color for text selection in the editor. Shared so the plain
+ * selection and the "selected word" occurrence markers always match, falling
+ * back to `colorPrimaryBgHover` on themes that don't define the token.
+ */
+export const editorSelectionColor = (token: SupersetTheme) =>
+ token.colorEditorSelection ?? token.colorPrimaryBgHover;
+
/**
* Theme-aware styling for the matched-prefix highlight in the autocomplete
* popup. Ace ships a hardcoded `color: #000` that is invisible on the dark
@@ -126,6 +134,20 @@ export const aceCompletionHighlightStyles = (token:
SupersetTheme) => css`
}
`;
+/**
+ * Theme-aware styling for the "selected word" markers Ace paints on every
other
+ * occurrence of the currently selected token. The light `github` theme
hardcodes
+ * a near-white box (`rgb(250,250,255)`); in dark mode the recolored token
glyphs
+ * sit on it unreadably. Reuse the same dark-aware selection color as
+ * `.ace_selection` so the markers stay legible in every theme.
+ */
+export const aceSelectedWordStyles = (token: SupersetTheme) => css`
+ .ace_editor .ace_marker-layer .ace_selected-word {
+ background-color: ${editorSelectionColor(token)} !important;
+ border: 1px solid ${token.colorBorder} !important;
+ }
+`;
+
/**
* Get an async AceEditor with automatical loading of specified ace modules.
*/
@@ -389,11 +411,11 @@ export function AsyncAceEditor(
}
/* Adjust selection color */
.ace_editor .ace_selection {
- background-color: ${
- token.colorEditorSelection ?? token.colorPrimaryBgHover
- } !important;
+ background-color: ${editorSelectionColor(token)} !important;
}
+ ${aceSelectedWordStyles(token)}
+
/* Improve active line highlighting */
.ace_editor .ace_active-line {
background-color: ${token.colorPrimaryBg} !important;