@atlaskit/editor-plugin-show-diff 10.2.0 → 10.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # @atlaskit/editor-plugin-show-diff
2
2
 
3
+ ## 10.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`21147179a56ea`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/21147179a56ea) -
8
+ [CCI-17809] Overhaul Post Stream Review segmenting.
9
+
10
+ `@atlaskit/editor-plugin-show-diff`: add a generic `computeDiffChanges` utility (new
11
+ `@atlaskit/editor-plugin-show-diff/calculate-diff` entry point) that returns the classified
12
+ `Change[]` and reconstructed new document for a given `originalDoc` + `steps`, without rendering
13
+ decorations. It reuses the same classifier the diff overlay renders from (default
14
+ `diffType: 'smart'`), so consumers can derive their own reviewable segments that line up with what
15
+ the overlay would draw. Exposes the `ComputeDiffChangesParams` type.
16
+
17
+ `@atlassian/editor-plugin-ai`: support two segmenting strategies in the Post Stream Review
18
+ step-through, selected by the new `platform_editor_ai_diff_based_segmenting` gate. Gate ON =
19
+ diff-based (`smart`) segmenting (via the show-diff `computeDiffChanges` utility, shaped into
20
+ reviewable segments in the AI plugin); gate OFF (default) = top-level-node segmenting, which no
21
+ longer splits a list into individual list items. Fine segments are now stored in plugin state and
22
+ remapped on every doc change, so per-segment undo/redo and view-changes stay correct after an
23
+ undo. Aggregate ("all changes") mode is unchanged.
24
+
25
+ ### Patch Changes
26
+
27
+ - Updated dependencies
28
+
3
29
  ## 10.2.0
4
30
 
