@strapi/content-manager 0.0.0-next.e98ae27d8c69c68823eb7c46cf329385637ba393 → 0.0.0-next.eb48c73c86cbc452c1ba8d727106f9ed9da0c834

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 (113) hide show
  1. package/dist/admin/chunks/{ComponentConfigurationPage-Bn-SMKJG.js → ComponentConfigurationPage-B-gE3kXG.js} +5 -6
  2. package/dist/admin/chunks/{ComponentConfigurationPage-Bn-SMKJG.js.map → ComponentConfigurationPage-B-gE3kXG.js.map} +1 -1
  3. package/dist/admin/chunks/{ComponentConfigurationPage-Cqi5KqZa.mjs → ComponentConfigurationPage-R5N6EwIp.mjs} +5 -6
  4. package/dist/admin/chunks/{ComponentConfigurationPage-Cqi5KqZa.mjs.map → ComponentConfigurationPage-R5N6EwIp.mjs.map} +1 -1
  5. package/dist/admin/chunks/{EditConfigurationPage-Dm16gciL.mjs → EditConfigurationPage-0y9O2nBU.mjs} +5 -6
  6. package/dist/admin/chunks/{EditConfigurationPage-Dm16gciL.mjs.map → EditConfigurationPage-0y9O2nBU.mjs.map} +1 -1
  7. package/dist/admin/chunks/{EditConfigurationPage-CRxsqOhS.js → EditConfigurationPage-C62WuGTf.js} +5 -6
  8. package/dist/admin/chunks/{EditConfigurationPage-CRxsqOhS.js.map → EditConfigurationPage-C62WuGTf.js.map} +1 -1
  9. package/dist/admin/chunks/{EditViewPage-Dk8CeS40.js → EditViewPage-CiYAHBOX.js} +102 -95
  10. package/dist/admin/chunks/EditViewPage-CiYAHBOX.js.map +1 -0
  11. package/dist/admin/chunks/{EditViewPage-eTCSh2HB.mjs → EditViewPage-CkO2YLdg.mjs} +102 -95
  12. package/dist/admin/chunks/EditViewPage-CkO2YLdg.mjs.map +1 -0
  13. package/dist/admin/chunks/{Form-PB1sNax8.js → Form-DUs6lQ0C.js} +5 -5
  14. package/dist/admin/chunks/{Form-PB1sNax8.js.map → Form-DUs6lQ0C.js.map} +1 -1
  15. package/dist/admin/chunks/{Form-K_hq8zU4.mjs → Form-Db9A3cMO.mjs} +3 -3
  16. package/dist/admin/chunks/{Form-K_hq8zU4.mjs.map → Form-Db9A3cMO.mjs.map} +1 -1
  17. package/dist/admin/chunks/{History-CD5uVchr.mjs → History-BQ7IAJjV.mjs} +16 -12
  18. package/dist/admin/chunks/History-BQ7IAJjV.mjs.map +1 -0
  19. package/dist/admin/chunks/{History-CfsF-U-3.js → History-D99wxPry.js} +17 -13
  20. package/dist/admin/chunks/History-D99wxPry.js.map +1 -0
  21. package/dist/admin/chunks/{Input-grmHplxs.mjs → Input-CHDnpU3S.mjs} +1340 -55
  22. package/dist/admin/chunks/Input-CHDnpU3S.mjs.map +1 -0
  23. package/dist/admin/chunks/{Input-CU93J6dl.js → Input-Gop65dq6.js} +1352 -64
  24. package/dist/admin/chunks/Input-Gop65dq6.js.map +1 -0
  25. package/dist/admin/chunks/{ListConfigurationPage-Eo8txFp0.mjs → ListConfigurationPage-Bte99c0c.mjs} +4 -5
  26. package/dist/admin/chunks/{ListConfigurationPage-Eo8txFp0.mjs.map → ListConfigurationPage-Bte99c0c.mjs.map} +1 -1
  27. package/dist/admin/chunks/{ListConfigurationPage-nZti--Mw.js → ListConfigurationPage-pAdM7Lbn.js} +6 -7
  28. package/dist/admin/chunks/{ListConfigurationPage-nZti--Mw.js.map → ListConfigurationPage-pAdM7Lbn.js.map} +1 -1
  29. package/dist/admin/chunks/{ListViewPage-BBX-zSka.mjs → ListViewPage-7RTPaDs2.mjs} +3 -5
  30. package/dist/admin/chunks/ListViewPage-7RTPaDs2.mjs.map +1 -0
  31. package/dist/admin/chunks/{ListViewPage-DQYvHCxn.js → ListViewPage-CaBwwCVc.js} +7 -9
  32. package/dist/admin/chunks/ListViewPage-CaBwwCVc.js.map +1 -0
  33. package/dist/admin/chunks/{NoContentTypePage-C-rbOQO1.mjs → NoContentTypePage-CuTk3q_I.mjs} +2 -2
  34. package/dist/admin/chunks/{NoContentTypePage-C-rbOQO1.mjs.map → NoContentTypePage-CuTk3q_I.mjs.map} +1 -1
  35. package/dist/admin/chunks/{NoContentTypePage-ChYgz3sN.js → NoContentTypePage-DLD28j4F.js} +2 -2
  36. package/dist/admin/chunks/{NoContentTypePage-ChYgz3sN.js.map → NoContentTypePage-DLD28j4F.js.map} +1 -1
  37. package/dist/admin/chunks/{NoPermissionsPage-CuxrInhz.mjs → NoPermissionsPage-DSIlPmvv.mjs} +2 -2
  38. package/dist/admin/chunks/{NoPermissionsPage-CuxrInhz.mjs.map → NoPermissionsPage-DSIlPmvv.mjs.map} +1 -1
  39. package/dist/admin/chunks/{NoPermissionsPage-H4SAlJeF.js → NoPermissionsPage-iVMEcUp0.js} +2 -2
  40. package/dist/admin/chunks/{NoPermissionsPage-H4SAlJeF.js.map → NoPermissionsPage-iVMEcUp0.js.map} +1 -1
  41. package/dist/admin/chunks/{Preview-HqnNXn4S.mjs → Preview-35wsOM_p.mjs} +136 -211
  42. package/dist/admin/chunks/Preview-35wsOM_p.mjs.map +1 -0
  43. package/dist/admin/chunks/{Preview-Bz2dgOiw.js → Preview-CSU1sCSK.js} +134 -209
  44. package/dist/admin/chunks/Preview-CSU1sCSK.js.map +1 -0
  45. package/dist/admin/chunks/{en-CImiNxXE.mjs → en-C2zEwS3-.mjs} +4 -1
  46. package/dist/admin/chunks/{en-CImiNxXE.mjs.map → en-C2zEwS3-.mjs.map} +1 -1
  47. package/dist/admin/chunks/{en-CLf4SuMQ.js → en-G976DLsg.js} +4 -1
  48. package/dist/admin/chunks/{en-CLf4SuMQ.js.map → en-G976DLsg.js.map} +1 -1
  49. package/dist/admin/chunks/{index-DfX0uEQT.js → index-Cmh0Byay.js} +213 -129
  50. package/dist/admin/chunks/index-Cmh0Byay.js.map +1 -0
  51. package/dist/admin/chunks/{index-CS_nLuw2.mjs → index-DFvqTQvr.mjs} +212 -131
  52. package/dist/admin/chunks/index-DFvqTQvr.mjs.map +1 -0
  53. package/dist/admin/chunks/{layout-B4Uk0v1z.js → layout-DuH1v82S.js} +86 -13
  54. package/dist/admin/chunks/layout-DuH1v82S.js.map +1 -0
  55. package/dist/admin/chunks/{layout-BInXGFmo.mjs → layout-zQ9o6rg0.mjs} +78 -5
  56. package/dist/admin/chunks/layout-zQ9o6rg0.mjs.map +1 -0
  57. package/dist/admin/chunks/{useDragAndDrop-gcqEJMnO.js → objects-BJTP843m.js} +73 -1
  58. package/dist/admin/chunks/objects-BJTP843m.js.map +1 -0
  59. package/dist/admin/chunks/{useDragAndDrop-HYwNDExe.mjs → objects-D2z-IJgu.mjs} +72 -2
  60. package/dist/admin/chunks/objects-D2z-IJgu.mjs.map +1 -0
  61. package/dist/admin/chunks/{relations-9C5dQI9e.js → usePrev-B_pceXQ5.js} +18 -2
  62. package/dist/admin/chunks/usePrev-B_pceXQ5.js.map +1 -0
  63. package/dist/admin/chunks/{relations-CwL0ThH9.mjs → usePrev-CJFOGBK2.mjs} +18 -4
  64. package/dist/admin/chunks/usePrev-CJFOGBK2.mjs.map +1 -0
  65. package/dist/admin/chunks/{zh-Hans-CI0HKio3.mjs → zh-Hans-DEAhqI3x.mjs} +3 -2
  66. package/dist/admin/chunks/{zh-Hans-CI0HKio3.mjs.map → zh-Hans-DEAhqI3x.mjs.map} +1 -1
  67. package/dist/admin/chunks/{zh-Hans-JVK9x7xr.js → zh-Hans-Djj7eGpO.js} +3 -2
  68. package/dist/admin/chunks/{zh-Hans-JVK9x7xr.js.map → zh-Hans-Djj7eGpO.js.map} +1 -1
  69. package/dist/admin/index.js +1 -1
  70. package/dist/admin/index.mjs +1 -1
  71. package/dist/admin/src/content-manager.d.ts +3 -0
  72. package/dist/admin/src/features/DocumentContext.d.ts +53 -0
  73. package/dist/admin/src/features/DocumentRBAC.d.ts +3 -2
  74. package/dist/admin/src/hooks/useDocument.d.ts +2 -0
  75. package/dist/admin/src/hooks/useDocumentActions.d.ts +1 -1
  76. package/dist/admin/src/pages/EditView/components/DocumentActions.d.ts +1 -1
  77. package/dist/admin/src/pages/EditView/components/FormInputs/Relations/RelationModal.d.ts +8 -0
  78. package/dist/admin/src/pages/EditView/components/FormInputs/{Relations.d.ts → Relations/Relations.d.ts} +9 -4
  79. package/dist/admin/src/pages/EditView/components/FormLayout.d.ts +8 -3
  80. package/dist/admin/src/pages/EditView/components/InputRenderer.d.ts +7 -4
  81. package/dist/admin/src/preview/components/PreviewHeader.d.ts +1 -2
  82. package/package.json +6 -6
  83. package/dist/admin/chunks/EditViewPage-Dk8CeS40.js.map +0 -1
  84. package/dist/admin/chunks/EditViewPage-eTCSh2HB.mjs.map +0 -1
  85. package/dist/admin/chunks/History-CD5uVchr.mjs.map +0 -1
  86. package/dist/admin/chunks/History-CfsF-U-3.js.map +0 -1
  87. package/dist/admin/chunks/Input-CU93J6dl.js.map +0 -1
  88. package/dist/admin/chunks/Input-grmHplxs.mjs.map +0 -1
  89. package/dist/admin/chunks/ListViewPage-BBX-zSka.mjs.map +0 -1
  90. package/dist/admin/chunks/ListViewPage-DQYvHCxn.js.map +0 -1
  91. package/dist/admin/chunks/Preview-Bz2dgOiw.js.map +0 -1
  92. package/dist/admin/chunks/Preview-HqnNXn4S.mjs.map +0 -1
  93. package/dist/admin/chunks/Relations-BMvL4yMN.mjs +0 -1291
  94. package/dist/admin/chunks/Relations-BMvL4yMN.mjs.map +0 -1
  95. package/dist/admin/chunks/Relations-CbCAt692.js +0 -1318
  96. package/dist/admin/chunks/Relations-CbCAt692.js.map +0 -1
  97. package/dist/admin/chunks/index-CS_nLuw2.mjs.map +0 -1
  98. package/dist/admin/chunks/index-DfX0uEQT.js.map +0 -1
  99. package/dist/admin/chunks/layout-B4Uk0v1z.js.map +0 -1
  100. package/dist/admin/chunks/layout-BInXGFmo.mjs.map +0 -1
  101. package/dist/admin/chunks/objects-C3EebVVe.js +0 -76
  102. package/dist/admin/chunks/objects-C3EebVVe.js.map +0 -1
  103. package/dist/admin/chunks/objects-wl73iEma.mjs +0 -73
  104. package/dist/admin/chunks/objects-wl73iEma.mjs.map +0 -1
  105. package/dist/admin/chunks/relations-9C5dQI9e.js.map +0 -1
  106. package/dist/admin/chunks/relations-CwL0ThH9.mjs.map +0 -1
  107. package/dist/admin/chunks/useDragAndDrop-HYwNDExe.mjs.map +0 -1
  108. package/dist/admin/chunks/useDragAndDrop-gcqEJMnO.js.map +0 -1
  109. package/dist/admin/chunks/usePrev-Bjw2dhmq.mjs +0 -18
  110. package/dist/admin/chunks/usePrev-Bjw2dhmq.mjs.map +0 -1
  111. package/dist/admin/chunks/usePrev-DIYl-IAL.js +0 -21
  112. package/dist/admin/chunks/usePrev-DIYl-IAL.js.map +0 -1
  113. package/dist/admin/src/preview/components/PreviewContent.d.ts +0 -3
