Skip to content

Commit 7ec4693

Browse files
committed
refactor(web): centralize <style> injection via injectStyleOnce + global tokens
1 parent 89bdf6e commit 7ec4693

3 files changed

Lines changed: 50 additions & 40 deletions

File tree

src/web/EnrichedMarkdownText.tsx

Lines changed: 18 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
1-
import {
2-
useState,
3-
useEffect,
4-
useMemo,
5-
Fragment,
6-
type CSSProperties,
7-
} from 'react';
1+
import { useState, useEffect, useMemo, type CSSProperties } from 'react';
82
import type { EnrichedMarkdownTextProps } from '../types/MarkdownTextProps.web';
93
import { normalizeMarkdownStyle } from '../normalizeMarkdownStyle.web';
104
import {
@@ -18,6 +12,7 @@ import type { ASTNode, RendererCallbacks, RenderCapabilities } from './types';
1812
import { indexTaskItems, markInlineImages } from './utils';
1913
import { loadKaTeX } from './katex';
2014
import type { KaTeXInstance } from './katex';
15+
import { ENRM_TEXT_CLASS, ENRM_SELECTION_BG_VAR } from './globalStyles';
2116

2217
export const EnrichedMarkdownText = ({
2318
markdown,
@@ -109,31 +104,17 @@ export const EnrichedMarkdownText = ({
109104
...(containerStyle as CSSProperties),
110105
...(selectable ? undefined : { userSelect: 'none' }),
111106
...(selectionColor
112-
? ({ ['--enrm-selection-bg']: selectionColor } as CSSProperties)
107+
? ({ [ENRM_SELECTION_BG_VAR]: selectionColor } as CSSProperties)
113108
: null),
114109
}),
115110
[containerStyle, selectable, selectionColor]
116111
);
117112

118-
const selectionStyle = selectionColor ? (
119-
<style>{`[data-enriched-markdown-text] ::selection {
120-
background-color: var(--enrm-selection-bg);
121-
}`}</style>
122-
) : null;
123-
124113
if (parseError) {
125114
return (
126-
<Fragment>
127-
{selectionStyle}
128-
<div
129-
data-enriched-markdown-text
130-
style={wrapperStyle}
131-
dir={dir}
132-
{...rest}
133-
>
134-
<pre style={parseErrorFallbackStyle}>{markdown}</pre>
135-
</div>
136-
</Fragment>
115+
<div className={ENRM_TEXT_CLASS} style={wrapperStyle} dir={dir} {...rest}>
116+
<pre style={parseErrorFallbackStyle}>{markdown}</pre>
117+
</div>
137118
);
138119
}
139120

@@ -143,21 +124,18 @@ export const EnrichedMarkdownText = ({
143124
const lastIdx = children.length - 1;
144125

145126
return (
146-
<Fragment>
147-
{selectionStyle}
148-
<div data-enriched-markdown-text style={wrapperStyle} dir={dir} {...rest}>
149-
{children.map((child, index) => (
150-
<RenderNode
151-
key={`${child.type}-${index}`}
152-
node={child}
153-
style={index === lastIdx ? lastChildStyle : normalizedStyle}
154-
styles={index === lastIdx ? lastChildStyles : styles}
155-
callbacks={callbacks}
156-
capabilities={capabilities}
157-
/>
158-
))}
159-
</div>
160-
</Fragment>
127+
<div className={ENRM_TEXT_CLASS} style={wrapperStyle} dir={dir} {...rest}>
128+
{children.map((child, index) => (
129+
<RenderNode
130+
key={`${child.type}-${index}`}
131+
node={child}
132+
style={index === lastIdx ? lastChildStyle : normalizedStyle}
133+
styles={index === lastIdx ? lastChildStyles : styles}
134+
callbacks={callbacks}
135+
capabilities={capabilities}
136+
/>
137+
))}
138+
</div>
161139
);
162140
};
163141

src/web/globalStyles.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { injectStyleOnce } from './injectStyle';
2+
3+
export const ENRM_TEXT_CLASS = 'enrm-text';
4+
export const ENRM_SELECTION_BG_VAR = '--enrm-selection-bg';
5+
6+
const RULES: ReadonlyArray<readonly [id: string, css: string]> = [
7+
[
8+
'enrm-selection-style',
9+
`.${ENRM_TEXT_CLASS} ::selection { background-color: var(${ENRM_SELECTION_BG_VAR}); }`,
10+
],
11+
];
12+
13+
for (const [id, css] of RULES) {
14+
injectStyleOnce(id, css);
15+
}

src/web/injectStyle.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/// <reference lib="dom" />
2+
3+
/**
4+
* Idempotently injects a `<style>` rule into `document.head`.
5+
*
6+
* Safe on SSR (no-op when `document` is undefined) and HMR (de-duped by `id`).
7+
* Intended for module-level invocation: `N` mounted components produce a
8+
* single `<style>` tag in the DOM.
9+
*/
10+
export const injectStyleOnce = (id: string, css: string): void => {
11+
if (typeof document === 'undefined') return;
12+
if (document.getElementById(id) != null) return;
13+
const style = document.createElement('style');
14+
style.id = id;
15+
style.textContent = css;
16+
document.head.appendChild(style);
17+
};

0 commit comments

Comments
 (0)