@atlaskit/app-provider 3.2.10 → 3.3.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.
Files changed (85) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/cjs/app-provider.js +4 -6
  3. package/dist/cjs/context.js +16 -0
  4. package/dist/cjs/index.js +12 -9
  5. package/dist/cjs/theme-provider/context/color-mode.js +16 -0
  6. package/dist/cjs/theme-provider/context/inside-theme-provider.js +11 -0
  7. package/dist/cjs/theme-provider/context/theme.js +16 -0
  8. package/dist/cjs/theme-provider/hooks/use-color-mode-for-migration.js +19 -0
  9. package/dist/cjs/theme-provider/hooks/use-color-mode.js +41 -0
  10. package/dist/cjs/theme-provider/hooks/use-is-inside-theme-provider.js +17 -0
  11. package/dist/cjs/theme-provider/hooks/use-set-color-mode.js +20 -0
  12. package/dist/cjs/theme-provider/hooks/use-set-theme.js +20 -0
  13. package/dist/cjs/theme-provider/hooks/use-theme.js +35 -0
  14. package/dist/cjs/theme-provider/index.js +221 -0
  15. package/dist/cjs/theme-provider/utils/is-theme-mounted.js +21 -0
  16. package/dist/cjs/theme-provider/utils/load-and-mount-themes.js +130 -0
  17. package/dist/es2019/app-provider.js +3 -3
  18. package/dist/es2019/context.js +11 -0
  19. package/dist/es2019/index.js +10 -1
  20. package/dist/es2019/theme-provider/context/color-mode.js +10 -0
  21. package/dist/es2019/theme-provider/context/inside-theme-provider.js +6 -0
  22. package/dist/es2019/theme-provider/context/theme.js +10 -0
  23. package/dist/es2019/theme-provider/hooks/use-color-mode-for-migration.js +14 -0
  24. package/dist/es2019/theme-provider/hooks/use-color-mode.js +29 -0
  25. package/dist/es2019/theme-provider/hooks/use-is-inside-theme-provider.js +12 -0
  26. package/dist/es2019/theme-provider/hooks/use-set-color-mode.js +15 -0
  27. package/dist/es2019/theme-provider/hooks/use-set-theme.js +15 -0
  28. package/dist/es2019/theme-provider/hooks/use-theme.js +23 -0
  29. package/dist/es2019/theme-provider/index.js +170 -0
  30. package/dist/es2019/theme-provider/utils/is-theme-mounted.js +16 -0
  31. package/dist/es2019/theme-provider/utils/load-and-mount-themes.js +47 -0
  32. package/dist/esm/app-provider.js +3 -3
  33. package/dist/esm/context.js +11 -0
  34. package/dist/esm/index.js +10 -1
  35. package/dist/esm/theme-provider/context/color-mode.js +10 -0
  36. package/dist/esm/theme-provider/context/inside-theme-provider.js +6 -0
  37. package/dist/esm/theme-provider/context/theme.js +10 -0
  38. package/dist/esm/theme-provider/hooks/use-color-mode-for-migration.js +14 -0
  39. package/dist/esm/theme-provider/hooks/use-color-mode.js +35 -0
  40. package/dist/esm/theme-provider/hooks/use-is-inside-theme-provider.js +12 -0
  41. package/dist/esm/theme-provider/hooks/use-set-color-mode.js +15 -0
  42. package/dist/esm/theme-provider/hooks/use-set-theme.js +15 -0
  43. package/dist/esm/theme-provider/hooks/use-theme.js +29 -0
  44. package/dist/esm/theme-provider/index.js +212 -0
  45. package/dist/esm/theme-provider/utils/is-theme-mounted.js +16 -0
  46. package/dist/esm/theme-provider/utils/load-and-mount-themes.js +123 -0
  47. package/dist/types/app-provider.d.ts +3 -2
  48. package/dist/types/context.d.ts +7 -0
  49. package/dist/types/index.d.ts +7 -2
  50. package/dist/types/theme-provider/context/color-mode.d.ts +10 -0
  51. package/dist/types/theme-provider/context/inside-theme-provider.d.ts +4 -0
  52. package/dist/types/theme-provider/context/theme.d.ts +10 -0
  53. package/dist/types/theme-provider/hooks/use-color-mode-for-migration.d.ts +9 -0
  54. package/dist/types/theme-provider/hooks/use-color-mode.d.ts +7 -0
  55. package/dist/types/theme-provider/hooks/use-is-inside-theme-provider.d.ts +6 -0
  56. package/dist/types/theme-provider/hooks/use-set-color-mode.d.ts +7 -0
  57. package/dist/types/theme-provider/hooks/use-set-theme.d.ts +7 -0
  58. package/dist/types/theme-provider/hooks/use-theme.d.ts +7 -0
  59. package/dist/types/theme-provider/index.d.ts +19 -0
  60. package/dist/types/theme-provider/utils/is-theme-mounted.d.ts +8 -0
  61. package/dist/types/theme-provider/utils/load-and-mount-themes.d.ts +2 -0
  62. package/dist/types-ts4.5/app-provider.d.ts +3 -2
  63. package/dist/types-ts4.5/context.d.ts +7 -0
  64. package/dist/types-ts4.5/index.d.ts +7 -2
  65. package/dist/types-ts4.5/theme-provider/context/color-mode.d.ts +10 -0
  66. package/dist/types-ts4.5/theme-provider/context/inside-theme-provider.d.ts +4 -0
  67. package/dist/types-ts4.5/theme-provider/context/theme.d.ts +10 -0
  68. package/dist/types-ts4.5/theme-provider/hooks/use-color-mode-for-migration.d.ts +9 -0
  69. package/dist/types-ts4.5/theme-provider/hooks/use-color-mode.d.ts +7 -0
  70. package/dist/types-ts4.5/theme-provider/hooks/use-is-inside-theme-provider.d.ts +6 -0
  71. package/dist/types-ts4.5/theme-provider/hooks/use-set-color-mode.d.ts +7 -0
  72. package/dist/types-ts4.5/theme-provider/hooks/use-set-theme.d.ts +7 -0
  73. package/dist/types-ts4.5/theme-provider/hooks/use-theme.d.ts +7 -0
  74. package/dist/types-ts4.5/theme-provider/index.d.ts +19 -0
  75. package/dist/types-ts4.5/theme-provider/utils/is-theme-mounted.d.ts +8 -0
  76. package/dist/types-ts4.5/theme-provider/utils/load-and-mount-themes.d.ts +2 -0
  77. package/package.json +10 -5
  78. package/dist/cjs/theme-provider.js +0 -286
  79. package/dist/es2019/theme-provider.js +0 -216
  80. package/dist/esm/theme-provider.js +0 -272
  81. package/dist/types/theme-provider.d.ts +0 -55
  82. package/dist/types-ts4.5/theme-provider.d.ts +0 -55
  83. /package/dist/cjs/{theme-provider.compiled.css → theme-provider/index.compiled.css} +0 -0
  84. /package/dist/es2019/{theme-provider.compiled.css → theme-provider/index.compiled.css} +0 -0
  85. /package/dist/esm/{theme-provider.compiled.css → theme-provider/index.compiled.css} +0 -0
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.loadAndMountThemes = void 0;
8
+ var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
9
+ var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
10
+ var _browserApis = require("@atlaskit/browser-apis");
11
+ var _tokens = require("@atlaskit/tokens");
12
+ var _isThemeMounted = require("./is-theme-mounted");
13
+ var loadThemeCss = /*#__PURE__*/function () {
14
+ var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(themeId) {
15
+ var _yield$themeImportMap, themeCss;
16
+ return _regenerator.default.wrap(function _callee$(_context) {
17
+ while (1) switch (_context.prev = _context.next) {
18
+ case 0:
19
+ if (!(0, _isThemeMounted.isThemeMounted)(themeId)) {
20
+ _context.next = 2;
21
+ break;
22
+ }
23
+ return _context.abrupt("return");
24
+ case 2:
25
+ if (themeId) {
26
+ _context.next = 4;
27
+ break;
28
+ }
29
+ return _context.abrupt("return");
30
+ case 4:
31
+ _context.next = 6;
32
+ return _tokens.themeImportMap[themeId]();
33
+ case 6:
34
+ _yield$themeImportMap = _context.sent;
35
+ themeCss = _yield$themeImportMap.default;
36
+ return _context.abrupt("return", themeCss);
37
+ case 9:
38
+ case "end":
39
+ return _context.stop();
40
+ }
41
+ }, _callee);
42
+ }));
43
+ return function loadThemeCss(_x) {
44
+ return _ref.apply(this, arguments);
45
+ };
46
+ }();
47
+ var mountThemeCss = /*#__PURE__*/function () {
48
+ var _ref2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2(css, themeId) {
49
+ var doc, style;
50
+ return _regenerator.default.wrap(function _callee2$(_context2) {
51
+ while (1) switch (_context2.prev = _context2.next) {
52
+ case 0:
53
+ // SSR-safe: Only mount on client side
54
+ doc = (0, _browserApis.getDocument)();
55
+ if (doc) {
56
+ _context2.next = 3;
57
+ break;
58
+ }
59
+ return _context2.abrupt("return");
60
+ case 3:
61
+ style = doc.createElement('style');
62
+ style.textContent = css;
63
+ style.dataset.theme = themeId;
64
+
65
+ // Prevent duplicate mounting of themes.
66
+ // It's possible the theme was already being loaded elsewhere.
67
+ if (!(0, _isThemeMounted.isThemeMounted)(themeId)) {
68
+ _context2.next = 8;
69
+ break;
70
+ }
71
+ return _context2.abrupt("return");
72
+ case 8:
73
+ doc.head.appendChild(style);
74
+ case 9:
75
+ case "end":
76
+ return _context2.stop();
77
+ }
78
+ }, _callee2);
79
+ }));
80
+ return function mountThemeCss(_x2, _x3) {
81
+ return _ref2.apply(this, arguments);
82
+ };
83
+ }();
84
+ var loadAndMountThemeCss = /*#__PURE__*/function () {
85
+ var _ref3 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee3(themeId) {
86
+ var themeCss;
87
+ return _regenerator.default.wrap(function _callee3$(_context3) {
88
+ while (1) switch (_context3.prev = _context3.next) {
89
+ case 0:
90
+ _context3.next = 2;
91
+ return loadThemeCss(themeId);
92
+ case 2:
93
+ themeCss = _context3.sent;
94
+ if (themeCss) {
95
+ _context3.next = 5;
96
+ break;
97
+ }
98
+ return _context3.abrupt("return");
99
+ case 5:
100
+ mountThemeCss(themeCss, themeId);
101
+ case 6:
102
+ case "end":
103
+ return _context3.stop();
104
+ }
105
+ }, _callee3);
106
+ }));
107
+ return function loadAndMountThemeCss(_x4) {
108
+ return _ref3.apply(this, arguments);
109
+ };
110
+ }();
111
+ var loadAndMountThemes = exports.loadAndMountThemes = /*#__PURE__*/function () {
112
+ var _ref4 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee4(themeState) {
113
+ var themesToLoad;
114
+ return _regenerator.default.wrap(function _callee4$(_context4) {
115
+ while (1) switch (_context4.prev = _context4.next) {
116
+ case 0:
117
+ themesToLoad = [themeState.light, themeState.dark, themeState.spacing, themeState.shape, themeState.typography].filter(function (themeId) {
118
+ return !!themeId;
119
+ });
120
+ themesToLoad.forEach(loadAndMountThemeCss);
121
+ case 2:
122
+ case "end":
123
+ return _context4.stop();
124
+ }
125
+ }, _callee4);
126
+ }));
127
+ return function loadAndMountThemes(_x5) {
128
+ return _ref4.apply(this, arguments);
129
+ };
130
+ }();
@@ -1,7 +1,7 @@
1
- import React, { createContext, useContext } from 'react';
1
+ import React from 'react';
2
+ import { InsideAppProviderContext, useIsInsideAppProvider } from './context';
2
3
  import RouterLinkProvider from './router-link-provider';