@@ -1,11 +1,11 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
- import { useState, useEffect, useCallback, memo } from 'react';
4
- import { useStrapiApp, useElementOnScreen, createContext, useField, useForm, useNotification, useAPIErrorHandler, useQueryParams, useFocusInputField, InputRenderer as InputRenderer$1 } from '@strapi/admin/strapi-admin';
5
- import { Box, SingleSelect, SingleSelectOption, Typography, Flex, Button, Popover, Field, Menu, IconButton, Tooltip, useComposedRefs, Portal, FocusTrap, Divider, VisuallyHidden, Accordion, MenuItem, Grid as Grid$1, TextInput, IconButtonGroup, TextButton } from '@strapi/design-system';
6
- import { CodeBlock as CodeBlock$1, HeadingOne, HeadingTwo, HeadingThree, HeadingFour, HeadingFive, HeadingSix, Image as Image$1, NumberList, BulletList, Paragraph, Quotes, More, Link as Link$1, Drag, Collapse, Bold, Italic, Underline, StrikeThrough, Code, Expand, PlusCircle, Trash, EyeStriked, CheckCircle, WarningCircle, Loader, ArrowClockwise, Plus } from '@strapi/icons';
3
+ import { useState, useEffect, useCallback } from 'react';
4
+ import { useStrapiApp, useElementOnScreen, createContext, useField, useForm, useNotification, Form, ConfirmDialog, useRBAC, DescriptionComponentRenderer, useQueryParams, useFocusInputField, useAPIErrorHandler, InputRenderer as InputRenderer$1 } from '@strapi/admin/strapi-admin';
5
+ import { Box, SingleSelect, SingleSelectOption, Typography, Flex, Button, Popover, Field, Menu, IconButton, Tooltip, useComposedRefs, Portal, FocusTrap, Divider, VisuallyHidden, Accordion, MenuItem, Grid as Grid$1, TextInput, Modal, TextButton, Dialog, Loader, EmptyStateLayout, Link as Link$2, Combobox, ComboboxOption, IconButtonGroup } from '@strapi/design-system';
6
+ import { CodeBlock as CodeBlock$1, HeadingOne, HeadingTwo, HeadingThree, HeadingFour, HeadingFive, HeadingSix, Image as Image$1, NumberList, BulletList, Paragraph, Quotes, More, Link as Link$1, Drag, Collapse, Bold, Italic, Underline, StrikeThrough, Code, Expand, PlusCircle, Trash, EyeStriked, ArrowLeft, ArrowsOut, WarningCircle, ArrowClockwise, Cross, CheckCircle, Loader as Loader$1, Plus } from '@strapi/icons';
7
7
  import { useIntl } from 'react-intl';
8
- import { g as getTranslation, m as useDocLayout, c as useDoc, n as createDefaultForm, t as transformDocument, e as contentManagerApi, o as CLONE_PATH, d as buildValidParams, f as useDocumentRBAC, S as SINGLE_TYPES } from './index-CS_nLuw2.mjs';
8
+ import { g as getTranslation, c as useDocumentContext, d as useDocumentLayout, e as createDefaultForm, t as transformDocument, f as useLazyGetDocumentQuery, h as createYupSchema, P as PERMISSIONS, i as DocumentRBAC, j as DocumentActionButton, D as DocumentStatus, C as COLLECTION_TYPES, S as SINGLE_TYPES, k as buildValidParams, l as contentManagerApi, m as useDoc, n as CLONE_PATH, o as useDocumentRBAC } from './index-DFvqTQvr.mjs';
9
9
  import { styled, css, keyframes } from 'styled-components';
10
10
  import { Editor as Editor$1, Transforms, Node, Element, Range, Path, Point, createEditor } from 'slate';
11
11
  import { withHistory } from 'slate-history';
@@ -61,16 +61,15 @@ import 'prismjs/components/prism-typescript';
61
61
  import 'prismjs/components/prism-tsx';
62
62
  import 'prismjs/components/prism-vbnet';
63
63
  import 'prismjs/components/prism-yaml';
64
- import { p as prefixFileUrlWithBackendUrl, u as usePrev } from './usePrev-Bjw2dhmq.mjs';
65
- import { a as DIRECTIONS, u as useDragAndDrop, I as ItemTypes } from './useDragAndDrop-HYwNDExe.mjs';
64
+ import { p as prefixFileUrlWithBackendUrl, u as useGetRelationsQuery, g as getRelationLabel, a as useDebounce, b as useLazySearchRelationsQuery, c as usePrev } from './usePrev-CJFOGBK2.mjs';
65
+ import { D as DIRECTIONS, u as useDragAndDrop, I as ItemTypes, g as getIn, a as DROP_SENSITIVITY } from './objects-D2z-IJgu.mjs';
66
66
  import * as Toolbar from '@radix-ui/react-toolbar';
67
67
  import { getEmptyImage } from 'react-dnd-html5-backend';
68
- import { useMatch, useLocation } from 'react-router-dom';
69
- import { g as getIn } from './objects-wl73iEma.mjs';
70
- import { u as useComponent, C as ComponentProvider, M as MemoizedUnstableRelationsField, a as MemoizedRelationsField } from './Relations-BMvL4yMN.mjs';
68
+ import { useNavigate, useLocation, useMatch } from 'react-router-dom';
71
69
  import pipe$1 from 'lodash/fp/pipe';
72
70
  import { C as ComponentIcon, a as COMPONENT_ICONS } from './ComponentIcon-BZcTc4rj.mjs';
73
- import { a as useDebounce } from './relations-CwL0ThH9.mjs';
71
+ import { generateNKeysBetween } from 'fractional-indexing';
72
+ import { FixedSizeList } from 'react-window';
74
73
  import CodeMirror from 'codemirror5';
75
74
  import sanitizeHtml from 'sanitize-html';
76
75
  import { getLanguage, highlight, highlightAuto } from 'highlight.js';
@@ -1779,6 +1778,7 @@ const EditorToolbarObserver = ({ observedComponents, menuTriggerVariant = 'ghost
1779
1778
  maxHeight: "100%",
1780
1779
  minWidth: "256px",
1781
1780
  popoverPlacement: "bottom-end",
1781
+ zIndex: 2,
1782
1782
  children: observedComponents.slice(menuIndex).map((component)=>/*#__PURE__*/ jsx(React.Fragment, {
1783
1783
  children: component.menu
1784
1784
  }, component.key))
@@ -3463,6 +3463,20 @@ const Initializer = ({ disabled, name, onClick })=>{
3463
3463
  });
3464
3464
  };
3465
3465
 
