@atlaskit/app-provider 1.4.2 → 1.5.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/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # @atlaskit/app-provider
2
2
 
3
+ ## 1.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#101755](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/101755)
8
+ [`07ee1368bd69d`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/07ee1368bd69d) -
9
+ Fixes a race condition with setting the theme. This change is behind a feature flag.
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies
14
+
15
+ ## 1.4.3
16
+
17
+ ### Patch Changes
18
+
19
+ - [#168743](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/168743)
20
+ [`90605435312ea`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/90605435312ea) -
21
+ Remove react-router-dom from devDependencies as it is incompatible with React 18.
22
+ - Updated dependencies
23
+
3
24
  ## 1.4.2
4
25
 
5
26
  ### Patch Changes
@@ -12,10 +12,13 @@ exports.useColorMode = useColorMode;
12
12
  exports.useSetColorMode = useSetColorMode;
13
13
  exports.useSetTheme = useSetTheme;
14
14
  exports.useTheme = useTheme;
15
+ var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
16
+ var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
15
17
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
16
18
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
17
19
  var _react = _interopRequireWildcard(require("react"));
18
20
  var _bindEventListener = require("bind-event-listener");
21
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
19
22
  var _tokens = require("@atlaskit/tokens");
20
23
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
21
24
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
@@ -153,10 +156,87 @@ function ThemeProvider(_ref) {
153
156
  });
154
157
  }, []);
155
158
  (0, _react.useEffect)(function () {
159
+ if ((0, _platformFeatureFlags.fg)('platform_dst_fix_set_theme_race')) {
160
+ return;
161
+ }
156
162
  (0, _tokens.setGlobalTheme)(_objectSpread(_objectSpread({}, theme), {}, {
157
163
  colorMode: reconciledColorMode
158
164
  }));
159
165
  }, [theme, reconciledColorMode]);
