@atlaskit/editor-plugin-show-diff 15.1.6 → 15.1.8

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 (37) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/dist/cjs/pm-plugins/calculateDiff/calculateDiffDecorations.js +5 -4
  3. package/dist/cjs/pm-plugins/decorations/colorSchemes/attributions.js +32 -7
  4. package/dist/cjs/pm-plugins/decorations/colorSchemes/factory.js +25 -4
  5. package/dist/cjs/pm-plugins/decorations/createInlineChangedDecoration.js +40 -14
  6. package/dist/cjs/pm-plugins/decorations/utils/wrapBlockNodeView.js +14 -0
  7. package/dist/cjs/pm-plugins/resolveDiffContributors.js +58 -7
  8. package/dist/cjs/ui/ContributorTag/buildContributorTagDom.js +70 -14
  9. package/dist/cjs/ui/ContributorTag/contributorAvatarRenderer.js +8 -2
  10. package/dist/cjs/ui/ContributorTag/contributorTagController.js +169 -150
  11. package/dist/cjs/ui/ContributorTag/contributorTagIcons.js +5 -1
  12. package/dist/es2019/pm-plugins/calculateDiff/calculateDiffDecorations.js +5 -4
  13. package/dist/es2019/pm-plugins/decorations/colorSchemes/attributions.js +17 -0
  14. package/dist/es2019/pm-plugins/decorations/colorSchemes/factory.js +25 -4
  15. package/dist/es2019/pm-plugins/decorations/createInlineChangedDecoration.js +40 -14
  16. package/dist/es2019/pm-plugins/decorations/utils/wrapBlockNodeView.js +14 -0
  17. package/dist/es2019/pm-plugins/resolveDiffContributors.js +57 -4
  18. package/dist/es2019/ui/ContributorTag/buildContributorTagDom.js +69 -13
  19. package/dist/es2019/ui/ContributorTag/contributorAvatarRenderer.js +8 -2
  20. package/dist/es2019/ui/ContributorTag/contributorTagController.js +164 -104
  21. package/dist/es2019/ui/ContributorTag/contributorTagIcons.js +5 -1
  22. package/dist/esm/pm-plugins/calculateDiff/calculateDiffDecorations.js +5 -4
  23. package/dist/esm/pm-plugins/decorations/colorSchemes/attributions.js +32 -7
  24. package/dist/esm/pm-plugins/decorations/colorSchemes/factory.js +25 -4
  25. package/dist/esm/pm-plugins/decorations/createInlineChangedDecoration.js +40 -14
  26. package/dist/esm/pm-plugins/decorations/utils/wrapBlockNodeView.js +14 -0
  27. package/dist/esm/pm-plugins/resolveDiffContributors.js +58 -7
  28. package/dist/esm/ui/ContributorTag/buildContributorTagDom.js +69 -13
  29. package/dist/esm/ui/ContributorTag/contributorAvatarRenderer.js +8 -2
  30. package/dist/esm/ui/ContributorTag/contributorTagController.js +171 -152
  31. package/dist/esm/ui/ContributorTag/contributorTagIcons.js +5 -1
  32. package/dist/types/pm-plugins/decorations/colorSchemes/factory.d.ts +8 -2
  33. package/dist/types/showDiffPluginType.d.ts +2 -2
  34. package/dist/types/ui/ContributorTag/buildContributorTagDom.d.ts +32 -3
  35. package/dist/types/ui/ContributorTag/contributorTagController.d.ts +32 -12
  36. package/dist/types/ui/ContributorTag/contributorTagIcons.d.ts +2 -2
  37. package/package.json +5 -5
@@ -1,9 +1,9 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
- import { bindAll } from 'bind-event-listener';
2
+ import { bind, bindAll } from 'bind-event-listener';
3
3
  import { VanillaTooltip } from '@atlaskit/editor-common/vanilla-tooltip';
4
4
  import { getAccentTokens } from '../../pm-plugins/decorations/colorSchemes/factory';
5
5
  import { colorSchemeRegistry } from '../../pm-plugins/decorations/colorSchemes/schemes';
6
- import { buildContributorTagDom, MIN_TAG_WIDTH } from './buildContributorTagDom';
6
+ import { buildContributorTagDom, CONTRIBUTOR_TAG_REVEALED_ATTRIBUTE, TAG_EXIT_FALLBACK_MS } from './buildContributorTagDom';
7
7
  import { contributorAvatarRenderer } from './contributorAvatarRenderer';
8
8
  import { contributorTagMessages } from './messages';