3466
+ /**
3467
+ * We use this component to wrap any individual component field in the Edit View,
3468
+ * this could be a component field in a dynamic zone, a component within a repeatable space,
3469
+ * or even nested components.
3470
+ *
3471
+ * We primarily need this to provide the component id to the components so that they can
3472
+ * correctly fetch their relations.
3473
+ */ const [ComponentProvider, useComponent] = createContext('ComponentContext', {
3474
+ id: undefined,
3475
+ level: -1,
3476
+ uid: undefined,
3477
+ type: undefined
3478
+ });
3479
+
3466
3480
  const AddComponentButton = ({ hasError, isDisabled, isOpen, children, onClick })=>{
3467
3481
  return /*#__PURE__*/ jsx(StyledButton, {
3468
3482
  type: "button",
@@ -3568,15 +3582,24 @@ const ComponentCategory = ({ category, components = [], variant = 'primary', onA
3568
3582
  const ResponsiveAccordionContent = styled(Accordion.Content)`
3569
3583
  container-type: inline-size;
3570
3584
  `;
3571
- const Grid = styled(Box)`
3572
- display: grid;
3573
- grid-template-columns: repeat(auto-fill, 100%);
3574
- grid-gap: ${({ theme })=>theme.spaces[1]};
3575
-
3576
- @container (min-width: ${()=>RESPONSIVE_CONTAINER_BREAKPOINTS.sm}) {
3577
- grid-template-columns: repeat(auto-fill, 14rem);
3578
- }
3579
- `;
3585
+ /**
3586
+ * TODO:
3587
+ * JSDOM cannot handle container queries.
3588
+ * This is a temporary workaround so that tests do not fail in the CI when jestdom throws an error
3589
+ * for failing to parse the stylesheet.
3590
+ */ const Grid = process.env.NODE_ENV !== 'test' ? styled(Box)`
3591
+ display: grid;
3592
+ grid-template-columns: repeat(auto-fill, 100%);
3593
+ grid-gap: 4px;
3594
+
3595
+ @container (min-width: ${()=>RESPONSIVE_CONTAINER_BREAKPOINTS.sm}) {
3596
+ grid-template-columns: repeat(auto-fill, 14rem);
3597
+ }
3598
+ ` : styled(Box)`
3599
+ display: grid;
3600
+ grid-template-columns: repeat(auto-fill, 100%);
3601
+ grid-gap: 4px;
3602
+ `;
3580
3603
  const ComponentBox = styled(Flex)`
3581
3604
  color: ${({ theme })=>theme.colors.neutral600};
3582
3605
  cursor: pointer;
@@ -3641,7 +3664,19 @@ const ComponentPicker = ({ dynamicComponentsByCategory = {}, isOpen, onClickAddC
3641
3664
  const DynamicComponent = ({ componentUid, disabled, index, name, onRemoveComponentClick, onMoveComponent, onGrabItem, onDropItem, onCancel, dynamicComponentsByCategory = {}, onAddComponent, children })=>{
3642
3665
  const { formatMessage } = useIntl();
3643
3666
  const formValues = useForm('DynamicComponent', (state)=>state.values);
3644
- const { edit: { components } } = useDocLayout();
3667
+ const documentMeta = useDocumentContext('DynamicComponent', (state)=>state.meta);
3668
+ const rootDocumentMeta = useDocumentContext('DynamicComponent', (state)=>state.rootDocumentMeta);
3669
+ const { edit: { components: rootComponents } } = useDocumentLayout(rootDocumentMeta.model);
3670
+ const { edit: { components: relatedComponents } } = useDocumentLayout(documentMeta.model);
3671
+ // Merge the root level components and related components
3672
+ const components = React.useMemo(()=>({
3673
+ ...rootComponents,
3674
+ ...relatedComponents
3675
+ }), [
3676
+ rootComponents,
3677
+ relatedComponents
3678
+ ]);
3679
+ const document = useDocumentContext('DynamicComponent', (state)=>state.document);
3645
3680
  const title = React.useMemo(()=>{
3646
3681
  const { mainField } = components[componentUid]?.settings ?? {
3647
3682
  mainField: 'id'
@@ -3873,9 +3908,11 @@ const DynamicComponent = ({ componentUid, disabled, index, name, onRemoveCompone
3873
3908
  alignItems: "stretch",
3874
3909
  children: children ? children({
3875
3910
  ...fieldWithTranslatedLabel,
3911
+ document,
3876
3912
  name: fieldName
3877
3913
  }) : /*#__PURE__*/ jsx(MemoizedInputRenderer, {
3878
3914
  ...fieldWithTranslatedLabel,
3915
+ document: document,
3879
3916
  name: fieldName
3880
3917
  })
3881
3918
  }, fieldName);
@@ -3993,7 +4030,8 @@ const DynamicZone = ({ attribute, disabled: disabledProp, hint, label, labelActi
3993
4030
  const { max = Infinity, min = -Infinity } = attribute ?? {};
3994
4031
  const [addComponentIsOpen, setAddComponentIsOpen] = React.useState(false);
3995
4032
  const [liveText, setLiveText] = React.useState('');
3996
- const { components, isLoading } = useDoc();
4033
+ const document = useDocumentContext('DynamicZone', (state)=>state.document);
4034
+ const { components, isLoading } = document;
3997
4035
  const disabled = disabledProp || isLoading;
3998
4036
  const { addFieldRow, removeFieldRow, moveFieldRow } = useForm('DynamicZone', ({ addFieldRow, removeFieldRow, moveFieldRow })=>({
3999
4037
  addFieldRow,
@@ -4226,6 +4264,1241 @@ const NotAllowedInput = ({ hint, label, required, name })=>{
4226
4264
  });
4227
4265
  };
4228
4266
 
4267
+ function getCollectionType(url) {
4268
+ const regex = new RegExp(`(${COLLECTION_TYPES}|${SINGLE_TYPES})`);
4269
+ const match = url.match(regex);
4270
+ return match ? match[1] : undefined;
4271
+ }
4272
+ const CustomModalContent = styled(Modal.Content)`
4273
+ width: 90%;
4274
+ max-width: 100%;
4275
+ height: 90%;
4276
+ max-height: 100%;
4277
+ `;
4278
+ const [RelationModalProvider, useRelationModal] = createContext('RelationModal', {
4279
+ parentModified: false,
4280
+ depth: 0
4281
+ });
4282
+ const RelationModalForm = ({ relation, triggerButtonLabel })=>{
4283
+ const navigate = useNavigate();
4284
+ const { pathname, search } = useLocation();
4285
+ const { formatMessage } = useIntl();
4286
+ const [triggerRefetchDocument] = useLazyGetDocumentQuery();
4287
+ const currentDocument = useDocumentContext('RelationModalForm', (state)=>state.document);
4288
+ const rootDocumentMeta = useDocumentContext('RelationModalForm', (state)=>state.rootDocumentMeta);
4289
+ const currentDocumentMeta = useDocumentContext('RelationModalForm', (state)=>state.meta);
4290
+ const changeDocument = useDocumentContext('RelationModalForm', (state)=>state.changeDocument);
4291
+ const documentHistory = useDocumentContext('RelationModalForm', (state)=>state.documentHistory);
4292
+ const setDocumentHistory = useDocumentContext('RelationModalForm', (state)=>state.setDocumentHistory);
4293
+ const [isConfirmationOpen, setIsConfirmationOpen] = React.useState(false);
4294
+ const [actionPosition, setActionPosition] = React.useState('cancel');
4295
+ const [isModalOpen, setIsModalOpen] = React.useState(false);
4296
+ // NOTE: Not sure about this relation modal context, maybe we should move this to DocumentContext?
4297
+ // Get parent modal context if it exists
4298
+ const parentContext = useRelationModal('RelationModalForm', (state)=>state);
4299
+ // Get depth of nested modals
4300
+ const depth = parentContext ? parentContext.depth + 1 : 0;
4301
+ // Check if this is a nested modal
4302
+ const isNested = depth > 0;
4303
+ const addDocumentToHistory = (document)=>setDocumentHistory((prev)=>[
4304
+ ...prev,
4305
+ document
4306
+ ]);
4307
+ const getPreviousDocument = ()=>{
4308
+ if (documentHistory.length === 0) return undefined;
4309
+ const lastDocument = documentHistory[documentHistory.length - 1];
4310
+ return lastDocument;
4311
+ };
4312
+ const removeLastDocumentFromHistory = ()=>{
4313
+ setDocumentHistory((prev)=>[
4314
+ ...prev
4315
+ ].slice(0, prev.length - 1));
4316
+ };
4317
+ const handleToggleModal = ()=>{
4318
+ if (isModalOpen) {
4319
+ setIsModalOpen(false);
4320
+ const document = {
4321
+ collectionType: rootDocumentMeta.collectionType,
4322
+ model: rootDocumentMeta.model,
4323
+ documentId: rootDocumentMeta.documentId
4324
+ };
4325
+ // Change back to the root document
4326
+ changeDocument(document);
4327
+ // Reset the document history
4328
+ setDocumentHistory([]);
4329
+ // Reset action position
4330
+ setActionPosition('cancel');
4331
+ // Read from cache or refetch root document
4332
+ triggerRefetchDocument(document, // Favor the cache
4333
+ true);
4334
+ } else {
4335
+ changeDocument(relation);
4336
+ setIsModalOpen(true);
4337
+ }
4338
+ };
4339
+ const getFullPageLink = ()=>{
4340
+ const isSingleType = currentDocumentMeta.collectionType === SINGLE_TYPES;
4341
+ const queryParams = currentDocumentMeta.params?.locale ? `?plugins[i18n][locale]=${currentDocumentMeta.params.locale}` : '';
4342
+ return `/content-manager/${currentDocumentMeta.collectionType}/${currentDocumentMeta.model}${isSingleType ? '' : '/' + currentDocumentMeta.documentId}${queryParams}`;
4343
+ };
4344
+ const handleRedirection = ()=>{
4345
+ const editViewUrl = `${pathname}${search}`;
4346
+ const isRootDocumentUrl = editViewUrl.includes(getFullPageLink());
4347
+ if (isRootDocumentUrl) {
4348
+ handleToggleModal();
4349
+ } else {
4350
+ navigate(getFullPageLink());
4351
+ }
4352
+ };
4353
+ const handleConfirm = ()=>{
4354
+ if (actionPosition === 'navigate') {
4355
+ handleRedirection();
4356
+ } else if (actionPosition === 'back') {
4357
+ const previousRelation = getPreviousDocument();
4358
+ if (previousRelation) {
4359
+ removeLastDocumentFromHistory();
4360
+ changeDocument(previousRelation);
4361
+ }
4362
+ } else {
4363
+ // Add current relation to history before opening a new one in case we are opening a new one
4364
+ if (currentDocumentMeta && Object.keys(currentDocumentMeta).length > 0) {
4365
+ addDocumentToHistory(currentDocumentMeta);
4366
+ }
4367
+ handleToggleModal();
4368
+ }
4369
+ };
4370
+ return /*#__PURE__*/ jsx(Form, {
4371
+ method: "PUT",
4372
+ initialValues: currentDocument.getInitialFormValues(),
4373
+ validate: (values, options)=>{
4374
+ const yupSchema = createYupSchema(currentDocument.schema?.attributes, currentDocument.components, {
4375
+ status: currentDocument.document?.status,
4376
+ ...options
4377
+ });
4378
+ return yupSchema.validate(values, {
4379
+ abortEarly: false
4380
+ });
4381
+ },
4382
+ children: ({ modified, isSubmitting, resetForm })=>{
4383
+ // We don't count the root document, so history starts after 1
4384
+ const hasHistory = documentHistory.length > 1;
4385
+ return /*#__PURE__*/ jsxs(RelationModalProvider, {
4386
+ parentModified: modified,
4387
+ depth: depth,
4388
+ children: [
4389
+ /*#__PURE__*/ jsxs(Modal.Root, {
4390
+ open: isModalOpen,
4391
+ onOpenChange: ()=>{
4392
+ if (isModalOpen) {
4393
+ if (modified && !isSubmitting) {
4394
+ setIsConfirmationOpen(true);
4395
+ } else {
4396
+ handleToggleModal();
4397
+ }
4398
+ }
4399
+ },
4400
+ children: [
4401
+ /*#__PURE__*/ jsx(Modal.Trigger, {
4402
+ children: /*#__PURE__*/ jsx(Tooltip, {
4403
+ description: triggerButtonLabel,
4404
+ children: /*#__PURE__*/ jsx(CustomTextButton, {
4405
+ onClick: ()=>{
4406
+ // Check if parent modal has unsaved changes
4407
+ if (isNested && parentContext.parentModified) {
4408
+ setIsConfirmationOpen(true);
4409
+ // Return early to avoid opening the modal
4410
+ return;
4411
+ } else {
4412
+ if (modified && !isSubmitting) {
4413
+ setIsConfirmationOpen(true);
4414
+ } else {
4415
+ // Add current relation to history before opening a new one
4416
+ if (currentDocumentMeta && Object.keys(currentDocumentMeta).length > 0) {
4417
+ addDocumentToHistory(currentDocumentMeta);
4418
+ }
4419
+ handleToggleModal();
4420
+ }
4421
+ if (!isModalOpen) {
4422
+ setIsModalOpen(true);
4423
+ }
4424
+ }
4425
+ },
4426
+ width: "100%",
4427
+ children: triggerButtonLabel
4428
+ })
4429
+ })
4430
+ }),
4431
+ /*#__PURE__*/ jsxs(CustomModalContent, {
4432
+ children: [
4433
+ /*#__PURE__*/ jsx(Modal.Header, {
4434
+ gap: 2,
4435
+ children: /*#__PURE__*/ jsx(Flex, {
4436
+ justifyContent: "space-between",
4437
+ alignItems: "center",
4438
+ width: "100%",
4439
+ children: /*#__PURE__*/ jsxs(Flex, {
4440
+ gap: 2,
4441
+ children: [
4442
+ /*#__PURE__*/ jsx(IconButton, {
4443
+ withTooltip: false,
4444
+ label: "Back",
4445
+ variant: "ghost",
4446
+ disabled: !hasHistory,
4447
+ onClick: ()=>{
4448
+ setActionPosition('back');
4449
+ if (modified && !isSubmitting) {
4450
+ setIsConfirmationOpen(true);
4451
+ } else {
4452
+ const previousRelation = getPreviousDocument();
4453
+ if (previousRelation) {
4454
+ removeLastDocumentFromHistory();
4455
+ changeDocument(previousRelation);
4456
+ }
4457
+ }
4458
+ },
4459
+ marginRight: 1,
4460
+ children: /*#__PURE__*/ jsx(ArrowLeft, {})
4461
+ }),
4462
+ /*#__PURE__*/ jsx(Typography, {
4463
+ tag: "span",
4464
+ fontWeight: 600,
4465
+ children: formatMessage({
4466
+ id: 'content-manager.components.RelationInputModal.modal-title',
4467
+ defaultMessage: 'Edit a relation'
4468
+ })
4469
+ })
4470
+ ]
4471
+ })
4472
+ })
4473
+ }),
4474
+ /*#__PURE__*/ jsx(RelationModalBody, {
4475
+ children: /*#__PURE__*/ jsx(IconButton, {
4476
+ onClick: ()=>{
4477
+ setActionPosition('navigate');
4478
+ if (modified && !isSubmitting) {
4479
+ setIsConfirmationOpen(true);
4480
+ } else {
4481
+ navigate(getFullPageLink());
4482
+ }
4483
+ },
4484
+ variant: "tertiary",
4485
+ label: formatMessage({
4486
+ id: 'content-manager.components.RelationInputModal.button-fullpage',
4487
+ defaultMessage: 'Go to entry'
4488
+ }),
4489
+ children: /*#__PURE__*/ jsx(ArrowsOut, {})
4490
+ })
4491
+ }),
4492
+ /*#__PURE__*/ jsx(Modal.Footer, {
4493
+ children: /*#__PURE__*/ jsx(Button, {
4494
+ onClick: ()=>{
4495
+ if (modified && !isSubmitting) {
4496
+ setIsConfirmationOpen(true);
4497
+ } else {
4498
+ handleToggleModal();
4499
+ }
4500
+ },
4501
+ variant: "tertiary",
4502
+ children: formatMessage({
4503
+ id: 'app.components.Button.cancel',
4504
+ defaultMessage: 'Cancel'
4505
+ })
4506
+ })
4507
+ })
4508
+ ]
4509
+ })
4510
+ ]
4511
+ }),
4512
+ /*#__PURE__*/ jsx(Dialog.Root, {
4513
+ open: isConfirmationOpen,
4514
+ onOpenChange: setIsConfirmationOpen,
4515
+ children: /*#__PURE__*/ jsx(ConfirmDialog, {
4516
+ onConfirm: ()=>{
4517
+ handleConfirm();
4518
+ setIsConfirmationOpen(false);
4519
+ resetForm();
4520
+ },
4521
+ onCancel: ()=>{
4522
+ setIsConfirmationOpen(false);
4523
+ },
4524
+ variant: "danger",
4525
+ children: formatMessage({
4526
+ id: 'content-manager.components.RelationInputModal.confirmation-message',
4527
+ defaultMessage: 'Some changes were not saved. Are you sure you want to close this relation? All changes that were not saved will be lost.'
4528
+ })
4529
+ })
4530
+ })
4531
+ ]
4532
+ });
4533
+ }
4534
+ });
4535
+ };
4536
+ const CustomTextButton = styled(TextButton)`
4537
+ & > span {
4538
+ font-size: ${({ theme })=>theme.fontSizes[2]};
4539
+ width: inherit;
4540
+ overflow: hidden;
4541
+ white-space: nowrap;
4542
+ text-overflow: ellipsis;
4543
+ }
4544
+ `;
4545
+ const RelationModalBody = ({ children })=>{
4546
+ const { formatMessage } = useIntl();
4547
+ const documentMeta = useDocumentContext('RelationModalBody', (state)=>state.meta);
4548
+ const documentResponse = useDocumentContext('RelationModalBody', (state)=>state.document);
4549
+ const onPreview = useDocumentContext('RelationModalBody', (state)=>state.onPreview);
4550
+ const documentLayoutResponse = useDocumentLayout(documentMeta.model);
4551
+ const plugins = useStrapiApp('RelationModalBody', (state)=>state.plugins);
4552
+ const initialValues = documentResponse.getInitialFormValues();
4553
+ const { permissions = [], isLoading: isLoadingPermissions, error } = useRBAC(PERMISSIONS.map((action)=>({
4554
+ action,
4555
+ subject: documentMeta.model
4556
+ })));
4557
+ const isLoading = isLoadingPermissions || documentLayoutResponse.isLoading || documentResponse.isLoading;
4558
+ if (isLoading && !documentResponse.document?.documentId) {
4559
+ return /*#__PURE__*/ jsx(Loader, {
4560
+ small: true,
4561
+ children: formatMessage({
4562
+ id: 'content-manager.ListViewTable.relation-loading',
4563
+ defaultMessage: 'Relations are loading'
4564
+ })
4565
+ });
4566
+ }
4567
+ if (error || !documentMeta.model || documentLayoutResponse.error || !documentResponse.document || !documentResponse.meta || !documentResponse.schema || !initialValues) {
4568
+ return /*#__PURE__*/ jsx(Flex, {
4569
+ alignItems: "center",
4570
+ height: "100%",
4571
+ justifyContent: "center",
4572
+ children: /*#__PURE__*/ jsx(EmptyStateLayout, {
4573
+ icon: /*#__PURE__*/ jsx(WarningCircle, {
4574
+ width: "16rem"
4575
+ }),
4576
+ content: formatMessage({
4577
+ id: 'anErrorOccurred',
4578
+ defaultMessage: 'Whoops! Something went wrong. Please, try again.'
4579
+ })
4580
+ })
4581
+ });
4582
+ }
4583
+ const documentTitle = documentResponse.getTitle(documentLayoutResponse.edit.settings.mainField);
4584
+ const hasDraftAndPublished = documentResponse.schema?.options?.draftAndPublish ?? false;
4585
+ const props = {
4586
+ activeTab: 'draft',
4587
+ collectionType: documentMeta.collectionType,
4588
+ model: documentMeta.model,
4589
+ documentId: documentMeta.documentId,
4590
+ document: documentResponse.document,
4591
+ meta: documentResponse.meta,
4592
+ onPreview,
4593
+ fromRelationModal: true,
4594
+ fromPreview: onPreview !== undefined
4595
+ };
4596
+ return /*#__PURE__*/ jsx(Modal.Body, {
4597
+ children: /*#__PURE__*/ jsxs(DocumentRBAC, {
4598
+ permissions: permissions,
4599
+ model: documentMeta.model,
4600
+ children: [
4601
+ /*#__PURE__*/ jsxs(Flex, {
4602
+ alignItems: "flex-start",
4603
+ direction: "column",
4604
+ gap: 2,
4605
+ children: [
4606
+ /*#__PURE__*/ jsxs(Flex, {
4607
+ width: "100%",
4608
+ justifyContent: "space-between",
4609
+ gap: 2,
4610
+ children: [
4611
+ /*#__PURE__*/ jsx(Typography, {
4612
+ tag: "h2",
4613
+ variant: "alpha",
4614
+ children: documentTitle
4615
+ }),
4616
+ /*#__PURE__*/ jsxs(Flex, {
4617
+ gap: 2,
4618
+ children: [
4619
+ children,
4620
+ /*#__PURE__*/ jsx(DescriptionComponentRenderer, {
4621
+ props: props,
4622
+ descriptions: plugins['content-manager'].apis.getDocumentActions('relation-modal'),
4623
+ children: (actions)=>{
4624
+ const filteredActions = actions.filter((action)=>{
4625
+ return [
4626
+ action.position
4627
+ ].flat().includes('relation-modal');
4628
+ });
4629
+ const [primaryAction, secondaryAction] = filteredActions;
4630
+ if (!primaryAction && !secondaryAction) return null;
4631
+ // Both actions are available when draft and publish enabled
4632
+ if (primaryAction && secondaryAction) {
4633
+ return /*#__PURE__*/ jsxs(Fragment, {
4634
+ children: [
4635
+ /*#__PURE__*/ jsx(DocumentActionButton, {
4636
+ ...secondaryAction,
4637
+ variant: secondaryAction.variant || 'secondary'
4638
+ }),
4639
+ /*#__PURE__*/ jsx(DocumentActionButton, {
4640
+ ...primaryAction,
4641
+ variant: primaryAction.variant || 'default'
4642
+ })
4643
+ ]
4644
+ });
4645
+ }
4646
+ // Otherwise we just have the save action
4647
+ return /*#__PURE__*/ jsx(DocumentActionButton, {
4648
+ ...primaryAction,
4649
+ variant: primaryAction.variant || 'secondary'
4650
+ });
4651
+ }
4652
+ })
4653
+ ]
4654
+ })
4655
+ ]
4656
+ }),
4657
+ hasDraftAndPublished ? /*#__PURE__*/ jsx(Box, {
4658
+ children: /*#__PURE__*/ jsx(DocumentStatus, {
4659
+ status: documentResponse.document?.status
4660
+ })
4661
+ }) : null
4662
+ ]
4663
+ }),
4664
+ /*#__PURE__*/ jsx(Flex, {
4665
+ flex: 1,
4666
+ overflow: "auto",
4667
+ alignItems: "stretch",
4668
+ paddingTop: 7,
4669
+ children: /*#__PURE__*/ jsx(Box, {
4670
+ overflow: "auto",
4671
+ flex: 1,
4672
+ children: /*#__PURE__*/ jsx(FormLayout, {
4673
+ layout: documentLayoutResponse.edit.layout,
4674
+ document: documentResponse,
4675
+ hasBackground: false
4676
+ })
4677
+ })
4678
+ })
4679
+ ]
4680
+ })
4681
+ });
4682
+ };
4683
+
4684
+ /**
4685
+ * Remove a relation, whether it's been already saved or not.
4686
+ * It's used both in RelationsList, where the "remove relation" button is, and in the input,
4687
+ * because we sometimes need to remove a previous relation when selecting a new one.
4688
+ */ function useHandleDisconnect(fieldName, consumerName) {
4689
+ const field = useField(fieldName);
4690
+ const removeFieldRow = useForm(consumerName, (state)=>state.removeFieldRow);
4691
+ const addFieldRow = useForm(consumerName, (state)=>state.addFieldRow);
4692
+ const handleDisconnect = (relation)=>{
4693
+ if (field.value && field.value.connect) {
4694
+ /**
4695
+ * A relation will exist in the `connect` array _if_ it has
4696
+ * been added without saving. In this case, we just remove it
4697
+ * from the connect array
4698
+ */ const indexOfRelationInConnectArray = field.value.connect.findIndex((rel)=>rel.id === relation.id);
4699
+ if (indexOfRelationInConnectArray >= 0) {
4700
+ removeFieldRow(`${fieldName}.connect`, indexOfRelationInConnectArray);
4701
+ return;
4702
+ }
4703
+ }
4704
+ addFieldRow(`${fieldName}.disconnect`, {
4705
+ id: relation.id,
4706
+ apiData: {
4707
+ id: relation.id,
4708
+ documentId: relation.documentId,
4709
+ locale: relation.locale
4710
+ }
4711
+ });
4712
+ };
4713
+ return handleDisconnect;
4714
+ }
4715
+ /* -------------------------------------------------------------------------------------------------
4716
+ * RelationsField
4717
+ * -----------------------------------------------------------------------------------------------*/ const RELATIONS_TO_DISPLAY = 5;
4718
+ const ONE_WAY_RELATIONS = [
4719
+ 'oneWay',
4720
+ 'oneToOne',
4721
+ 'manyToOne',
4722
+ 'oneToManyMorph',
4723
+ 'oneToOneMorph'
4724
+ ];
4725
+ /**
4726
+ * TODO: we get a rather ugly flash when we remove a single relation from the list leaving
4727
+ * no other relations when we press save. The initial relation re-renders, probably because
4728
+ * of the lag in the Form cleaning it's "disconnect" array, whilst our data has not been invalidated.
4729
+ *
4730
+ * Could we invalidate relation data on the document actions? Should we?
4731
+ */ /**
4732
+ * @internal
4733
+ * @description The relations field holds a lot of domain logic for handling relations which is rather complicated
4734
+ * At present we do not expose this to plugin developers, however, they are able to overwrite it themselves should
4735
+ * they wish to do so.
4736
+ */ const RelationsField = /*#__PURE__*/ React.forwardRef(({ disabled, label, ...props }, ref)=>{
4737
+ const currentDocumentMeta = useDocumentContext('RelationsField', (state)=>state.meta);
4738
+ const currentDocument = useDocumentContext('RelationsField', (state)=>state.document);
4739
+ const rootDocumentMeta = useDocumentContext('RelationsField', (state)=>state.rootDocumentMeta);
4740
+ const [currentPage, setCurrentPage] = React.useState(1);
4741
+ const isRootDocument = rootDocumentMeta.documentId === currentDocumentMeta.documentId;
4742
+ const documentMeta = isRootDocument ? rootDocumentMeta : currentDocumentMeta;
4743
+ // Use the documentId from the actual document, not the params (meta)
4744
+ const documentId = currentDocument.document?.documentId;
4745
+ const { formatMessage } = useIntl();
4746
+ const [{ query }] = useQueryParams();
4747
+ const params = documentMeta.params ?? buildValidParams(query);
4748
+ const isMorph = props.attribute.relation.toLowerCase().includes('morph');
4749
+ const isDisabled = isMorph || disabled;
4750
+ const { componentId, componentUID } = useComponent('RelationsField', ({ uid, id })=>({
4751
+ componentId: id,
4752
+ componentUID: uid
4753
+ }));
4754
+ const isSubmitting = useForm('RelationsList', (state)=>state.isSubmitting);
4755
+ React.useEffect(()=>{
4756
+ setCurrentPage(1);
4757
+ }, [
4758
+ isSubmitting
4759
+ ]);
4760
+ const component = componentUID && currentDocument.components[componentUID];
4761
+ /**
4762
+ * We'll always have a documentId in a created entry, so we look for a componentId first.
4763
+ * Same with `uid` and `documentModel`.
4764
+ */ const model = component ? component.uid : documentMeta.model;
4765
+ const id = component && componentId ? componentId.toString() : documentId;
4766
+ /**
4767
+ * The `name` prop is a complete path to the field, e.g. `field1.field2.field3`.
4768
+ * Where the above example would a nested field within two components, however
4769
+ * we only require the field on the component not the complete path since we query
4770
+ * individual components. Therefore we split the string and take the last item.
4771
+ */ const [targetField] = props.name.split('.').slice(-1);
4772
+ const schemaAttributes = component ? component.attributes ?? {} : currentDocument.schema?.attributes ?? {};
4773
+ /**
4774
+ * Confirm the target field is related to the current document.
4775
+ * Since relations can exist in a modal on top of the root document,
4776
+ * we need to ensure we are fetching relations for the correct document (root document vs related document),
4777
+ */ const isRelatedToCurrentDocument = Object.values(schemaAttributes).filter((attribute)=>attribute.type === 'relation' && 'target' in attribute && 'target' in props.attribute && attribute.target === props.attribute.target).length > 0;
4778
+ const { data, isLoading, isFetching } = useGetRelationsQuery({
4779
+ model,
4780
+ targetField,
4781
+ // below we don't run the query if there is no id.
4782
+ id,
4783
+ params: {
4784
+ ...params,
4785
+ pageSize: RELATIONS_TO_DISPLAY,
4786
+ page: currentPage
4787
+ }
4788
+ }, {
4789
+ refetchOnMountOrArgChange: true,
4790
+ skip: !id || !isRelatedToCurrentDocument,
4791
+ selectFromResult: (result)=>{
4792
+ return {
4793
+ ...result,
4794
+ data: {
4795
+ ...result.data,
4796
+ results: result.data?.results ? result.data.results : []
4797
+ }
4798
+ };
4799
+ }
4800
+ });
4801
+ const handleLoadMore = ()=>{
4802
+ setCurrentPage((prev)=>prev + 1);
4803
+ };
4804
+ const field = useField(props.name);
4805
+ const isFetchingMoreRelations = isLoading || isFetching;
4806
+ const realServerRelationsCount = 'pagination' in data && data.pagination ? data.pagination.total : 0;
4807
+ /**
4808
+ * Items that are already connected, but reordered would be in
4809
+ * this list, so to get an accurate figure, we remove them.
4810
+ */ const relationsConnected = (field.value?.connect ?? []).filter((rel)=>data.results.findIndex((relation)=>relation.id === rel.id) === -1).length ?? 0;
4811
+ const relationsDisconnected = field.value?.disconnect?.length ?? 0;
4812
+ const relationsCount = realServerRelationsCount + relationsConnected - relationsDisconnected;
4813
+ /**
4814
+ * This is it, the source of truth for reordering in conjunction with partial loading & updating
4815
+ * of relations. Relations on load are given __temp_key__ when fetched, because we don't want to
4816
+ * create brand new keys everytime the data updates, just keep adding them onto the newly loaded ones.
4817
+ */ const relations = React.useMemo(()=>{
4818
+ const ctx = {
4819
+ field: field.value,
4820
+ // @ts-expect-error – targetModel does exist on the attribute. But it's not typed.
4821
+ href: `../${COLLECTION_TYPES}/${props.attribute.targetModel}`,
4822
+ mainField: props.mainField
4823
+ };
4824
+ /**
4825
+ * Tidy up our data.
4826
+ */ const transformations = pipe$1(removeConnected(ctx), removeDisconnected(ctx), addLabelAndHref(ctx));
4827
+ const transformedRels = transformations([
4828
+ ...data.results
4829
+ ]);
4830
+ /**
4831
+ * THIS IS CRUCIAL. If you don't sort by the __temp_key__ which comes from fractional indexing
4832
+ * then the list will be in the wrong order.
4833
+ */ return [
4834
+ ...transformedRels,
4835
+ ...field.value?.connect ?? []
4836
+ ].sort((a, b)=>{
4837
+ if (a.__temp_key__ < b.__temp_key__) return -1;
4838
+ if (a.__temp_key__ > b.__temp_key__) return 1;
4839
+ return 0;
4840
+ });
4841
+ }, [
4842
+ data.results,
4843
+ field.value,
4844
+ // @ts-expect-error – targetModel does exist on the attribute. But it's not typed.
4845
+ props.attribute.targetModel,
4846
+ props.mainField
4847
+ ]);
4848
+ const handleDisconnect = useHandleDisconnect(props.name, 'RelationsField');
4849
+ const handleConnect = (relation)=>{
4850
+ const [lastItemInList] = relations.slice(-1);
4851
+ const item = {
4852
+ id: relation.id,
4853
+ apiData: {
4854
+ id: relation.id,
4855
+ documentId: relation.documentId,
4856
+ locale: relation.locale
4857
+ },
4858
+ status: relation.status,
4859
+ /**
4860
+ * If there's a last item, that's the first key we use to generate out next one.
4861
+ */ __temp_key__: generateNKeysBetween(lastItemInList?.__temp_key__ ?? null, null, 1)[0],
4862
+ // Fallback to `id` if there is no `mainField` value, which will overwrite the above `id` property with the exact same data.
4863
+ [props.mainField?.name ?? 'documentId']: relation[props.mainField?.name ?? 'documentId'],
4864
+ label: getRelationLabel(relation, props.mainField),
4865
+ // @ts-expect-error – targetModel does exist on the attribute, but it's not typed.
4866
+ href: `../${COLLECTION_TYPES}/${props.attribute.targetModel}/${relation.documentId}?${relation.locale ? `plugins[i18n][locale]=${relation.locale}` : ''}`
4867
+ };
4868
+ if (ONE_WAY_RELATIONS.includes(props.attribute.relation)) {
4869
+ // Remove any existing relation so they can be replaced with the new one
4870
+ field.value?.connect?.forEach(handleDisconnect);
4871
+ relations.forEach(handleDisconnect);
4872
+ field.onChange(`${props.name}.connect`, [
4873
+ item
4874
+ ]);
4875
+ } else {
4876
+ field.onChange(`${props.name}.connect`, [
4877
+ ...field.value?.connect ?? [],
4878
+ item
4879
+ ]);
4880
+ }
4881
+ };
4882
+ return /*#__PURE__*/ jsxs(Flex, {
4883
+ ref: ref,
4884
+ direction: "column",
4885
+ gap: 3,
4886
+ justifyContent: "space-between",
4887
+ alignItems: "stretch",
4888
+ wrap: "wrap",
4889
+ children: [
4890
+ /*#__PURE__*/ jsxs(StyledFlex, {
4891
+ direction: "column",
4892
+ alignItems: "start",
4893
+ gap: 2,
4894
+ width: "100%",
4895
+ children: [
4896
+ /*#__PURE__*/ jsx(RelationsInput, {
4897
+ disabled: isDisabled,
4898
+ // NOTE: we should not default to using the documentId if the component is being created (componentUID is undefined)
4899
+ id: componentUID && component ? componentId ? `${componentId}` : '' : documentId,
4900
+ label: `${label} ${relationsCount > 0 ? `(${relationsCount})` : ''}`,
4901
+ model: model,
4902
+ onChange: handleConnect,
4903
+ isRelatedToCurrentDocument: isRelatedToCurrentDocument,
4904
+ ...props
4905
+ }),
4906
+ 'pagination' in data && data.pagination && data.pagination.pageCount > data.pagination.page ? /*#__PURE__*/ jsx(TextButton, {
4907
+ disabled: isFetchingMoreRelations,
4908
+ onClick: handleLoadMore,
4909
+ loading: isFetchingMoreRelations,
4910
+ startIcon: /*#__PURE__*/ jsx(ArrowClockwise, {}),
4911
+ // prevent the label from line-wrapping
4912
+ shrink: 0,
4913
+ children: formatMessage({
4914
+ id: getTranslation('relation.loadMore'),
4915
+ defaultMessage: 'Load More'
4916
+ })
4917
+ }) : null
4918
+ ]
4919
+ }),
4920
+ /*#__PURE__*/ jsx(RelationsList, {
4921
+ data: relations,
4922
+ serverData: data.results,
4923
+ disabled: isDisabled,
4924
+ name: props.name,
4925
+ isLoading: isFetchingMoreRelations,
4926
+ relationType: props.attribute.relation,
4927
+ // @ts-expect-error – targetModel does exist on the attribute. But it's not typed.
4928
+ targetModel: props.attribute.targetModel
4929
+ })
4930
+ ]
4931
+ });
4932
+ });
4933
+ /**
4934
+ * TODO: this can be removed once we stop shipping Inputs with
4935
+ * labels wrapped round in DS@2.
4936
+ */ const StyledFlex = styled(Flex)`
4937
+ & > div {
4938
+ width: 100%;
4939
+ }
4940
+ `;
4941
+ /**
4942
+ * If it's in the connected array, it can get out of our data array,
4943
+ * we'll be putting it back in later and sorting it anyway.
4944
+ */ const removeConnected = ({ field })=>(relations)=>{
4945
+ return relations.filter((relation)=>{
4946
+ const connectedRelations = field?.connect ?? [];
4947
+ return connectedRelations.findIndex((rel)=>rel.id === relation.id) === -1;
4948
+ });
4949
+ };
4950
+ /**
4951
+ * @description Removes relations that are in the `disconnect` array of the field
4952
+ */ const removeDisconnected = ({ field })=>(relations)=>relations.filter((relation)=>{
4953
+ const disconnectedRelations = field?.disconnect ?? [];
4954
+ return disconnectedRelations.findIndex((rel)=>rel.id === relation.id) === -1;
4955
+ });
4956
+ /**
4957
+ * @description Adds a label and href to the relation object we use this to render
4958
+ * a better UI where we can link to the relation and display a human-readable label.
4959
+ */ const addLabelAndHref = ({ mainField, href })=>(relations)=>relations.map((relation)=>{
4960
+ return {
4961
+ ...relation,
4962
+ // Fallback to `id` if there is no `mainField` value, which will overwrite the above `documentId` property with the exact same data.
4963
+ [mainField?.name ?? 'documentId']: relation[mainField?.name ?? 'documentId'],
4964
+ label: getRelationLabel(relation, mainField),
4965
+ href: `${href}/${relation.documentId}?${relation.locale ? `plugins[i18n][locale]=${relation.locale}` : ''}`
4966
+ };
4967
+ });
4968
+ /**
4969
+ * @description Contains all the logic for the combobox that can search
4970
+ * for relations and then add them to the field's connect array.
4971
+ */ const RelationsInput = ({ hint, id, model, label, labelAction, name, mainField, placeholder, required, unique: _unique, 'aria-label': _ariaLabel, onChange, isRelatedToCurrentDocument, ...props })=>{
4972
+ const [textValue, setTextValue] = React.useState('');
4973
+ const [searchParams, setSearchParams] = React.useState({
4974
+ _q: '',
4975
+ page: 1
4976
+ });
4977
+ const { toggleNotification } = useNotification();
4978
+ const [{ query }] = useQueryParams();
4979
+ const currentDocumentMeta = useDocumentContext('RelationsInput', (state)=>state.meta);
4980
+ const rootDocumentMeta = useDocumentContext('RelationsInput', (state)=>state.rootDocumentMeta);
4981
+ const isRootDocument = rootDocumentMeta.documentId === currentDocumentMeta.documentId;
4982
+ const documentMeta = isRootDocument ? rootDocumentMeta : currentDocumentMeta;
4983
+ const { formatMessage } = useIntl();
4984
+ const fieldRef = useFocusInputField(name);
4985
+ const field = useField(name);
4986
+ const searchParamsDebounced = useDebounce(searchParams, 300);
4987
+ const [searchForTrigger, { data, isLoading }] = useLazySearchRelationsQuery();
4988
+ /**
4989
+ * Because we're using a lazy query, we need to trigger the search
4990
+ * when the component mounts and when the search params change.
4991
+ * We also need to trigger the search when the field value changes
4992
+ * so that we can filter out the relations that are already connected.
4993
+ */ React.useEffect(()=>{
4994
+ /**
4995
+ * The `name` prop is a complete path to the field, e.g. `field1.field2.field3`.
4996
+ * Where the above example would a nested field within two components, however
4997
+ * we only require the field on the component not the complete path since we query
4998
+ * individual components. Therefore we split the string and take the last item.
4999
+ */ const [targetField] = name.split('.').slice(-1);
5000
+ // Return early if there is no relation to the document
5001
+ if (!isRelatedToCurrentDocument) return;
5002
+ const params = documentMeta.params ?? buildValidParams(query);
5003
+ searchForTrigger({
5004
+ model,
5005
+ targetField,
5006
+ params: {
5007
+ ...params,
5008
+ id: id ?? '',
5009
+ pageSize: 10,
5010
+ idsToInclude: field.value?.disconnect?.map((rel)=>rel.id.toString()) ?? [],
5011
+ idsToOmit: field.value?.connect?.map((rel)=>rel.id.toString()) ?? [],
5012
+ ...searchParamsDebounced
5013
+ }
5014
+ });
5015
+ }, [
5016
+ field.value?.connect,
5017
+ field.value?.disconnect,
5018
+ id,
5019
+ model,
5020
+ name,
5021
+ query,
5022
+ searchForTrigger,
5023
+ searchParamsDebounced,
5024
+ isRelatedToCurrentDocument,
5025
+ documentMeta
5026
+ ]);
5027
+ const handleSearch = async (search)=>{
5028
+ setSearchParams((s)=>({
5029
+ ...s,
5030
+ _q: search,
5031
+ page: 1
5032
+ }));
5033
+ };
5034
+ const hasNextPage = data?.pagination ? data.pagination.page < data.pagination.pageCount : false;
5035
+ const options = data?.results ?? [];
5036
+ const handleChange = (relationId)=>{
5037
+ if (!relationId) {
5038
+ return;
5039
+ }
5040
+ const relation = options.find((opt)=>opt.id.toString() === relationId);
5041
+ if (!relation) {
5042
+ // This is very unlikely to happen, but it ensures we don't have any data for.
5043
+ console.error("You've tried to add a relation with an id that does not exist in the options you can see, this is likely a bug with Strapi. Please open an issue.");
5044
+ toggleNotification({
5045
+ message: formatMessage({
5046
+ id: getTranslation('relation.error-adding-relation'),
5047
+ defaultMessage: 'An error occurred while trying to add the relation.'
5048
+ }),
5049
+ type: 'danger'
5050
+ });
5051
+ return;
5052
+ }
5053
+ /**
5054
+ * You need to give this relation a correct _temp_key_ but
5055
+ * this component doesn't know about those ones, you can't rely
5056
+ * on the connect array because that doesn't hold items that haven't
5057
+ * moved. So use a callback to fill in the gaps when connecting.
5058
+ *
5059
+ */ onChange(relation);
5060
+ };
5061
+ const handleLoadMore = ()=>{
5062
+ if (!data || !data.pagination) {
5063
+ return;
5064
+ } else if (data.pagination.page < data.pagination.pageCount) {
5065
+ setSearchParams((s)=>({
5066
+ ...s,
5067
+ page: s.page + 1
5068
+ }));
5069
+ }
5070
+ };
5071
+ React.useLayoutEffect(()=>{
5072
+ setTextValue('');
5073
+ }, [
5074
+ field.value
5075
+ ]);
5076
+ return /*#__PURE__*/ jsxs(Field.Root, {
5077
+ error: field.error,
5078
+ hint: hint,
5079
+ name: name,
5080
+ required: required,
5081
+ children: [
5082
+ /*#__PURE__*/ jsx(Field.Label, {
5083
+ action: labelAction,
5084
+ children: label
5085
+ }),
5086
+ /*#__PURE__*/ jsx(Combobox, {
5087
+ ref: fieldRef,
5088
+ name: name,
5089
+ autocomplete: "list",
5090
+ placeholder: placeholder || formatMessage({
5091
+ id: getTranslation('relation.add'),
5092
+ defaultMessage: 'Add relation'
5093
+ }),
5094
+ hasMoreItems: hasNextPage,
5095
+ loading: isLoading,
5096
+ onOpenChange: ()=>{
5097
+ handleSearch(textValue ?? '');
5098
+ },
5099
+ noOptionsMessage: ()=>formatMessage({
5100
+ id: getTranslation('relation.notAvailable'),
5101
+ defaultMessage: 'No relations available'
5102
+ }),
5103
+ loadingMessage: formatMessage({
5104
+ id: getTranslation('relation.isLoading'),
5105
+ defaultMessage: 'Relations are loading'
5106
+ }),
5107
+ onLoadMore: handleLoadMore,
5108
+ textValue: textValue,
5109
+ onChange: handleChange,
5110
+ onTextValueChange: (text)=>{
5111
+ setTextValue(text);
5112
+ },
5113
+ onInputChange: (event)=>{
5114
+ handleSearch(event.currentTarget.value);
5115
+ },
5116
+ ...props,
5117
+ children: options.map((opt)=>{
5118
+ const textValue = getRelationLabel(opt, mainField);
5119
+ return /*#__PURE__*/ jsx(ComboboxOption, {
5120
+ value: opt.id.toString(),
5121
+ textValue: textValue,
5122
+ children: /*#__PURE__*/ jsxs(Flex, {
5123
+ gap: 2,
5124
+ justifyContent: "space-between",
5125
+ children: [
5126
+ /*#__PURE__*/ jsx(Typography, {
5127
+ ellipsis: true,
5128
+ children: textValue
5129
+ }),
5130
+ opt.status ? /*#__PURE__*/ jsx(DocumentStatus, {
5131
+ status: opt.status
5132
+ }) : null
5133
+ ]
5134
+ })
5135
+ }, opt.id);
5136
+ })
5137
+ }),
5138
+ /*#__PURE__*/ jsx(Field.Error, {}),
5139
+ /*#__PURE__*/ jsx(Field.Hint, {})
5140
+ ]
5141
+ });
5142
+ };
5143
+ /* -------------------------------------------------------------------------------------------------
5144
+ * RelationsList
5145
+ * -----------------------------------------------------------------------------------------------*/ const RELATION_ITEM_HEIGHT = 50;
5146
+ const RELATION_GUTTER = 4;
5147
+ const RelationsList = ({ data, serverData, disabled, name, isLoading, relationType, targetModel })=>{
5148
+ const ariaDescriptionId = React.useId();
5149
+ const { formatMessage } = useIntl();
5150
+ const listRef = React.useRef(null);
5151
+ const outerListRef = React.useRef(null);
5152
+ const [overflow, setOverflow] = React.useState();
5153
+ const [liveText, setLiveText] = React.useState('');
5154
+ const field = useField(name);
5155
+ React.useEffect(()=>{
5156
+ if (data.length <= RELATIONS_TO_DISPLAY) {
5157
+ return setOverflow(undefined);
5158
+ }
5159
+ const handleNativeScroll = (e)=>{
5160
+ const el = e.target;
5161
+ const parentScrollContainerHeight = el.parentNode.scrollHeight;
5162
+ const maxScrollBottom = el.scrollHeight - el.scrollTop;
5163
+ if (el.scrollTop === 0) {
5164
+ return setOverflow('bottom');
5165
+ }
5166
+ if (maxScrollBottom === parentScrollContainerHeight) {
5167
+ return setOverflow('top');
5168
+ }
5169
+ return setOverflow('top-bottom');
5170
+ };
5171
+ const outerListRefCurrent = outerListRef?.current;
5172
+ if (!isLoading && data.length > 0 && outerListRefCurrent) {
5173
+ outerListRef.current.addEventListener('scroll', handleNativeScroll);
5174
+ }
5175
+ return ()=>{
5176
+ if (outerListRefCurrent) {
5177
+ outerListRefCurrent.removeEventListener('scroll', handleNativeScroll);
5178
+ }
5179
+ };
5180
+ }, [
5181
+ isLoading,
5182
+ data.length
5183
+ ]);
5184
+ const getItemPos = (index)=>`${index + 1} of ${data.length}`;
5185
+ const handleMoveItem = (newIndex, oldIndex)=>{
5186
+ const item = data[oldIndex];
5187
+ setLiveText(formatMessage({
5188
+ id: getTranslation('dnd.reorder'),
5189
+ defaultMessage: '{item}, moved. New position in list: {position}.'
5190
+ }, {
5191
+ item: item.label ?? item.documentId,
5192
+ position: getItemPos(newIndex)
5193
+ }));
5194
+ /**
5195
+ * Splicing mutates the array, so we need to create a new array
5196
+ */ const newData = [
5197
+ ...data
5198
+ ];
5199
+ const currentRow = data[oldIndex];
5200
+ const startKey = oldIndex > newIndex ? newData[newIndex - 1]?.__temp_key__ : newData[newIndex]?.__temp_key__;
5201
+ const endKey = oldIndex > newIndex ? newData[newIndex]?.__temp_key__ : newData[newIndex + 1]?.__temp_key__;
5202
+ /**
5203
+ * We're moving the relation between two other relations, so
5204
+ * we need to generate a new key that keeps the order
5205
+ */ const [newKey] = generateNKeysBetween(startKey, endKey, 1);
5206
+ newData.splice(oldIndex, 1);
5207
+ newData.splice(newIndex, 0, {
5208
+ ...currentRow,
5209
+ __temp_key__: newKey
5210
+ });
5211
+ /**
5212
+ * Now we diff against the server to understand what's different so we
5213
+ * can keep the connect array nice and tidy. It also needs reversing because
5214
+ * we reverse the relations from the server in the first place.
5215
+ */ const connectedRelations = newData.reduce((acc, relation, currentIndex, array)=>{
5216
+ const relationOnServer = serverData.find((oldRelation)=>oldRelation.id === relation.id);
5217
+ const relationInFront = array[currentIndex + 1];
5218
+ if (!relationOnServer || relationOnServer.__temp_key__ !== relation.__temp_key__) {
5219
+ const position = relationInFront ? {
5220
+ before: relationInFront.documentId,
5221
+ locale: relationInFront.locale,
5222
+ status: 'publishedAt' in relationInFront && relationInFront.publishedAt ? 'published' : 'draft'
5223
+ } : {
5224
+ end: true
5225
+ };
5226
+ const relationWithPosition = {
5227
+ ...relation,
5228
+ ...{
5229
+ apiData: {
5230
+ id: relation.id,
5231
+ documentId: relation.documentId ?? relation.apiData?.documentId ?? '',
5232
+ locale: relation.locale || relation.apiData?.locale,
5233
+ position
5234
+ }
5235
+ }
5236
+ };
5237
+ return [
5238
+ ...acc,
5239
+ relationWithPosition
5240
+ ];
5241
+ }
5242
+ return acc;
5243
+ }, []).toReversed();
5244
+ field.onChange(`${name}.connect`, connectedRelations);
5245
+ };
5246
+ const handleGrabItem = (index)=>{
5247
+ const item = data[index];
5248
+ setLiveText(formatMessage({
5249
+ id: getTranslation('dnd.grab-item'),
5250
+ defaultMessage: `{item}, grabbed. Current position in list: {position}. Press up and down arrow to change position, Spacebar to drop, Escape to cancel.`
5251
+ }, {
5252
+ item: item.label ?? item.documentId,
5253
+ position: getItemPos(index)
5254
+ }));
5255
+ };
5256
+ const handleDropItem = (index)=>{
5257
+ const { href: _href, label, ...item } = data[index];
5258
+ setLiveText(formatMessage({
5259
+ id: getTranslation('dnd.drop-item'),
5260
+ defaultMessage: `{item}, dropped. Final position in list: {position}.`
5261
+ }, {
5262
+ item: label ?? item.documentId,
5263
+ position: getItemPos(index)
5264
+ }));
5265
+ };
5266
+ const handleCancel = (index)=>{
5267
+ const item = data[index];
5268
+ setLiveText(formatMessage({
5269
+ id: getTranslation('dnd.cancel-item'),
5270
+ defaultMessage: '{item}, dropped. Re-order cancelled.'
5271
+ }, {
5272
+ item: item.label ?? item.documentId
5273
+ }));
5274
+ };
5275
+ const handleDisconnect = useHandleDisconnect(name, 'RelationsList');
5276
+ /**
5277
+ * These relation types will only ever have one item
5278
+ * in their list, so you can't reorder a single item!
5279
+ */ const canReorder = !ONE_WAY_RELATIONS.includes(relationType);
5280
+ const dynamicListHeight = data.length > RELATIONS_TO_DISPLAY ? Math.min(data.length, RELATIONS_TO_DISPLAY) * (RELATION_ITEM_HEIGHT + RELATION_GUTTER) + RELATION_ITEM_HEIGHT / 2 : Math.min(data.length, RELATIONS_TO_DISPLAY) * (RELATION_ITEM_HEIGHT + RELATION_GUTTER);
5281
+ return /*#__PURE__*/ jsxs(ShadowBox, {
5282
+ $overflowDirection: overflow,
5283
+ children: [
5284
+ /*#__PURE__*/ jsx(VisuallyHidden, {
5285
+ id: ariaDescriptionId,
5286
+ children: formatMessage({
5287
+ id: getTranslation('dnd.instructions'),
5288
+ defaultMessage: `Press spacebar to grab and re-order`
5289
+ })
5290
+ }),
5291
+ /*#__PURE__*/ jsx(VisuallyHidden, {
5292
+ "aria-live": "assertive",
5293
+ children: liveText
5294
+ }),
5295
+ /*#__PURE__*/ jsx(FixedSizeList, {
5296
+ height: dynamicListHeight,
5297
+ ref: listRef,
5298
+ outerRef: outerListRef,
5299
+ itemCount: data.length,
5300
+ itemSize: RELATION_ITEM_HEIGHT + RELATION_GUTTER,
5301
+ itemData: {
5302
+ ariaDescribedBy: ariaDescriptionId,
5303
+ canDrag: canReorder,
5304
+ disabled,
5305
+ handleCancel,
5306
+ handleDropItem,
5307
+ handleGrabItem,
5308
+ handleMoveItem,
5309
+ name,
5310
+ handleDisconnect,
5311
+ relations: data,
5312
+ targetModel
5313
+ },
5314
+ itemKey: (index)=>data[index].id,
5315
+ innerElementType: "ol",
5316
+ children: ListItem
5317
+ })
5318
+ ]
5319
+ });
5320
+ };
5321
+ const ShadowBox = styled(Box)`
5322
+ position: relative;
5323
+ overflow: hidden;
5324
+ flex: 1;
5325
+
5326
+ &:before,
5327
+ &:after {
5328
+ position: absolute;
5329
+ width: 100%;
5330
+ height: 4px;
5331
+ z-index: 1;
5332
+ }
5333
+
5334
+ &:before {
5335
+ /* TODO: as for DS Table component we would need this to be handled by the DS theme */
5336
+ content: '';
5337
+ background: linear-gradient(rgba(3, 3, 5, 0.2) 0%, rgba(0, 0, 0, 0) 100%);
5338
+ top: 0;
5339
+ opacity: ${({ $overflowDirection })=>$overflowDirection === 'top-bottom' || $overflowDirection === 'top' ? 1 : 0};
5340
+ transition: opacity 0.2s ease-in-out;
5341
+ }
5342
+
5343
+ &:after {
5344
+ /* TODO: as for DS Table component we would need this to be handled by the DS theme */
5345
+ content: '';
5346
+ background: linear-gradient(0deg, rgba(3, 3, 5, 0.2) 0%, rgba(0, 0, 0, 0) 100%);
5347
+ bottom: 0;
5348
+ opacity: ${({ $overflowDirection })=>$overflowDirection === 'top-bottom' || $overflowDirection === 'bottom' ? 1 : 0};
5349
+ transition: opacity 0.2s ease-in-out;
5350
+ }
5351
+ `;
5352
+ const ListItem = ({ data, index, style })=>{
5353
+ const { ariaDescribedBy, canDrag = false, disabled = false, handleCancel, handleDisconnect, handleDropItem, handleGrabItem, handleMoveItem, name, relations, targetModel } = data;
5354
+ const { formatMessage } = useIntl();
5355
+ const { href, id, label, status, documentId, apiData, locale } = relations[index];
5356
+ const [{ handlerId, isDragging, handleKeyDown }, relationRef, dropRef, dragRef, dragPreviewRef] = useDragAndDrop(canDrag && !disabled, {
5357
+ type: `${ItemTypes.RELATION}_${name}`,
5358
+ index,
5359
+ item: {
5360
+ displayedValue: label,
5361
+ status,
5362
+ id: id,
5363
+ index
5364
+ },
5365
+ onMoveItem: handleMoveItem,
5366
+ onDropItem: handleDropItem,
5367
+ onGrabItem: handleGrabItem,
5368
+ onCancel: handleCancel,
5369
+ dropSensitivity: DROP_SENSITIVITY.REGULAR
5370
+ });
5371
+ const composedRefs = useComposedRefs(relationRef, dragRef);
5372
+ React.useEffect(()=>{
5373
+ dragPreviewRef(getEmptyImage());
5374
+ }, [
5375
+ dragPreviewRef
5376
+ ]);
5377
+ return /*#__PURE__*/ jsx(Box, {
5378
+ style: style,
5379
+ tag: "li",
5380
+ ref: dropRef,
5381
+ "aria-describedby": ariaDescribedBy,
5382
+ cursor: canDrag ? 'all-scroll' : 'default',
5383
+ children: isDragging ? /*#__PURE__*/ jsx(RelationItemPlaceholder, {}) : /*#__PURE__*/ jsxs(Flex, {
5384
+ paddingTop: 2,
5385
+ paddingBottom: 2,
5386
+ paddingLeft: canDrag ? 2 : 4,
5387
+ paddingRight: 4,
5388
+ hasRadius: true,
5389
+ borderColor: "neutral200",
5390
+ background: disabled ? 'neutral150' : 'neutral0',
5391
+ justifyContent: "space-between",
5392
+ ref: composedRefs,
5393
+ "data-handler-id": handlerId,
5394
+ children: [
5395
+ /*#__PURE__*/ jsxs(FlexWrapper, {
5396
+ gap: 1,
5397
+ children: [
5398
+ canDrag ? /*#__PURE__*/ jsx(IconButton, {
5399
+ tag: "div",
5400
+ role: "button",
5401
+ tabIndex: 0,
5402
+ withTooltip: false,
5403
+ label: formatMessage({
5404
+ id: getTranslation('components.RelationInput.icon-button-aria-label'),
5405
+ defaultMessage: 'Drag'
5406
+ }),
5407
+ variant: "ghost",
5408
+ onKeyDown: handleKeyDown,
5409
+ disabled: disabled,
5410
+ children: /*#__PURE__*/ jsx(Drag, {})
5411
+ }) : null,
5412
+ /*#__PURE__*/ jsxs(Flex, {
5413
+ width: "100%",
5414
+ minWidth: 0,
5415
+ justifyContent: "space-between",
5416
+ children: [
5417
+ /*#__PURE__*/ jsx(Box, {
5418
+ minWidth: 0,
5419
+ paddingTop: 1,
5420
+ paddingBottom: 1,
5421
+ paddingRight: 4,
5422
+ children: /*#__PURE__*/ jsx(RelationModalForm, {
5423
+ triggerButtonLabel: label,
5424
+ relation: {
5425
+ documentId: documentId ?? apiData?.documentId,
5426
+ model: targetModel,
5427
+ collectionType: getCollectionType(href),
5428
+ params: {
5429
+ locale: locale || apiData?.locale || null
5430
+ }
5431
+ }
5432
+ })
5433
+ }),
5434
+ status ? /*#__PURE__*/ jsx(DocumentStatus, {
5435
+ status: status
5436
+ }) : null
5437
+ ]
5438
+ })
5439
+ ]
5440
+ }),
5441
+ /*#__PURE__*/ jsx(Box, {
5442
+ paddingLeft: 4,
5443
+ children: /*#__PURE__*/ jsx(IconButton, {
5444
+ onClick: ()=>handleDisconnect(relations[index]),
5445
+ disabled: disabled,
5446
+ label: formatMessage({
5447
+ id: getTranslation('relation.disconnect'),
5448
+ defaultMessage: 'Remove'
5449
+ }),
5450
+ variant: "ghost",
5451
+ size: "S",
5452
+ children: /*#__PURE__*/ jsx(Cross, {})
5453
+ })
5454
+ })
5455
+ ]
5456
+ })
5457
+ });
5458
+ };
5459
+ const FlexWrapper = styled(Flex)`
5460
+ width: 100%;
5461
+ /* Used to prevent endAction to be pushed out of container */
5462
+ min-width: 0;
5463
+
5464
+ & > div[role='button'] {
5465
+ cursor: all-scroll;
5466
+ }
5467
+ `;
5468
+ const DisconnectButton = styled.button`
5469
+ svg path {
5470
+ fill: ${({ theme, disabled })=>disabled ? theme.colors.neutral600 : theme.colors.neutral500};
5471
+ }
5472
+
5473
+ &:hover svg path,
5474
+ &:focus svg path {
5475
+ fill: ${({ theme, disabled })=>!disabled && theme.colors.neutral600};
5476
+ }
5477
+ `;
5478
+ const LinkEllipsis = styled(Link$2)`
5479
+ display: block;
5480
+
5481
+ & > span {
5482
+ white-space: nowrap;
5483
+ overflow: hidden;
5484
+ text-overflow: ellipsis;
5485
+ display: block;
5486
+ }
5487
+ `;
5488
+ const RelationItemPlaceholder = ()=>/*#__PURE__*/ jsx(Box, {
5489
+ paddingTop: 2,
5490
+ paddingBottom: 2,
5491
+ paddingLeft: 4,
5492
+ paddingRight: 4,
5493
+ hasRadius: true,
5494
+ borderStyle: "dashed",
5495
+ borderColor: "primary600",
5496
+ borderWidth: "1px",
5497
+ background: "primary100",
5498
+ height: `calc(100% - ${RELATION_GUTTER}px)`
5499
+ });
5500
+ const MemoizedRelationsField = /*#__PURE__*/ React.memo(RelationsField);
5501
+
4229
5502
  const uidApi = contentManagerApi.injectEndpoints({
4230
5503
  endpoints: (builder)=>({
4231
5504
  getDefaultUID: builder.query({
@@ -4467,7 +5740,7 @@ const UIDInput = /*#__PURE__*/ React.forwardRef(({ hint, label, labelAction, nam
4467
5740
  onMouseLeave: ()=>setShowRegenerate(false),
4468
5741
  children: isLoading ? /*#__PURE__*/ jsx(LoadingWrapper, {
4469
5742
  "data-testid": "loading-wrapper",
4470
- children: /*#__PURE__*/ jsx(Loader, {})
5743
+ children: /*#__PURE__*/ jsx(Loader$1, {})
4471
5744
  }) : /*#__PURE__*/ jsx(ArrowClockwise, {})
4472
5745
  })
4473
5746
  ]
@@ -6401,17 +7674,25 @@ const MemoizedWysiwyg = /*#__PURE__*/ React.memo(Wysiwyg);
6401
7674
  * specifically to be used in the EditView of the content-manager this understands
6402
7675
  * the complete EditFieldLayout and will handle RBAC conditions and rendering CM specific
6403
7676
  * components such as Blocks / Relations.
6404
- */ const InputRenderer = ({ visible, hint: providedHint, ...props })=>{
6405
- const { id, document, collectionType } = useDoc();
6406
- const isFormDisabled = useForm('InputRenderer', (state)=>state.disabled);
7677
+ */ const InputRenderer = ({ visible, hint: providedHint, document, ...props })=>{
7678
+ const { model: rootModel } = useDoc();
7679
+ const rootDocumentMeta = useDocumentContext('DynamicComponent', (state)=>state.rootDocumentMeta);
7680
+ const { edit: { components: rootComponents } } = useDocumentLayout(rootDocumentMeta.model);
7681
+ const { edit: { components: relatedComponents } } = useDocumentLayout(document.schema?.uid ?? rootModel);
7682
+ const components = {
7683
+ ...rootComponents,
7684
+ ...relatedComponents
7685
+ };
7686
+ const collectionType = document.schema?.kind === 'collectionType' ? 'collection-types' : 'single-types';
6407
7687
  const isInDynamicZone = useDynamicZone('isInDynamicZone', (state)=>state.isInDynamicZone);
7688
+ const isFormDisabled = useForm('InputRenderer', (state)=>state.disabled);
6408
7689
  const canCreateFields = useDocumentRBAC('InputRenderer', (rbac)=>rbac.canCreateFields);
6409
7690
  const canReadFields = useDocumentRBAC('InputRenderer', (rbac)=>rbac.canReadFields);
6410
7691
  const canUpdateFields = useDocumentRBAC('InputRenderer', (rbac)=>rbac.canUpdateFields);
6411
7692
  const canUserAction = useDocumentRBAC('InputRenderer', (rbac)=>rbac.canUserAction);
6412
- let idToCheck = id;
7693
+ let idToCheck = document.document?.documentId;
6413
7694
  if (collectionType === SINGLE_TYPES) {
6414
- idToCheck = document?.documentId;
7695
+ idToCheck = document?.document?.documentId;
6415
7696
  }
6416
7697
  const editableFields = idToCheck ? canUpdateFields : canCreateFields;
6417
7698
  const readableFields = idToCheck ? canReadFields : canCreateFields;
@@ -6425,7 +7706,6 @@ const MemoizedWysiwyg = /*#__PURE__*/ React.memo(Wysiwyg);
6425
7706
  props.attribute.customField
6426
7707
  ] : undefined);
6427
7708
  const hint = useFieldHint(providedHint, props.attribute);
6428
- const { edit: { components } } = useDocLayout();
6429
7709
  // We pass field in case of Custom Fields to keep backward compatibility
6430
7710
  const field = useField(props.name);
6431
7711
  if (!visible) {
@@ -6502,13 +7782,6 @@ const MemoizedWysiwyg = /*#__PURE__*/ React.memo(Wysiwyg);
6502
7782
  disabled: fieldIsDisabled
6503
7783
  });
6504
7784
  case 'relation':
6505
- if (window.strapi.future.isEnabled('unstableRelationsOnTheFly')) {
6506
- return /*#__PURE__*/ jsx(MemoizedUnstableRelationsField, {
6507
- ...props,
6508
- hint: hint,
6509
- disabled: fieldIsDisabled
6510
- });
6511
- }
6512
7785
  return /*#__PURE__*/ jsx(MemoizedRelationsField, {
6513
7786
  ...props,
6514
7787
  hint: hint,
@@ -6606,7 +7879,7 @@ const getMinMax = (attribute)=>{
6606
7879
  };
6607
7880
  }
6608
7881
  };
6609
- const MemoizedInputRenderer = /*#__PURE__*/ memo(InputRenderer);
7882
+ const MemoizedInputRenderer = /*#__PURE__*/ React.memo(InputRenderer);
6610
7883
 
6611
7884
  const RESPONSIVE_CONTAINER_BREAKPOINTS = {
6612
7885
  sm: '27.5rem'
@@ -6614,16 +7887,22 @@ const RESPONSIVE_CONTAINER_BREAKPOINTS = {
6614
7887
  const ResponsiveGridRoot = styled(Grid$1.Root)`
6615
7888
  container-type: inline-size;
6616
7889
  `;
6617
- const ResponsiveGridItem = styled(Grid$1.Item)`
6618
- grid-column: span 12;
6619
-
6620
- @container (min-width: ${RESPONSIVE_CONTAINER_BREAKPOINTS.sm}) {
6621
- ${({ col })=>col && `grid-column: span ${col};`}
6622
- }
6623
- `;
6624
- const FormLayout = ({ layout, hasBackground = false })=>{
7890
+ const ResponsiveGridItem = /**
7891
+ * TODO:
7892
+ * JSDOM cannot handle container queries.
7893
+ * This is a temporary workaround so that tests do not fail in the CI when jestdom throws an error
7894
+ * for failing to parse the stylesheet.
7895
+ */ process.env.NODE_ENV !== 'test' ? styled(Grid$1.Item)`
7896
+ grid-column: span 12;
7897
+ @container (min-width: ${RESPONSIVE_CONTAINER_BREAKPOINTS.sm}) {
7898
+ ${({ col })=>col && `grid-column: span ${col};`}
7899
+ }
7900
+ ` : styled(Grid$1.Item)`
7901
+ grid-column: span 12;
7902
+ `;
7903
+ const FormLayout = ({ layout, document, hasBackground = true })=>{
6625
7904
  const { formatMessage } = useIntl();
6626
- const { model } = useDoc();
7905
+ const model = document.schema?.modelName;
6627
7906
  return /*#__PURE__*/ jsx(Flex, {
6628
7907
  direction: "column",
6629
7908
  alignItems: "stretch",
@@ -6648,13 +7927,14 @@ const FormLayout = ({ layout, hasBackground = false })=>{
6648
7927
  direction: "column",
6649
7928
  alignItems: "stretch",
6650
7929
  children: /*#__PURE__*/ jsx(MemoizedInputRenderer, {
6651
- ...fieldWithTranslatedLabel
7930
+ ...fieldWithTranslatedLabel,
7931
+ document: document
6652
7932
  })
6653
7933
  })
6654
7934
  }, field.name);
6655
7935
  }
6656
7936
  return /*#__PURE__*/ jsx(Box, {
6657
- ...!hasBackground && {
7937
+ ...hasBackground && {
6658
7938
  padding: 6,
6659
7939
  borderColor: 'neutral150',
6660
7940
  background: 'neutral0',
@@ -6682,7 +7962,8 @@ const FormLayout = ({ layout, hasBackground = false })=>{
6682
7962
  direction: "column",
6683
7963
  alignItems: "stretch",
6684
7964
  children: /*#__PURE__*/ jsx(MemoizedInputRenderer, {
6685
- ...fieldWithTranslatedLabel
7965
+ ...fieldWithTranslatedLabel,
7966
+ document: document
6686
7967
  })
6687
7968
  }, field.name);
6688
7969
  })
@@ -6698,6 +7979,7 @@ const NonRepeatableComponent = ({ attribute, name, children, layout })=>{
6698
7979
  const { value } = useField(name);
6699
7980
  const level = useComponent('NonRepeatableComponent', (state)=>state.level);
6700
7981
  const isNested = level > 0;
7982
+ const currentDocument = useDocumentContext('NonRepeatableComponent', (state)=>state.document);
6701
7983
  return /*#__PURE__*/ jsx(ComponentProvider, {
6702
7984
  id: value?.id,
6703
7985
  uid: attribute.component,
@@ -6738,7 +8020,8 @@ const NonRepeatableComponent = ({ attribute, name, children, layout })=>{
6738
8020
  children: children({
6739
8021
  ...field,
6740
8022
  label: translatedLabel,
6741
- name: completeFieldName
8023
+ name: completeFieldName,
8024
+ document: currentDocument
6742
8025
  })
6743
8026
  }, completeFieldName);
6744
8027
  })
@@ -6756,7 +8039,8 @@ const RepeatableComponent = ({ attribute, disabled, name, mainField, children, l
6756
8039
  const search = React.useMemo(()=>new URLSearchParams(searchString), [
6757
8040
  searchString
6758
8041
  ]);
6759
- const { components } = useDoc();
8042
+ const currentDocument = useDocumentContext('RepeatableComponent', (state)=>state.document);
8043
+ const components = currentDocument.components;
6760
8044
  const { value = [], error, rawError } = useField(name);
6761
8045
  const addFieldRow = useForm('RepeatableComponent', (state)=>state.addFieldRow);
6762
8046
  const moveFieldRow = useForm('RepeatableComponent', (state)=>state.moveFieldRow);
@@ -6959,7 +8243,8 @@ const RepeatableComponent = ({ attribute, disabled, name, mainField, children, l
6959
8243
  children: children({
6960
8244
  ...field,
6961
8245
  label: translatedLabel,
6962
- name: completeFieldName
8246
+ name: completeFieldName,
8247
+ document: currentDocument
6963
8248
  })
6964
8249
  }, completeFieldName);
6965
8250
  })
@@ -7121,7 +8406,7 @@ const ComponentInput = ({ label, required, name, attribute, disabled, labelActio
7121
8406
  const { formatMessage } = useIntl();
7122
8407
  const field = useField(name);
7123
8408
  const showResetComponent = !attribute.repeatable && field.value && !disabled;
7124
- const { components } = useDoc();
8409
+ const components = useDocumentContext('ComponentInput', (state)=>state.document.components);
7125
8410
  const handleInitialisationClick = ()=>{
7126
8411
  const schema = components[attribute.component];
7127
8412
  const form = createDefaultForm(schema, components);
@@ -7186,5 +8471,5 @@ const ComponentInput = ({ label, required, name, attribute, disabled, labelActio
7186
8471
  };
7187
8472
  const MemoizedComponentInput = /*#__PURE__*/ React.memo(ComponentInput);
7188
8473
 
7189
- export { DynamicZone as D, FormLayout as F, MemoizedUIDInput as M, NotAllowedInput as N, useDynamicZone as a, useFieldHint as b, MemoizedWysiwyg as c, MemoizedComponentInput as d, MemoizedBlocksInput as e, useLazyComponents as u };
7190
- //# sourceMappingURL=Input-grmHplxs.mjs.map
8474
+ export { DisconnectButton as D, FlexWrapper as F, LinkEllipsis as L, MemoizedUIDInput as M, NotAllowedInput as N, FormLayout as a, useDynamicZone as b, useFieldHint as c, MemoizedWysiwyg as d, DynamicZone as e, MemoizedComponentInput as f, MemoizedBlocksInput as g, useLazyComponents as u };
8475
+ //# sourceMappingURL=Input-CHDnpU3S.mjs.map