3
4
  import ThemeProvider from './theme-provider';
4
- const InsideAppProviderContext = /*#__PURE__*/createContext(false);
5
5
  /**
6
6
  * __App provider__
7
7
  *
@@ -16,7 +16,7 @@ function AppProvider({
16
16
  routerLinkComponent,
17
17
  UNSAFE_isThemingDisabled
18
18
  }) {
19
- const isInsideAppProvider = useContext(InsideAppProviderContext);
19
+ const isInsideAppProvider = useIsInsideAppProvider();
20
20
  if (isInsideAppProvider) {
21
21
  throw new Error('App provider should not be nested within another app provider.');
22
22
  }
@@ -0,0 +1,11 @@
1
+ import { createContext, useContext } from 'react';
2
+
3
+ /**
4
+ * __Inside app provider context__
5
+ *
6
+ * A context that indicates if the current component is inside an AppProvider.
7
+ */
8
+ export const InsideAppProviderContext = /*#__PURE__*/createContext(false);
9
+ export const useIsInsideAppProvider = () => {
10
+ return useContext(InsideAppProviderContext);
11
+ };
@@ -1,5 +1,14 @@
1
1
  export { default } from './app-provider';
2
+
3
+ // Theme provider
4
+ export { UNSAFE_useColorModeForMigration } from './theme-provider/hooks/use-color-mode-for-migration';
5
+ export { useColorMode } from './theme-provider/hooks/use-color-mode';
6
+ export { useSetColorMode } from './theme-provider/hooks/use-set-color-mode';
7
+ export { useSetTheme } from './theme-provider/hooks/use-set-theme';
8
+ export { useTheme } from './theme-provider/hooks/use-theme';
2
9
  export { default as ThemeProvider } from './theme-provider';
