tfrere HF Staff Cursor commited on
Commit
be7d6fa
·
1 Parent(s): 27abbe5

fix(embed): SVG text now follows the iframe theme

Browse files

Three stacked bugs were keeping the chart text black in dark mode:

1. EmbedStudio mounted both preview iframes with
sandbox="allow-scripts" only (no allow-same-origin). The bootstrap
script couldn't reach window.parent.document, so the cascade fell
through localStorage to matchMedia(prefers-color-scheme) - which
returns the OS preference, not the app theme. Result: the iframe
could land on a different theme than the article. Aligned the
sandbox with HtmlEmbedView ("allow-scripts allow-same-origin").

2. The THEME_BOOTSTRAP in shared/embed-doc.ts always overwrote the
srcdoc-provided data-theme attribute, even when every detection
path failed. Honour the existing data-theme before falling back
to matchMedia so an explicit isDark from buildEmbedSrcdoc wins.

3. SVG text/line/path don't inherit CSS color the way HTML elements
do - their default fill/stroke is "black", not "currentColor".
Charts that scoped colours via `body { color: var(--text-color) }`
still rendered black axes/labels in dark mode. Added defaults to
BASE_STYLES so any SVG text without an explicit fill, and any d3
axis/grid line/path without an explicit stroke, inherits the
cascaded color via currentColor. Charts that paint their own
colours via attribute or inline style still win by specificity.

Co-authored-by: Cursor <cursoragent@cursor.com>

backend/src/shared/embed-doc.ts CHANGED
@@ -149,6 +149,15 @@ const THEME_BOOTSTRAP = `
149
  if (!theme) {
150
  try { theme = window.localStorage && window.localStorage.getItem('theme'); } catch(e) {}
151
  }
 
 
 
 
 
 
 
 
 
152
  if (!theme) {
153
  theme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
154
  }
@@ -221,6 +230,18 @@ body {
221
  scroll container that would trap scroll events or inflate scrollHeight. */
222
  overflow: clip;
223
  }
 
 
 
 
 
 
 
 
 
 
 
 
224
  `.trim();
225
 
226
  /**
 
149
  if (!theme) {
150
  try { theme = window.localStorage && window.localStorage.getItem('theme'); } catch(e) {}
151
  }
152
+ // Respect the srcdoc-provided data-theme before falling back to the
153
+ // OS preference. Without this, a cross-origin sandbox iframe (e.g.
154
+ // an embed-studio preview with sandbox="allow-scripts" but no
155
+ // allow-same-origin) can't read the parent and the bootstrap would
156
+ // otherwise clobber the explicitly-set initial theme with
157
+ // matchMedia's guess.
158
+ if (!theme) {
159
+ theme = document.documentElement.getAttribute('data-theme') || null;
160
+ }
161
  if (!theme) {
162
  theme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
163
  }
 
230
  scroll container that would trap scroll events or inflate scrollHeight. */
231
  overflow: clip;
232
  }
233
+ /* SVG quirk: text/line/path do not inherit CSS color the way HTML does.
234
+ Their default fill/stroke is "black", not "currentColor". A chart
235
+ that only sets body color via var(--text-color) would still render
236
+ black axes/labels in dark mode. Default everything without its own
237
+ fill/stroke to currentColor so the cascaded body color drives the
238
+ look. Charts that want a specific colour just set the attribute or
239
+ inline style and win by specificity. */
240
+ svg text:not([fill]) { fill: currentColor; }
241
+ svg .axis path:not([stroke]),
242
+ svg .axis line:not([stroke]),
243
+ svg .domain:not([stroke]) { stroke: currentColor; }
244
+ svg .grid line:not([stroke]) { stroke: currentColor; opacity: 0.12; }
245
  `.trim();
246
 
247
  /**
frontend/src/components/EmbedStudio.tsx CHANGED
@@ -393,14 +393,14 @@ export function EmbedStudio({
393
  <iframe
394
  ref={frameA}
395
  title="Chart preview A"
396
- sandbox="allow-scripts"
397
  onLoad={() => handleFrameLoad("a")}
398
  style={frameStyle("a")}
399
  />
400
  <iframe
401
  ref={frameB}
402
  title="Chart preview B"
403
- sandbox="allow-scripts"
404
  onLoad={() => handleFrameLoad("b")}
405
  style={frameStyle("b")}
406
  />
 
393
  <iframe
394
  ref={frameA}
395
  title="Chart preview A"
396
+ sandbox="allow-scripts allow-same-origin"
397
  onLoad={() => handleFrameLoad("a")}
398
  style={frameStyle("a")}
399
  />
400
  <iframe
401
  ref={frameB}
402
  title="Chart preview B"
403
+ sandbox="allow-scripts allow-same-origin"
404
  onLoad={() => handleFrameLoad("b")}
405
  style={frameStyle("b")}
406
  />