stream-chat-react 11.1.0 → 11.1.2

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 (26) hide show
  1. package/dist/browser.full-bundle.js +110 -73
  2. package/dist/browser.full-bundle.js.map +1 -1
  3. package/dist/browser.full-bundle.min.js +4 -4
  4. package/dist/browser.full-bundle.min.js.map +1 -1
  5. package/dist/components/AutoCompleteTextarea/Textarea.d.ts +1 -0
  6. package/dist/components/AutoCompleteTextarea/Textarea.d.ts.map +1 -1
  7. package/dist/components/AutoCompleteTextarea/Textarea.js +10 -8
  8. package/dist/components/Channel/Channel.d.ts.map +1 -1
  9. package/dist/components/Channel/Channel.js +17 -10
  10. package/dist/components/Gallery/ModalGallery.d.ts.map +1 -1
  11. package/dist/components/Gallery/ModalGallery.js +11 -1
  12. package/dist/components/MessageInput/CooldownTimer.d.ts +1 -1
  13. package/dist/components/MessageInput/CooldownTimer.d.ts.map +1 -1
  14. package/dist/components/MessageInput/CooldownTimer.js +14 -11
  15. package/dist/components/MessageInput/hooks/useCooldownTimer.d.ts.map +1 -1
  16. package/dist/components/MessageInput/hooks/useCooldownTimer.js +11 -2
  17. package/dist/components/MessageList/hooks/VirtualizedMessageList/usePrependMessagesCount.d.ts.map +1 -1
  18. package/dist/components/MessageList/hooks/VirtualizedMessageList/usePrependMessagesCount.js +31 -32
  19. package/dist/css/v2/index.css +1 -1
  20. package/dist/css/v2/index.layout.css +1 -1
  21. package/dist/index.cjs.js +109 -72
  22. package/dist/scss/v2/AttachmentList/AttachmentList-layout.scss +1 -0
  23. package/dist/scss/v2/vendor/react-image-gallery.scss +19 -0
  24. package/dist/version.d.ts +1 -1
  25. package/dist/version.js +1 -1
  26. package/package.json +2 -2
package/dist/index.cjs.js CHANGED
@@ -296,6 +296,15 @@ var Modal = function (_a) {
296
296
  React__default["default"].createElement("div", { className: 'str-chat__modal__inner str-chat-react__modal__inner', ref: innerRef }, children)));
297
297
  };
298
298
 
299
+ var onError = function (e) {
300
+ // Prevent having alt attribute on img as the img takes the height of the alt text
301
+ // instead of the CSS / element width & height when the CSS mask (fallback) is applied.
302
+ e.target.alt = '';
303
+ };
304
+ var renderItem = function (_a) {
305
+ var original = _a.original, originalAlt = _a.originalAlt;
306
+ return (React__default["default"].createElement(BaseImage, { alt: originalAlt, className: 'image-gallery-image', onError: onError, src: original }));
307
+ };
299
308
  var ModalGallery = function (props) {
300
309
  var images = props.images, index = props.index;
301
310
  var t = icons.useTranslationContext('ModalGallery').t;
@@ -309,7 +318,7 @@ var ModalGallery = function (props) {
309
318
  };
310
319
  });
311
320
  }, [images]);
312
- return (React__default["default"].createElement(ImageGallery__default["default"], { items: formattedArray, showIndex: true, showPlayButton: false, showThumbnails: false, startIndex: index }));
321
+ return (React__default["default"].createElement(ImageGallery__default["default"], { items: formattedArray, renderItem: renderItem, showIndex: true, showPlayButton: false, showThumbnails: false, startIndex: index }));
313
322
  };
314
323
 