3
- export { UNSAFE_useColorModeForMigration, useColorMode, useSetColorMode, useSetTheme, useTheme } from './theme-provider';
10
+
11
+ // Router link provider
12
+
4
13
  import useRouterLink from './router-link-provider/hooks/use-router-link';
5
14
  export { useRouterLink };
@@ -0,0 +1,10 @@
1
+ import { createContext } from 'react';
2
+ /**
3
+ * __Color mode context__
4
+ */
5
+ export const ColorModeContext = /*#__PURE__*/createContext(undefined);
6
+
7
+ /**
8
+ * __Set color mode context__
9
+ */
10
+ export const SetColorModeContext = /*#__PURE__*/createContext(undefined);
@@ -0,0 +1,6 @@
1
+ import { createContext } from 'react';
2
+
3
+ /**
4
+ * __Inside theme provider context__
5
+ */
6
+ export const InsideThemeProviderContext = /*#__PURE__*/createContext(false);
@@ -0,0 +1,10 @@
1
+ import { createContext } from 'react';
2
+ /**
3
+ * __Theme context__
4
+ */
5
+ export const ThemeContext = /*#__PURE__*/createContext(undefined);
6
+
7
+ /**
8
+ * __Set theme context__
9
+ */
10
+ export const SetThemeContext = /*#__PURE__*/createContext(undefined);
@@ -0,0 +1,14 @@
1
+ import { useContext } from 'react';
2
+ import { ColorModeContext } from '../context/color-mode';
3
+
4
+ /**
5
+ * __UNSAFE_useColorModeForMigration()__
6
+ *
7
+ * Returns the current color mode when inside the app provider.
8
+ * Unlike useColorMode, this utility returns undefined, instead of throwing an error, when the app provider is missing.
9
+ * This allows it to be used by components that need to operate with and without an app provider.
10
+ */
11
+ export function UNSAFE_useColorModeForMigration() {
12
+ const value = useContext(ColorModeContext);
13
+ return value;
14
+ }
@@ -0,0 +1,29 @@
1
+ import { useContext, useEffect, useState } from 'react';
2
+ import { getGlobalTheme, ThemeMutationObserver } from '@atlaskit/tokens';
3
+ import { ColorModeContext } from '../context/color-mode';
4
+
5
+ /**
6
+ * __useColorMode()__
7
+ *
8
+ * Returns the current color mode when inside the app provider.
9
+ */
10
+ export function useColorMode() {
11
+ const value = useContext(ColorModeContext);
12
+
13
+ // TODO: This will only return 'light' or 'dark' but never 'auto', which in some cases
14
+ // may be desirable. We should consider returning both the reconciled color mode (e.g. 'light' or 'dark') and the selected color mode ("auto").
15
+ const theme = getGlobalTheme();
16
+ const [resolvedColorMode, setResolvedColorMode] = useState((theme === null || theme === void 0 ? void 0 : theme.colorMode) || 'light');
17
+ useEffect(() => {
18
+ // We are using theme from context so no need to reference the DOM
19
+ if (value) {
20
+ return;
21
+ }
22
+ const observer = new ThemeMutationObserver(theme => {
23
+ setResolvedColorMode((theme === null || theme === void 0 ? void 0 : theme.colorMode) || 'light');
24
+ });
25
+ observer.observe();
26
+ return () => observer.disconnect();
27
+ }, [value]);
28
+ return value ? value : resolvedColorMode;
29
+ }
@@ -0,0 +1,12 @@
1
+ import { useContext } from 'react';
2
+ import { InsideThemeProviderContext } from '../context/inside-theme-provider';
3
+
4
+ /**
5
+ * __useIsInsideThemeProvider()__
6
+ *
7
+ * Returns true if the current component is inside a ThemeProvider.
8
+ */
9
+ export const useIsInsideThemeProvider = () => {
10
+ var _useContext;
11
+ return (_useContext = useContext(InsideThemeProviderContext)) !== null && _useContext !== void 0 ? _useContext : false;
12
+ };
@@ -0,0 +1,15 @@
1
+ import { useContext } from 'react';
2
+ import { SetColorModeContext } from '../context/color-mode';
3
+
4
+ /**
5
+ * __useSetColorMode()__
6
+ *
7
+ * Returns the color mode setter when inside the app provider.
8
+ */
9
+ export function useSetColorMode() {
10
+ const value = useContext(SetColorModeContext);
11
+ if (!value) {
12
+ throw new Error('useSetColorMode must be used within ThemeProvider.');
13
+ }
14
+ return value;
15
+ }
@@ -0,0 +1,15 @@
1
+ import { useContext } from 'react';
2
+ import { SetThemeContext } from '../context/theme';
3
+
4
+ /**
5
+ * __useSetTheme()__
6
+ *
7
+ * Returns the theme setter when inside the app provider.
8
+ */
9
+ export function useSetTheme() {
10
+ const value = useContext(SetThemeContext);
11
+ if (!value) {
12
+ throw new Error('useSetTheme must be used within ThemeProvider.');
13
+ }
14
+ return value;
15
+ }
@@ -0,0 +1,23 @@
1
+ import { useContext, useEffect, useState } from 'react';
2
+ import { getGlobalTheme, ThemeMutationObserver } from '@atlaskit/tokens';
3
+ import { ThemeContext } from '../context/theme';
4
+
5
+ /**
6
+ * __useTheme()__
7
+ *
8
+ * Returns the current theme settings when inside the app provider.
9
+ */
10
+ export function useTheme() {
11
+ const theme = useContext(ThemeContext);
12
+ const [resolvedTheme, setResolvedTheme] = useState(theme || getGlobalTheme());
13
+ useEffect(() => {
14
+ // We are using theme from context so no need to reference the DOM
15
+ if (theme) {
16
+ return;
17
+ }
18
+ const observer = new ThemeMutationObserver(setResolvedTheme);
19
+ observer.observe();
20
+ return () => observer.disconnect();
21
+ }, [theme]);
22
+ return theme ? theme : resolvedTheme;
23
+ }
@@ -0,0 +1,170 @@
1
+ /* index.tsx generated by @compiled/babel-plugin v0.38.1 */
2
+ import _extends from "@babel/runtime/helpers/extends";
3
+ import "./index.compiled.css";
4
+ import { ax, ix } from "@compiled/react/runtime";
5
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
6
+ import { bind } from 'bind-event-listener';
7
+ import { fg } from '@atlaskit/platform-feature-flags';
8
+ import { getThemeHtmlAttrs, setGlobalTheme, SUBTREE_THEME_ATTRIBUTE, themeStateDefaults } from '@atlaskit/tokens';
9
+ import { useIsInsideAppProvider } from '../context';
10
+ import { ColorModeContext, SetColorModeContext } from './context/color-mode';
11
+ import { InsideThemeProviderContext } from './context/inside-theme-provider';
12
+ import { SetThemeContext, ThemeContext } from './context/theme';
13
+ import { useIsInsideThemeProvider } from './hooks/use-is-inside-theme-provider';
14
+ import { loadAndMountThemes } from './utils/load-and-mount-themes';
15
+ const defaultThemeSettings = {
16
+ dark: 'dark',
17
+ light: 'light',
18
+ spacing: 'spacing',
19
+ typography: 'typography'
20
+ };
21
+ const isMatchMediaAvailable = typeof window !== 'undefined' && 'matchMedia' in window;
22
+ const prefersDarkModeMql = isMatchMediaAvailable ? window.matchMedia('(prefers-color-scheme: dark)') : undefined;
23
+
24
+ // TODO: currently 'auto' color mode will always return 'light' in SSR.
25
+ // Additional work required: https://product-fabric.atlassian.net/browse/DSP-9781
26
+ function getReconciledColorMode(colorMode) {
27
+ if (colorMode === 'auto') {
28
+ return prefersDarkModeMql !== null && prefersDarkModeMql !== void 0 && prefersDarkModeMql.matches ? 'dark' : 'light';
29
+ }
30
+ return colorMode;
31
+ }
32
+ const contentStyles = {
33
+ body: "_1e0c1bgi"
34
+ };
35
+ /**
36
+ * __Theme provider__
37
+ *
38
+ * Provides global theming configuration.
39
+ */
40
+ function ThemeProvider({
41
+ children,
42
+ defaultColorMode = 'auto',
43
+ defaultTheme
44
+ }) {
45
+ const [chosenColorMode, setChosenColorMode] = useState(defaultColorMode);
46
+ const [reconciledColorMode, setReconciledColorMode] = useState(getReconciledColorMode(defaultColorMode));
47
+ const [theme, setTheme] = useState(() => ({
48
+ ...defaultThemeSettings,
49
+ ...defaultTheme
50
+ }));
51
+ const setColorMode = useCallback(colorMode => {
52
+ setChosenColorMode(colorMode);
53
+ setReconciledColorMode(getReconciledColorMode(colorMode));
54
+ }, []);
55
+ const setPartialTheme = useCallback(nextTheme => {
56
+ setTheme(theme => ({
57
+ ...theme,
58
+ ...nextTheme
59
+ }));
60
+ }, []);
61
+ const lastSetGlobalThemePromiseRef = useRef(null);
62
+ const isInsideAppProvider = useIsInsideAppProvider();
63
+ const isInsideThemeProvider = useIsInsideThemeProvider();
64
+ const isRootThemeProvider = isInsideAppProvider && !isInsideThemeProvider;
65
+ useEffect(() => {
66
+ if (
67
+ /**
68
+ * A top-level ThemeProvider is detected by being the first ThemeProvider inside an AppProvider.
69
+ *
70
+ * This will not use sub-tree theming but instead set the global theme state using the
71
+ * `@atlaskit/tokens` APIs, as it's required for styling root `html` and `body` elements
72
+ * for compatibility with `@atlaskit/css-reset`.
73
+ *
74
+ * In the future we could consider moving away from DOM mutations and require AppProvider to wrap
75
+ * `html` in order to apply global theme state, which would allow a more consistent approach to
76
+ * theme loading.
77
+ */
78
+ isRootThemeProvider || (
79
+ /**
80
+ * Or when not behind feature flag, partially revert to legacy behavior.
81
+ * This only affects theme providers that are not inside an AppProvider or a ThemeProvider,
82
+ * as we still need to set global theme state to prevent breaking existing apps,
83
+ * but also prevent multiple theme providers from loading conflicting theme states.
84
+ *
85
+ * After we roll out the feature flag, this will be removed as we will
86
+ * only support sub-tree theming when used outside of AppProvider.
87
+ */
88
+ !isInsideAppProvider && !isInsideThemeProvider && !fg('platform_dst_subtree_theming'))) {
89
+ /**
90
+ * We need to wait for any previous `setGlobalTheme` calls to finish before calling it again.
91
+ * This is to prevent race conditions as `setGlobalTheme` is async and mutates the DOM (e.g. sets the
92
+ * `data-color-mode` attribute on the root element).
93
+ *
94
+ * Since we can't safely abort the `setGlobalTheme` execution, we need to wait for it to properly finish before
95
+ * applying the new theme.
96
+ *
97
+ * Without this, we can end up in the following scenario:
98
+ * 1. app loads with the default 'light' theme, kicking off `setGlobalTheme`
99
+ * 2. app switches to 'dark' theme after retrieving value persisted in local storage, calling `setGlobalTheme` again
100
+ * 3. `setGlobalTheme` function execution for `dark` finishes before the initial `light` execution
101
+ * 4. `setGlobalTheme` function execution for `light` then finishes, resulting in the 'light' theme being applied.
102
+ */
103
+ const cleanupLastFnCall = async () => {
104
+ if (lastSetGlobalThemePromiseRef.current) {
105
+ const unbindFn = await lastSetGlobalThemePromiseRef.current;
106
+ unbindFn();
107
+ lastSetGlobalThemePromiseRef.current = null;
108
+ }
109
+ };
110
+ const safelySetGlobalTheme = async () => {
111
+ await cleanupLastFnCall();
112
+ const promise = setGlobalTheme({
113
+ ...theme,
114
+ colorMode: reconciledColorMode
115
+ });
116
+ lastSetGlobalThemePromiseRef.current = promise;
117
+ };
118
+ safelySetGlobalTheme();
119
+ return function cleanup() {
120
+ cleanupLastFnCall();
121
+ };
122
+ } else {
123
+ // For other theme providers (whether outside AppProvider or nested inside a ThemeProvider),
124
+ // we treat them as sub-tree themes that do not load global theme state.
125
+ const themeState = {
126
+ ...theme,
127
+ colorMode: reconciledColorMode,
128
+ contrastMode: themeStateDefaults.contrastMode,
129
+ shape: themeStateDefaults.shape(),
130
+ typography: themeStateDefaults.typography()
131
+ };
132
+ loadAndMountThemes(themeState);
133
+ }
134
+ }, [isInsideAppProvider, isInsideThemeProvider, isRootThemeProvider, reconciledColorMode, theme]);
135
+ useEffect(() => {
136
+ if (!prefersDarkModeMql) {
137
+ return;
138
+ }
139
+ const unbindListener = bind(prefersDarkModeMql, {
140
+ type: 'change',
141
+ listener: event => {
142
+ if (chosenColorMode === 'auto') {
143
+ setReconciledColorMode(event.matches ? 'dark' : 'light');
144
+ }
145
+ }
146
+ });
147
+ return unbindListener;
148
+ }, [chosenColorMode]);
149
+ const attrs = {
150
+ ...getThemeHtmlAttrs({
151
+ ...theme,
152
+ colorMode: reconciledColorMode
153
+ }),
154
+ [SUBTREE_THEME_ATTRIBUTE]: true
155
+ };
156
+ return /*#__PURE__*/React.createElement(InsideThemeProviderContext.Provider, {
157
+ value: true
158
+ }, /*#__PURE__*/React.createElement(ColorModeContext.Provider, {
159
+ value: reconciledColorMode
160
+ }, /*#__PURE__*/React.createElement(SetColorModeContext.Provider, {
161
+ value: setColorMode
162
+ }, /*#__PURE__*/React.createElement(ThemeContext.Provider, {
163
+ value: theme
164
+ }, /*#__PURE__*/React.createElement(SetThemeContext.Provider, {
165
+ value: setPartialTheme
166
+ }, fg('platform_dst_subtree_theming') ? /*#__PURE__*/React.createElement("div", _extends({}, attrs, {
167
+ className: ax([contentStyles.body])
168
+ }), children) : children)))));
169
+ }
170
+ export default ThemeProvider;
@@ -0,0 +1,16 @@
1
+ import { getDocument } from '@atlaskit/browser-apis';
2
+ import { THEME_DATA_ATTRIBUTE } from '@atlaskit/tokens';
3
+
4
+ /**
5
+ * Checks if a theme is mounted in the document head.
6
+ *
7
+ * Eventually this won't be necessary as we'll utilise AppProvider context
8
+ * to track theme loading.
9
+ */
10
+ export function isThemeMounted(themeId) {
11
+ const doc = getDocument();
12
+ if (!doc) {
13
+ return false;
14
+ }
15
+ return doc.head.querySelector(`style[${THEME_DATA_ATTRIBUTE}="${themeId}"]`);
16
+ }
@@ -0,0 +1,47 @@
1
+ import { getDocument } from '@atlaskit/browser-apis';
2
+ import { themeImportMap } from '@atlaskit/tokens';
3
+ import { isThemeMounted } from './is-theme-mounted';
4
+ const loadThemeCss = async themeId => {
5
+ // Prevent duplicate loading of themes
6
+ if (isThemeMounted(themeId)) {
7
+ return;
8
+ }
9
+
10
+ // Gracefully handles falsy values like `null` and `undefined` when makers ignore
11
+ // TS to avoid loading default themes.
12
+ if (!themeId) {
13
+ return;
14
+ }
15
+ const {
16
+ default: themeCss
17
+ } = await themeImportMap[themeId]();
18
+ return themeCss;
19
+ };
20
+ const mountThemeCss = async (css, themeId) => {
21
+ // SSR-safe: Only mount on client side
22
+ const doc = getDocument();
23
+ if (!doc) {
24
+ return;
25
+ }
26
+ const style = doc.createElement('style');
27
+ style.textContent = css;
28
+ style.dataset.theme = themeId;
29
+
30
+ // Prevent duplicate mounting of themes.
31
+ // It's possible the theme was already being loaded elsewhere.
32
+ if (isThemeMounted(themeId)) {
33
+ return;
34
+ }
35
+ doc.head.appendChild(style);
36
+ };
37
+ const loadAndMountThemeCss = async themeId => {
38
+ const themeCss = await loadThemeCss(themeId);
39
+ if (!themeCss) {
40
+ return;
41
+ }
42
+ mountThemeCss(themeCss, themeId);
43
+ };
44
+ export const loadAndMountThemes = async themeState => {
45
+ const themesToLoad = [themeState.light, themeState.dark, themeState.spacing, themeState.shape, themeState.typography].filter(themeId => !!themeId);
46
+ themesToLoad.forEach(loadAndMountThemeCss);
47
+ };
@@ -1,7 +1,7 @@
1
- import React, { createContext, useContext } from 'react';
1
+ import React from 'react';
2
+ import { InsideAppProviderContext, useIsInsideAppProvider } from './context';
2
3
  import RouterLinkProvider from './router-link-provider';