5
31
  ### Minor Changes
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "@atlaskit/editor-plugin-show-diff/calculate-diff",
3
+ "main": "../dist/cjs/entry-points/calculate-diff.js",
4
+ "module": "../dist/esm/entry-points/calculate-diff.js",
5
+ "module:es2019": "../dist/es2019/entry-points/calculate-diff.js",
6
+ "sideEffects": false,
7
+ "types": "../dist/types/entry-points/calculate-diff.d.ts"
8
+ }
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "computeDiffChanges", {
7
+ enumerable: true,
8
+ get: function get() {
9
+ return _computeDiffChanges.computeDiffChanges;
10
+ }
11
+ });
12
+ var _computeDiffChanges = require("../pm-plugins/calculateDiff/computeDiffChanges");
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.computeDiffChanges = void 0;
7
+ var _prosemirrorChangeset = require("prosemirror-changeset");
8
+ var _diffBySteps = require("./diffBySteps");
9
+ var _groupChangesByBlock = require("./groupChangesByBlock");
10
+ var _optimizeChanges = require("./optimizeChanges");
11
+ var _simplifySteps = require("./simplifySteps");
12
+ var _classifySmartChanges = require("./smart/classifySmartChanges");
13
+ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
14
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
15
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } /**
16
+ * Generic, editor-state-free diff-change computation.
17
+ *
18
+ * Turns an `originalDoc` + forward `steps` into the classified `Change[]` for the
19
+ * requested `diffType`, plus the reconstructed new document — the same classifier
20
+ * the diff overlay uses (`calculateDiffDecorations` → `getChanges`), exposed as a
21
+ * reusable utility (e.g. to derive reviewable segments without rendering
22
+ * decorations). It runs the requested `diffType` DIRECTLY and does NOT apply the
23
+ * overlay's `platform_editor_ai_smart_diff` gate, so a `smart` result here can
24
+ * differ from what the overlay draws when that gate is off (the overlay falls back
25
+ * to inline); callers that need overlay parity must account for that gate.
26
+ *
27
+ * Pure: no `EditorState`, no transactions, no React. The new document is
28
+ * reconstructed by applying the (simplified) steps to `originalDoc`.
29
+ */
30
+ /**
31
+ * Apply the (simplified) steps to `originalDoc` to reconstruct the new doc, and
32
+ * compute the classified `Change[]` for the requested `diffType`. Mirrors the
33
+ * body of `calculateDiffDecorations` → `getChanges` so callers stay in lock-step
34
+ * with the rendered diff. Returns an empty change list (and `originalDoc` as the
35
+ * new doc) when there are no steps.
36
+ */
37
+ var computeDiffChanges = exports.computeDiffChanges = function computeDiffChanges(_ref) {
38
+ var originalDoc = _ref.originalDoc,
39
+ steps = _ref.steps,
40
+ _ref$diffType = _ref.diffType,
41
+ diffType = _ref$diffType === void 0 ? 'smart' : _ref$diffType,
42
+ _ref$locale = _ref.locale,
43
+ locale = _ref$locale === void 0 ? 'en' : _ref$locale,
44
+ smartThresholds = _ref.smartThresholds;
45
+ if (!steps || steps.length === 0) {
46
+ return {
47
+ changes: [],
48
+ newDoc: originalDoc
49
+ };
50
+ }
51
+ var simplifiedSteps = (0, _simplifySteps.simplifySteps)(steps, originalDoc);
52
+ var steppedDoc = originalDoc;
53
+ var stepMaps = [];
54
+ try {
55
+ var _iterator = _createForOfIteratorHelper(simplifiedSteps),
56
+ _step;
57
+ try {
58
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
59
+ var step = _step.value;
60
+ var result = step.apply(steppedDoc);
61
+ if (result.failed === null && result.doc) {
62
+ stepMaps.push(step.getMap());
63
+ steppedDoc = result.doc;
64
+ }
65
+ }
66
+ } catch (err) {
67
+ _iterator.e(err);
68
+ } finally {
69
+ _iterator.f();
70
+ }
71
+ } catch (_unused) {
72
+ // A step's positions fell outside the current doc — `ReplaceStep.apply`
73
+ // throws a RangeError (rather than returning a failed result) when a
74
+ // position exceeds the doc size. This can happen for pathological or
75
+ // inconsistent step sequences. Bail with no changes rather than letting the
76
+ // error propagate to the caller (which, for the PSR, would crash the
77
+ // toolbar); the consumer then falls back to its non-diff segmenting path.
78
+ return {
79
+ changes: [],
80
+ newDoc: originalDoc
81
+ };
82
+ }
83
+ var changeset = _prosemirrorChangeset.ChangeSet.create(originalDoc).addSteps(steppedDoc, stepMaps, steppedDoc);
84
+ if (diffType === 'smart') {
85
+ return {
86
+ changes: (0, _classifySmartChanges.classifySmartChanges)({
87
+ changes: (0, _prosemirrorChangeset.simplifyChanges)(changeset.changes, steppedDoc),
88
+ originalDoc: originalDoc,
89
+ newDoc: steppedDoc,
90
+ locale: locale,
91
+ thresholds: smartThresholds
92
+ }),
93
+ newDoc: steppedDoc
94
+ };
95
+ }
96
+ if (diffType === 'block') {
97
+ return {
98
+ changes: (0, _groupChangesByBlock.groupChangesByBlock)(changeset.changes, originalDoc, steppedDoc),
99
+ newDoc: steppedDoc
100
+ };
101
+ }
102
+ if (diffType === 'step') {
103
+ return {
104
+ changes: (0, _diffBySteps.diffBySteps)(originalDoc, simplifiedSteps),
105
+ newDoc: steppedDoc
106
+ };
107
+ }
108
+ return {
109
+ changes: (0, _optimizeChanges.optimizeChanges)((0, _prosemirrorChangeset.simplifyChanges)(changeset.changes, steppedDoc)),
110
+ newDoc: steppedDoc
111
+ };
112
+ };
@@ -0,0 +1,2 @@
1
+ /* eslint-disable @atlaskit/editor/no-re-export */
2
+ export { computeDiffChanges } from '../pm-plugins/calculateDiff/computeDiffChanges';
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Generic, editor-state-free diff-change computation.
3
+ *
4
+ * Turns an `originalDoc` + forward `steps` into the classified `Change[]` for the
5
+ * requested `diffType`, plus the reconstructed new document — the same classifier
6
+ * the diff overlay uses (`calculateDiffDecorations` → `getChanges`), exposed as a
7
+ * reusable utility (e.g. to derive reviewable segments without rendering
8
+ * decorations). It runs the requested `diffType` DIRECTLY and does NOT apply the
9
+ * overlay's `platform_editor_ai_smart_diff` gate, so a `smart` result here can
10
+ * differ from what the overlay draws when that gate is off (the overlay falls back
11
+ * to inline); callers that need overlay parity must account for that gate.
12
+ *
13
+ * Pure: no `EditorState`, no transactions, no React. The new document is
14
+ * reconstructed by applying the (simplified) steps to `originalDoc`.
15
+ */
16
+ import { ChangeSet, simplifyChanges } from 'prosemirror-changeset';
17
+ import { diffBySteps } from './diffBySteps';
18
+ import { groupChangesByBlock } from './groupChangesByBlock';
19
+ import { optimizeChanges } from './optimizeChanges';
20
+ import { simplifySteps } from './simplifySteps';
21
+ import { classifySmartChanges } from './smart/classifySmartChanges';
22
+ /**
23
+ * Apply the (simplified) steps to `originalDoc` to reconstruct the new doc, and
24
+ * compute the classified `Change[]` for the requested `diffType`. Mirrors the
25
+ * body of `calculateDiffDecorations` → `getChanges` so callers stay in lock-step
26
+ * with the rendered diff. Returns an empty change list (and `originalDoc` as the
27
+ * new doc) when there are no steps.
28
+ */
29
+ export const computeDiffChanges = ({
30
+ originalDoc,
31
+ steps,
32
+ diffType = 'smart',
33
+ locale = 'en',
34
+ smartThresholds
35
+ }) => {
36
+ if (!steps || steps.length === 0) {
37
+ return {
38
+ changes: [],
39
+ newDoc: originalDoc
40
+ };
41
+ }
42
+ const simplifiedSteps = simplifySteps(steps, originalDoc);
43
+ let steppedDoc = originalDoc;
44
+ const stepMaps = [];
45
+ try {
46
+ for (const step of simplifiedSteps) {
47
+ const result = step.apply(steppedDoc);
48
+ if (result.failed === null && result.doc) {
49
+ stepMaps.push(step.getMap());
50
+ steppedDoc = result.doc;
51
+ }
52
+ }
53
+ } catch {
54
+ // A step's positions fell outside the current doc — `ReplaceStep.apply`
55
+ // throws a RangeError (rather than returning a failed result) when a
56
+ // position exceeds the doc size. This can happen for pathological or
57
+ // inconsistent step sequences. Bail with no changes rather than letting the
58
+ // error propagate to the caller (which, for the PSR, would crash the
59
+ // toolbar); the consumer then falls back to its non-diff segmenting path.
60
+ return {
61
+ changes: [],
62
+ newDoc: originalDoc
63
+ };
64
+ }
65
+ const changeset = ChangeSet.create(originalDoc).addSteps(steppedDoc, stepMaps, steppedDoc);
66
+ if (diffType === 'smart') {
67
+ return {
68
+ changes: classifySmartChanges({
69
+ changes: simplifyChanges(changeset.changes, steppedDoc),
70
+ originalDoc,
71
+ newDoc: steppedDoc,
72
+ locale,
73
+ thresholds: smartThresholds
74
+ }),
75
+ newDoc: steppedDoc
76
+ };
77
+ }
78
+ if (diffType === 'block') {
79
+ return {
80
+ changes: groupChangesByBlock(changeset.changes, originalDoc, steppedDoc),
81
+ newDoc: steppedDoc
82
+ };
83
+ }
84
+ if (diffType === 'step') {
85
+ return {
86
+ changes: diffBySteps(originalDoc, simplifiedSteps),
87
+ newDoc: steppedDoc
88
+ };
89
+ }
90
+ return {
91
+ changes: optimizeChanges(simplifyChanges(changeset.changes, steppedDoc)),
92
+ newDoc: steppedDoc
93
+ };
94
+ };
@@ -0,0 +1,2 @@
1
+ /* eslint-disable @atlaskit/editor/no-re-export */
2
+ export { computeDiffChanges } from '../pm-plugins/calculateDiff/computeDiffChanges';
@@ -0,0 +1,107 @@
1
+ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
2
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
3
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
4
+ /**
5
+ * Generic, editor-state-free diff-change computation.
6
+ *
7
+ * Turns an `originalDoc` + forward `steps` into the classified `Change[]` for the
8
+ * requested `diffType`, plus the reconstructed new document — the same classifier
9
+ * the diff overlay uses (`calculateDiffDecorations` → `getChanges`), exposed as a
10
+ * reusable utility (e.g. to derive reviewable segments without rendering
11
+ * decorations). It runs the requested `diffType` DIRECTLY and does NOT apply the
12
+ * overlay's `platform_editor_ai_smart_diff` gate, so a `smart` result here can
13
+ * differ from what the overlay draws when that gate is off (the overlay falls back
14
+ * to inline); callers that need overlay parity must account for that gate.
15
+ *
16
+ * Pure: no `EditorState`, no transactions, no React. The new document is
17
+ * reconstructed by applying the (simplified) steps to `originalDoc`.
18
+ */
19
+ import { ChangeSet, simplifyChanges } from 'prosemirror-changeset';
20
+ import { diffBySteps } from './diffBySteps';
21
+ import { groupChangesByBlock } from './groupChangesByBlock';
22
+ import { optimizeChanges } from './optimizeChanges';
23
+ import { simplifySteps } from './simplifySteps';
24
+ import { classifySmartChanges } from './smart/classifySmartChanges';
25
+ /**
26
+ * Apply the (simplified) steps to `originalDoc` to reconstruct the new doc, and
27
+ * compute the classified `Change[]` for the requested `diffType`. Mirrors the
28
+ * body of `calculateDiffDecorations` → `getChanges` so callers stay in lock-step
29
+ * with the rendered diff. Returns an empty change list (and `originalDoc` as the
30
+ * new doc) when there are no steps.
31
+ */
32
+ export var computeDiffChanges = function computeDiffChanges(_ref) {
33
+ var originalDoc = _ref.originalDoc,
34
+ steps = _ref.steps,
35
+ _ref$diffType = _ref.diffType,
36
+ diffType = _ref$diffType === void 0 ? 'smart' : _ref$diffType,
37
+ _ref$locale = _ref.locale,
38
+ locale = _ref$locale === void 0 ? 'en' : _ref$locale,
39
+ smartThresholds = _ref.smartThresholds;
40
+ if (!steps || steps.length === 0) {
41
+ return {
42
+ changes: [],
43
+ newDoc: originalDoc
44
+ };
45
+ }
46
+ var simplifiedSteps = simplifySteps(steps, originalDoc);
47
+ var steppedDoc = originalDoc;
48
+ var stepMaps = [];
49
+ try {
50
+ var _iterator = _createForOfIteratorHelper(simplifiedSteps),
51
+ _step;
52
+ try {
53
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
54
+ var step = _step.value;
55
+ var result = step.apply(steppedDoc);
56
+ if (result.failed === null && result.doc) {
57
+ stepMaps.push(step.getMap());
58
+ steppedDoc = result.doc;
59
+ }
60
+ }
61
+ } catch (err) {
62
+ _iterator.e(err);
63
+ } finally {
64
+ _iterator.f();
65
+ }
66
+ } catch (_unused) {
67
+ // A step's positions fell outside the current doc — `ReplaceStep.apply`
68
+ // throws a RangeError (rather than returning a failed result) when a
69
+ // position exceeds the doc size. This can happen for pathological or
70
+ // inconsistent step sequences. Bail with no changes rather than letting the
71
+ // error propagate to the caller (which, for the PSR, would crash the
72
+ // toolbar); the consumer then falls back to its non-diff segmenting path.
73
+ return {
74
+ changes: [],
75
+ newDoc: originalDoc
76
+ };
77
+ }
78
+ var changeset = ChangeSet.create(originalDoc).addSteps(steppedDoc, stepMaps, steppedDoc);
79
+ if (diffType === 'smart') {
80
+ return {
81
+ changes: classifySmartChanges({
82
+ changes: simplifyChanges(changeset.changes, steppedDoc),
83
+ originalDoc: originalDoc,
84
+ newDoc: steppedDoc,
85
+ locale: locale,
86
+ thresholds: smartThresholds
87
+ }),
88
+ newDoc: steppedDoc
89
+ };
90
+ }
91
+ if (diffType === 'block') {
92
+ return {
93
+ changes: groupChangesByBlock(changeset.changes, originalDoc, steppedDoc),
94
+ newDoc: steppedDoc
95
+ };
96
+ }
97
+ if (diffType === 'step') {
98
+ return {
99
+ changes: diffBySteps(originalDoc, simplifiedSteps),
100
+ newDoc: steppedDoc
101
+ };
102
+ }
103
+ return {
104
+ changes: optimizeChanges(simplifyChanges(changeset.changes, steppedDoc)),
105
+ newDoc: steppedDoc
106
+ };
107
+ };
@@ -0,0 +1,2 @@
1
+ export { computeDiffChanges } from '../pm-plugins/calculateDiff/computeDiffChanges';
2
+ export type { ComputeDiffChangesParams } from '../pm-plugins/calculateDiff/computeDiffChanges';
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Generic, editor-state-free diff-change computation.
3
+ *
4
+ * Turns an `originalDoc` + forward `steps` into the classified `Change[]` for the
5
+ * requested `diffType`, plus the reconstructed new document — the same classifier
6
+ * the diff overlay uses (`calculateDiffDecorations` → `getChanges`), exposed as a
7
+ * reusable utility (e.g. to derive reviewable segments without rendering
8
+ * decorations). It runs the requested `diffType` DIRECTLY and does NOT apply the
9
+ * overlay's `platform_editor_ai_smart_diff` gate, so a `smart` result here can
10
+ * differ from what the overlay draws when that gate is off (the overlay falls back
11
+ * to inline); callers that need overlay parity must account for that gate.
12
+ *
13
+ * Pure: no `EditorState`, no transactions, no React. The new document is
14
+ * reconstructed by applying the (simplified) steps to `originalDoc`.
15
+ */
16
+ import { type Change } from 'prosemirror-changeset';
17
+ import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
18
+ import type { Step as ProseMirrorStep } from '@atlaskit/editor-prosemirror/transform';
19
+ import type { DiffType, SmartDiffThresholds } from '../../showDiffPluginType';
20
+ export type ComputeDiffChangesParams = {
21
+ /**
22
+ * Which classification to run. Defaults to `smart` — the density-aware model.
23
+ * The other types are supported for parity with the diff overlay's
24
+ * `getChanges`.
25
+ */
26
+ diffType?: DiffType;
27
+ /** BCP-47 locale for `smart` sentence/word segmentation. Defaults to `en`. */
28
+ locale?: string;
29
+ /** The pre-edit document (the diff's left/original side). */
30
+ originalDoc: PMNode;
31
+ /** Optional overrides for the `smart` density thresholds. Ignored otherwise. */
32
+ smartThresholds?: Partial<SmartDiffThresholds>;
33
+ /** Forward `original → new` steps (the standard non-inverted contract). */
34
+ steps: ProseMirrorStep[];
35
+ };
36
+ /**
37
+ * Apply the (simplified) steps to `originalDoc` to reconstruct the new doc, and
38
+ * compute the classified `Change[]` for the requested `diffType`. Mirrors the
39
+ * body of `calculateDiffDecorations` → `getChanges` so callers stay in lock-step
40
+ * with the rendered diff. Returns an empty change list (and `originalDoc` as the
41
+ * new doc) when there are no steps.
42
+ */
43
+ export declare const computeDiffChanges: ({ originalDoc, steps, diffType, locale, smartThresholds, }: ComputeDiffChangesParams) => {
44
+ changes: Change[];
45
+ newDoc: PMNode;
46
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-show-diff",
3
- "version": "10.2.0",
3
+ "version": "10.3.0",
4
4
  "description": "ShowDiff plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -28,7 +28,7 @@
28
28
  "@atlaskit/editor-prosemirror": "^8.0.0",
29
29
  "@atlaskit/editor-tables": "^3.0.0",
30
30
  "@atlaskit/platform-feature-flags": "^2.0.0",
31
- "@atlaskit/tmp-editor-statsig": "^124.0.0",
31
+ "@atlaskit/tmp-editor-statsig": "^124.1.0",
32
32
  "@atlaskit/tokens": "^15.5.0",
33
33
  "@babel/runtime": "^7.0.0",
34
34
  "@compiled/react": "^0.20.0",