315
324
  var UnMemoizedGallery = function (props) {
@@ -31289,6 +31298,7 @@ class ReactTextareaAutocomplete extends React__default["default"].Component {
31289
31298
  currentTrigger: null,
31290
31299
  data: null,
31291
31300
  dataLoading: false,
31301
+ isComposing: false,
31292
31302
  left: null,
31293
31303
  selectionEnd: 0,
31294
31304
  selectionStart: 0,
@@ -31339,22 +31349,23 @@ class ReactTextareaAutocomplete extends React__default["default"].Component {
31339
31349
  SuggestionItem,
31340
31350
  SuggestionList = List
31341
31351
  } = this.props;
31352
+ const {
31353
+ isComposing
31354
+ } = this.state;
31342
31355
  const triggerProps = this.getTriggerProps();
31343
- if (triggerProps.values && triggerProps.currentTrigger && !(disableMentions && triggerProps.currentTrigger === '@')) {
31344
- return /*#__PURE__*/React__default["default"].createElement("div", {
31345
- className: clsx('rta__autocomplete', 'str-chat__suggestion-list-container', dropdownClassName),
31346
- ref: this.setDropdownRef,
31347
- style: dropdownStyle
31348
- }, /*#__PURE__*/React__default["default"].createElement(SuggestionList, _extends__default["default"]({
31349
- className: clsx('str-chat__suggestion-list', listClassName),
31350
- dropdownScroll: this._dropdownScroll,
31351
- itemClassName: clsx('str-chat__suggestion-list-item', itemClassName),
31352
- itemStyle: itemStyle,
31353
- onSelect: this._onSelect,
31354
- SuggestionItem: SuggestionItem
31355
- }, triggerProps)));
31356
- }
31357
- return null;
31356
+ if (isComposing || !triggerProps.values || !triggerProps.currentTrigger || disableMentions && triggerProps.currentTrigger === '@') return null;
31357
+ return /*#__PURE__*/React__default["default"].createElement("div", {
31358
+ className: clsx('rta__autocomplete', 'str-chat__suggestion-list-container', dropdownClassName),
31359
+ ref: this.setDropdownRef,
31360
+ style: dropdownStyle
31361
+ }, /*#__PURE__*/React__default["default"].createElement(SuggestionList, _extends__default["default"]({
31362
+ className: clsx('str-chat__suggestion-list', listClassName),
31363
+ dropdownScroll: this._dropdownScroll,
31364
+ itemClassName: clsx('str-chat__suggestion-list-item', itemClassName),
31365
+ itemStyle: itemStyle,
31366
+ onSelect: this._onSelect,
31367
+ SuggestionItem: SuggestionItem
31368
+ }, triggerProps)));
31358
31369
  }
31359
31370
  render() {
31360
31371
  const {
@@ -31408,6 +31419,14 @@ class ReactTextareaAutocomplete extends React__default["default"].Component {
31408
31419
  this._onClickAndBlurHandler(e);
31409
31420
  onClick === null || onClick === void 0 ? void 0 : onClick(e);
31410
31421
  },
31422
+ onCompositionEnd: () => this.setState(pv => ({
31423
+ ...pv,
31424
+ isComposing: false
31425
+ })),
31426
+ onCompositionStart: () => this.setState(pv => ({
31427
+ ...pv,
31428
+ isComposing: true
31429
+ })),
31411
31430
  onFocus: e => {
31412
31431
  var _this$props$onFocus, _this$props;
31413
31432
  (_this$props$onFocus = (_this$props = this.props).onFocus) === null || _this$props$onFocus === void 0 ? void 0 : _this$props$onFocus.call(_this$props, e);
@@ -32611,19 +32630,22 @@ var FilePreviewItem = function (_a) {
32611
32630
  };
32612
32631
 
32613
32632
  var CooldownTimer = function (_a) {
32614
- var cooldownInterval = _a.cooldownInterval, setCooldownRemaining = _a.setCooldownRemaining;
32615
- var _b = React.useState(cooldownInterval), seconds = _b[0], setSeconds = _b[1];
32633
+ var cooldownInterval = _a.cooldownInterval;
32634
+ var _b = React.useState(), seconds = _b[0], setSeconds = _b[1];
32616
32635
  React.useEffect(function () {
32617
- var countdownInterval = setInterval(function () {
32618
- if (seconds > 0) {
32636
+ var countdownTimeout;
32637
+ if (typeof seconds === 'number' && seconds > 0) {
32638
+ countdownTimeout = setTimeout(function () {
32619
32639
  setSeconds(seconds - 1);
32620
- }
32621
- else {
32622
- setCooldownRemaining(0);
32623
- }
32624
- }, 1000);
32625
- return function () { return clearInterval(countdownInterval); };
32626
- });
32640
+ }, 1000);
32641
+ }
32642
+ return function () {
32643
+ clearTimeout(countdownTimeout);
32644
+ };
32645
+ }, [seconds]);
32646
+ React.useEffect(function () {
32647
+ setSeconds(cooldownInterval !== null && cooldownInterval !== void 0 ? cooldownInterval : 0);
32648
+ }, [cooldownInterval]);
32627
32649
  return (React__default["default"].createElement("div", { className: 'str-chat__message-input-cooldown', "data-testid": 'cooldown-timer' }, seconds));
32628
32650
  };
32629
32651
 
@@ -33353,11 +33375,20 @@ var useCooldownTimer = function () {
33353
33375
  ? // prevent negative values
33354
33376
  Math.max(0, (new Date().getTime() - ownLatestMessageDate.getTime()) / 1000)
33355
33377
  : undefined;
33356
- setCooldownRemaining(!skipCooldown &&
33378
+ var remaining = !skipCooldown &&
33357
33379
  typeof timeSinceOwnLastMessage !== 'undefined' &&
33358
33380
  cooldownInterval > timeSinceOwnLastMessage
33359
33381
  ? Math.round(cooldownInterval - timeSinceOwnLastMessage)
33360
- : 0);
33382
+ : 0;
33383
+ setCooldownRemaining(remaining);
33384
+ if (!remaining)
33385
+ return;
33386
+ var timeout = setTimeout(function () {
33387
+ setCooldownRemaining(0);
33388
+ }, remaining * 1000);
33389
+ return function () {
33390
+ clearTimeout(timeout);
33391
+ };
33361
33392
  }, [cooldownInterval, ownLatestMessageDate, skipCooldown]);
33362
33393
  return {
33363
33394
  cooldownInterval: cooldownInterval,
@@ -35276,9 +35307,9 @@ var ChannelInner = function (props) {
35276
35307
  var markReadThrottled = throttle__default["default"](markRead, 500, { leading: true, trailing: true });
35277
35308
  var handleEvent = function (event) { return icons.__awaiter(void 0, void 0, void 0, function () {
35278
35309
  var mainChannelUpdated, unread, messageDate, cid, oldestID;
35279
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
35280
- return icons.__generator(this, function (_m) {
35281
- switch (_m.label) {
35310
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
35311
+ return icons.__generator(this, function (_k) {
35312
+ switch (_k.label) {
35282
35313
  case 0:
35283
35314
  if (event.message) {
35284
35315
  dispatch({
@@ -35300,7 +35331,7 @@ var ChannelInner = function (props) {
35300
35331
  if (((_a = event.message) === null || _a === void 0 ? void 0 : _a.parent_id) && !((_b = event.message) === null || _b === void 0 ? void 0 : _b.show_in_channel)) {
35301
35332
  mainChannelUpdated = false;
35302
35333
  }
35303
- if (mainChannelUpdated && ((_d = (_c = event.message) === null || _c === void 0 ? void 0 : _c.user) === null || _d === void 0 ? void 0 : _d.id) !== client.userID) {
35334
+ if (mainChannelUpdated) {
35304
35335
  if (!document.hidden) {
35305
35336
  markReadThrottled();
35306
35337
  }
@@ -35314,9 +35345,9 @@ var ChannelInner = function (props) {
35314
35345
  }
35315
35346
  }
35316
35347
  }
35317
- if (((_f = (_e = event.message) === null || _e === void 0 ? void 0 : _e.user) === null || _f === void 0 ? void 0 : _f.id) === client.userID &&
35318
- ((_g = event === null || event === void 0 ? void 0 : event.message) === null || _g === void 0 ? void 0 : _g.created_at) &&
35319
- ((_h = event === null || event === void 0 ? void 0 : event.message) === null || _h === void 0 ? void 0 : _h.cid)) {
35348
+ if (((_d = (_c = event.message) === null || _c === void 0 ? void 0 : _c.user) === null || _d === void 0 ? void 0 : _d.id) === client.userID &&
35349
+ ((_e = event === null || event === void 0 ? void 0 : event.message) === null || _e === void 0 ? void 0 : _e.created_at) &&
35350
+ ((_f = event === null || event === void 0 ? void 0 : event.message) === null || _f === void 0 ? void 0 : _f.cid)) {
35320
35351
  messageDate = new Date(event.message.created_at);
35321
35352
  cid = event.message.cid;
35322
35353
  if (!latestMessageDatesByChannels[cid] ||
@@ -35326,7 +35357,7 @@ var ChannelInner = function (props) {
35326
35357
  }
35327
35358
  }
35328
35359
  if (!(event.type === 'user.deleted')) return [3 /*break*/, 2];
35329
- oldestID = (_l = (_k = (_j = channel.state) === null || _j === void 0 ? void 0 : _j.messages) === null || _k === void 0 ? void 0 : _k[0]) === null || _l === void 0 ? void 0 : _l.id;
35360
+ oldestID = (_j = (_h = (_g = channel.state) === null || _g === void 0 ? void 0 : _g.messages) === null || _h === void 0 ? void 0 : _h[0]) === null || _j === void 0 ? void 0 : _j.id;
35330
35361
  /**
35331
35362
  * As the channel state is not normalized we re-fetch the channel data. Thus, we avoid having to search for user references in the channel state.
35332
35363
  */
@@ -35340,8 +35371,8 @@ var ChannelInner = function (props) {
35340
35371
  * As the channel state is not normalized we re-fetch the channel data. Thus, we avoid having to search for user references in the channel state.
35341
35372
  */
35342
35373
  // FIXME: we should use channelQueryOptions if they are available
35343
- _m.sent();
35344
- _m.label = 2;
35374
+ _k.sent();
35375
+ _k.label = 2;
35345
35376
  case 2:
35346
35377
  throttledCopyStateFromChannel();
35347
35378
  return [2 /*return*/];
@@ -35403,6 +35434,13 @@ var ChannelInner = function (props) {
35403
35434
  hasMore: hasMoreMessagesProbably(channel.state.messages.length, (_e = (_d = channelQueryOptions === null || channelQueryOptions === void 0 ? void 0 : channelQueryOptions.messages) === null || _d === void 0 ? void 0 : _d.limit) !== null && _e !== void 0 ? _e : DEFAULT_INITIAL_CHANNEL_PAGE_SIZE),
35404
35435
  type: 'initStateFromChannel',
35405
35436
  });
35437
+ /**
35438
+ * TODO: maybe pass last_read to the countUnread method to get proper value
35439
+ * combined with channel.countUnread adjustment (_countMessageAsUnread)
35440
+ * to allow counting own messages too
35441
+ *
35442
+ * const lastRead = channel.state.read[client.userID as string].last_read;
35443
+ */
35406
35444
  if (channel.countUnread() > 0)
35407
35445
  markRead();
35408
35446
  // The more complex sync logic is done in Chat
@@ -37186,7 +37224,7 @@ var UnMemoizedChannelList = function (props) {
37186
37224
  */
37187
37225
  var ChannelList = React__default["default"].memo(UnMemoizedChannelList);
37188
37226
 
37189
- var version = '11.1.0';
37227
+ var version = '11.1.2';
37190
37228
 
37191
37229
  var useChat = function (_a) {
37192
37230
  var _b, _c;
@@ -38756,11 +38794,14 @@ function useNewMessageNotification(messages, currentUserId, hasMoreNewer) {
38756
38794
  };
38757
38795
  }
38758
38796
 
38759
- var STATUSES_EXCLUDED_FROM_PREPEND = ['sending', 'failed'];
38797
+ var STATUSES_EXCLUDED_FROM_PREPEND = {
38798
+ failed: true,
38799
+ sending: true,
38800
+ };
38760
38801
  function usePrependedMessagesCount(messages, hasDateSeparator) {
38761
38802
  var firstRealMessageIndex = hasDateSeparator ? 1 : 0;
38762
- var firstMessageId = React.useRef();
38763
- var earliestMessageId = React.useRef();
38803
+ var firstMessageOnFirstLoadedPage = React.useRef();
38804
+ var previousFirstMessageOnFirstLoadedPage = React.useRef();
38764
38805
  var previousNumItemsPrepended = React.useRef(0);
38765
38806
  var numItemsPrepended = React.useMemo(function () {
38766
38807
  var _a, _b;
@@ -38768,47 +38809,43 @@ function usePrependedMessagesCount(messages, hasDateSeparator) {
38768
38809
  previousNumItemsPrepended.current = 0;
38769
38810
  return 0;
38770
38811
  }
38771
- var currentFirstMessageId = (_a = messages === null || messages === void 0 ? void 0 : messages[firstRealMessageIndex]) === null || _a === void 0 ? void 0 : _a.id;
38772
- // if no new messages were prepended, return early (same amount as before)
38773
- if (currentFirstMessageId === earliestMessageId.current) {
38812
+ var currentFirstMessage = messages === null || messages === void 0 ? void 0 : messages[firstRealMessageIndex];
38813
+ var noNewMessages = (currentFirstMessage === null || currentFirstMessage === void 0 ? void 0 : currentFirstMessage.id) === ((_a = previousFirstMessageOnFirstLoadedPage.current) === null || _a === void 0 ? void 0 : _a.id);
38814
+ // This is possible only, when sending messages very quickly (basically single char messages submitted like a crazy) in empty channel (first page)
38815
+ // Optimistic UI update, when sending messages, can lead to a situation, when
38816
+ // the order of the messages changes for a moment. This can happen, when a user
38817
+ // sends multiple messages withing few milliseconds. E.g. we send a message A
38818
+ // then message B. At first we have message array with both messages of status "sending"
38819
+ // then response for message A is received with a new - later - created_at timestamp
38820
+ // this leads to rearrangement of 1.B ("sending"), 2.A ("received"). Still firstMessageOnFirstLoadedPage.current
38821
+ // points to message A, but now this message has index 1 => previousNumItemsPrepended.current === 1
38822
+ // That in turn leads to incorrect index calculation in VirtualizedMessageList trying to access a message
38823
+ // at non-existent index. Therefore, we ignore messages of status "sending" / "failed" in order they are
38824
+ // not considered as prepended messages.
38825
+ var firstMsgMovedAfterMessagesInExcludedStatus = (currentFirstMessage === null || currentFirstMessage === void 0 ? void 0 : currentFirstMessage.status) && STATUSES_EXCLUDED_FROM_PREPEND[currentFirstMessage.status];
38826
+ if (noNewMessages || firstMsgMovedAfterMessagesInExcludedStatus) {
38774
38827
  return previousNumItemsPrepended.current;
38775
38828
  }
38776
- if (!firstMessageId.current) {
38777
- firstMessageId.current = currentFirstMessageId;
38829
+ if (!firstMessageOnFirstLoadedPage.current) {
38830
+ firstMessageOnFirstLoadedPage.current = currentFirstMessage;
38778
38831
  }
38779
- earliestMessageId.current = currentFirstMessageId;
38832
+ previousFirstMessageOnFirstLoadedPage.current = currentFirstMessage;
38780
38833
  // if new messages were prepended, find out how many
38781
38834
  // start with this number because there cannot be fewer prepended items than before
38782
- var adjustPrependedMessageCount = 0;
38783
- for (var i = previousNumItemsPrepended.current; i < messages.length; i += 1) {
38784
- // Optimistic UI update, when sending messages, can lead to a situation, when
38785
- // the order of the messages changes for a moment. This can happen, when a user
38786
- // sends multiple messages withing few milliseconds. E.g. we send a message A
38787
- // then message B. At first we have message array with both messages of status "sending"
38788
- // then response for message A is received with a new - later - created_at timestamp
38789
- // this leads to rearrangement of 1.B ("sending"), 2.A ("received"). Still firstMessageId.current
38790
- // points to message A, but now this message has index 1 => previousNumItemsPrepended.current === 1
38791
- // That in turn leads to incorrect index calculation in VirtualizedMessageList trying to access a message
38792
- // at non-existent index. Therefore, we ignore messages of status "sending" / "failed" in order they are
38793
- // not considered as prepended messages.
38794
- if (((_b = messages[i]) === null || _b === void 0 ? void 0 : _b.status) &&
38795
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
38796
- STATUSES_EXCLUDED_FROM_PREPEND.includes(messages[i].status) &&
38797
- messages[i].id !== firstMessageId.current) {
38798
- adjustPrependedMessageCount++;
38799
- }
38800
- if (messages[i].id === firstMessageId.current) {
38801
- previousNumItemsPrepended.current = i - adjustPrependedMessageCount;
38802
- return previousNumItemsPrepended.current;
38835
+ for (var prependedMessageCount = previousNumItemsPrepended.current; prependedMessageCount < messages.length; prependedMessageCount += 1) {
38836
+ var messageIsFirstOnFirstLoadedPage = messages[prependedMessageCount].id === ((_b = firstMessageOnFirstLoadedPage.current) === null || _b === void 0 ? void 0 : _b.id);
38837
+ if (messageIsFirstOnFirstLoadedPage) {
38838
+ previousNumItemsPrepended.current = prependedMessageCount;
38839
+ return prependedMessageCount;
38803
38840
  }
38804
38841
  }
38805
38842
  // if no match has found, we have jumped - reset the prepended item count.
38806
- firstMessageId.current = currentFirstMessageId;
38843
+ firstMessageOnFirstLoadedPage.current = currentFirstMessage;
38807
38844
  previousNumItemsPrepended.current = 0;
38808
38845
  return 0;
38809
38846
  // TODO: there's a bug here, the messages prop is the same array instance (something mutates it)
38810
38847
  // that's why the second dependency is necessary
38811
- }, [messages, messages === null || messages === void 0 ? void 0 : messages.length]);
38848
+ }, [firstRealMessageIndex, messages, messages === null || messages === void 0 ? void 0 : messages.length]);
38812
38849
  return numItemsPrepended;
38813
38850
  }
38814
38851
 
@@ -191,6 +191,7 @@
191
191
  .str-chat__gallery-image {
192
192
  padding: 0;
193
193
  margin: 0;
194
+ position: relative;
194
195
 
195
196
  img {
196
197
  width: 100%;
@@ -127,6 +127,9 @@ $ig-shadow: 0 2px 2px lighten($ig-black, 10%);
127
127
  @include vendor-prefix('user-select', none);
128
128
  -webkit-tap-highlight-color: $ig-transparent;
129
129
  position: relative;
130
+ display: flex;
131
+ justify-content: center;
132
+ align-items: center;
130
133
 
131
134
  &.fullscreen-modal {
132
135
  background: $ig-black;
@@ -151,6 +154,22 @@ $ig-shadow: 0 2px 2px lighten($ig-black, 10%);
151
154
  line-height: 0;
152
155
  top: 0;
153
156
 
157
+ .image-gallery-slide {
158
+ background-color: var(--str-chat__secondary-surface-color);
159
+
160
+ .str-chat__base-image--load-failed {
161
+ height: var(--str-chat__attachment-max-width);
162
+ width: var(--str-chat__attachment-max-width);
163
+ font-size: 0;
164
+ }
165
+
166
+ .str-chat__message-attachment-file--item-download {
167
+ position: absolute;
168
+ left: 0.375rem;
169
+ top: 0.375rem;
170
+ }
171
+ }
172
+
154
173
  &.fullscreen {
155
174
  background: $ig-black;
156
175
 
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const version = "11.1.0";
1
+ export declare const version = "11.1.2";
2
2
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export var version = '11.1.0';
1
+ export var version = '11.1.2';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stream-chat-react",
3
- "version": "11.1.0",
3
+ "version": "11.1.2",
4
4
  "description": "React components to create chat conversations or livestream style chat",
5
5
  "author": "GetStream",
6
6
  "homepage": "https://getstream.io/chat/",
@@ -142,7 +142,7 @@
142
142
  "@semantic-release/changelog": "^6.0.2",
143
143
  "@semantic-release/git": "^10.0.1",
144
144
  "@stream-io/rollup-plugin-node-builtins": "^2.1.5",
145
- "@stream-io/stream-chat-css": "^4.1.0",
145
+ "@stream-io/stream-chat-css": "^4.2.0",
146
146
  "@testing-library/jest-dom": "^6.1.4",
147
147
  "@testing-library/react": "^13.1.1",
148
148
  "@testing-library/react-hooks": "^8.0.0",