3
4
  import ThemeProvider from './theme-provider';
4
- var InsideAppProviderContext = /*#__PURE__*/createContext(false);
5
5
  /**
6
6
  * __App provider__
7
7
  *
@@ -16,7 +16,7 @@ function AppProvider(_ref) {
16
16
  defaultTheme = _ref.defaultTheme,
17
17
  routerLinkComponent = _ref.routerLinkComponent,
18
18
  UNSAFE_isThemingDisabled = _ref.UNSAFE_isThemingDisabled;
19
- var isInsideAppProvider = useContext(InsideAppProviderContext);
19
+ var isInsideAppProvider = useIsInsideAppProvider();
20
20
  if (isInsideAppProvider) {
21
21
  throw new Error('App provider should not be nested within another app provider.');
22
22
  }
@@ -0,0 +1,11 @@
1
+ import { createContext, useContext } from 'react';
2
+
3
+ /**
4
+ * __Inside app provider context__
5
+ *
6
+ * A context that indicates if the current component is inside an AppProvider.
7
+ */
8
+ export var InsideAppProviderContext = /*#__PURE__*/createContext(false);
9
+ export var useIsInsideAppProvider = function useIsInsideAppProvider() {
10
+ return useContext(InsideAppProviderContext);
11
+ };
package/dist/esm/index.js CHANGED
@@ -1,5 +1,14 @@
1
1
  export { default } from './app-provider';
