Skip to content

Commit 9be02f6

Browse files
authored
fix: TextPlaceholder is empty in Safari (#3128)
* fix: TextPlaceholder is empty in Safari * add aria-hidden * hide text on font load fail
1 parent 4200885 commit 9be02f6

6 files changed

Lines changed: 42 additions & 12 deletions

File tree

changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
**What's Fixed**
77
* [RadioInput]: fixed the checked-state dot rendering off-center and with fractional pixel sizes at fractional browser/OS zoom levels.
8+
* [TextPlaceholder]: fixed placeholder bars not being visible in Safari ([#3127](https://github.com/epam/UUI/issues/3127))
9+
810

911
# 6.5.2 - 20.08.2026
1012

uui-core/src/helpers/fonts.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { isClientSide } from './ssr';
2+
3+
const fontAvailabilityCache = new Map<string, Promise<boolean>>();
4+
5+
/** Checks whether a font is loaded and available for use, caching the result per font so repeat calls are free. */
6+
export function isFontAvailable(font: string): Promise<boolean> {
7+
if (!fontAvailabilityCache.has(font)) {
8+
const promise = isClientSide && document.fonts
9+
? document.fonts.ready.then(() => document.fonts.check(font)).catch(() => false)
10+
: Promise.resolve(true);
11+
fontAvailabilityCache.set(font, promise);
12+
}
13+
return fontAvailabilityCache.get(font)!;
14+
}

uui-core/src/helpers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ export * from './orderBy';
2222
export * from './range';
2323
export * from './typeHelpers';
2424
export * from './getDir';
25+
export * from './fonts';

uui/components/typography/TextPlaceholder.module.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
color: var(--uui-text_placeholder);
2222
vertical-align: 1px; // to fix vertical over-sizing of line
2323

24+
&.font-failed {
25+
visibility: hidden; // hide fallback-font letterforms (and their shimmer) instead of showing readable text
26+
}
27+
2428
&.animated-loading {
2529
color: transparent;
2630
background-clip: border-box;

uui/components/typography/TextPlaceholder.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as React from 'react';
22
import cx from 'classnames';
3-
import { IHasCX, IHasRawProps } from '@epam/uui-core';
3+
import { IHasCX, IHasRawProps, isFontAvailable } from '@epam/uui-core';
44
import css from './TextPlaceholder.module.scss';
55
import { PropsWithChildren } from 'react';
66

@@ -14,7 +14,9 @@ export interface ITextPlaceholderProps extends IHasRawProps<React.HTMLAttributes
1414
export type TextPlaceholderProps = PropsWithChildren<ITextPlaceholderProps>;
1515

1616
export const TextPlaceholder: React.FunctionComponent<PropsWithChildren<ITextPlaceholderProps>> = (props) => {
17-
const pattern = '&nbsp;';
17+
const [isFontFailed, setIsFontFailed] = React.useState(false);
18+
19+
const pattern = 'x';
1820
const text = React.useMemo(() => {
1921
const words = [];
2022
for (let i = 0; i < (props.wordsCount || 1); i++) {
@@ -24,16 +26,21 @@ export const TextPlaceholder: React.FunctionComponent<PropsWithChildren<ITextPla
2426
return words;
2527
}, [props.wordsCount]);
2628

29+
React.useEffect(() => {
30+
isFontAvailable('1em Redacted').then((isAvailable) => setIsFontFailed(!isAvailable));
31+
}, []);
32+
2733
return (
28-
<div aria-busy={ true } className={ cx(css.root, 'uui-text-placeholder') } { ...props.rawProps }>
34+
<div aria-busy={ true } aria-hidden="true" className={ cx(css.root, 'uui-text-placeholder') } { ...props.rawProps }>
2935
{text.map((it: string, index: number) => (
3036
<span
3137
key={ index }
3238
className={ cx([
33-
props.cx, css.loadingWord, !props.isNotAnimated && css.animatedLoading,
39+
props.cx, css.loadingWord, !props.isNotAnimated && css.animatedLoading, isFontFailed && css.fontFailed,
3440
]) }
35-
dangerouslySetInnerHTML={ { __html: it } }
36-
/>
41+
>
42+
{it}
43+
</span>
3744
))}
3845
</div>
3946
);

uui/components/typography/__tests__/__snapshots__/TextPlaceholder.test.tsx.snap

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ exports[`TextPlaceholder should be rendered correctly 1`] = `
44
<DocumentFragment>
55
<div
66
aria-busy="true"
7+
aria-hidden="true"
78
class="root uui-text-placeholder"
89
>
910
<span
1011
class="loadingWord animatedLoading"
1112
>
12-
             
13+
xxxxxxxxxxxxx
1314
</span>
1415
</div>
1516
</DocumentFragment>
@@ -19,32 +20,33 @@ exports[`TextPlaceholder should be rendered correctly 2`] = `
1920
<DocumentFragment>
2021
<div
2122
aria-busy="true"
23+
aria-hidden="true"
2224
class="root uui-text-placeholder"
2325
>
2426
<span
2527
class="loadingWord"
2628
>
27-
             
29+
xxxxxxxxxxxxx
2830
</span>
2931
<span
3032
class="loadingWord"
3133
>
32-
             
34+
xxxxxxxxxxxxx
3335
</span>
3436
<span
3537
class="loadingWord"
3638
>
37-
             
39+
xxxxxxxxxxxxx
3840
</span>
3941
<span
4042
class="loadingWord"
4143
>
42-
             
44+
xxxxxxxxxxxxx
4345
</span>
4446
<span
4547
class="loadingWord"
4648
>
47-
             
49+
xxxxxxxxxxxxx
4850
</span>
4951
</div>
5052
</DocumentFragment>

0 commit comments

Comments
 (0)