@atlaskit/editor-plugin-media 0.3.3 → 0.3.5

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,17 @@
1
1
  # @atlaskit/editor-plugin-media
2
2
 
3
+ ## 0.3.5
4
+
5
+ ### Patch Changes
6
+
7
+ - [#40861](https://bitbucket.org/atlassian/atlassian-frontend/pull-requests/40861) [`a7e65721b8b`](https://bitbucket.org/atlassian/atlassian-frontend/commits/a7e65721b8b) - ECA11Y-73: Add announcements for the screen reader users when the user types incorrect values in Alt text input field
8
+
9
+ ## 0.3.4
10
+
11
+ ### Patch Changes
12
+
13
+ - [#41343](https://bitbucket.org/atlassian/atlassian-frontend/pull-requests/41343) [`243143e8007`](https://bitbucket.org/atlassian/atlassian-frontend/commits/243143e8007) - Improved media single performance by preventing unnecessary updates to collab service on every component update
14
+
3
15
  ## 0.3.3
4
16
 
5
17
  ### Patch Changes
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.hasPrivateAttrsChanged = void 0;
7
+ var hasPrivateAttrsChanged = exports.hasPrivateAttrsChanged = function hasPrivateAttrsChanged(currentAttrs, newAttrs) {
8
+ return currentAttrs.__fileName !== newAttrs.__fileName || currentAttrs.__fileMimeType !== newAttrs.__fileMimeType || currentAttrs.__fileSize !== newAttrs.__fileSize || currentAttrs.__contextId !== newAttrs.__contextId;
9
+ };
@@ -15,6 +15,8 @@ var _analytics = require("@atlaskit/editor-common/analytics");
15
15
  var _mediaSingle = require("@atlaskit/editor-common/media-single");
16
16
  var _mediaClient = require("@atlaskit/media-client");
17
17
  var _helpers = require("../commands/helpers");
18
+ 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; }
19
+ 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) { (0, _defineProperty2.default)(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; }
18
20
  var MediaNodeUpdater = exports.MediaNodeUpdater = /*#__PURE__*/function () {
19
21
  function MediaNodeUpdater(props) {
20
22
  var _this = this;
@@ -601,6 +603,11 @@ var MediaNodeUpdater = exports.MediaNodeUpdater = /*#__PURE__*/function () {
601
603
  this.props = props;
602
604
  }
603
605
  (0, _createClass2.default)(MediaNodeUpdater, [{
606
+ key: "setProps",
607
+ value: function setProps(newComponentProps) {
608
+ this.props = _objectSpread(_objectSpread({}, this.props), newComponentProps);
609
+ }
610
+ }, {
604
611
  key: "isMediaBlobUrl",
605
612
  value: function isMediaBlobUrl() {
606
613
  var attrs = this.getAttrs();
@@ -37,6 +37,7 @@ var _CaptionPlaceholder = _interopRequireDefault(require("../ui/CaptionPlacehold
37
37
  var _ResizableMediaSingle = _interopRequireDefault(require("../ui/ResizableMediaSingle"));
38
38
  var _ResizableMediaSingleNext = _interopRequireDefault(require("../ui/ResizableMediaSingle/ResizableMediaSingleNext"));
39
39
  var _mediaCommon2 = require("../utils/media-common");
40
+ var _helpers = require("./helpers");
40
41
  var _mediaNodeUpdater = require("./mediaNodeUpdater");
41
42
  var _styles = require("./styles");
42
43
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
@@ -46,8 +47,8 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
46
47
  function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2.default)(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2.default)(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2.default)(this, result); }; }
47
48
  function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } /** @jsx jsx */
48
49
  // eslint-disable-next-line @repo/internal/react/no-class-components
49
- var MediaSingleNode = exports.default = /*#__PURE__*/function (_Component) {
50
- (0, _inherits2.default)(MediaSingleNode, _Component);
50
+ var MediaSingleNode = exports.default = /*#__PURE__*/function (_PureComponent) {
51
+ (0, _inherits2.default)(MediaSingleNode, _PureComponent);
51
52
  var _super = _createSuper(MediaSingleNode);
52
53
  function MediaSingleNode() {
53
54
  var _this;
@@ -56,6 +57,7 @@ var MediaSingleNode = exports.default = /*#__PURE__*/function (_Component) {
56
57
  args[_key] = arguments[_key];
57
58
  }
58
59
  _this = _super.call.apply(_super, [this].concat(args));
60
+ (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "mediaNodeUpdater", null);
59
61
  (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "state", {
60
62
  width: undefined,
61
63
  height: undefined,
@@ -64,13 +66,19 @@ var MediaSingleNode = exports.default = /*#__PURE__*/function (_Component) {
64
66
  });
65
67
  (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "mediaSingleWrapperRef", /*#__PURE__*/_react.default.createRef());
66
68
  (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "captionPlaceHolderRef", /*#__PURE__*/_react.default.createRef());
67
- (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "createMediaNodeUpdater", function (props) {
69
+ (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "createOrUpdateMediaNodeUpdater", function (props) {
68
70
  var node = _this.props.node.firstChild;
69
- return new _mediaNodeUpdater.MediaNodeUpdater(_objectSpread(_objectSpread({}, props), {}, {
71
+ var updaterProps = _objectSpread(_objectSpread({}, props), {}, {
70
72
  isMediaSingle: true,
71
73
  node: node ? node : _this.props.node,
72
74
  dispatchAnalyticsEvent: _this.props.dispatchAnalyticsEvent
73
- }));
75
+ });
76
+ if (!_this.mediaNodeUpdater) {
77
+ _this.mediaNodeUpdater = new _mediaNodeUpdater.MediaNodeUpdater(updaterProps);
78
+ } else {
79
+ var _this$mediaNodeUpdate;
80
+ (_this$mediaNodeUpdate = _this.mediaNodeUpdater) === null || _this$mediaNodeUpdate === void 0 || _this$mediaNodeUpdate.setProps(updaterProps);
81
+ }
74
82
  });
75
83
  (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "setViewMediaClientConfig", /*#__PURE__*/function () {
76
84
  var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(props) {
@@ -100,11 +108,12 @@ var MediaSingleNode = exports.default = /*#__PURE__*/function (_Component) {
100
108
  }());
101
109
  (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "updateMediaNodeAttributes", /*#__PURE__*/function () {
102
110
  var _ref2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2(props) {
103
- var mediaNodeUpdater, addPendingTask, node, updatedDimensions, updatingNode, contextId, hasDifferentContextId, copyNode;
111
+ var _this$mediaNodeUpdate2, _this$props$node$firs, _this$mediaNodeUpdate4, _this$mediaNodeUpdate6;
112
+ var addPendingTask, node, updatedDimensions, currentAttrs, _this$mediaNodeUpdate3, updatingNode, contextId, _this$mediaNodeUpdate5, hasDifferentContextId, copyNode;
104
113
  return _regenerator.default.wrap(function _callee2$(_context2) {
105
114
  while (1) switch (_context2.prev = _context2.next) {
106
115
  case 0:
107
- mediaNodeUpdater = _this.createMediaNodeUpdater(props);
116
+ _this.createOrUpdateMediaNodeUpdater(props);
108
117
  addPendingTask = _this.props.mediaPluginState.addPendingTask; // we want the first child of MediaSingle (type "media")
109
118
  node = _this.props.node.firstChild;
110
119
  if (node) {
@@ -114,64 +123,65 @@ var MediaSingleNode = exports.default = /*#__PURE__*/function (_Component) {
114
123
  return _context2.abrupt("return");
115
124
  case 5:
116
125
  _context2.next = 7;
117
- return mediaNodeUpdater.getRemoteDimensions();
126
+ return (_this$mediaNodeUpdate2 = _this.mediaNodeUpdater) === null || _this$mediaNodeUpdate2 === void 0 ? void 0 : _this$mediaNodeUpdate2.getRemoteDimensions();
118
127
  case 7:
119
128
  updatedDimensions = _context2.sent;
120
- if (updatedDimensions) {
121
- mediaNodeUpdater.updateDimensions(updatedDimensions);
129
+ currentAttrs = (_this$props$node$firs = _this.props.node.firstChild) === null || _this$props$node$firs === void 0 ? void 0 : _this$props$node$firs.attrs;
130
+ if (updatedDimensions && ((currentAttrs === null || currentAttrs === void 0 ? void 0 : currentAttrs.width) !== updatedDimensions.width || (currentAttrs === null || currentAttrs === void 0 ? void 0 : currentAttrs.height) !== updatedDimensions.height)) {
131
+ (_this$mediaNodeUpdate3 = _this.mediaNodeUpdater) === null || _this$mediaNodeUpdate3 === void 0 || _this$mediaNodeUpdate3.updateDimensions(updatedDimensions);
122
132
  }
123
133
  if (!(node.attrs.type === 'external' && node.attrs.__external)) {
124
- _context2.next = 15;
134
+ _context2.next = 16;
125
135
  break;
126
136
  }
127
- updatingNode = mediaNodeUpdater.handleExternalMedia(_this.props.getPos);
137
+ updatingNode = _this.mediaNodeUpdater.handleExternalMedia(_this.props.getPos);
128
138
  addPendingTask(updatingNode);
129
- _context2.next = 14;
139
+ _context2.next = 15;
130
140
  return updatingNode;
131
- case 14:
132
- return _context2.abrupt("return");
133
141
  case 15:
134
- contextId = mediaNodeUpdater.getNodeContextId();
142
+ return _context2.abrupt("return");
143
+ case 16:
144
+ contextId = (_this$mediaNodeUpdate4 = _this.mediaNodeUpdater) === null || _this$mediaNodeUpdate4 === void 0 ? void 0 : _this$mediaNodeUpdate4.getNodeContextId();
135
145
  if (contextId) {
136
- _context2.next = 19;
146
+ _context2.next = 20;
137
147
  break;
138
148
  }
139
- _context2.next = 19;
140
- return mediaNodeUpdater.updateContextId();
141
- case 19:
142
- _context2.next = 21;
143
- return mediaNodeUpdater.hasDifferentContextId();
144
- case 21:
149
+ _context2.next = 20;
150
+ return (_this$mediaNodeUpdate5 = _this.mediaNodeUpdater) === null || _this$mediaNodeUpdate5 === void 0 ? void 0 : _this$mediaNodeUpdate5.updateContextId();
151
+ case 20:
152
+ _context2.next = 22;
153
+ return (_this$mediaNodeUpdate6 = _this.mediaNodeUpdater) === null || _this$mediaNodeUpdate6 === void 0 ? void 0 : _this$mediaNodeUpdate6.hasDifferentContextId();
154
+ case 22:
145
155
  hasDifferentContextId = _context2.sent;
146
156
  if (!hasDifferentContextId) {
147
- _context2.next = 34;
157
+ _context2.next = 35;
148
158
  break;
149
159
  }
150
160
  _this.setState({
151
161
  isCopying: true
152
162
  });
153
- _context2.prev = 24;
154
- copyNode = mediaNodeUpdater.copyNode({
163
+ _context2.prev = 25;
164
+ copyNode = _this.mediaNodeUpdater.copyNode({
155
165
  traceId: node.attrs.__mediaTraceId
156
166
  });
157
167
  addPendingTask(copyNode);
158
- _context2.next = 29;
168
+ _context2.next = 30;
159
169
  return copyNode;
160
- case 29:
161
- _context2.next = 34;
170
+ case 30:
171
+ _context2.next = 35;
162
172
  break;
163
- case 31:
164
- _context2.prev = 31;
165
- _context2.t0 = _context2["catch"](24);
173
+ case 32:
174
+ _context2.prev = 32;
175
+ _context2.t0 = _context2["catch"](25);
166
176
  // if copyNode fails, let's set isCopying false so we can show the eventual error
167
177
  _this.setState({
168
178
  isCopying: false
169
179
  });
170
- case 34:
180
+ case 35:
171
181
  case "end":
172
182
  return _context2.stop();
173
183
  }
174
- }, _callee2, null, [[24, 31]]);
184
+ }, _callee2, null, [[25, 32]]);
175
185
  }));
176
186
  return function (_x2) {
177
187
  return _ref2.apply(this, arguments);
@@ -254,6 +264,9 @@ var MediaSingleNode = exports.default = /*#__PURE__*/function (_Component) {
254
264
  (0, _createClass2.default)(MediaSingleNode, [{
255
265
  key: "UNSAFE_componentWillReceiveProps",
256
266
  value: function UNSAFE_componentWillReceiveProps(nextProps) {
267
+ if (!this.mediaNodeUpdater) {
268
+ this.createOrUpdateMediaNodeUpdater(nextProps);
269
+ }
257
270
  if (nextProps.mediaProvider !== this.props.mediaProvider) {
258
271
  this.setViewMediaClientConfig(nextProps);
259
272
  }
@@ -262,10 +275,19 @@ var MediaSingleNode = exports.default = /*#__PURE__*/function (_Component) {
262
275
  if (nextProps.isCopyPasteEnabled === false) {
263
276
  return;
264
277
  }
265
-
266
- // We need to call this method on any prop change since attrs can get removed with collab editing
267
- // the method internally checks if we already have all attrs
268
- this.createMediaNodeUpdater(nextProps).updateMediaSingleFileAttrs();
278
+ if (nextProps.mediaProvider !== this.props.mediaProvider) {
279
+ var _this$mediaNodeUpdate7;
280
+ this.createOrUpdateMediaNodeUpdater(nextProps);
281
+ (_this$mediaNodeUpdate7 = this.mediaNodeUpdater) === null || _this$mediaNodeUpdate7 === void 0 || _this$mediaNodeUpdate7.updateMediaSingleFileAttrs();
282
+ } else if (nextProps.node.firstChild && this.props.node.firstChild) {
283
+ var attrsChanged = (0, _helpers.hasPrivateAttrsChanged)(this.props.node.firstChild.attrs, nextProps.node.firstChild.attrs);
284
+ if (attrsChanged) {
285
+ var _this$mediaNodeUpdate8;
286
+ this.createOrUpdateMediaNodeUpdater(nextProps);
287
+ // We need to call this method on any prop change since attrs can get removed with collab editing
288
+ (_this$mediaNodeUpdate8 = this.mediaNodeUpdater) === null || _this$mediaNodeUpdate8 === void 0 || _this$mediaNodeUpdate8.updateMediaSingleFileAttrs();
289
+ }
290
+ }
269
291
  }
270
292
  }, {
271
293
  key: "componentDidMount",
@@ -276,19 +298,20 @@ var MediaSingleNode = exports.default = /*#__PURE__*/function (_Component) {
276
298
  while (1) switch (_context3.prev = _context3.next) {
277
299
  case 0:
278
300
  contextIdentifierProvider = this.props.contextIdentifierProvider;
279
- _context3.next = 3;
301
+ this.createOrUpdateMediaNodeUpdater(this.props);
302
+ _context3.next = 4;
280
303
  return Promise.all([this.setViewMediaClientConfig(this.props), this.updateMediaNodeAttributes(this.props)]);
281
- case 3:
304
+ case 4:
282
305
  _context3.t0 = this;
283
- _context3.next = 6;
306
+ _context3.next = 7;
284
307
  return contextIdentifierProvider;
285
- case 6:
308
+ case 7:
286
309
  _context3.t1 = _context3.sent;
287
310
  _context3.t2 = {
288
311
  contextIdentifierProvider: _context3.t1
289
312
  };
290
313
  _context3.t0.setState.call(_context3.t0, _context3.t2);
291
- case 9:
314
+ case 10:
292
315
  case "end":
293
316
  return _context3.stop();
294
317
  }
@@ -419,7 +442,7 @@ var MediaSingleNode = exports.default = /*#__PURE__*/function (_Component) {
419
442
  }
420
443
  }]);
421
444
  return MediaSingleNode;
422
- }(_react.Component);
445
+ }(_react.PureComponent);
423
446
  (0, _defineProperty2.default)(MediaSingleNode, "defaultProps", {
424
447
  mediaOptions: {}
425
448
  });
@@ -75,7 +75,18 @@ var AltTextEditComponent = exports.AltTextEditComponent = /*#__PURE__*/function
75
75
  (0, _commands.updateAltText)(newValue)(view.state, view.dispatch);
76
76
  });
77
77
  (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "handleOnChange", function (newAltText) {
78
+ var _this$state;
78
79
  var validationErrors = _this.getValidationErrors(newAltText);
80
+ if (((_this$state = _this.state) === null || _this$state === void 0 || (_this$state = _this$state.validationErrors) === null || _this$state === void 0 ? void 0 : _this$state.length) !== (validationErrors === null || validationErrors === void 0 ? void 0 : validationErrors.length)) {
81
+ // If number of errors was changed we need to reset attribute to get new SR announcement
82
+
83
+ if (_this.errorsListRef) {
84
+ var _this$errorsListRef;
85
+ var errorsArea = (_this$errorsListRef = _this.errorsListRef) === null || _this$errorsListRef === void 0 ? void 0 : _this$errorsListRef.current;
86
+ errorsArea === null || errorsArea === void 0 || errorsArea.removeAttribute('aria-live');
87
+ errorsArea === null || errorsArea === void 0 || errorsArea.setAttribute('aria-live', 'assertive');
88
+ }
89
+ }
79
90
  _this.setState({
80
91
  showClearTextButton: Boolean(newAltText),
81
92
  validationErrors: validationErrors,
@@ -98,6 +109,7 @@ var AltTextEditComponent = exports.AltTextEditComponent = /*#__PURE__*/function
98
109
  });
99
110
  var createAnalyticsEvent = props.createAnalyticsEvent;
100
111
  _this.fireCustomAnalytics = (0, _analytics.fireAnalyticsEvent)(createAnalyticsEvent);
112
+ _this.errorsListRef = /*#__PURE__*/_react.default.createRef();
101
113
  return _this;
102
114
  }
103
115
  (0, _createClass2.default)(AltTextEditComponent, [{
@@ -144,6 +156,7 @@ var AltTextEditComponent = exports.AltTextEditComponent = /*#__PURE__*/function
144
156
  key: index
145
157
  }, error);
146
158
  });
159
+ var hasErrors = !!errorsList.length;
147
160
  return (0, _react2.jsx)("div", {
148
161
  css: container
149
162
  }, (0, _react2.jsx)("section", {
@@ -160,7 +173,7 @@ var AltTextEditComponent = exports.AltTextEditComponent = /*#__PURE__*/function
160
173
  })), (0, _react2.jsx)(_ui.PanelTextInput, {
161
174
  testId: "alt-text-input",
162
175
  ariaLabel: formatMessage(_messages.messages.placeholder),
163
- describedById: "support-text",
176
+ describedById: "".concat(hasErrors ? 'errors-list' : '', " support-text"),
164
177
  placeholder: formatMessage(_messages.messages.placeholder),
165
178
  defaultValue: this.state.lastValue,
166
179
  onCancel: this.dispatchCancelEvent,
@@ -168,6 +181,8 @@ var AltTextEditComponent = exports.AltTextEditComponent = /*#__PURE__*/function
168
181
  onBlur: this.handleOnBlur,
169
182
  onSubmit: this.closeMediaAltTextMenu,
170
183
  maxLength: MAX_ALT_TEXT_LENGTH,
184
+ ariaRequired: true,
185
+ ariaInvalid: hasErrors,
171
186
  autoFocus: true
172
187
  }), showClearTextButton && (0, _react2.jsx)("div", {
173
188
  css: buttonWrapper
@@ -181,7 +196,10 @@ var AltTextEditComponent = exports.AltTextEditComponent = /*#__PURE__*/function
181
196
  })),
182
197
  tooltipContent: formatMessage(_messages.messages.clear),
183
198
  onClick: this.handleClearText
184
- }))), !!errorsList.length && (0, _react2.jsx)("section", {
199
+ }))), hasErrors && (0, _react2.jsx)("section", {
200
+ id: "errors-list",
201
+ ref: this.errorsListRef,
202
+ "aria-live": "assertive",
185
203
  css: validationWrapper
186
204
  }, errorsList), (0, _react2.jsx)("p", {
187
205
  css: supportText,
@@ -0,0 +1,3 @@
1
+ export const hasPrivateAttrsChanged = (currentAttrs, newAttrs) => {
2
+ return currentAttrs.__fileName !== newAttrs.__fileName || currentAttrs.__fileMimeType !== newAttrs.__fileMimeType || currentAttrs.__fileSize !== newAttrs.__fileSize || currentAttrs.__contextId !== newAttrs.__contextId;
3
+ };
@@ -311,6 +311,12 @@ export class MediaNodeUpdater {
311
311
  });
312
312
  this.props = props;
313
313
  }
314
+ setProps(newComponentProps) {
315
+ this.props = {
316
+ ...this.props,
317
+ ...newComponentProps
318
+ };
319
+ }
314
320
  isMediaBlobUrl() {
315
321
  const attrs = this.getAttrs();
316
322
  return !!(attrs && attrs.type === 'external' && isMediaBlobUrl(attrs.url));
@@ -2,7 +2,7 @@ import _extends from "@babel/runtime/helpers/extends";
2
2
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
3
3
  /** @jsx jsx */
4
4
 
5
- import React, { Component } from 'react';
5
+ import React, { PureComponent } from 'react';
6
6
  import { jsx } from '@emotion/react';
7
7
  import { useSharedPluginState } from '@atlaskit/editor-common/hooks';
8
8
  import { calcMediaSinglePixelWidth, DEFAULT_IMAGE_HEIGHT, DEFAULT_IMAGE_WIDTH, getMaxWidthForNestedNode, MEDIA_SINGLE_GUTTER_SIZE } from '@atlaskit/editor-common/media-single';
@@ -22,12 +22,14 @@ import CaptionPlaceholder from '../ui/CaptionPlaceholder';
22
22
  import ResizableMediaSingle from '../ui/ResizableMediaSingle';
23
23
  import ResizableMediaSingleNext from '../ui/ResizableMediaSingle/ResizableMediaSingleNext';
24
24
  import { isMediaBlobUrlFromAttrs } from '../utils/media-common';
25
+ import { hasPrivateAttrsChanged } from './helpers';
25
26
  import { MediaNodeUpdater } from './mediaNodeUpdater';
26
27
  import { figureWrapper, MediaSingleNodeSelector } from './styles';
27
28
  // eslint-disable-next-line @repo/internal/react/no-class-components
28
- export default class MediaSingleNode extends Component {
29
+ export default class MediaSingleNode extends PureComponent {
29
30
  constructor(...args) {
30
31
  super(...args);
32
+ _defineProperty(this, "mediaNodeUpdater", null);
31
33
  _defineProperty(this, "state", {
32
34
  width: undefined,
33
35
  height: undefined,
@@ -36,14 +38,20 @@ export default class MediaSingleNode extends Component {
36
38
  });
37
39
  _defineProperty(this, "mediaSingleWrapperRef", /*#__PURE__*/React.createRef());
38
40
  _defineProperty(this, "captionPlaceHolderRef", /*#__PURE__*/React.createRef());
39
- _defineProperty(this, "createMediaNodeUpdater", props => {
41
+ _defineProperty(this, "createOrUpdateMediaNodeUpdater", props => {
40
42
  const node = this.props.node.firstChild;
41
- return new MediaNodeUpdater({
43
+ const updaterProps = {
42
44
  ...props,
43
45
  isMediaSingle: true,
44
46
  node: node ? node : this.props.node,
45
47
  dispatchAnalyticsEvent: this.props.dispatchAnalyticsEvent
46
- });
48
+ };
49
+ if (!this.mediaNodeUpdater) {
50
+ this.mediaNodeUpdater = new MediaNodeUpdater(updaterProps);
51
+ } else {
52
+ var _this$mediaNodeUpdate;
53
+ (_this$mediaNodeUpdate = this.mediaNodeUpdater) === null || _this$mediaNodeUpdate === void 0 ? void 0 : _this$mediaNodeUpdate.setProps(updaterProps);
54
+ }
47
55
  });
48
56
  _defineProperty(this, "setViewMediaClientConfig", async props => {
49
57
  const mediaProvider = await props.mediaProvider;
@@ -55,7 +63,8 @@ export default class MediaSingleNode extends Component {
55
63
  }
56
64
  });
57
65
  _defineProperty(this, "updateMediaNodeAttributes", async props => {
58
- const mediaNodeUpdater = this.createMediaNodeUpdater(props);
66
+ var _this$mediaNodeUpdate2, _this$props$node$firs, _this$mediaNodeUpdate4, _this$mediaNodeUpdate6;
67
+ this.createOrUpdateMediaNodeUpdater(props);
59
68
  const {
60
69
  addPendingTask
61
70
  } = this.props.mediaPluginState;
@@ -65,27 +74,30 @@ export default class MediaSingleNode extends Component {
65
74
  if (!node) {
66
75
  return;
67
76
  }
68
- const updatedDimensions = await mediaNodeUpdater.getRemoteDimensions();
69
- if (updatedDimensions) {
70
- mediaNodeUpdater.updateDimensions(updatedDimensions);
77
+ const updatedDimensions = await ((_this$mediaNodeUpdate2 = this.mediaNodeUpdater) === null || _this$mediaNodeUpdate2 === void 0 ? void 0 : _this$mediaNodeUpdate2.getRemoteDimensions());
78
+ const currentAttrs = (_this$props$node$firs = this.props.node.firstChild) === null || _this$props$node$firs === void 0 ? void 0 : _this$props$node$firs.attrs;
79
+ if (updatedDimensions && ((currentAttrs === null || currentAttrs === void 0 ? void 0 : currentAttrs.width) !== updatedDimensions.width || (currentAttrs === null || currentAttrs === void 0 ? void 0 : currentAttrs.height) !== updatedDimensions.height)) {
80
+ var _this$mediaNodeUpdate3;
81
+ (_this$mediaNodeUpdate3 = this.mediaNodeUpdater) === null || _this$mediaNodeUpdate3 === void 0 ? void 0 : _this$mediaNodeUpdate3.updateDimensions(updatedDimensions);
71
82
  }
72
83
  if (node.attrs.type === 'external' && node.attrs.__external) {
73
- const updatingNode = mediaNodeUpdater.handleExternalMedia(this.props.getPos);
84
+ const updatingNode = this.mediaNodeUpdater.handleExternalMedia(this.props.getPos);
74
85
  addPendingTask(updatingNode);
75
86
  await updatingNode;
76
87
  return;
77
88
  }
78
- const contextId = mediaNodeUpdater.getNodeContextId();
89
+ const contextId = (_this$mediaNodeUpdate4 = this.mediaNodeUpdater) === null || _this$mediaNodeUpdate4 === void 0 ? void 0 : _this$mediaNodeUpdate4.getNodeContextId();
79
90
  if (!contextId) {
80
- await mediaNodeUpdater.updateContextId();
91
+ var _this$mediaNodeUpdate5;
92
+ await ((_this$mediaNodeUpdate5 = this.mediaNodeUpdater) === null || _this$mediaNodeUpdate5 === void 0 ? void 0 : _this$mediaNodeUpdate5.updateContextId());
81
93
  }
82
- const hasDifferentContextId = await mediaNodeUpdater.hasDifferentContextId();
94
+ const hasDifferentContextId = await ((_this$mediaNodeUpdate6 = this.mediaNodeUpdater) === null || _this$mediaNodeUpdate6 === void 0 ? void 0 : _this$mediaNodeUpdate6.hasDifferentContextId());
83
95
  if (hasDifferentContextId) {
84
96
  this.setState({
85
97
  isCopying: true
86
98
  });
87
99
  try {
88
- const copyNode = mediaNodeUpdater.copyNode({
100
+ const copyNode = this.mediaNodeUpdater.copyNode({
89
101
  traceId: node.attrs.__mediaTraceId
90
102
  });
91
103
  addPendingTask(copyNode);
@@ -178,6 +190,9 @@ export default class MediaSingleNode extends Component {
178
190
  });
179
191
  }
180
192
  UNSAFE_componentWillReceiveProps(nextProps) {
193
+ if (!this.mediaNodeUpdater) {
194
+ this.createOrUpdateMediaNodeUpdater(nextProps);
195
+ }
181
196
  if (nextProps.mediaProvider !== this.props.mediaProvider) {
182
197
  this.setViewMediaClientConfig(nextProps);
183
198
  }
@@ -186,15 +201,25 @@ export default class MediaSingleNode extends Component {
186
201
  if (nextProps.isCopyPasteEnabled === false) {
187
202
  return;
188
203
  }
189
-
190
- // We need to call this method on any prop change since attrs can get removed with collab editing
191
- // the method internally checks if we already have all attrs
192
- this.createMediaNodeUpdater(nextProps).updateMediaSingleFileAttrs();
204
+ if (nextProps.mediaProvider !== this.props.mediaProvider) {
205
+ var _this$mediaNodeUpdate7;
206
+ this.createOrUpdateMediaNodeUpdater(nextProps);
207
+ (_this$mediaNodeUpdate7 = this.mediaNodeUpdater) === null || _this$mediaNodeUpdate7 === void 0 ? void 0 : _this$mediaNodeUpdate7.updateMediaSingleFileAttrs();
208
+ } else if (nextProps.node.firstChild && this.props.node.firstChild) {
209
+ const attrsChanged = hasPrivateAttrsChanged(this.props.node.firstChild.attrs, nextProps.node.firstChild.attrs);
210
+ if (attrsChanged) {
211
+ var _this$mediaNodeUpdate8;
212
+ this.createOrUpdateMediaNodeUpdater(nextProps);
213
+ // We need to call this method on any prop change since attrs can get removed with collab editing
214
+ (_this$mediaNodeUpdate8 = this.mediaNodeUpdater) === null || _this$mediaNodeUpdate8 === void 0 ? void 0 : _this$mediaNodeUpdate8.updateMediaSingleFileAttrs();
215
+ }
216
+ }
193
217
  }
194
218
  async componentDidMount() {
195
219
  const {
196
220
  contextIdentifierProvider
197
221
  } = this.props;
222
+ this.createOrUpdateMediaNodeUpdater(this.props);
198
223
  await Promise.all([this.setViewMediaClientConfig(this.props), this.updateMediaNodeAttributes(this.props)]);
199
224
  this.setState({
200
225
  contextIdentifierProvider: await contextIdentifierProvider
@@ -93,7 +93,18 @@ export class AltTextEditComponent extends React.Component {
93
93
  updateAltText(newValue)(view.state, view.dispatch);
94
94
  });
95
95
  _defineProperty(this, "handleOnChange", newAltText => {
96
+ var _this$state, _this$state$validatio;
96
97
  const validationErrors = this.getValidationErrors(newAltText);
98
+ if (((_this$state = this.state) === null || _this$state === void 0 ? void 0 : (_this$state$validatio = _this$state.validationErrors) === null || _this$state$validatio === void 0 ? void 0 : _this$state$validatio.length) !== (validationErrors === null || validationErrors === void 0 ? void 0 : validationErrors.length)) {
99
+ // If number of errors was changed we need to reset attribute to get new SR announcement
100
+
101
+ if (this.errorsListRef) {
102
+ var _this$errorsListRef;
103
+ const errorsArea = (_this$errorsListRef = this.errorsListRef) === null || _this$errorsListRef === void 0 ? void 0 : _this$errorsListRef.current;
104
+ errorsArea === null || errorsArea === void 0 ? void 0 : errorsArea.removeAttribute('aria-live');
105
+ errorsArea === null || errorsArea === void 0 ? void 0 : errorsArea.setAttribute('aria-live', 'assertive');
106
+ }
107
+ }
97
108
  this.setState({
98
109
  showClearTextButton: Boolean(newAltText),
99
110
  validationErrors,
@@ -120,6 +131,7 @@ export class AltTextEditComponent extends React.Component {
120
131
  createAnalyticsEvent
121
132
  } = props;
122
133
  this.fireCustomAnalytics = fireAnalyticsEvent(createAnalyticsEvent);
134
+ this.errorsListRef = /*#__PURE__*/React.createRef();
123
135
  }
124
136
  componentDidMount() {
125
137
  this.prevValue = this.props.value;
@@ -165,6 +177,7 @@ export class AltTextEditComponent extends React.Component {
165
177
  key: index
166
178
  }, error);
167
179
  });
180
+ const hasErrors = !!errorsList.length;
168
181
  return jsx("div", {
169
182
  css: container
170
183
  }, jsx("section", {
@@ -181,7 +194,7 @@ export class AltTextEditComponent extends React.Component {
181
194
  })), jsx(PanelTextInput, {
182
195
  testId: "alt-text-input",
183
196
  ariaLabel: formatMessage(messages.placeholder),
184
- describedById: "support-text",
197
+ describedById: `${hasErrors ? 'errors-list' : ''} support-text`,
185
198
  placeholder: formatMessage(messages.placeholder),
186
199
  defaultValue: this.state.lastValue,
187
200
  onCancel: this.dispatchCancelEvent,
@@ -189,6 +202,8 @@ export class AltTextEditComponent extends React.Component {
189
202
  onBlur: this.handleOnBlur,
190
203
  onSubmit: this.closeMediaAltTextMenu,
191
204
  maxLength: MAX_ALT_TEXT_LENGTH,
205
+ ariaRequired: true,
206
+ ariaInvalid: hasErrors,
192
207
  autoFocus: true
193
208
  }), showClearTextButton && jsx("div", {
194
209
  css: buttonWrapper
@@ -202,7 +217,10 @@ export class AltTextEditComponent extends React.Component {
202
217
  })),
203
218
  tooltipContent: formatMessage(messages.clear),
204
219
  onClick: this.handleClearText
205
- }))), !!errorsList.length && jsx("section", {
220
+ }))), hasErrors && jsx("section", {
221
+ id: "errors-list",
222
+ ref: this.errorsListRef,
223
+ "aria-live": "assertive",
206
224
  css: validationWrapper
207
225
  }, errorsList), jsx("p", {
208
226
  css: supportText,
@@ -0,0 +1,3 @@
1
+ export var hasPrivateAttrsChanged = function hasPrivateAttrsChanged(currentAttrs, newAttrs) {
2
+ return currentAttrs.__fileName !== newAttrs.__fileName || currentAttrs.__fileMimeType !== newAttrs.__fileMimeType || currentAttrs.__fileSize !== newAttrs.__fileSize || currentAttrs.__contextId !== newAttrs.__contextId;
3
+ };
@@ -2,6 +2,8 @@ import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
2
2
  import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
3
3
  import _createClass from "@babel/runtime/helpers/createClass";
4
4
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
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; }
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
7
  import _regeneratorRuntime from "@babel/runtime/regenerator";
6
8
  import uuidV4 from 'uuid/v4';
7
9
  import { ACTION, ACTION_SUBJECT, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
@@ -594,6 +596,11 @@ export var MediaNodeUpdater = /*#__PURE__*/function () {
594
596
  this.props = props;
595
597
  }
596
598
  _createClass(MediaNodeUpdater, [{
599
+ key: "setProps",
600
+ value: function setProps(newComponentProps) {
601
+ this.props = _objectSpread(_objectSpread({}, this.props), newComponentProps);
602
+ }
603
+ }, {
597
604
  key: "isMediaBlobUrl",
598
605
  value: function isMediaBlobUrl() {
599
606
  var attrs = this.getAttrs();
@@ -15,7 +15,7 @@ function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflec
15
15
  function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
16
16
  /** @jsx jsx */
17
17
 
18
- import React, { Component } from 'react';
18
+ import React, { PureComponent } from 'react';
19
19
  import { jsx } from '@emotion/react';
20
20
  import { useSharedPluginState } from '@atlaskit/editor-common/hooks';
21
21
  import { calcMediaSinglePixelWidth, DEFAULT_IMAGE_HEIGHT, DEFAULT_IMAGE_WIDTH, getMaxWidthForNestedNode, MEDIA_SINGLE_GUTTER_SIZE } from '@atlaskit/editor-common/media-single';
@@ -35,11 +35,12 @@ import CaptionPlaceholder from '../ui/CaptionPlaceholder';
35
35
  import ResizableMediaSingle from '../ui/ResizableMediaSingle';
36
36
  import ResizableMediaSingleNext from '../ui/ResizableMediaSingle/ResizableMediaSingleNext';
37
37
  import { isMediaBlobUrlFromAttrs } from '../utils/media-common';
38
+ import { hasPrivateAttrsChanged } from './helpers';
38
39
  import { MediaNodeUpdater } from './mediaNodeUpdater';
39
40
  import { figureWrapper, MediaSingleNodeSelector } from './styles';
40
41
  // eslint-disable-next-line @repo/internal/react/no-class-components
41
- var MediaSingleNode = /*#__PURE__*/function (_Component) {
42
- _inherits(MediaSingleNode, _Component);
42
+ var MediaSingleNode = /*#__PURE__*/function (_PureComponent) {
43
+ _inherits(MediaSingleNode, _PureComponent);
43
44
  var _super = _createSuper(MediaSingleNode);
44
45
  function MediaSingleNode() {
45
46
  var _this;
@@ -48,6 +49,7 @@ var MediaSingleNode = /*#__PURE__*/function (_Component) {
48
49
  args[_key] = arguments[_key];
49
50
  }
50
51
  _this = _super.call.apply(_super, [this].concat(args));
52
+ _defineProperty(_assertThisInitialized(_this), "mediaNodeUpdater", null);
51
53
  _defineProperty(_assertThisInitialized(_this), "state", {
52
54
  width: undefined,
53
55
  height: undefined,
@@ -56,13 +58,19 @@ var MediaSingleNode = /*#__PURE__*/function (_Component) {
56
58
  });
57
59
  _defineProperty(_assertThisInitialized(_this), "mediaSingleWrapperRef", /*#__PURE__*/React.createRef());
58
60
  _defineProperty(_assertThisInitialized(_this), "captionPlaceHolderRef", /*#__PURE__*/React.createRef());
59
- _defineProperty(_assertThisInitialized(_this), "createMediaNodeUpdater", function (props) {
61
+ _defineProperty(_assertThisInitialized(_this), "createOrUpdateMediaNodeUpdater", function (props) {
60
62
  var node = _this.props.node.firstChild;
61
- return new MediaNodeUpdater(_objectSpread(_objectSpread({}, props), {}, {
63
+ var updaterProps = _objectSpread(_objectSpread({}, props), {}, {
62
64
  isMediaSingle: true,
63
65
  node: node ? node : _this.props.node,
64
66
  dispatchAnalyticsEvent: _this.props.dispatchAnalyticsEvent
65
- }));
67
+ });
68
+ if (!_this.mediaNodeUpdater) {
69
+ _this.mediaNodeUpdater = new MediaNodeUpdater(updaterProps);
70
+ } else {
71
+ var _this$mediaNodeUpdate;
72
+ (_this$mediaNodeUpdate = _this.mediaNodeUpdater) === null || _this$mediaNodeUpdate === void 0 || _this$mediaNodeUpdate.setProps(updaterProps);
73
+ }
66
74
  });
67
75
  _defineProperty(_assertThisInitialized(_this), "setViewMediaClientConfig", /*#__PURE__*/function () {
68
76
  var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(props) {
@@ -92,11 +100,12 @@ var MediaSingleNode = /*#__PURE__*/function (_Component) {
92
100
  }());
93
101
  _defineProperty(_assertThisInitialized(_this), "updateMediaNodeAttributes", /*#__PURE__*/function () {
94
102
  var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(props) {
95
- var mediaNodeUpdater, addPendingTask, node, updatedDimensions, updatingNode, contextId, hasDifferentContextId, copyNode;
103
+ var _this$mediaNodeUpdate2, _this$props$node$firs, _this$mediaNodeUpdate4, _this$mediaNodeUpdate6;
104
+ var addPendingTask, node, updatedDimensions, currentAttrs, _this$mediaNodeUpdate3, updatingNode, contextId, _this$mediaNodeUpdate5, hasDifferentContextId, copyNode;
96
105
  return _regeneratorRuntime.wrap(function _callee2$(_context2) {
97
106
  while (1) switch (_context2.prev = _context2.next) {
98
107
  case 0:
99
- mediaNodeUpdater = _this.createMediaNodeUpdater(props);
108
+ _this.createOrUpdateMediaNodeUpdater(props);
100
109
  addPendingTask = _this.props.mediaPluginState.addPendingTask; // we want the first child of MediaSingle (type "media")
101
110
  node = _this.props.node.firstChild;
102
111
  if (node) {
@@ -106,64 +115,65 @@ var MediaSingleNode = /*#__PURE__*/function (_Component) {
106
115
  return _context2.abrupt("return");
107
116
  case 5:
108
117
  _context2.next = 7;
109
- return mediaNodeUpdater.getRemoteDimensions();
118
+ return (_this$mediaNodeUpdate2 = _this.mediaNodeUpdater) === null || _this$mediaNodeUpdate2 === void 0 ? void 0 : _this$mediaNodeUpdate2.getRemoteDimensions();
110
119
  case 7:
111
120
  updatedDimensions = _context2.sent;
112
- if (updatedDimensions) {
113
- mediaNodeUpdater.updateDimensions(updatedDimensions);
121
+ currentAttrs = (_this$props$node$firs = _this.props.node.firstChild) === null || _this$props$node$firs === void 0 ? void 0 : _this$props$node$firs.attrs;
122
+ if (updatedDimensions && ((currentAttrs === null || currentAttrs === void 0 ? void 0 : currentAttrs.width) !== updatedDimensions.width || (currentAttrs === null || currentAttrs === void 0 ? void 0 : currentAttrs.height) !== updatedDimensions.height)) {
123
+ (_this$mediaNodeUpdate3 = _this.mediaNodeUpdater) === null || _this$mediaNodeUpdate3 === void 0 || _this$mediaNodeUpdate3.updateDimensions(updatedDimensions);
114
124
  }
115
125
  if (!(node.attrs.type === 'external' && node.attrs.__external)) {
116
- _context2.next = 15;
126
+ _context2.next = 16;
117
127
  break;
118
128
  }
119
- updatingNode = mediaNodeUpdater.handleExternalMedia(_this.props.getPos);
129
+ updatingNode = _this.mediaNodeUpdater.handleExternalMedia(_this.props.getPos);
120
130
  addPendingTask(updatingNode);
121
- _context2.next = 14;
131
+ _context2.next = 15;
122
132
  return updatingNode;
123
- case 14:
124
- return _context2.abrupt("return");
125
133
  case 15:
126
- contextId = mediaNodeUpdater.getNodeContextId();
134
+ return _context2.abrupt("return");
135
+ case 16:
136
+ contextId = (_this$mediaNodeUpdate4 = _this.mediaNodeUpdater) === null || _this$mediaNodeUpdate4 === void 0 ? void 0 : _this$mediaNodeUpdate4.getNodeContextId();
127
137
  if (contextId) {
128
- _context2.next = 19;
138
+ _context2.next = 20;
129
139
  break;
130
140
  }
131
- _context2.next = 19;
132
- return mediaNodeUpdater.updateContextId();
133
- case 19:
134
- _context2.next = 21;
135
- return mediaNodeUpdater.hasDifferentContextId();
136
- case 21:
141
+ _context2.next = 20;
142
+ return (_this$mediaNodeUpdate5 = _this.mediaNodeUpdater) === null || _this$mediaNodeUpdate5 === void 0 ? void 0 : _this$mediaNodeUpdate5.updateContextId();
143
+ case 20:
144
+ _context2.next = 22;
145
+ return (_this$mediaNodeUpdate6 = _this.mediaNodeUpdater) === null || _this$mediaNodeUpdate6 === void 0 ? void 0 : _this$mediaNodeUpdate6.hasDifferentContextId();
146
+ case 22:
137
147
  hasDifferentContextId = _context2.sent;
138
148
  if (!hasDifferentContextId) {
139
- _context2.next = 34;
149
+ _context2.next = 35;
140
150
  break;
141
151
  }
142
152
  _this.setState({
143
153
  isCopying: true
144
154
  });
145
- _context2.prev = 24;
146
- copyNode = mediaNodeUpdater.copyNode({
155
+ _context2.prev = 25;
156
+ copyNode = _this.mediaNodeUpdater.copyNode({
147
157
  traceId: node.attrs.__mediaTraceId
148
158
  });
149
159
  addPendingTask(copyNode);
150
- _context2.next = 29;
160
+ _context2.next = 30;
151
161
  return copyNode;
152
- case 29:
153
- _context2.next = 34;
162
+ case 30:
163
+ _context2.next = 35;
154
164
  break;
155
- case 31:
156
- _context2.prev = 31;
157
- _context2.t0 = _context2["catch"](24);
165
+ case 32:
166
+ _context2.prev = 32;
167
+ _context2.t0 = _context2["catch"](25);
158
168
  // if copyNode fails, let's set isCopying false so we can show the eventual error
159
169
  _this.setState({
160
170
  isCopying: false
161
171
  });
162
- case 34:
172
+ case 35:
163
173
  case "end":
164
174
  return _context2.stop();
165
175
  }
166
- }, _callee2, null, [[24, 31]]);
176
+ }, _callee2, null, [[25, 32]]);
167
177
  }));
168
178
  return function (_x2) {
169
179
  return _ref2.apply(this, arguments);
@@ -246,6 +256,9 @@ var MediaSingleNode = /*#__PURE__*/function (_Component) {
246
256
  _createClass(MediaSingleNode, [{
247
257
  key: "UNSAFE_componentWillReceiveProps",
248
258
  value: function UNSAFE_componentWillReceiveProps(nextProps) {
259
+ if (!this.mediaNodeUpdater) {
260
+ this.createOrUpdateMediaNodeUpdater(nextProps);
261
+ }
249
262
  if (nextProps.mediaProvider !== this.props.mediaProvider) {
250
263
  this.setViewMediaClientConfig(nextProps);
251
264
  }
@@ -254,10 +267,19 @@ var MediaSingleNode = /*#__PURE__*/function (_Component) {
254
267
  if (nextProps.isCopyPasteEnabled === false) {
255
268
  return;
256
269
  }
257
-
258
- // We need to call this method on any prop change since attrs can get removed with collab editing
259
- // the method internally checks if we already have all attrs
260
- this.createMediaNodeUpdater(nextProps).updateMediaSingleFileAttrs();
270
+ if (nextProps.mediaProvider !== this.props.mediaProvider) {
271
+ var _this$mediaNodeUpdate7;
272
+ this.createOrUpdateMediaNodeUpdater(nextProps);
273
+ (_this$mediaNodeUpdate7 = this.mediaNodeUpdater) === null || _this$mediaNodeUpdate7 === void 0 || _this$mediaNodeUpdate7.updateMediaSingleFileAttrs();
274
+ } else if (nextProps.node.firstChild && this.props.node.firstChild) {
275
+ var attrsChanged = hasPrivateAttrsChanged(this.props.node.firstChild.attrs, nextProps.node.firstChild.attrs);
276
+ if (attrsChanged) {
277
+ var _this$mediaNodeUpdate8;
278
+ this.createOrUpdateMediaNodeUpdater(nextProps);
279
+ // We need to call this method on any prop change since attrs can get removed with collab editing
280
+ (_this$mediaNodeUpdate8 = this.mediaNodeUpdater) === null || _this$mediaNodeUpdate8 === void 0 || _this$mediaNodeUpdate8.updateMediaSingleFileAttrs();
281
+ }
282
+ }
261
283
  }
262
284
  }, {
263
285
  key: "componentDidMount",
@@ -268,19 +290,20 @@ var MediaSingleNode = /*#__PURE__*/function (_Component) {
268
290
  while (1) switch (_context3.prev = _context3.next) {
269
291
  case 0:
270
292
  contextIdentifierProvider = this.props.contextIdentifierProvider;
271
- _context3.next = 3;
293
+ this.createOrUpdateMediaNodeUpdater(this.props);
294
+ _context3.next = 4;
272
295
  return Promise.all([this.setViewMediaClientConfig(this.props), this.updateMediaNodeAttributes(this.props)]);
273
- case 3:
296
+ case 4:
274
297
  _context3.t0 = this;
275
- _context3.next = 6;
298
+ _context3.next = 7;
276
299
  return contextIdentifierProvider;
277
- case 6:
300
+ case 7:
278
301
  _context3.t1 = _context3.sent;
279
302
  _context3.t2 = {
280
303
  contextIdentifierProvider: _context3.t1
281
304
  };
282
305
  _context3.t0.setState.call(_context3.t0, _context3.t2);
283
- case 9:
306
+ case 10:
284
307
  case "end":
285
308
  return _context3.stop();
286
309
  }
@@ -411,7 +434,7 @@ var MediaSingleNode = /*#__PURE__*/function (_Component) {
411
434
  }
412
435
  }]);
413
436
  return MediaSingleNode;
414
- }(Component);
437
+ }(PureComponent);
415
438
  _defineProperty(MediaSingleNode, "defaultProps", {
416
439
  mediaOptions: {}
417
440
  });
@@ -72,7 +72,18 @@ export var AltTextEditComponent = /*#__PURE__*/function (_React$Component) {
72
72
  updateAltText(newValue)(view.state, view.dispatch);
73
73
  });
74
74
  _defineProperty(_assertThisInitialized(_this), "handleOnChange", function (newAltText) {
75
+ var _this$state;
75
76
  var validationErrors = _this.getValidationErrors(newAltText);
77
+ if (((_this$state = _this.state) === null || _this$state === void 0 || (_this$state = _this$state.validationErrors) === null || _this$state === void 0 ? void 0 : _this$state.length) !== (validationErrors === null || validationErrors === void 0 ? void 0 : validationErrors.length)) {
78
+ // If number of errors was changed we need to reset attribute to get new SR announcement
79
+
80
+ if (_this.errorsListRef) {
81
+ var _this$errorsListRef;
82
+ var errorsArea = (_this$errorsListRef = _this.errorsListRef) === null || _this$errorsListRef === void 0 ? void 0 : _this$errorsListRef.current;
83
+ errorsArea === null || errorsArea === void 0 || errorsArea.removeAttribute('aria-live');
84
+ errorsArea === null || errorsArea === void 0 || errorsArea.setAttribute('aria-live', 'assertive');
85
+ }
86
+ }
76
87
  _this.setState({
77
88
  showClearTextButton: Boolean(newAltText),
78
89
  validationErrors: validationErrors,
@@ -95,6 +106,7 @@ export var AltTextEditComponent = /*#__PURE__*/function (_React$Component) {
95
106
  });
96
107
  var createAnalyticsEvent = props.createAnalyticsEvent;
97
108
  _this.fireCustomAnalytics = fireAnalyticsEvent(createAnalyticsEvent);
109
+ _this.errorsListRef = /*#__PURE__*/React.createRef();
98
110
  return _this;
99
111
  }
100
112
  _createClass(AltTextEditComponent, [{
@@ -141,6 +153,7 @@ export var AltTextEditComponent = /*#__PURE__*/function (_React$Component) {
141
153
  key: index
142
154
  }, error);
143
155
  });
156
+ var hasErrors = !!errorsList.length;
144
157
  return jsx("div", {
145
158
  css: container
146
159
  }, jsx("section", {
@@ -157,7 +170,7 @@ export var AltTextEditComponent = /*#__PURE__*/function (_React$Component) {
157
170
  })), jsx(PanelTextInput, {
158
171
  testId: "alt-text-input",
159
172
  ariaLabel: formatMessage(messages.placeholder),
160
- describedById: "support-text",
173
+ describedById: "".concat(hasErrors ? 'errors-list' : '', " support-text"),
161
174
  placeholder: formatMessage(messages.placeholder),
162
175
  defaultValue: this.state.lastValue,
163
176
  onCancel: this.dispatchCancelEvent,
@@ -165,6 +178,8 @@ export var AltTextEditComponent = /*#__PURE__*/function (_React$Component) {
165
178
  onBlur: this.handleOnBlur,
166
179
  onSubmit: this.closeMediaAltTextMenu,
167
180
  maxLength: MAX_ALT_TEXT_LENGTH,
181
+ ariaRequired: true,
182
+ ariaInvalid: hasErrors,
168
183
  autoFocus: true
169
184
  }), showClearTextButton && jsx("div", {
170
185
  css: buttonWrapper
@@ -178,7 +193,10 @@ export var AltTextEditComponent = /*#__PURE__*/function (_React$Component) {
178
193
  })),
179
194
  tooltipContent: formatMessage(messages.clear),
180
195
  onClick: this.handleClearText
181
- }))), !!errorsList.length && jsx("section", {
196
+ }))), hasErrors && jsx("section", {
197
+ id: "errors-list",
198
+ ref: this.errorsListRef,
199
+ "aria-live": "assertive",
182
200
  css: validationWrapper
183
201
  }, errorsList), jsx("p", {
184
202
  css: supportText,
@@ -4,6 +4,7 @@ export declare class MediaNodeUpdater {
4
4
  static mockReset(): void;
5
5
  constructor();
6
6
  static setMock(thisKey: string, value: any): void;
7
+ setProps(): void;
7
8
  updateContextId(): Promise<void>;
8
9
  updateNodeContextId(): Promise<void>;
9
10
  getAttrs(): void;
@@ -0,0 +1,2 @@
1
+ import type { MediaAttributes } from '@atlaskit/adf-schema';
2
+ export declare const hasPrivateAttrsChanged: (currentAttrs: MediaAttributes, newAttrs: Partial<MediaAttributes>) => boolean;
@@ -22,6 +22,7 @@ export interface MediaNodeUpdaterProps {
22
22
  export declare class MediaNodeUpdater {
23
23
  props: MediaNodeUpdaterProps;
24
24
  constructor(props: MediaNodeUpdaterProps);
25
+ setProps(newComponentProps: Partial<MediaNodeUpdaterProps>): void;
25
26
  isMediaBlobUrl(): boolean;
26
27
  updateContextId: () => Promise<void>;
27
28
  updateNodeContextId: (getPos: ProsemirrorGetPosHandler) => Promise<void>;
@@ -1,6 +1,6 @@
1
1
  /** @jsx jsx */
2
2
  import type { MouseEvent } from 'react';
3
- import React, { Component } from 'react';
3
+ import React, { PureComponent } from 'react';
4
4
  import { jsx } from '@emotion/react';
5
5
  import type { RichMediaLayout as MediaSingleLayout } from '@atlaskit/adf-schema';
6
6
  import type { DispatchAnalyticsEvent } from '@atlaskit/editor-common/analytics';
@@ -25,13 +25,14 @@ export interface MediaSingleNodeState {
25
25
  contextIdentifierProvider?: ContextIdentifierProvider;
26
26
  isCopying: boolean;
27
27
  }
28
- export default class MediaSingleNode extends Component<MediaSingleNodeProps, MediaSingleNodeState> {
28
+ export default class MediaSingleNode extends PureComponent<MediaSingleNodeProps, MediaSingleNodeState> {
29
29
  static defaultProps: Partial<MediaSingleNodeProps>;
30
30
  static displayName: string;
31
+ mediaNodeUpdater: MediaNodeUpdater | null;
31
32
  state: MediaSingleNodeState;
32
33
  mediaSingleWrapperRef: React.RefObject<HTMLDivElement>;
33
34
  captionPlaceHolderRef: React.RefObject<HTMLSpanElement>;
34
- createMediaNodeUpdater: (props: MediaSingleNodeProps) => MediaNodeUpdater;
35
+ createOrUpdateMediaNodeUpdater: (props: MediaSingleNodeProps) => void;
35
36
  UNSAFE_componentWillReceiveProps(nextProps: MediaSingleNodeProps): void;
36
37
  setViewMediaClientConfig: (props: MediaSingleNodeProps) => Promise<void>;
37
38
  updateMediaNodeAttributes: (props: MediaSingleNodeProps) => Promise<void>;
@@ -18,6 +18,7 @@ export type AltTextEditComponentState = {
18
18
  };
19
19
  export declare class AltTextEditComponent extends React.Component<Props, AltTextEditComponentState> {
20
20
  private fireCustomAnalytics?;
21
+ private errorsListRef;
21
22
  state: {
22
23
  showClearTextButton: boolean;
23
24
  validationErrors: string[];
@@ -4,6 +4,7 @@ export declare class MediaNodeUpdater {
4
4
  static mockReset(): void;
5
5
  constructor();
6
6
  static setMock(thisKey: string, value: any): void;
7
+ setProps(): void;
7
8
  updateContextId(): Promise<void>;
8
9
  updateNodeContextId(): Promise<void>;
9
10
  getAttrs(): void;
@@ -0,0 +1,2 @@
1
+ import type { MediaAttributes } from '@atlaskit/adf-schema';
2
+ export declare const hasPrivateAttrsChanged: (currentAttrs: MediaAttributes, newAttrs: Partial<MediaAttributes>) => boolean;
@@ -22,6 +22,7 @@ export interface MediaNodeUpdaterProps {
22
22
  export declare class MediaNodeUpdater {
23
23
  props: MediaNodeUpdaterProps;
24
24
  constructor(props: MediaNodeUpdaterProps);
25
+ setProps(newComponentProps: Partial<MediaNodeUpdaterProps>): void;
25
26
  isMediaBlobUrl(): boolean;
26
27
  updateContextId: () => Promise<void>;
27
28
  updateNodeContextId: (getPos: ProsemirrorGetPosHandler) => Promise<void>;
@@ -1,6 +1,6 @@
1
1
  /** @jsx jsx */
2
2
  import type { MouseEvent } from 'react';
3
- import React, { Component } from 'react';
3
+ import React, { PureComponent } from 'react';
4
4
  import { jsx } from '@emotion/react';
5
5
  import type { RichMediaLayout as MediaSingleLayout } from '@atlaskit/adf-schema';
6
6
  import type { DispatchAnalyticsEvent } from '@atlaskit/editor-common/analytics';
@@ -25,13 +25,14 @@ export interface MediaSingleNodeState {
25
25
  contextIdentifierProvider?: ContextIdentifierProvider;
26
26
  isCopying: boolean;
27
27
  }
28
- export default class MediaSingleNode extends Component<MediaSingleNodeProps, MediaSingleNodeState> {
28
+ export default class MediaSingleNode extends PureComponent<MediaSingleNodeProps, MediaSingleNodeState> {
29
29
  static defaultProps: Partial<MediaSingleNodeProps>;
30
30
  static displayName: string;
31
+ mediaNodeUpdater: MediaNodeUpdater | null;
31
32
  state: MediaSingleNodeState;
32
33
  mediaSingleWrapperRef: React.RefObject<HTMLDivElement>;
33
34
  captionPlaceHolderRef: React.RefObject<HTMLSpanElement>;
34
- createMediaNodeUpdater: (props: MediaSingleNodeProps) => MediaNodeUpdater;
35
+ createOrUpdateMediaNodeUpdater: (props: MediaSingleNodeProps) => void;
35
36
  UNSAFE_componentWillReceiveProps(nextProps: MediaSingleNodeProps): void;
36
37
  setViewMediaClientConfig: (props: MediaSingleNodeProps) => Promise<void>;
37
38
  updateMediaNodeAttributes: (props: MediaSingleNodeProps) => Promise<void>;
@@ -18,6 +18,7 @@ export type AltTextEditComponentState = {
18
18
  };
19
19
  export declare class AltTextEditComponent extends React.Component<Props, AltTextEditComponentState> {
20
20
  private fireCustomAnalytics?;
21
+ private errorsListRef;
21
22
  state: {
22
23
  showClearTextButton: boolean;
23
24
  validationErrors: string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-media",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "description": "Media plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -36,7 +36,7 @@
36
36
  "@atlaskit/analytics-namespaced-context": "^6.7.0",
37
37
  "@atlaskit/analytics-next": "^9.1.0",
38
38
  "@atlaskit/button": "^16.10.0",
39
- "@atlaskit/editor-common": "^76.9.0",
39
+ "@atlaskit/editor-common": "^76.11.0",
40
40
  "@atlaskit/editor-palette": "1.5.1",
41
41
  "@atlaskit/editor-plugin-analytics": "^0.2.0",
42
42
  "@atlaskit/editor-plugin-decorations": "^0.2.0",