2
+
3
+ // Theme provider
4
+ export { UNSAFE_useColorModeForMigration } from './theme-provider/hooks/use-color-mode-for-migration';
5
+ export { useColorMode } from './theme-provider/hooks/use-color-mode';
6
+ export { useSetColorMode } from './theme-provider/hooks/use-set-color-mode';
7
+ export { useSetTheme } from './theme-provider/hooks/use-set-theme';
8
+ export { useTheme } from './theme-provider/hooks/use-theme';
2
9
  export { default as ThemeProvider } from './theme-provider';
3
- export { UNSAFE_useColorModeForMigration, useColorMode, useSetColorMode, useSetTheme, useTheme } from './theme-provider';
10
+
11
+ // Router link provider
12
+
4
13
  import useRouterLink from './router-link-provider/hooks/use-router-link';
5
14
  export { useRouterLink };
@@ -0,0 +1,10 @@
1
+ import { createContext } from 'react';
2
+ /**
3
+ * __Color mode context__
4
+ */
5
+ export var ColorModeContext = /*#__PURE__*/createContext(undefined);
6
+
7
+ /**
8
+ * __Set color mode context__
9
+ */
10
+ export var SetColorModeContext = /*#__PURE__*/createContext(undefined);
@@ -0,0 +1,6 @@
1
+ import { createContext } from 'react';
2
+
3
+ /**
4
+ * __Inside theme provider context__
5
+ */
6
+ export var InsideThemeProviderContext = /*#__PURE__*/createContext(false);
@@ -0,0 +1,10 @@
1
+ import { createContext } from 'react';
2
+ /**
3
+ * __Theme context__
4
+ */
5
+ export var ThemeContext = /*#__PURE__*/createContext(undefined);
6
+
7
+ /**
8
+ * __Set theme context__
9
+ */
10
+ export var SetThemeContext = /*#__PURE__*/createContext(undefined);
@@ -0,0 +1,14 @@
1
+ import { useContext } from 'react';
2
+ import { ColorModeContext } from '../context/color-mode';
3
+
4
+ /**
5
+ * __UNSAFE_useColorModeForMigration()__
6
+ *
7
+ * Returns the current color mode when inside the app provider.
8
+ * Unlike useColorMode, this utility returns undefined, instead of throwing an error, when the app provider is missing.
9
+ * This allows it to be used by components that need to operate with and without an app provider.
10
+ */
11
+ export function UNSAFE_useColorModeForMigration() {
12
+ var value = useContext(ColorModeContext);
13
+ return value;
14
+ }