166
+ var lastSetGlobalThemePromiseRef = (0, _react.useRef)(null);
167
+ (0, _react.useEffect)(function () {
168
+ if (!(0, _platformFeatureFlags.fg)('platform_dst_fix_set_theme_race')) {
169
+ return;
170
+ }
171
+
172
+ /**
173
+ * We need to wait for any previous `setGlobalTheme` calls to finish before calling it again.
174
+ * This is to prevent race conditions as `setGlobalTheme` is async and mutates the DOM (e.g. sets the
175
+ * `data-color-mode` attribute on the root element).
176
+ *
177
+ * Since we can't safely abort the `setGlobalTheme` execution, we need to wait for it to properly finish before
178
+ * applying the new theme.
179
+ *
180
+ * Without this, we can end up in the following scenario:
181
+ * 1. app loads with the default 'light' theme, kicking off `setGlobalTheme`
182
+ * 2. app switches to 'dark' theme after retrieving value persisted in local storage, calling `setGlobalTheme` again
183
+ * 3. `setGlobalTheme` function execution for `dark` finishes before the initial `light` execution
184
+ * 4. `setGlobalTheme` function execution for `light` then finishes, resulting in the 'light' theme being applied.
185
+ */
186
+ var cleanupLastFnCall = /*#__PURE__*/function () {
187
+ var _ref2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee() {
188
+ var unbindFn;
189
+ return _regenerator.default.wrap(function _callee$(_context) {
190
+ while (1) switch (_context.prev = _context.next) {
191
+ case 0:
192
+ if (!lastSetGlobalThemePromiseRef.current) {
193
+ _context.next = 6;
194
+ break;
195
+ }
196
+ _context.next = 3;
197
+ return lastSetGlobalThemePromiseRef.current;
198
+ case 3:
199
+ unbindFn = _context.sent;
200
+ unbindFn();
201
+ lastSetGlobalThemePromiseRef.current = null;
202
+ case 6:
203
+ case "end":
204
+ return _context.stop();
205
+ }
206
+ }, _callee);
207
+ }));
208
+ return function cleanupLastFnCall() {
209
+ return _ref2.apply(this, arguments);
210
+ };
211
+ }();
212
+ var safelySetGlobalTheme = /*#__PURE__*/function () {
213
+ var _ref3 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2() {
214
+ var promise;
215
+ return _regenerator.default.wrap(function _callee2$(_context2) {
216
+ while (1) switch (_context2.prev = _context2.next) {
217
+ case 0:
218
+ _context2.next = 2;
219
+ return cleanupLastFnCall();
220
+ case 2:
221
+ promise = (0, _tokens.setGlobalTheme)(_objectSpread(_objectSpread({}, theme), {}, {
222
+ colorMode: reconciledColorMode
223
+ }));
224
+ lastSetGlobalThemePromiseRef.current = promise;
225
+ case 4:
226
+ case "end":
227
+ return _context2.stop();
228
+ }
229
+ }, _callee2);
230
+ }));
231
+ return function safelySetGlobalTheme() {
232
+ return _ref3.apply(this, arguments);
233
+ };
234
+ }();
235
+ safelySetGlobalTheme();
236
+ return function cleanup() {
237
+ cleanupLastFnCall();
238
+ };
239
+ }, [theme, reconciledColorMode]);
160
240
  (0, _react.useEffect)(function () {
161
241
  if (!prefersDarkModeMql) {
162
242
  return;
@@ -1,5 +1,6 @@
1
- import React, { createContext, useCallback, useContext, useEffect, useState } from 'react';
1
+ import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react';
2
2
  import { bind } from 'bind-event-listener';
3
+ import { fg } from '@atlaskit/platform-feature-flags';
3
4
  import { setGlobalTheme } from '@atlaskit/tokens';
4
5
  const defaultThemeSettings = {
5
6
  dark: 'dark',
@@ -123,11 +124,54 @@ export function ThemeProvider({
123
124
  }));
124
125
  }, []);
125
126
  useEffect(() => {
127
+ if (fg('platform_dst_fix_set_theme_race')) {
128
+ return;
129
+ }
126
130
  setGlobalTheme({
127
131
  ...theme,
128
132
  colorMode: reconciledColorMode
129
133
  });
130
134
  }, [theme, reconciledColorMode]);
135
+ const lastSetGlobalThemePromiseRef = useRef(null);
136
+ useEffect(() => {
137
+ if (!fg('platform_dst_fix_set_theme_race')) {
138
+ return;
139
+ }
140
+
141
+ /**
142
+ * We need to wait for any previous `setGlobalTheme` calls to finish before calling it again.
143
+ * This is to prevent race conditions as `setGlobalTheme` is async and mutates the DOM (e.g. sets the
144
+ * `data-color-mode` attribute on the root element).
145
+ *
146
+ * Since we can't safely abort the `setGlobalTheme` execution, we need to wait for it to properly finish before
147
+ * applying the new theme.
148
+ *
149
+ * Without this, we can end up in the following scenario:
150
+ * 1. app loads with the default 'light' theme, kicking off `setGlobalTheme`
151
+ * 2. app switches to 'dark' theme after retrieving value persisted in local storage, calling `setGlobalTheme` again
152
+ * 3. `setGlobalTheme` function execution for `dark` finishes before the initial `light` execution
153
+ * 4. `setGlobalTheme` function execution for `light` then finishes, resulting in the 'light' theme being applied.
154
+ */
155
+ const cleanupLastFnCall = async () => {
156
+ if (lastSetGlobalThemePromiseRef.current) {
157
+ const unbindFn = await lastSetGlobalThemePromiseRef.current;
158
+ unbindFn();
159
+ lastSetGlobalThemePromiseRef.current = null;
160
+ }
161
+ };
162
+ const safelySetGlobalTheme = async () => {
163
+ await cleanupLastFnCall();
164
+ const promise = setGlobalTheme({
165
+ ...theme,
166
+ colorMode: reconciledColorMode
167
+ });
168
+ lastSetGlobalThemePromiseRef.current = promise;
169
+ };
170
+ safelySetGlobalTheme();
171
+ return function cleanup() {
172
+ cleanupLastFnCall();
173
+ };
174
+ }, [theme, reconciledColorMode]);
131
175
  useEffect(() => {
132
176
  if (!prefersDarkModeMql) {
133
177
  return;
@@ -1,9 +1,12 @@
1
+ import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
1
2
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
3
  import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
4
+ import _regeneratorRuntime from "@babel/runtime/regenerator";
3
5
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
4
6
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
5
- import React, { createContext, useCallback, useContext, useEffect, useState } from 'react';
7
+ import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react';
6
8
  import { bind } from 'bind-event-listener';
9
+ import { fg } from '@atlaskit/platform-feature-flags';
7
10
  import { setGlobalTheme } from '@atlaskit/tokens';
8
11
  var defaultThemeSettings = {
9
12
  dark: 'dark',
@@ -137,10 +140,87 @@ export function ThemeProvider(_ref) {
137
140
  });
138
141
  }, []);
139
142
  useEffect(function () {
143
+ if (fg('platform_dst_fix_set_theme_race')) {
144
+ return;
145
+ }
140
146
  setGlobalTheme(_objectSpread(_objectSpread({}, theme), {}, {
141
147
  colorMode: reconciledColorMode
142
148
  }));
143
149
  }, [theme, reconciledColorMode]);
150
+ var lastSetGlobalThemePromiseRef = useRef(null);
151
+ useEffect(function () {
152
+ if (!fg('platform_dst_fix_set_theme_race')) {
153
+ return;
154
+ }
155
+
156
+ /**
157
+ * We need to wait for any previous `setGlobalTheme` calls to finish before calling it again.
158
+ * This is to prevent race conditions as `setGlobalTheme` is async and mutates the DOM (e.g. sets the
159
+ * `data-color-mode` attribute on the root element).
160
+ *
161
+ * Since we can't safely abort the `setGlobalTheme` execution, we need to wait for it to properly finish before
162
+ * applying the new theme.
163
+ *
164
+ * Without this, we can end up in the following scenario:
165
+ * 1. app loads with the default 'light' theme, kicking off `setGlobalTheme`
166
+ * 2. app switches to 'dark' theme after retrieving value persisted in local storage, calling `setGlobalTheme` again
167
+ * 3. `setGlobalTheme` function execution for `dark` finishes before the initial `light` execution
168
+ * 4. `setGlobalTheme` function execution for `light` then finishes, resulting in the 'light' theme being applied.
169
+ */
170
+ var cleanupLastFnCall = /*#__PURE__*/function () {
171
+ var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
172
+ var unbindFn;
173
+ return _regeneratorRuntime.wrap(function _callee$(_context) {
174
+ while (1) switch (_context.prev = _context.next) {
175
+ case 0:
176
+ if (!lastSetGlobalThemePromiseRef.current) {
177
+ _context.next = 6;
178
+ break;
179
+ }
180
+ _context.next = 3;
181
+ return lastSetGlobalThemePromiseRef.current;
182
+ case 3:
183
+ unbindFn = _context.sent;
184
+ unbindFn();
185
+ lastSetGlobalThemePromiseRef.current = null;
186
+ case 6:
187
+ case "end":
188
+ return _context.stop();
189
+ }
190
+ }, _callee);
191
+ }));
192
+ return function cleanupLastFnCall() {
193
+ return _ref2.apply(this, arguments);
194
+ };
195
+ }();
196
+ var safelySetGlobalTheme = /*#__PURE__*/function () {
197
+ var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2() {
198
+ var promise;
199
+ return _regeneratorRuntime.wrap(function _callee2$(_context2) {
200
+ while (1) switch (_context2.prev = _context2.next) {
201
+ case 0:
202
+ _context2.next = 2;
203
+ return cleanupLastFnCall();
204
+ case 2:
205
+ promise = setGlobalTheme(_objectSpread(_objectSpread({}, theme), {}, {
206
+ colorMode: reconciledColorMode
207
+ }));
208
+ lastSetGlobalThemePromiseRef.current = promise;
209
+ case 4:
210
+ case "end":
211
+ return _context2.stop();
212
+ }
213
+ }, _callee2);
214
+ }));
215
+ return function safelySetGlobalTheme() {
216
+ return _ref3.apply(this, arguments);
217
+ };
218
+ }();
219
+ safelySetGlobalTheme();
220
+ return function cleanup() {
221
+ cleanupLastFnCall();
222
+ };
223
+ }, [theme, reconciledColorMode]);
144
224
  useEffect(function () {
145
225
  if (!prefersDarkModeMql) {
146
226
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/app-provider",
3
- "version": "1.4.2",
3
+ "version": "1.5.0",
4
4
  "description": "A top level provider for the Design System.",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -11,7 +11,7 @@
11
11
  "team": "Design System Team",
12
12
  "website": {
13
13
  "name": "App provider",
14
- "category": "Components",
14
+ "category": "Tooling",
15
15
  "status": {
16
16
  "type": "beta"
17
17
  }
@@ -39,7 +39,8 @@
39
39
  ".": "./src/index.tsx"
40
40
  },
41
41
  "dependencies": {
42
- "@atlaskit/tokens": "^2.2.0",
42
+ "@atlaskit/platform-feature-flags": "^0.3.0",
43
+ "@atlaskit/tokens": "^3.0.0",
43
44
  "@babel/runtime": "^7.0.0",
44
45
  "bind-event-listener": "^3.0.0"
45
46
  },
@@ -48,7 +49,7 @@
48
49
  },
49
50
  "devDependencies": {
50
51
  "@af/visual-regression": "*",
51
- "@atlaskit/ds-lib": "^3.2.0",
52
+ "@atlaskit/ds-lib": "^3.3.0",
52
53
  "@atlaskit/ssr": "*",
53
54
  "@atlaskit/visual-regression": "*",
54
55
  "@testing-library/react": "^12.1.5",
@@ -56,7 +57,6 @@
56
57
  "@testing-library/user-event": "^14.4.3",
57
58
  "react-dom": "^16.8.0",
58
59
  "react-resource-router": "^0.20.0",
59
- "react-router-dom": "^4.2.2",
60
60
  "typescript": "~5.4.2",
61
61
  "wait-for-expect": "^1.2.0"
62
62
  },
@@ -91,5 +91,10 @@
91
91
  ]
92
92
  }
93
93
  },
94
- "homepage": "https://atlassian.design/components/app-provider"
94
+ "homepage": "https://atlassian.design/components/app-provider",
95
+ "platform-feature-flags": {
96
+ "platform_dst_fix_set_theme_race": {
97
+ "type": "boolean"
98
+ }
99
+ }
95
100
  }