@oxyhq/bloom 0.87.1 → 0.89.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/accordion/Accordion.js +7 -5
- package/lib/commonjs/accordion/Accordion.js.map +1 -1
- package/lib/commonjs/bottom-sheet/BottomSheetBase.js +7 -12
- package/lib/commonjs/bottom-sheet/BottomSheetBase.js.map +1 -1
- package/lib/commonjs/button/Button.js +8 -7
- package/lib/commonjs/button/Button.js.map +1 -1
- package/lib/commonjs/button/Button.web.js +2 -6
- package/lib/commonjs/button/Button.web.js.map +1 -1
- package/lib/commonjs/checkbox/Checkbox.js +10 -5
- package/lib/commonjs/checkbox/Checkbox.js.map +1 -1
- package/lib/commonjs/chip/Chip.js +10 -1
- package/lib/commonjs/chip/Chip.js.map +1 -1
- package/lib/commonjs/collapsible/Collapsible.js +5 -4
- package/lib/commonjs/collapsible/Collapsible.js.map +1 -1
- package/lib/commonjs/composition-bar/CompositionBar.js +6 -1
- package/lib/commonjs/composition-bar/CompositionBar.js.map +1 -1
- package/lib/commonjs/connection-dots/ConnectionDots.web.js +2 -6
- package/lib/commonjs/connection-dots/ConnectionDots.web.js.map +1 -1
- package/lib/commonjs/design-tokens/index.js +19 -0
- package/lib/commonjs/design-tokens/index.js.map +1 -1
- package/lib/commonjs/design-tokens/tokens-json.js +149 -0
- package/lib/commonjs/design-tokens/tokens-json.js.map +1 -0
- package/lib/commonjs/design-tokens/tokens.json +6681 -0
- package/lib/commonjs/dialog/Dialog.web.js +2 -6
- package/lib/commonjs/dialog/Dialog.web.js.map +1 -1
- package/lib/commonjs/fab/Fab.web.js +2 -6
- package/lib/commonjs/fab/Fab.web.js.map +1 -1
- package/lib/commonjs/fonts/apply-font-faces.web.js +8 -10
- package/lib/commonjs/fonts/apply-font-faces.web.js.map +1 -1
- package/lib/commonjs/frosted-icon-button/FrostedIconButton.js +7 -1
- package/lib/commonjs/frosted-icon-button/FrostedIconButton.js.map +1 -1
- package/lib/commonjs/frosted-icon-button/FrostedIconButton.web.js +2 -6
- package/lib/commonjs/frosted-icon-button/FrostedIconButton.web.js.map +1 -1
- package/lib/commonjs/input-group/InputGroup.js +6 -4
- package/lib/commonjs/input-group/InputGroup.js.map +1 -1
- package/lib/commonjs/item/Item.js +23 -5
- package/lib/commonjs/item/Item.js.map +1 -1
- package/lib/commonjs/loading/SpinnerIcon.web.js +2 -6
- package/lib/commonjs/loading/SpinnerIcon.web.js.map +1 -1
- package/lib/commonjs/segmented-control/index.js +12 -5
- package/lib/commonjs/segmented-control/index.js.map +1 -1
- package/lib/commonjs/select/index.js +6 -4
- package/lib/commonjs/select/index.js.map +1 -1
- package/lib/commonjs/select/index.web.js +6 -4
- package/lib/commonjs/select/index.web.js.map +1 -1
- package/lib/commonjs/settings-list/SettingsList.js +6 -4
- package/lib/commonjs/settings-list/SettingsList.js.map +1 -1
- package/lib/commonjs/slider/Slider.js +12 -9
- package/lib/commonjs/slider/Slider.js.map +1 -1
- package/lib/commonjs/styles/adopt-style-sheet.js +126 -0
- package/lib/commonjs/styles/adopt-style-sheet.js.map +1 -0
- package/lib/commonjs/switch/Switch.js +9 -5
- package/lib/commonjs/switch/Switch.js.map +1 -1
- package/lib/commonjs/tab-bar/TabBarBase.js +6 -4
- package/lib/commonjs/tab-bar/TabBarBase.js.map +1 -1
- package/lib/module/accordion/Accordion.js +7 -5
- package/lib/module/accordion/Accordion.js.map +1 -1
- package/lib/module/bottom-sheet/BottomSheetBase.js +7 -12
- package/lib/module/bottom-sheet/BottomSheetBase.js.map +1 -1
- package/lib/module/button/Button.js +8 -7
- package/lib/module/button/Button.js.map +1 -1
- package/lib/module/button/Button.web.js +2 -6
- package/lib/module/button/Button.web.js.map +1 -1
- package/lib/module/checkbox/Checkbox.js +10 -5
- package/lib/module/checkbox/Checkbox.js.map +1 -1
- package/lib/module/chip/Chip.js +10 -1
- package/lib/module/chip/Chip.js.map +1 -1
- package/lib/module/collapsible/Collapsible.js +5 -4
- package/lib/module/collapsible/Collapsible.js.map +1 -1
- package/lib/module/composition-bar/CompositionBar.js +6 -1
- package/lib/module/composition-bar/CompositionBar.js.map +1 -1
- package/lib/module/connection-dots/ConnectionDots.web.js +2 -6
- package/lib/module/connection-dots/ConnectionDots.web.js.map +1 -1
- package/lib/module/design-tokens/index.js +5 -0
- package/lib/module/design-tokens/index.js.map +1 -1
- package/lib/module/design-tokens/tokens-json.js +144 -0
- package/lib/module/design-tokens/tokens-json.js.map +1 -0
- package/lib/module/design-tokens/tokens.json +6681 -0
- package/lib/module/dialog/Dialog.web.js +2 -6
- package/lib/module/dialog/Dialog.web.js.map +1 -1
- package/lib/module/fab/Fab.web.js +2 -6
- package/lib/module/fab/Fab.web.js.map +1 -1
- package/lib/module/fonts/apply-font-faces.web.js +8 -10
- package/lib/module/fonts/apply-font-faces.web.js.map +1 -1
- package/lib/module/frosted-icon-button/FrostedIconButton.js +7 -1
- package/lib/module/frosted-icon-button/FrostedIconButton.js.map +1 -1
- package/lib/module/frosted-icon-button/FrostedIconButton.web.js +2 -6
- package/lib/module/frosted-icon-button/FrostedIconButton.web.js.map +1 -1
- package/lib/module/input-group/InputGroup.js +6 -4
- package/lib/module/input-group/InputGroup.js.map +1 -1
- package/lib/module/item/Item.js +23 -5
- package/lib/module/item/Item.js.map +1 -1
- package/lib/module/loading/SpinnerIcon.web.js +2 -6
- package/lib/module/loading/SpinnerIcon.web.js.map +1 -1
- package/lib/module/segmented-control/index.js +12 -5
- package/lib/module/segmented-control/index.js.map +1 -1
- package/lib/module/select/index.js +6 -4
- package/lib/module/select/index.js.map +1 -1
- package/lib/module/select/index.web.js +6 -4
- package/lib/module/select/index.web.js.map +1 -1
- package/lib/module/settings-list/SettingsList.js +6 -4
- package/lib/module/settings-list/SettingsList.js.map +1 -1
- package/lib/module/slider/Slider.js +12 -9
- package/lib/module/slider/Slider.js.map +1 -1
- package/lib/module/styles/adopt-style-sheet.js +121 -0
- package/lib/module/styles/adopt-style-sheet.js.map +1 -0
- package/lib/module/switch/Switch.js +9 -5
- package/lib/module/switch/Switch.js.map +1 -1
- package/lib/module/tab-bar/TabBarBase.js +6 -4
- package/lib/module/tab-bar/TabBarBase.js.map +1 -1
- package/lib/typescript/commonjs/accordion/Accordion.d.ts.map +1 -1
- package/lib/typescript/commonjs/bottom-sheet/BottomSheetBase.d.ts.map +1 -1
- package/lib/typescript/commonjs/button/Button.d.ts.map +1 -1
- package/lib/typescript/commonjs/button/Button.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/checkbox/Checkbox.d.ts.map +1 -1
- package/lib/typescript/commonjs/chip/Chip.d.ts.map +1 -1
- package/lib/typescript/commonjs/collapsible/Collapsible.d.ts.map +1 -1
- package/lib/typescript/commonjs/composition-bar/CompositionBar.d.ts.map +1 -1
- package/lib/typescript/commonjs/connection-dots/ConnectionDots.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/design-tokens/index.d.ts +2 -0
- package/lib/typescript/commonjs/design-tokens/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/design-tokens/tokens-json.d.ts +85 -0
- package/lib/typescript/commonjs/design-tokens/tokens-json.d.ts.map +1 -0
- package/lib/typescript/commonjs/dialog/Dialog.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/fab/Fab.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/fonts/apply-font-faces.web.d.ts +5 -3
- package/lib/typescript/commonjs/fonts/apply-font-faces.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/frosted-icon-button/FrostedIconButton.d.ts.map +1 -1
- package/lib/typescript/commonjs/frosted-icon-button/FrostedIconButton.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/input-group/InputGroup.d.ts.map +1 -1
- package/lib/typescript/commonjs/item/Item.d.ts.map +1 -1
- package/lib/typescript/commonjs/loading/SpinnerIcon.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/segmented-control/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/select/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/select/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/settings-list/SettingsList.d.ts.map +1 -1
- package/lib/typescript/commonjs/slider/Slider.d.ts.map +1 -1
- package/lib/typescript/commonjs/styles/adopt-style-sheet.d.ts +22 -0
- package/lib/typescript/commonjs/styles/adopt-style-sheet.d.ts.map +1 -0
- package/lib/typescript/commonjs/switch/Switch.d.ts.map +1 -1
- package/lib/typescript/commonjs/tab-bar/TabBarBase.d.ts.map +1 -1
- package/lib/typescript/module/accordion/Accordion.d.ts.map +1 -1
- package/lib/typescript/module/bottom-sheet/BottomSheetBase.d.ts.map +1 -1
- package/lib/typescript/module/button/Button.d.ts.map +1 -1
- package/lib/typescript/module/button/Button.web.d.ts.map +1 -1
- package/lib/typescript/module/checkbox/Checkbox.d.ts.map +1 -1
- package/lib/typescript/module/chip/Chip.d.ts.map +1 -1
- package/lib/typescript/module/collapsible/Collapsible.d.ts.map +1 -1
- package/lib/typescript/module/composition-bar/CompositionBar.d.ts.map +1 -1
- package/lib/typescript/module/connection-dots/ConnectionDots.web.d.ts.map +1 -1
- package/lib/typescript/module/design-tokens/index.d.ts +2 -0
- package/lib/typescript/module/design-tokens/index.d.ts.map +1 -1
- package/lib/typescript/module/design-tokens/tokens-json.d.ts +85 -0
- package/lib/typescript/module/design-tokens/tokens-json.d.ts.map +1 -0
- package/lib/typescript/module/dialog/Dialog.web.d.ts.map +1 -1
- package/lib/typescript/module/fab/Fab.web.d.ts.map +1 -1
- package/lib/typescript/module/fonts/apply-font-faces.web.d.ts +5 -3
- package/lib/typescript/module/fonts/apply-font-faces.web.d.ts.map +1 -1
- package/lib/typescript/module/frosted-icon-button/FrostedIconButton.d.ts.map +1 -1
- package/lib/typescript/module/frosted-icon-button/FrostedIconButton.web.d.ts.map +1 -1
- package/lib/typescript/module/input-group/InputGroup.d.ts.map +1 -1
- package/lib/typescript/module/item/Item.d.ts.map +1 -1
- package/lib/typescript/module/loading/SpinnerIcon.web.d.ts.map +1 -1
- package/lib/typescript/module/segmented-control/index.d.ts.map +1 -1
- package/lib/typescript/module/select/index.d.ts.map +1 -1
- package/lib/typescript/module/select/index.web.d.ts.map +1 -1
- package/lib/typescript/module/settings-list/SettingsList.d.ts.map +1 -1
- package/lib/typescript/module/slider/Slider.d.ts.map +1 -1
- package/lib/typescript/module/styles/adopt-style-sheet.d.ts +22 -0
- package/lib/typescript/module/styles/adopt-style-sheet.d.ts.map +1 -0
- package/lib/typescript/module/switch/Switch.d.ts.map +1 -1
- package/lib/typescript/module/tab-bar/TabBarBase.d.ts.map +1 -1
- package/package.json +4 -2
- package/src/__tests__/Button.test.tsx +11 -3
- package/src/__tests__/InputGroup.test.tsx +4 -1
- package/src/__tests__/Item.test.tsx +5 -1
- package/src/__tests__/Slider.test.tsx +11 -2
- package/src/__tests__/TabBar.test.tsx +9 -1
- package/src/__tests__/TabBarNoSelection.test.tsx +3 -1
- package/src/__tests__/adopt-style-sheet.node.test.ts +21 -0
- package/src/__tests__/adopt-style-sheet.test.ts +165 -0
- package/src/__tests__/apply-font-faces.test.ts +32 -0
- package/src/__tests__/aria-state-web.test.tsx +466 -0
- package/src/__tests__/design-tokens-json.test.ts +112 -0
- package/src/__tests__/exports-map-contract.test.ts +5 -1
- package/src/__tests__/support/constructed-style-sheets.ts +68 -0
- package/src/accordion/Accordion.tsx +5 -1
- package/src/bottom-sheet/BottomSheetBase.tsx +10 -14
- package/src/button/Button.tsx +6 -1
- package/src/button/Button.web.tsx +2 -6
- package/src/checkbox/Checkbox.tsx +8 -1
- package/src/chip/Chip.tsx +8 -0
- package/src/collapsible/Collapsible.tsx +3 -1
- package/src/composition-bar/CompositionBar.tsx +4 -0
- package/src/connection-dots/ConnectionDots.web.tsx +2 -6
- package/src/design-tokens/index.ts +16 -0
- package/src/design-tokens/tokens-json.ts +214 -0
- package/src/design-tokens/tokens.json +6681 -0
- package/src/dialog/Dialog.web.tsx +2 -6
- package/src/fab/Fab.web.tsx +2 -6
- package/src/fonts/apply-font-faces.web.ts +11 -11
- package/src/frosted-icon-button/FrostedIconButton.tsx +5 -0
- package/src/frosted-icon-button/FrostedIconButton.web.tsx +2 -6
- package/src/input-group/InputGroup.tsx +4 -1
- package/src/item/Item.tsx +24 -2
- package/src/loading/SpinnerIcon.web.tsx +2 -6
- package/src/segmented-control/index.tsx +8 -1
- package/src/select/index.tsx +4 -1
- package/src/select/index.web.tsx +4 -1
- package/src/settings-list/SettingsList.tsx +4 -1
- package/src/slider/Slider.tsx +10 -2
- package/src/styles/adopt-style-sheet.ts +131 -0
- package/src/switch/Switch.tsx +7 -1
- package/src/tab-bar/TabBarBase.tsx +4 -1
|
@@ -25,6 +25,7 @@ import { RemoveScrollBar } from 'react-remove-scroll-bar';
|
|
|
25
25
|
|
|
26
26
|
import { Backdrop, OverlayRoot } from '../overlay';
|
|
27
27
|
import { Portal } from '../portal/index.web';
|
|
28
|
+
import { adoptStyleSheet } from '../styles/adopt-style-sheet';
|
|
28
29
|
import { Z_INDEX } from '../styles/z-index';
|
|
29
30
|
import { WEB_POSITION_FIXED, type WebCssStyle } from '../styles/web-view-style';
|
|
30
31
|
import { bloomShadowStyle } from '../design-tokens/shadows';
|
|
@@ -106,12 +107,7 @@ const DIALOG_STYLE_ID = 'bloom-dialog-web-css';
|
|
|
106
107
|
|
|
107
108
|
function useDialogCss(): void {
|
|
108
109
|
useEffect(() => {
|
|
109
|
-
|
|
110
|
-
if (document.getElementById(DIALOG_STYLE_ID)) return;
|
|
111
|
-
const style = document.createElement('style');
|
|
112
|
-
style.id = DIALOG_STYLE_ID;
|
|
113
|
-
style.textContent = BLOOM_DIALOG_CSS;
|
|
114
|
-
document.head.appendChild(style);
|
|
110
|
+
adoptStyleSheet(DIALOG_STYLE_ID, BLOOM_DIALOG_CSS);
|
|
115
111
|
}, []);
|
|
116
112
|
}
|
|
117
113
|
|
package/src/fab/Fab.web.tsx
CHANGED
|
@@ -10,6 +10,7 @@ import React, {
|
|
|
10
10
|
|
|
11
11
|
import { useTheme } from '../theme/use-theme';
|
|
12
12
|
import type { Theme } from '../theme/types';
|
|
13
|
+
import { adoptStyleSheet } from '../styles/adopt-style-sheet';
|
|
13
14
|
import { flattenWebStyle } from '../styles/flatten-web-style';
|
|
14
15
|
import type { FabPlacement, FabProps, FabSize, FabVariant } from './types';
|
|
15
16
|
|
|
@@ -103,12 +104,7 @@ const BLOOM_FAB_CSS = `
|
|
|
103
104
|
|
|
104
105
|
function useFabCss(): void {
|
|
105
106
|
useEffect(() => {
|
|
106
|
-
|
|
107
|
-
if (document.getElementById(STYLE_ID)) return;
|
|
108
|
-
const style = document.createElement('style');
|
|
109
|
-
style.id = STYLE_ID;
|
|
110
|
-
style.textContent = BLOOM_FAB_CSS;
|
|
111
|
-
document.head.appendChild(style);
|
|
107
|
+
adoptStyleSheet(STYLE_ID, BLOOM_FAB_CSS);
|
|
112
108
|
}, []);
|
|
113
109
|
}
|
|
114
110
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { adoptStyleSheet } from '../styles/adopt-style-sheet';
|
|
1
2
|
import {
|
|
2
3
|
blomusModernusRegular,
|
|
3
4
|
blomusModernusBold,
|
|
@@ -14,9 +15,11 @@ const STYLE_ID = 'bloom-fonts';
|
|
|
14
15
|
* Web-only. The native counterpart in `apply-font-faces.ts` is a no-op
|
|
15
16
|
* stub — native loads its fonts through `useFonts(FONT_ASSETS)`, so the file
|
|
16
17
|
* split keeps the web font payload out of the native bundle entirely.
|
|
17
|
-
* Idempotent: safe to call multiple times;
|
|
18
|
-
*
|
|
19
|
-
*
|
|
18
|
+
* Idempotent: safe to call multiple times; `adoptStyleSheet` keys the sheet by
|
|
19
|
+
* id, so repeat calls re-parse nothing. SSR-safe via its `typeof document`
|
|
20
|
+
* guard. `BloomThemeProvider` calls this on every mount by default, which is
|
|
21
|
+
* why it also has to survive a page whose CSP forbids inline styles — see
|
|
22
|
+
* `styles/adopt-style-sheet.ts`.
|
|
20
23
|
*
|
|
21
24
|
* The URLs come from `./font-urls.web`, which imports the `.woff2` files so
|
|
22
25
|
* the consuming bundler emits them as separate, cacheable assets. See that
|
|
@@ -24,12 +27,9 @@ const STYLE_ID = 'bloom-fonts';
|
|
|
24
27
|
* provide (Metro needs `woff2` in `assetExts`).
|
|
25
28
|
*/
|
|
26
29
|
export function applyFontFaces(): void {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const style = document.createElement('style');
|
|
31
|
-
style.id = STYLE_ID;
|
|
32
|
-
style.textContent = `
|
|
30
|
+
adoptStyleSheet(
|
|
31
|
+
STYLE_ID,
|
|
32
|
+
`
|
|
33
33
|
@font-face { font-family: 'BlomusModernus'; src: url("${blomusModernusRegular}") format('woff2'); font-weight: 400; font-style: normal; font-display: swap; }
|
|
34
34
|
@font-face { font-family: 'BlomusModernus'; src: url("${blomusModernusBold}") format('woff2'); font-weight: 700; font-style: normal; font-display: swap; }
|
|
35
35
|
@font-face { font-family: 'Inter'; src: url("${interVariable}") format('woff2-variations'); font-weight: 100 900; font-style: normal; font-display: swap; }
|
|
@@ -39,6 +39,6 @@ export function applyFontFaces(): void {
|
|
|
39
39
|
--bloom-font-sans: ${fontFamilies.sans};
|
|
40
40
|
--bloom-font-mono: ${fontFamilies.mono};
|
|
41
41
|
}
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
`,
|
|
43
|
+
);
|
|
44
44
|
}
|
|
@@ -110,7 +110,12 @@ const FrostedIconButtonComponent: React.FC<FrostedIconButtonProps> = ({
|
|
|
110
110
|
accessibilityRole="button"
|
|
111
111
|
accessibilityLabel={accessibilityLabel}
|
|
112
112
|
accessibilityHint={accessibilityHint}
|
|
113
|
+
// `aria-pressed` matches what the `.web.tsx` fork emits, so the same
|
|
114
|
+
// button announces the same state whichever fork a bundler picks.
|
|
115
|
+
// Both spellings are needed: react-native-web ignores
|
|
116
|
+
// `accessibilityState`, React Native has no `aria-pressed`.
|
|
113
117
|
accessibilityState={{ disabled, selected: active }}
|
|
118
|
+
aria-pressed={active}
|
|
114
119
|
testID={testID}
|
|
115
120
|
>
|
|
116
121
|
{/* Clip layer: blur (frosted only) + translucent/solid tint, rounded. */}
|
|
@@ -9,6 +9,7 @@ import React, {
|
|
|
9
9
|
} from 'react';
|
|
10
10
|
|
|
11
11
|
import { useTheme } from '../theme/use-theme';
|
|
12
|
+
import { adoptStyleSheet } from '../styles/adopt-style-sheet';
|
|
12
13
|
import { flattenWebStyle } from '../styles/flatten-web-style';
|
|
13
14
|
import {
|
|
14
15
|
applyIconColor,
|
|
@@ -74,12 +75,7 @@ const BLOOM_FROSTED_ICON_BUTTON_CSS = `
|
|
|
74
75
|
|
|
75
76
|
function useFrostedIconButtonCss(): void {
|
|
76
77
|
useEffect(() => {
|
|
77
|
-
|
|
78
|
-
if (document.getElementById(STYLE_ID)) return;
|
|
79
|
-
const style = document.createElement('style');
|
|
80
|
-
style.id = STYLE_ID;
|
|
81
|
-
style.textContent = BLOOM_FROSTED_ICON_BUTTON_CSS;
|
|
82
|
-
document.head.appendChild(style);
|
|
78
|
+
adoptStyleSheet(STYLE_ID, BLOOM_FROSTED_ICON_BUTTON_CSS);
|
|
83
79
|
}, []);
|
|
84
80
|
}
|
|
85
81
|
|
|
@@ -126,7 +126,10 @@ const InputGroupComponent = function InputGroup({
|
|
|
126
126
|
<InputGroupContext.Provider value={ctx}>
|
|
127
127
|
<View
|
|
128
128
|
testID={testID}
|
|
129
|
-
|
|
129
|
+
// `aria-disabled`, not `accessibilityState`: this is a `View`, so
|
|
130
|
+
// react-native-web has no `disabled` prop to derive the attribute from
|
|
131
|
+
// and never reads `accessibilityState`. React Native folds it back.
|
|
132
|
+
aria-disabled={disabled || undefined}
|
|
130
133
|
// Capture focus bubbling from a nested input so the whole chrome
|
|
131
134
|
// reflects focus — RN-web bubbles focus/blur, native does not but a
|
|
132
135
|
// nested TextInput's own focus ring is sufficient there.
|
package/src/item/Item.tsx
CHANGED
|
@@ -28,7 +28,11 @@ const ItemComponent = function Item({
|
|
|
28
28
|
onLongPress,
|
|
29
29
|
disabled = false,
|
|
30
30
|
destructive = false,
|
|
31
|
-
|
|
31
|
+
// Deliberately NOT defaulted: a row that never mentions selection is not a
|
|
32
|
+
// toggle, and defaulting to `false` made every plain navigation row announce
|
|
33
|
+
// itself as an unpressed toggle button on web. Absence and "off" are
|
|
34
|
+
// different statements to assistive tech; both remain falsy for styling.
|
|
35
|
+
selected,
|
|
32
36
|
active = false,
|
|
33
37
|
density = 'comfortable',
|
|
34
38
|
style,
|
|
@@ -109,6 +113,19 @@ const ItemComponent = function Item({
|
|
|
109
113
|
|
|
110
114
|
if (onPress || onLongPress) {
|
|
111
115
|
const resolvedRole = accessibilityRole ?? 'button';
|
|
116
|
+
// Web reads only `aria-*` — react-native-web never consults
|
|
117
|
+
// `accessibilityState` — and ARIA scopes these states by role: `option`
|
|
118
|
+
// carries a selected state, a `button` toggle carries a pressed one, and
|
|
119
|
+
// `menuitem`/`listitem` carry neither, so those emit nothing. React Native
|
|
120
|
+
// keeps reading `accessibilityState` below, which is why both are set.
|
|
121
|
+
const selectedAria =
|
|
122
|
+
selected == null
|
|
123
|
+
? null
|
|
124
|
+
: role === 'option'
|
|
125
|
+
? { 'aria-selected': selected }
|
|
126
|
+
: role == null
|
|
127
|
+
? { 'aria-pressed': selected }
|
|
128
|
+
: null;
|
|
112
129
|
const handlePress = disabled || !onPress ? undefined : onPress;
|
|
113
130
|
const handleLongPress = disabled || !onLongPress ? undefined : onLongPress;
|
|
114
131
|
return (
|
|
@@ -126,6 +143,7 @@ const ItemComponent = function Item({
|
|
|
126
143
|
}
|
|
127
144
|
accessibilityHint={accessibilityHint}
|
|
128
145
|
accessibilityState={{ disabled, selected }}
|
|
146
|
+
{...selectedAria}
|
|
129
147
|
style={[
|
|
130
148
|
disabled && styles.disabled,
|
|
131
149
|
pressed && !disabled && styles.pressed,
|
|
@@ -143,7 +161,11 @@ const ItemComponent = function Item({
|
|
|
143
161
|
accessibilityLabel ?? (typeof title === 'string' ? title : undefined)
|
|
144
162
|
}
|
|
145
163
|
accessibilityHint={accessibilityHint}
|
|
146
|
-
|
|
164
|
+
// A `View` has no `disabled` prop for react-native-web to derive
|
|
165
|
+
// `aria-disabled` from, and it ignores `accessibilityState`, so without
|
|
166
|
+
// this the non-pressable row announced nothing. React Native folds
|
|
167
|
+
// `aria-disabled` back into `accessibilityState`.
|
|
168
|
+
aria-disabled={disabled || undefined}
|
|
147
169
|
style={disabled ? styles.disabled : undefined}>
|
|
148
170
|
{content}
|
|
149
171
|
</View>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { useEffect } from 'react';
|
|
2
2
|
import { StyleSheet, View, type ViewStyle } from 'react-native';
|
|
3
3
|
|
|
4
|
+
import { adoptStyleSheet } from '../styles/adopt-style-sheet';
|
|
4
5
|
import type { WebCssStyle } from '../styles/web-view-style';
|
|
5
6
|
|
|
6
7
|
interface SpinnerIconProps {
|
|
@@ -72,12 +73,7 @@ const SPIN_STYLE: WebCssStyle = {
|
|
|
72
73
|
|
|
73
74
|
function useKeyframes(): void {
|
|
74
75
|
useEffect(() => {
|
|
75
|
-
|
|
76
|
-
if (document.getElementById(KEYFRAMES_ID)) return;
|
|
77
|
-
const style = document.createElement('style');
|
|
78
|
-
style.id = KEYFRAMES_ID;
|
|
79
|
-
style.textContent = BLOOM_SPINNER_CSS;
|
|
80
|
-
document.head.appendChild(style);
|
|
76
|
+
adoptStyleSheet(KEYFRAMES_ID, BLOOM_SPINNER_CSS);
|
|
81
77
|
}, []);
|
|
82
78
|
}
|
|
83
79
|
|
|
@@ -240,7 +240,14 @@ export function SegmentedControlItem({
|
|
|
240
240
|
onPressOut={disabled ? undefined : onPressOut}
|
|
241
241
|
accessibilityLabel={accessibilityLabel}
|
|
242
242
|
accessibilityHint={accessibilityHint}
|
|
243
|
-
|
|
243
|
+
// The active state has to be spelled as an `aria-*` prop, because
|
|
244
|
+
// react-native-web never reads `accessibilityState` (React Native folds
|
|
245
|
+
// these back into it, so native is unaffected) — and WHICH prop depends
|
|
246
|
+
// on the role: ARIA gives `tab` a selected state and `radio` a checked
|
|
247
|
+
// one, so a single spelling would be invalid for one of the two modes.
|
|
248
|
+
{...(itemRole === 'tab'
|
|
249
|
+
? { 'aria-selected': active }
|
|
250
|
+
: { 'aria-checked': active })}
|
|
244
251
|
role={itemRole}
|
|
245
252
|
disabled={disabled}
|
|
246
253
|
testID={testID}
|
package/src/select/index.tsx
CHANGED
|
@@ -289,7 +289,10 @@ export function SelectItem({ children, value, label, style }: SelectItemProps) {
|
|
|
289
289
|
<Pressable
|
|
290
290
|
accessibilityRole="radio"
|
|
291
291
|
accessibilityLabel={label}
|
|
292
|
-
|
|
292
|
+
// ARIA gives `role="radio"` a checked state, not a selected one. Spelled
|
|
293
|
+
// as `aria-checked` because react-native-web never reads
|
|
294
|
+
// `accessibilityState`; React Native folds this back into it.
|
|
295
|
+
aria-checked={isSelected}
|
|
293
296
|
onPress={handlePress}
|
|
294
297
|
onFocus={onFocus}
|
|
295
298
|
onBlur={onBlur}
|
package/src/select/index.web.tsx
CHANGED
|
@@ -324,7 +324,10 @@ export function SelectItem({ ref, value, label, children, style }: SelectItemPro
|
|
|
324
324
|
// dropped it, leaving every option a `role="radio"` with no accessible
|
|
325
325
|
// name for a screen reader to announce.
|
|
326
326
|
accessibilityLabel={label}
|
|
327
|
-
|
|
327
|
+
// `aria-checked` is the state ARIA defines for `role="radio"`, and the
|
|
328
|
+
// only one react-native-web emits — it never reads `accessibilityState`,
|
|
329
|
+
// so this fork's options announced no selection at all.
|
|
330
|
+
aria-checked={isSelected}
|
|
328
331
|
onPress={() => {
|
|
329
332
|
ctx.onValueChange?.(value);
|
|
330
333
|
ctx.close();
|
|
@@ -128,7 +128,10 @@ export const SettingsListItem = memo<SettingsListItemProps>(function SettingsLis
|
|
|
128
128
|
accessibilityRole={role}
|
|
129
129
|
accessibilityLabel={label}
|
|
130
130
|
accessibilityHint={accessibilityHint}
|
|
131
|
-
|
|
131
|
+
// See the pressable branch above, which gets `aria-disabled` for free
|
|
132
|
+
// from its `disabled` prop. A `View` has no such prop, and
|
|
133
|
+
// react-native-web never reads `accessibilityState`.
|
|
134
|
+
aria-disabled={disabled || undefined}
|
|
132
135
|
>
|
|
133
136
|
{content}
|
|
134
137
|
</View>
|
package/src/slider/Slider.tsx
CHANGED
|
@@ -198,8 +198,16 @@ const SliderComponent = function Slider({
|
|
|
198
198
|
testID={testID}
|
|
199
199
|
accessibilityRole="adjustable"
|
|
200
200
|
accessibilityLabel={accessibilityLabel}
|
|
201
|
-
|
|
202
|
-
|
|
201
|
+
// react-native-web reads the FLAT `aria-value*` props; it has no handling
|
|
202
|
+
// for the `accessibilityValue` object at all, so this rendered a
|
|
203
|
+
// `role="slider"` carrying no value whatsoever — a screen reader could
|
|
204
|
+
// say the control existed but never what it was set to. React Native
|
|
205
|
+
// folds these three back into `accessibilityValue`, and `aria-disabled`
|
|
206
|
+
// into `accessibilityState`, so native keeps what it had.
|
|
207
|
+
aria-valuemin={min}
|
|
208
|
+
aria-valuemax={max}
|
|
209
|
+
aria-valuenow={value}
|
|
210
|
+
aria-disabled={disabled || undefined}
|
|
203
211
|
style={[
|
|
204
212
|
styles.root,
|
|
205
213
|
{ height: Math.max(thumbSize, trackHeight) + 8 },
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// adoptStyleSheet — the ONE way a Bloom web fork puts CSS on the page.
|
|
3
|
+
//
|
|
4
|
+
// Bloom's `.web.tsx` forks self-inject the CSS they need (`@keyframes`,
|
|
5
|
+
// pseudo-classes, `::-webkit-scrollbar`) rather than asking consumers to paste
|
|
6
|
+
// it into a global stylesheet — see AGENTS.md, "Web Fork CSS & Style
|
|
7
|
+
// Conventions". The mechanism used to be a `<style>` element carrying the CSS
|
|
8
|
+
// as text content. That is exactly the shape a Content-Security-Policy of
|
|
9
|
+
// `style-src 'self'` blocks: the check runs when a style element's text
|
|
10
|
+
// content is parsed into a sheet, so the rules are dropped, the component
|
|
11
|
+
// renders with none of its CSS, and NOTHING is thrown — the fork's own
|
|
12
|
+
// "already injected?" guard even finds its element in the DOM and reports
|
|
13
|
+
// success. Astro's `astro://` WebUI pages ship that CSP, and every other Oxy
|
|
14
|
+
// surface behind one would fail the same silent way.
|
|
15
|
+
//
|
|
16
|
+
// A constructed stylesheet is not subject to `style-src` at all — CSSOM is
|
|
17
|
+
// outside CSP's inline hooks — so `new CSSStyleSheet()` + `replaceSync()` +
|
|
18
|
+
// `document.adoptedStyleSheets` applies the same CSS under a strict policy
|
|
19
|
+
// with no nonce, no hash, and no consumer-side wiring. (This is also why
|
|
20
|
+
// react-native-web is unaffected: it appends an EMPTY style element and fills
|
|
21
|
+
// it through `sheet.insertRule`, never through text content.)
|
|
22
|
+
//
|
|
23
|
+
// The `<style>` path survives only as the fallback for a document without
|
|
24
|
+
// constructed-stylesheet support — jsdom 26 has the `CSSStyleSheet`
|
|
25
|
+
// constructor but neither `replaceSync` nor `adoptedStyleSheets`, so the whole
|
|
26
|
+
// jest suite takes it, and so do browsers older than Chrome 73 / Safari 16.4 /
|
|
27
|
+
// Firefox 101. There it behaves exactly as Bloom did before, CSP included:
|
|
28
|
+
// the fallback cannot make anything worse, and cannot make a strict policy
|
|
29
|
+
// work either.
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
interface AdoptedSheet {
|
|
33
|
+
sheet: CSSStyleSheet;
|
|
34
|
+
/** The CSS currently in `sheet`, so a re-render re-parses nothing. */
|
|
35
|
+
css: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The sheets Bloom has constructed, per document. Keyed by document rather than
|
|
40
|
+
* held in one flat map because a constructed sheet belongs to the realm that
|
|
41
|
+
* created it — adopting one into a different document throws — and Bloom runs
|
|
42
|
+
* in more than one when a consumer opens a popout window.
|
|
43
|
+
*/
|
|
44
|
+
const adoptedByDocument = new WeakMap<Document, Map<string, AdoptedSheet>>();
|
|
45
|
+
|
|
46
|
+
function sheetsFor(doc: Document): Map<string, AdoptedSheet> {
|
|
47
|
+
const existing = adoptedByDocument.get(doc);
|
|
48
|
+
if (existing) return existing;
|
|
49
|
+
const created = new Map<string, AdoptedSheet>();
|
|
50
|
+
adoptedByDocument.set(doc, created);
|
|
51
|
+
return created;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Whether `doc` can take a constructed stylesheet. Evaluated per call, not once
|
|
56
|
+
* at module scope: a test installs the API on its window after this module has
|
|
57
|
+
* already been imported, and a module-scope constant would freeze the answer
|
|
58
|
+
* from before that.
|
|
59
|
+
*/
|
|
60
|
+
function supportsAdoption(doc: Document): boolean {
|
|
61
|
+
return (
|
|
62
|
+
'adoptedStyleSheets' in doc &&
|
|
63
|
+
typeof CSSStyleSheet === 'function' &&
|
|
64
|
+
typeof CSSStyleSheet.prototype.replaceSync === 'function'
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Make `css` apply to the document, under the identity `id`.
|
|
70
|
+
*
|
|
71
|
+
* Idempotent in both directions: calling it again with the same CSS re-parses
|
|
72
|
+
* nothing and adopts nothing twice (the usual case — every mount of every
|
|
73
|
+
* instance of a component calls it), and calling it with DIFFERENT CSS under
|
|
74
|
+
* the same id replaces the previous content in place (the theme-dependent case,
|
|
75
|
+
* `BottomSheetBase`'s scrollbar colours).
|
|
76
|
+
*
|
|
77
|
+
* No-op off the web, so a platform-neutral component can call it unguarded.
|
|
78
|
+
*/
|
|
79
|
+
export function adoptStyleSheet(id: string, css: string): void {
|
|
80
|
+
if (typeof document === 'undefined') return;
|
|
81
|
+
|
|
82
|
+
if (supportsAdoption(document)) {
|
|
83
|
+
const sheets = sheetsFor(document);
|
|
84
|
+
let entry = sheets.get(id);
|
|
85
|
+
if (!entry) {
|
|
86
|
+
entry = { sheet: new CSSStyleSheet(), css: '' };
|
|
87
|
+
sheets.set(id, entry);
|
|
88
|
+
}
|
|
89
|
+
if (entry.css !== css) {
|
|
90
|
+
entry.sheet.replaceSync(css);
|
|
91
|
+
entry.css = css;
|
|
92
|
+
}
|
|
93
|
+
// Re-adoption after a consumer (or a test) has reset the list. `includes`
|
|
94
|
+
// is what makes the common repeat call free.
|
|
95
|
+
if (!document.adoptedStyleSheets.includes(entry.sheet)) {
|
|
96
|
+
document.adoptedStyleSheets = [...document.adoptedStyleSheets, entry.sheet];
|
|
97
|
+
}
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const existing = document.getElementById(id);
|
|
102
|
+
if (existing instanceof HTMLStyleElement) {
|
|
103
|
+
if (existing.textContent !== css) existing.textContent = css;
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const element = document.createElement('style');
|
|
107
|
+
element.id = id;
|
|
108
|
+
element.textContent = css;
|
|
109
|
+
document.head.appendChild(element);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Detach the stylesheet `id`, whichever path installed it.
|
|
114
|
+
*
|
|
115
|
+
* Only for CSS whose lifetime is a component's — `BottomSheetBase` removes its
|
|
116
|
+
* scrollbar rules when the last sheet unmounts. The static per-component CSS is
|
|
117
|
+
* never dropped: it is the same bytes for the life of the page, and removing it
|
|
118
|
+
* on unmount would make a re-mount re-parse it.
|
|
119
|
+
*/
|
|
120
|
+
export function dropStyleSheet(id: string): void {
|
|
121
|
+
if (typeof document === 'undefined') return;
|
|
122
|
+
|
|
123
|
+
const entry = adoptedByDocument.get(document)?.get(id);
|
|
124
|
+
if (entry) {
|
|
125
|
+
document.adoptedStyleSheets = document.adoptedStyleSheets.filter(
|
|
126
|
+
(sheet) => sheet !== entry.sheet,
|
|
127
|
+
);
|
|
128
|
+
adoptedByDocument.get(document)?.delete(id);
|
|
129
|
+
}
|
|
130
|
+
document.getElementById(id)?.remove();
|
|
131
|
+
}
|
package/src/switch/Switch.tsx
CHANGED
|
@@ -82,7 +82,13 @@ const SwitchComponent = React.forwardRef<React.ElementRef<typeof Pressable>, Swi
|
|
|
82
82
|
ref={ref}
|
|
83
83
|
role="switch"
|
|
84
84
|
aria-checked={value}
|
|
85
|
-
|
|
85
|
+
// The disabled state has to travel on this prop, not on `aria-disabled`:
|
|
86
|
+
// react-native-web's `Pressable` appends its own `aria-disabled` from
|
|
87
|
+
// `disabled` AFTER spreading the caller's props, so a caller-supplied
|
|
88
|
+
// one is overwritten. `handlePress` already no-ops when disabled, so
|
|
89
|
+
// this only adds what was missing — the announced state, and skipping
|
|
90
|
+
// the control in the tab order.
|
|
91
|
+
disabled={disabled}
|
|
86
92
|
onPress={handlePress}
|
|
87
93
|
onPressIn={onPressIn}
|
|
88
94
|
onPressOut={onPressOut}
|
|
@@ -569,7 +569,10 @@ function TabBarButtonBody({
|
|
|
569
569
|
// Accessibility defaults come BEFORE the spread so an explicit prop — or
|
|
570
570
|
// one injected by a router trigger — still wins.
|
|
571
571
|
accessibilityRole="tab"
|
|
572
|
-
|
|
572
|
+
// `aria-selected` is the state ARIA defines for `role="tab"`, and the one
|
|
573
|
+
// spelling both platforms read: react-native-web ignores
|
|
574
|
+
// `accessibilityState`, React Native folds `aria-selected` into it.
|
|
575
|
+
aria-selected={focused}
|
|
573
576
|
accessibilityLabel={item.label}
|
|
574
577
|
{...pressableProps}
|
|
575
578
|
onPress={(event) => {
|