9
9
  const getContributorName = (contributor, formatMessage) => {
@@ -22,8 +22,8 @@ const getContributorName = (contributor, formatMessage) => {
22
22
  };
23
23
 
24
24
  /**
25
- * Resolves the tag's accent from the same scheme value and token maps the highlight used, so the
26
- * two can never drift.
25
+ * Resolves the tag's accent from the same scheme value the highlight was drawn from, so the colour
26
+ * the two carry can never drift — only the tone does, which is `getAccentTokens`'s to pick.
27
27
  */
28
28
  const getTagAccent = ({
29
29
  colorScheme,
@@ -35,17 +35,66 @@ const getTagAccent = ({
35
35
  return getAccentTokens(accent);
36
36
  };
37
37
 
38
- /** The line the change starts on: topmost of its fragments, leftmost among those. */
39
- const findChangeStartLine = highlights => {
40
- let start;
41
- for (const highlight of highlights) {
42
- for (const rect of highlight.getClientRects()) {
43
- if (!start || rect.top < start.top - 1 || Math.abs(rect.top - start.top) <= 1 && rect.left < start.left) {
44
- start = rect;
38
+ /**
39
+ * Keyboard focus, as a member of `revealSources` alongside the elements the pointer can be over. It
40
+ * has no element of its own to key on — the tag is already in the set under its own hover.
41
+ */
42
+ const FOCUS_SOURCE = Symbol('contributor-tag-focus');
43
+
44
+ /** Moves the tag between the two ends of the fade `contributorTagStyles` declares. */
45
+ const setRevealed = (tag, isVisible) => {
46
+ tag.toggleAttribute(CONTRIBUTOR_TAG_REVEALED_ATTRIBUTE, isVisible);
47
+ };
48
+
49
+ /**
50
+ * Whether flipping the tag's state will actually run a transition on it.
51
+ *
52
+ * Asked of the element rather than of `matchMedia`, because reduced motion and a missing stylesheet
53
+ * both compute to a zero duration — and a transition that never starts fires no `transitionend`, so
54
+ * a tag waiting for one would sit on screen until the backstop timeout.
55
+ *
56
+ * Read only when a tag is being taken down: it resolves style.
57
+ */
58
+ const willTransition = tag => window.getComputedStyle(tag).transitionDuration.split(',').some(duration => parseFloat(duration) > 0);
59
+
60
+ /**
61
+ * Fades a tag that is still on screen out, and releases it once the fade has finished.
62
+ *
63
+ * No controller owns the element by the time this runs — it is released up front so a republished
64
+ * model's tag can fade in over the top — so everything the removal needs is captured here.
65
+ */
66
+ const fadeTagOut = (tag, release) => {
67
+ setRevealed(tag, false);
68
+
69
+ // Nothing to wait for: reduced motion, or no stylesheet to fade it with.
70
+ if (!willTransition(tag)) {
71
+ release();
72
+ return;
73
+ }
74
+ let unbind;
75
+ let isFinished = false;
76
+ const finish = () => {
77
+ var _unbind;
78
+ // Whichever of the two signals arrives first wins; the other must not release twice.
79
+ if (isFinished) {
80
+ return;
81
+ }
82
+ isFinished = true;
83
+ (_unbind = unbind) === null || _unbind === void 0 ? void 0 : _unbind();
84
+ unbind = undefined;
85
+ clearTimeout(fallbackId);
86
+ release();
87
+ };
88
+ unbind = bind(tag, {
89
+ type: 'transitionend',
90
+ // `transitionend` bubbles, and opacity is not the only property that could ever carry one.
91
+ listener: event => {
92
+ if (event.target === tag && event.propertyName === 'opacity') {
93
+ finish();
45
94
  }
46
95
  }
47
- }
48
- return start;
96
+ });
97
+ const fallbackId = setTimeout(finish, TAG_EXIT_FALLBACK_MS);
49
98
  };
50
99
 
51
100
  /** The contributors a tag draws, keyed so a republished model can be recognised as the same pair. */
@@ -71,8 +120,18 @@ export class ContributorTagController {
71
120
  _defineProperty(this, "bindToken", 0);
72
121
  _defineProperty(this, "fullLabel", '');
73
122
  _defineProperty(this, "isDestroyed", false);
74
- _defineProperty(this, "isHighlightHovered", false);
75
- _defineProperty(this, "isHoveredOrFocused", false);
123
+ /** Last visibility written, so a teardown knows whether there is anything on screen to fade. */
124
+ _defineProperty(this, "isVisible", false);
125
+ /**
126
+ * Everything currently asking for the tag to be up, other than the model: each hovered highlight,
127
+ * the tag itself while the pointer is on it, and `FOCUS_SOURCE` while it holds focus.
128
+ *
129
+ * One set rather than independent booleans, which is what let the exit's trailing delay go: a
130
+ * pointer crossing from the highlight onto the tag removes one member and adds another in the same
131
+ * dispatch, so no style is resolved in between and the browser never starts the exit. Booleans
132
+ * could not see each other, so the crossing wrote the tag out and straight back in.
133
+ */
134
+ _defineProperty(this, "revealSources", new Set());
76
135
  _defineProperty(this, "unbindHighlights", []);
77
136
  this.options = options;
78
137
  }
@@ -124,56 +183,96 @@ export class ContributorTagController {
124
183
  this.renderLabels(next);
125
184
  this.applyAccent(next);
126
185
  this.syncHighlightBindings(next);
127
- this.applyStyles();
186
+ this.applyVisibility();
128
187
  this.syncTooltip();
129
188
  }
130
189
 
131
- /** Takes the tag's DOM down, keeping the subscription so a republished model redraws it. */
190
+ /**
191
+ * Takes the tag's DOM down, keeping the subscription so a republished model redraws it.
192
+ *
193
+ * A tag that is still on screen is released here and fades out on its own, so a republished
194
+ * model's tag fades in over the top of the outgoing one — see `fadeTagOut`.
195
+ */
132
196
  teardownTag() {
133
- var _this$unbindTag, _this$tooltip, _this$dom;
197
+ var _this$unbindTag, _this$tooltip;
198
+ // Unbound synchronously, ahead of any fade: a tag on its way out must not be revealed again by
199
+ // the pointer, and these listeners are what hold this controller.
134
200
  this.unbindHighlightState();
135
201
  this.boundSelector = undefined;
136
202
  (_this$unbindTag = this.unbindTag) === null || _this$unbindTag === void 0 ? void 0 : _this$unbindTag.call(this);
137
203
  this.unbindTag = undefined;
204
+ // Destroyed rather than carried through the fade: it is a popover in the top layer.
138
205
  (_this$tooltip = this.tooltip) === null || _this$tooltip === void 0 ? void 0 : _this$tooltip.destroy();
139
206
  this.tooltip = undefined;
140
207
  this.tooltipContent = undefined;
141
- this.avatars.forEach(avatar => avatar.destroy());
208
+ const {
209
+ dom,
210
+ isVisible
211
+ } = this;
212
+ // Destroying an avatar removes its element, so the avatars go with whoever removes the tag —
213
+ // destroyed here and it would fade out empty.
214
+ const {
215
+ avatars
216
+ } = this;
142
217
  this.avatars = [];
143
- (_this$dom = this.dom) === null || _this$dom === void 0 ? void 0 : _this$dom.root.remove();
144
218
  this.dom = undefined;
145
- this.maxWidthPx = undefined;
146
- this.isHoveredOrFocused = false;
147
- // Both hover flags belong to the listeners just unbound: a republished model rebinds them, and
148
- // stale `true` would draw the tag as visible with the pointer nowhere near it.
149
- this.isHighlightHovered = false;
219
+ this.isVisible = false;
220
+ // The set belongs to the listeners just unbound: a stale member would draw the tag as visible
221
+ // with the pointer nowhere near it.
222
+ this.revealSources.clear();
223
+ if (!dom) {
224
+ return;
225
+ }
226
+ const release = () => {
227
+ avatars.forEach(avatar => avatar.destroy());
228
+ dom.root.remove();
229
+ };
230
+
231
+ // Nothing on screen to fade. `isConnected` covers ProseMirror destroying the widget: a fade on
232
+ // a detached element never starts.
233
+ if (!isVisible || !dom.tag.isConnected) {
234
+ release();
235
+ return;
236
+ }
237
+ fadeTagOut(dom.tag, release);
150
238
  }
151
239
  bindTag({
152
240
  tag
153
241
  }) {
154
- const setHoveredOrFocused = isHoveredOrFocused => () => {
155
- this.isHoveredOrFocused = isHoveredOrFocused;
156
- this.applyStyles();
157
- };
242
+ // `mouseover`/`mouseout` rather than the enter/leave pair, because they are what fire as the
243
+ // pointer moves between the tag's own children. Both are keyed on the tag, so those moves add
244
+ // and remove the same member and the set never empties.
158
245
  this.unbindTag = bindAll(tag, [{
159
246
  type: 'mouseover',
160
- listener: setHoveredOrFocused(true)
247
+ listener: this.trackRevealSource(tag, true)
161
248
  }, {
162
249
  type: 'mouseout',
163
- listener: setHoveredOrFocused(false)
250
+ listener: this.trackRevealSource(tag, false)
164
251
  }, {
165
252
  type: 'focus',
166
- listener: setHoveredOrFocused(true)
253
+ listener: this.trackRevealSource(FOCUS_SOURCE, true)
167
254
  }, {
168
255
  type: 'blur',
169
- listener: setHoveredOrFocused(false)
256
+ listener: this.trackRevealSource(FOCUS_SOURCE, false)
170
257
  }]);
171
258
  }
259
+
260
+ /** One listener shape for every reveal source, so all of them land in the same set. */
261
+ trackRevealSource(source, isRevealing) {
262
+ return () => {
263
+ if (isRevealing) {
264
+ this.revealSources.add(source);
265
+ } else {
266
+ this.revealSources.delete(source);
267
+ }
268
+ this.applyVisibility();
269
+ };
270
+ }
172
271
  renderAvatars(model) {
173
- var _this$dom2;
272
+ var _this$dom;
174
273
  const {
175
274
  avatars: container
176
- } = (_this$dom2 = this.dom) !== null && _this$dom2 !== void 0 ? _this$dom2 : {};
275
+ } = (_this$dom = this.dom) !== null && _this$dom !== void 0 ? _this$dom : {};
177
276
  if (!container) {
178
277
  return;
179
278
  }
@@ -231,8 +330,8 @@ export class ContributorTagController {
231
330
  const agentName = isPrimaryAgent ? primaryName : secondaryName;
232
331
  const userName = isPrimaryAgent ? secondaryName : primaryName;
233
332
 
234
- // A connected pair only ever shows the agent; the pair is spelled out in the label, because
235
- // the tag is never wider than the highlight.
333
+ // A connected pair only ever shows the agent; the pair is spelled out in the label, because the
334
+ // tag is capped at `MAX_TAG_WIDTH` and two names rarely fit inside it.
236
335
  this.dom.name.textContent = model.connectedContributor ? agentName || primaryName : primaryName;
237
336
  this.fullLabel = model.connectedContributor ? formatMessage(contributorTagMessages.changedByConnected, {
238
337
  agentName,
@@ -256,30 +355,30 @@ export class ContributorTagController {
256
355
  }
257
356
  const accent = this.accentOf(model);
258
357
  this.dom.tag.style.setProperty('background-color', accent.background);
259
- this.dom.tag.style.setProperty('border-bottom-color', accent.border);
260
358
  this.dom.name.style.setProperty('color', accent.text);
261
359
  this.avatars.forEach(avatar => avatar.setRingColor(accent.background));
262
360
  }
263
361
 
264
- /** Width clamp and visibility — the two things that are not carried by the model alone. */
265
- applyStyles() {
362
+ /**
363
+ * Visibility — the one thing the model does not carry on its own, because hover is not in it.
364
+ *
365
+ * One idempotent attribute write, with nothing to guard: whether the flip is a real one, whether
366
+ * the tag is attached yet, and whether motion is wanted at all are all the stylesheet's to answer.
367
+ */
368
+ applyVisibility() {
266
369
  var _this$model;
267
370
  if (!this.dom) {
268
371
  return;
269
372
  }
270
- this.dom.root.style.setProperty('max-width',
271
- // Clamped to the highlight's own width, floored at `MIN_TAG_WIDTH` so a one-character
272
- // change still shows an avatar.
273
- this.maxWidthPx === undefined ? '' : `max(${Math.round(this.maxWidthPx)}px, ${MIN_TAG_WIDTH})`);
274
- const isVisible = Boolean((_this$model = this.model) === null || _this$model === void 0 ? void 0 : _this$model.isActive) || this.isHighlightHovered || this.isHoveredOrFocused;
275
- this.dom.tag.style.setProperty('opacity', isVisible ? '1' : '0');
276
- this.dom.tag.style.setProperty('pointer-events', isVisible ? 'auto' : 'none');
373
+ const isVisible = Boolean((_this$model = this.model) === null || _this$model === void 0 ? void 0 : _this$model.isActive) || this.revealSources.size > 0;
374
+ this.isVisible = isVisible;
375
+ setRevealed(this.dom.tag, isVisible);
277
376
  }
278
377
 
279
378
  /**
280
- * The full label is always the tooltip, whether or not the name it draws is clipped: the tag is
281
- * never wider than the highlight, so a name that fits at one width is ellipsised at the next, and
282
- * a connected pair spells out a contributor the tag does not show at any width.
379
+ * The full label is always the tooltip, whether or not the name it draws is clipped: a name longer
380
+ * than `MAX_TAG_WIDTH` is ellipsised, and a connected pair spells out a contributor the tag does
381
+ * not show at any width.
283
382
  *
284
383
  * `VanillaTooltip` has no "set new content" call, so it is rebuilt when the label changes — the
285
384
  * shape of `syncVanillaDisabledTooltip` in `mentionNodeView`.
@@ -308,9 +407,9 @@ export class ContributorTagController {
308
407
  }
309
408
 
310
409
  /**
311
- * Observes the highlight this tag describes, for what CSS here cannot see: its hover state (the
410
+ * Observes the highlight this tag describes, for what CSS here cannot see: its hover state. The
312
411
  * highlight is a ProseMirror decoration in a different DOM subtree, so listeners are bound
313
- * imperatively via `data-diff-id`) and the width the tag is clamped to.
412
+ * imperatively via `data-diff-id`.
314
413
  *
315
414
  * Deliberately not routed through plugin state: a transaction per pointer move would re-run the
316
415
  * memoised decoration calculation.
@@ -327,8 +426,8 @@ export class ContributorTagController {
327
426
  this.boundSelector = selector;
328
427
  this.unbindHighlightState();
329
428
 
330
- // ProseMirror draws the tag's host in the same pass as the highlights the tag measures, so
331
- // those may not be in the document yet. A microtask lands once that pass has finished.
429
+ // ProseMirror draws the tag's host in the same pass as the highlights it binds to, so those may
430
+ // not be in the document yet. A microtask lands once that pass has finished.
332
431
  const pendingBind = ++this.bindToken;
333
432
  queueMicrotask(() => {
334
433
  if (!this.isDestroyed && pendingBind === this.bindToken) {
@@ -337,66 +436,27 @@ export class ContributorTagController {
337
436
  });
338
437
  }
339
438
  bindHighlightState(selector) {
340
- // Both queries are scoped to this editor's content root, so a tag can only ever bind to the
341
- // decorations its own plugin instance rendered — another editor on the page, or one nested
342
- // inside this one, cannot supply the geometry.
343
- const editorRoot = this.options.getEditorRoot();
344
- const highlights = editorRoot === null || editorRoot === void 0 ? void 0 : editorRoot.querySelectorAll(selector);
345
- if (!editorRoot || !(highlights !== null && highlights !== void 0 && highlights.length)) {
439
+ var _this$options$getEdit;
440
+ // Scoped to this editor's content root, so a tag can only ever bind to the decorations its own
441
+ // plugin instance rendered — another editor on the page, or one nested inside this one, cannot
442
+ // reveal this tag.
443
+ const highlights = (_this$options$getEdit = this.options.getEditorRoot()) === null || _this$options$getEdit === void 0 ? void 0 : _this$options$getEdit.querySelectorAll(selector);
444
+ if (!(highlights !== null && highlights !== void 0 && highlights.length)) {
346
445
  return;
347
446
  }
348
447
 
349
- // The width comes from this tag's OWN decoration; sibling halves of the change are here for
350
- // hover only.
351
- const ownElements = Array.from(editorRoot.querySelectorAll(`[data-diff-id="${this.options.diffId}"]`));
352
- const measure = () => {
353
- var _findChangeStartLine;
354
- // Counted by distinct line box, not by client rect: an inline node inside the change (a
355
- // mention, emoji, date) splits the highlight into several rects on the same line, and
356
- // treating that as a wrap would drop the width clamp on changes that never wrapped.
357
- const lineTops = new Set();
358
- for (const highlight of ownElements) {
359
- for (const rect of highlight.getClientRects()) {
360
- lineTops.add(Math.round(rect.top));
361
- }
362
- }
363
-
364
- // A wrapped change's first line says nothing useful about how wide the tag may be.
365
- this.maxWidthPx = lineTops.size > 1 ? undefined : (_findChangeStartLine = findChangeStartLine(ownElements)) === null || _findChangeStartLine === void 0 ? void 0 : _findChangeStartLine.width;
366
- this.applyStyles();
367
- };
368
- measure();
369
-
370
- // An inline element cannot be observed itself (ResizeObserver skips boxes with no principal
371
- // box), so the block it sits in is observed instead.
372
- this.resizeObserver = new ResizeObserver(measure);
373
- for (const parent of new Set(ownElements.map(({
374
- parentElement
375
- }) => parentElement))) {
376
- if (parent) {
377
- this.resizeObserver.observe(parent);
378
- }
379
- }
380
-
381
- // `bindAll` takes a single target, and a change can render more than one highlight.
448
+ // `bindAll` takes a single target, and a change can render more than one highlight. Each is its
449
+ // own member of `revealSources`, so the pointer can cross between them without the set
450
+ // emptying.
382
451
  this.unbindHighlights = Array.from(highlights).map(highlight => bindAll(highlight, [{
383
452
  type: 'mouseenter',
384
- listener: () => {
385
- this.isHighlightHovered = true;
386
- this.applyStyles();
387
- }
453
+ listener: this.trackRevealSource(highlight, true)
388
454
  }, {
389
455
  type: 'mouseleave',
390
- listener: () => {
391
- this.isHighlightHovered = false;
392
- this.applyStyles();
393
- }
456
+ listener: this.trackRevealSource(highlight, false)
394
457
  }]));
395
458
  }
396
459
  unbindHighlightState() {
397
- var _this$resizeObserver;
398
- (_this$resizeObserver = this.resizeObserver) === null || _this$resizeObserver === void 0 ? void 0 : _this$resizeObserver.disconnect();
399
- this.resizeObserver = undefined;
400
460
  this.unbindHighlights.forEach(unbind => unbind());
401
461
  this.unbindHighlights = [];
402
462
  }
@@ -2,7 +2,7 @@
2
2
  * The icons a contributor tag can draw, as inline SVG.
3
3
  *
4
4
  * Neither `@atlaskit/icon`, `@atlaskit/icon-lab` nor `@atlaskit/logo` exposes a raw-SVG entry
5
- * point, so the markup is copied here rather than kept as a React island for three glyphs. Keep in
5
+ * point, so the markup is copied here rather than kept as a React island for these glyphs. Keep in
6
6
  * sync with the sources named on each constant.
7
7
  *
8
8
  * The Rovo hex is the `brand` appearance, whose `--rovo-*-color` variables resolve to `initial` in
@@ -17,8 +17,12 @@ const AI_BOT_ICON_SVG = `<svg width="12" height="12" fill="none" viewBox="0 0 16
17
17
 
18
18
  /** `RovoHexIcon` from `@atlaskit/logo/rovo-hex/icon`, at the 16px `xxsmall` renders. */
19
19
  const ROVO_HEX_ICON_SVG = `<svg width="16" height="16" viewBox="0 0 24 24" role="presentation" style="display:block"><path fill="#1868db" fill-rule="evenodd" d="m13.227 16.789-7.154 4.169-3.403-1.96a2.72 2.72 0 0 1-1.362-2.355V7.366c0-.973.518-1.87 1.361-2.354l6.044-3.487-.038.114a3.6 3.6 0 0 0-.172 1.088v9.278c0 1.274.68 2.45 1.786 3.085z" clip-rule="evenodd"/><path fill="#6a9a23" fill-rule="evenodd" d="m11.296 15.671-7.193 4.153 6.607 3.812a2.73 2.73 0 0 0 2.676.026l.048-.033a2.73 2.73 0 0 0 1.232-1.525 2.7 2.7 0 0 0 .127-.822v-3.594z" clip-rule="evenodd"/><path fill="#af59e1" fill-rule="evenodd" d="m21.477 5.003-4.404-2.539-5.904 4.89 2.69 1.556a3.56 3.56 0 0 1 1.785 3.086v9.277a3.6 3.6 0 0 1-.21 1.202l6.044-3.486a2.71 2.71 0 0 0 1.362-2.355V7.357c0-.97-.521-1.87-1.363-2.354" clip-rule="evenodd"/><path fill="#fca700" fill-rule="evenodd" d="M12.74 8.266 9.353 6.312V2.718c0-.283.044-.56.127-.821A2.73 2.73 0 0 1 10.71.372V.37a.4.4 0 0 0 .048-.033 2.73 2.73 0 0 1 2.676.026l6.499 3.75z" clip-rule="evenodd"/></svg>`;
20
+
21
+ /** ClaudeLogo from ai-mate/studio-browse-kit, inset to fit the agent hexagon. */
22
+ const CLAUDE_ICON_SVG = `<svg width="12" height="12" viewBox="8 8 32 32" role="presentation" style="display:block"><path fill="#FFFFFF" d="M14.2785 29.274L20.5732 25.7445L20.6785 25.4369L20.5732 25.2669H20.2653L19.2122 25.2021L15.6152 25.105L12.4962 24.9755L9.47443 24.8136L8.71291 24.6517L8 23.7126L8.07291 23.2431L8.71291 22.8141L9.62835 22.895L11.6537 23.0326L14.6916 23.2431L16.8952 23.3726L20.16 23.7126H20.6785L20.7514 23.5022L20.5732 23.3726L20.4354 23.2431L17.2922 21.1141L13.8896 18.8636L12.1073 17.5684L11.1433 16.9127L10.6572 16.2975L10.4466 14.9537L11.3215 13.9904L12.4962 14.0713L12.7959 14.1523L13.9868 15.067L16.5306 17.0342L19.8522 19.4789L20.3382 19.8836L20.5327 19.746L20.557 19.6489L20.3382 19.2846L18.5316 16.0223L16.6035 12.7033L15.7448 11.3271L15.518 10.5014C15.437 10.1614 15.3803 9.87807 15.3803 9.52998L16.3767 8.17809L16.9276 8L18.2562 8.17809L18.8152 8.6638L19.6415 10.55L20.9782 13.5209L23.0522 17.5603L23.6597 18.7584L23.9838 19.8674L24.1053 20.2074H24.3159V20.0132L24.4861 17.7384L24.802 14.9456L25.1099 11.3514L25.2152 10.3395L25.7175 9.12522L26.7139 8.46952L27.4916 8.84189L28.1316 9.75664L28.0425 10.3476L27.6618 12.8166L26.9165 16.6861L26.4304 19.2765H26.7139L27.038 18.9527L28.3504 17.2122L30.5539 14.4599L31.5261 13.3671L32.6603 12.1609L33.3894 11.5861H34.7666L35.7792 13.0918L35.3256 14.6461L33.9079 16.4432L32.7332 17.9651L31.0481 20.2317L29.9949 22.045L30.0922 22.1907L30.3433 22.1665L34.1509 21.3569L36.2086 20.9846L38.6633 20.5636L39.7732 21.0817L39.8947 21.6079L39.4572 22.6845L36.8324 23.3322L33.7539 23.9474L29.1686 25.0321L29.1119 25.0726L29.1767 25.1536L31.2425 25.3478L32.1256 25.3964H34.2886L38.3149 25.6959L39.3681 26.3921L40 27.2421L39.8947 27.8897L38.2744 28.7154L36.0871 28.1973L30.9833 26.9831L29.2334 26.5459H28.9904V26.6916L30.4486 28.1164L33.122 30.5287L36.4678 33.6372L36.638 34.4063L36.2086 35.0134L35.7549 34.9486L32.8142 32.7387L31.68 31.743L29.1119 29.5816H28.9418V29.8082L29.5332 30.6744L32.6603 35.3696L32.8223 36.8105L32.5954 37.28L31.7853 37.5634L30.8942 37.4015L29.0633 34.8353L27.1757 31.9454L25.6527 29.3549L25.4663 29.4602L24.5671 39.1338L24.1458 39.6276L23.1737 40L22.3635 39.3848L21.9342 38.3891L22.3635 36.422L22.882 33.8558L23.3033 31.8158L23.6841 29.2821L23.9109 28.4402L23.8947 28.3835L23.7084 28.4078L21.7965 31.0306L18.8881 34.9567L16.5873 37.4177L16.0365 37.6362L15.0805 37.1424L15.1696 36.2601L15.7043 35.4748L18.8881 31.4273L20.8081 28.9178L22.0476 27.4688L22.0395 27.2583H21.9666L13.5089 32.7468L12.002 32.9411L11.3539 32.3339L11.4349 31.3382L11.7428 31.0144L14.2866 29.2659L14.2785 29.274Z"/></svg>`;
20
23
  const markup = {
21
24
  aiBot: AI_BOT_ICON_SVG,
25
+ claude: CLAUDE_ICON_SVG,
22
26
  person: PERSON_ICON_SVG,
23
27
  rovoHex: ROVO_HEX_ICON_SVG
24
28
  };
@@ -14,7 +14,7 @@ import { Mapping } from '@atlaskit/editor-prosemirror/transform';
14
14
  import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
15
15
  import { UNSAFE_expValNoExposure } from '@atlaskit/platform-feature-experiments/unsafe-exp-val-no-exposure';
16
16
  import { fg } from '@atlaskit/platform-feature-flags/fg';
17
- import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
17
+ import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
18
18
  import { areDocsEqualByBlockStructureAndText } from '../areDocsEqualByBlockStructureAndText';
19
19
  import { createDocMarginAnchorWidget } from '../decorations/createAnchorDecorationWidgets';
20
20
  import { createBlockChangedDecoration } from '../decorations/createBlockChangedDecoration';
@@ -514,8 +514,9 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
514
514
  // gets highlighted, and structural gaps never do.
515
515
  var isSmartNodeLevel = diffType === 'smart' && fg('platform_editor_ai_smart_diff') && smartChangeLevel(change) === 'node';
516
516
  // For a large inserted table, skip the per-cell inline decorations that
517
- // freeze the browser on re-render. Only active in the AIFC experience.
518
- var useCoarseTableDecoration = isSmartNodeLevel && expValEquals('platform_editor_ai_new_aifc_editor_experience', 'isBackendReviewMomentEnabled', true) && isLargeInsertedTableRange(tr.doc, change.fromB, change.toB);
517
+ // freeze the browser on re-render. Only active when Review moment is
518
+ // eligible (xstate + M1).
519
+ var useCoarseTableDecoration = isSmartNodeLevel && expValEqualsNoExposure('platform_editor_ai_xstate_migration', 'isEnabled', true) && UNSAFE_expValNoExposure('platform_editor_ai_streaming_ux_experience_m1', 'isEnabled', false) === true && isLargeInsertedTableRange(tr.doc, change.fromB, change.toB);
519
520
  // Whether the deleted-content widget will actually be rendered for this
520
521
  // change. Used to decide if indicator anchor positions should be adjusted
521
522
  // inward — when the widget is present the anchor must stay at the block
@@ -714,7 +715,7 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
714
715
  })));
715
716
  // If the original node position is known (e.g. a panel type change), also render
716
717
  // the old block node as a "deleted" widget so the reviewer sees the before/after.
717
- var isSmartNodeLevelAttrChange = diffType === 'smart' && fg('platform_editor_ai_smart_diff') && expValEquals('platform_editor_ai_new_aifc_editor_experience', 'isEnabled', true);
718
+ var isSmartNodeLevelAttrChange = diffType === 'smart' && fg('platform_editor_ai_smart_diff') && expValEqualsNoExposure('platform_editor_ai_xstate_migration', 'isEnabled', true) && UNSAFE_expValNoExposure('platform_editor_ai_streaming_ux_experience_m1', 'isEnabled', false) === true;
718
719
  if (isSmartNodeLevelAttrChange && change.fromA !== undefined && change.toA !== undefined) {
719
720
  var placeBelow = deletedDiffPlacement === 'bottom';
720
721
  decorations.push.apply(decorations, _toConsumableArray(createNodeChangedDecorationWidget(_objectSpread(_objectSpread({
@@ -126,29 +126,54 @@ export var isContributorTagsEnabled = function isContributorTagsEnabled(stepAttr
126
126
  export var createAttributionColorMap = function createAttributionColorMap(stepAttributions) {
127
127
  var colors = new Map();
128
128
  var allocatedColors = new Set();
129
+
130
+ // Reserve shared brand colours before hashing other actors, regardless of step order.
129
131
  var _iterator2 = _createForOfIteratorHelper(stepAttributions),
130
132
  _step2;
131
133
  try {
132
134
  for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
133
135
  var attribution = _step2.value;
134
136
  var identity = getAttributionIdentity(attribution);
135
- if (!identity || colors.has(identity.key)) {
137
+ if (!identity) {
136
138
  continue;
137
139
  }
138
- var _getParticipantColor = getParticipantColor(identity.colorSeed),
139
- index = _getParticipantColor.index;
140
- var hashedColor = HASHED_PARTICIPANT_COLOR_SCHEMES[index];
140
+ var _getParticipantColor = getParticipantColor(identity.colorSeed, attribution === null || attribution === void 0 ? void 0 : attribution.agentType),
141
+ index = _getParticipantColor.index,
142
+ isFixed = _getParticipantColor.isFixed;
143
+ var color = HASHED_PARTICIPANT_COLOR_SCHEMES[index];
144
+ if (isFixed && color) {
145
+ colors.set(identity.key, color);
146
+ allocatedColors.add(color);
147
+ }
148
+ }
149
+ } catch (err) {
150
+ _iterator2.e(err);
151
+ } finally {
152
+ _iterator2.f();
153
+ }
154
+ var _iterator3 = _createForOfIteratorHelper(stepAttributions),
155
+ _step3;
156
+ try {
157
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
158
+ var _attribution = _step3.value;
159
+ var _identity = getAttributionIdentity(_attribution);
160
+ if (!_identity || colors.has(_identity.key)) {
161
+ continue;
162
+ }
163
+ var _getParticipantColor2 = getParticipantColor(_identity.colorSeed),
164
+ _index = _getParticipantColor2.index;
165
+ var hashedColor = HASHED_PARTICIPANT_COLOR_SCHEMES[_index];
141
166
  if (!hashedColor) {
142
167
  continue;
143
168
  }
144
169
  var participantColorScheme = getAvailableColor(hashedColor, allocatedColors);
145
- colors.set(identity.key, participantColorScheme);
170
+ colors.set(_identity.key, participantColorScheme);
146
171
  allocatedColors.add(participantColorScheme);
147
172
  }
148
173
  } catch (err) {
149
- _iterator2.e(err);
174
+ _iterator3.e(err);
150
175
  } finally {
151
- _iterator2.f();
176
+ _iterator3.f();
152
177
  }
153
178
  return colors;
154
179
  };
@@ -65,6 +65,18 @@ var bgSubtlerPressedMap = {
65
65
  gray: "var(--ds-background-accent-gray-subtler-pressed, #8C8F97)",
66
66
  lime: "var(--ds-background-accent-lime-subtler-pressed, #B3DF72)"
67
67
  };
68
+ var bgBolderMap = {
69
+ green: "var(--ds-background-accent-green-bolder, #1F845A)",
70
+ teal: "var(--ds-background-accent-teal-bolder, #227D9B)",
71
+ blue: "var(--ds-background-accent-blue-bolder, #1868DB)",
72
+ purple: "var(--ds-background-accent-purple-bolder, #964AC0)",
73
+ red: "var(--ds-background-accent-red-bolder, #C9372C)",
74
+ orange: "var(--ds-background-accent-orange-bolder, #BD5B00)",
75
+ yellow: "var(--ds-background-accent-yellow-bolder, #946F00)",
76
+ magenta: "var(--ds-background-accent-magenta-bolder, #AE4787)",
77
+ gray: "var(--ds-background-accent-gray-bolder, #6B6E76)",
78
+ lime: "var(--ds-background-accent-lime-bolder, #5B7F24)"
79
+ };
68
80
  var borderAccentMap = {
69
81
  green: "var(--ds-border-accent-green, #22A06B)",
70
82
  teal: "var(--ds-border-accent-teal, #2898BD)",
@@ -98,6 +110,9 @@ function bgSubtlestPressed(color) {
98
110
  function bgSubtler(color) {
99
111
  return bgSubtlerMap[color];
100
112
  }
113
+ function bgBolder(color) {
114
+ return bgBolderMap[color];
115
+ }
101
116
  function bgSubtlerPressed(color) {
102
117
  return bgSubtlerPressedMap[color];
103
118
  }
@@ -108,12 +123,18 @@ function textAccent(color) {
108
123
  return textAccentMap[color];
109
124
  }
110
125
 
111
- /** Presentation tokens for one diff colour, shared by highlights and contributor tags. */
126
+ /**
127
+ * Presentation tokens for a contributor tag in one diff colour.
128
+ *
129
+ * A `bolder` fill rather than the `subtlest` tint the highlight uses: the tag is a label on the
130
+ * change, and at tag size a tint reads as a second, weaker highlight sitting above the real one.
131
+ * `inverse` is the only text tone that reads on a bolder fill, and it needs no per-colour map —
132
+ * which is also why the tag carries no border: the fill alone bounds it.
133
+ */
112
134
  export function getAccentTokens(color) {
113
135
  return {
114
- background: bgSubtlest(color),
115
- border: borderAccent(color),
116
- text: textAccent(color)
136
+ background: bgBolder(color),
137
+ text: "var(--ds-text-inverse, #FFFFFF)"
117
138
  };
118
139
  }
119
140
 
@@ -5,6 +5,7 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
5
5
  import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
6
6
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
7
7
  import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
8
+ import { CONTRIBUTOR_TAG_Z_INDEX } from '../../ui/ContributorTag/buildContributorTagDom';
8
9
  import { isExtendedEnabled } from '../isExtendedEnabled';
9
10
  import { hasVisibleContent } from '../utils/hasVisibleContent';
10
11
  import { buildAtomicInlineChangedCSSVariables, buildDeletedInlineContentStyleExtended, buildDeletedInlineStyle, buildDeletedInlineStyleStandard, buildInsertStyle, buildInsertStyleActive, buildInsertStyleExtended, buildInsertStyleExtendedActive, buildInsertStyleExtendedNoUnderline, buildInsertStyleExtendedNoUnderlineActive } from './colorSchemes/factory';
@@ -17,6 +18,25 @@ import { REVEAL_ATTR, resolveRevealStyle } from './revealStyles';
17
18
  var displayNoneStyle = convertToInlineCss({
18
19
  display: 'none'
19
20
  });
21
+
22
+ /**
23
+ * Stacks the change's highlight below its contributor tag, so no highlight can slice through a tag:
24
+ * a tag overlays the line *above* its own change, and a highlight already on that line — the tail of
25
+ * this change, or another change entirely — would otherwise paint straight over it.
26
+ *
27
+ * The tag still reads as attached to the change rather than laid on top of it, because it cannot
28
+ * paint over this highlight at all: its bottom edge is pinned to the highlight's top edge and
29
+ * clipped there. See `CONTRIBUTOR_TAG_Z_INDEX`.
30
+ *
31
+ * prosemirror-view places the tag's host widget as a *sibling* of this decoration's span, so
32
+ * painting order is decided by stacking level — and the host is absolutely positioned at
33
+ * `CONTRIBUTOR_TAG_Z_INDEX`. `position: relative` is what makes `z-index` apply to an otherwise
34
+ * static inline box; it adds no offset.
35
+ */
36
+ var stackBelowContributorTagStyle = convertToInlineCss({
37
+ position: 'relative',
38
+ zIndex: CONTRIBUTOR_TAG_Z_INDEX - 1
39
+ });
20
40
  var getColorScheme = function getColorScheme(colorScheme) {
21
41
  return colorSchemeRegistry[colorScheme !== null && colorScheme !== void 0 ? colorScheme : 'standard'];
22
42
  };
@@ -176,8 +196,23 @@ export var createInlineChangedDecoration = function createInlineChangedDecoratio
176
196
  });
177
197
  var isAtomicInlineInsertion = isAtomicInlineNode && isInserted;
178
198
  var atomicInlineAttrs = isAtomicInlineInsertion ? resolveAtomicInlineAttrs(inlineNodeName, colorScheme, colors) : undefined;
199
+
200
+ // Built from the raw change range, not the indicator anchors resolved below: a tag lines up with
201
+ // the changed text, while the bar's anchors are held on a block boundary when a deleted widget
202
+ // renders beside them.
203
+ //
204
+ // Built up here, ahead of where it is pushed, so the highlight's stacking below keys off whether a
205
+ // tag exists rather than re-deriving the condition and risking disagreement.
206
+ var tagWidget = canTagChange && doc ? createContributorTagWidget({
207
+ doc: doc,
208
+ from: change.fromB,
209
+ to: change.toB,
210
+ diffId: diffId,
211
+ mountContext: tagMountContext
212
+ }) : undefined;
213
+ var inlineStyle = [style, atomicInlineAttrs === null || atomicInlineAttrs === void 0 ? void 0 : atomicInlineAttrs.styleSuffix, tagWidget ? stackBelowContributorTagStyle : undefined].filter(Boolean).join('');
179
214
  var decorations = [Decoration.inline(change.fromB, change.toB, _objectSpread(_objectSpread(_objectSpread({
180
- style: atomicInlineAttrs ? "".concat(style).concat(atomicInlineAttrs.styleSuffix) : style
215
+ style: inlineStyle
181
216
  }, atomicInlineAttrs && {
182
217
  class: atomicInlineAttrs.className
183
218
  }), revealed && _defineProperty({}, REVEAL_ATTR, revealed.role)), {}, {
@@ -228,19 +263,10 @@ export var createInlineChangedDecoration = function createInlineChangedDecoratio
228
263
  })));
229
264
  }
230
265
 
231
- // Built from the raw change range, not the anchors above: a tag lines up with the changed text,
232
- // while the bar's anchors are held on a block boundary when a deleted widget renders beside them.
233
- if (canTagChange && doc) {
234
- var tagWidget = createContributorTagWidget({
235
- doc: doc,
236
- from: change.fromB,
237
- to: change.toB,
238
- diffId: diffId,
239
- mountContext: tagMountContext
240
- });
241
- if (tagWidget) {
242
- decorations.push(tagWidget);
243
- }
266
+ // Pushed after the indicator anchors so the decoration order is unchanged — see `tagWidget` above
267
+ // for why it is built before them.
268
+ if (tagWidget) {
269
+ decorations.push(tagWidget);
244
270
  }
245
271
  return decorations;
246
272
  };