@strapi/content-manager 5.11.0 → 5.12.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/dist/admin/chunks/{ComponentConfigurationPage-BBCYsnnE.js → ComponentConfigurationPage-CBvwLnAK.js} +5 -6
  2. package/dist/admin/chunks/{ComponentConfigurationPage-BBCYsnnE.js.map → ComponentConfigurationPage-CBvwLnAK.js.map} +1 -1
  3. package/dist/admin/chunks/{ComponentConfigurationPage-DNBN8tI3.mjs → ComponentConfigurationPage-w_ss7xEy.mjs} +5 -6
  4. package/dist/admin/chunks/{ComponentConfigurationPage-DNBN8tI3.mjs.map → ComponentConfigurationPage-w_ss7xEy.mjs.map} +1 -1
  5. package/dist/admin/chunks/{EditConfigurationPage-Cu0Vt-uc.js → EditConfigurationPage-CbAhYSyH.js} +5 -6
  6. package/dist/admin/chunks/{EditConfigurationPage-Cu0Vt-uc.js.map → EditConfigurationPage-CbAhYSyH.js.map} +1 -1
  7. package/dist/admin/chunks/{EditConfigurationPage-CoivzF-x.mjs → EditConfigurationPage-D1iHIzSR.mjs} +5 -6
  8. package/dist/admin/chunks/{EditConfigurationPage-CoivzF-x.mjs.map → EditConfigurationPage-D1iHIzSR.mjs.map} +1 -1
  9. package/dist/admin/chunks/{EditViewPage-BpGtaOKG.mjs → EditViewPage-BtunY0ZP.mjs} +102 -95
  10. package/dist/admin/chunks/EditViewPage-BtunY0ZP.mjs.map +1 -0
  11. package/dist/admin/chunks/{EditViewPage-B48r4DLX.js → EditViewPage-Cf_TPJZd.js} +102 -95
  12. package/dist/admin/chunks/EditViewPage-Cf_TPJZd.js.map +1 -0
  13. package/dist/admin/chunks/{Form-rQQtxfmF.mjs → Form-DsvK3WTh.mjs} +3 -3
  14. package/dist/admin/chunks/{Form-rQQtxfmF.mjs.map → Form-DsvK3WTh.mjs.map} +1 -1
  15. package/dist/admin/chunks/{Form-DD49gSWW.js → Form-UMrizqJP.js} +5 -5
  16. package/dist/admin/chunks/{Form-DD49gSWW.js.map → Form-UMrizqJP.js.map} +1 -1
  17. package/dist/admin/chunks/{History-RLJ9Svg6.js → History-Crvb_hzd.js} +17 -13
  18. package/dist/admin/chunks/History-Crvb_hzd.js.map +1 -0
  19. package/dist/admin/chunks/{History-BdsTgpW-.mjs → History-DsRVSUF7.mjs} +16 -12
  20. package/dist/admin/chunks/History-DsRVSUF7.mjs.map +1 -0
  21. package/dist/admin/chunks/{Input-BUVu6H9n.js → Input-C2r54bIL.js} +1327 -64
  22. package/dist/admin/chunks/Input-C2r54bIL.js.map +1 -0
  23. package/dist/admin/chunks/{Input-CtIJ2J0c.mjs → Input-D6obstp0.mjs} +1315 -55
  24. package/dist/admin/chunks/Input-D6obstp0.mjs.map +1 -0
  25. package/dist/admin/chunks/{ListConfigurationPage-38s4e-VJ.mjs → ListConfigurationPage-DW-wAwmW.mjs} +4 -5
  26. package/dist/admin/chunks/{ListConfigurationPage-38s4e-VJ.mjs.map → ListConfigurationPage-DW-wAwmW.mjs.map} +1 -1
  27. package/dist/admin/chunks/{ListConfigurationPage-DfnrA3dw.js → ListConfigurationPage-DlQ2pLyr.js} +6 -7
  28. package/dist/admin/chunks/{ListConfigurationPage-DfnrA3dw.js.map → ListConfigurationPage-DlQ2pLyr.js.map} +1 -1
  29. package/dist/admin/chunks/{ListViewPage-BTmlktgT.mjs → ListViewPage-DcUhPA9a.mjs} +3 -4
  30. package/dist/admin/chunks/{ListViewPage-BTmlktgT.mjs.map → ListViewPage-DcUhPA9a.mjs.map} +1 -1
  31. package/dist/admin/chunks/{ListViewPage-D9-LGPq_.js → ListViewPage-nJJ227Fo.js} +7 -8
  32. package/dist/admin/chunks/{ListViewPage-D9-LGPq_.js.map → ListViewPage-nJJ227Fo.js.map} +1 -1
  33. package/dist/admin/chunks/{NoContentTypePage-owzdv-pl.js → NoContentTypePage-Cxt-uFq7.js} +2 -2
  34. package/dist/admin/chunks/{NoContentTypePage-owzdv-pl.js.map → NoContentTypePage-Cxt-uFq7.js.map} +1 -1
  35. package/dist/admin/chunks/{NoContentTypePage-CcyR2i1K.mjs → NoContentTypePage-DbRXR2cr.mjs} +2 -2
  36. package/dist/admin/chunks/{NoContentTypePage-CcyR2i1K.mjs.map → NoContentTypePage-DbRXR2cr.mjs.map} +1 -1
  37. package/dist/admin/chunks/{NoPermissionsPage-CM_r8w5u.js → NoPermissionsPage-BeMTv_SG.js} +2 -2
  38. package/dist/admin/chunks/{NoPermissionsPage-CM_r8w5u.js.map → NoPermissionsPage-BeMTv_SG.js.map} +1 -1
  39. package/dist/admin/chunks/{NoPermissionsPage-lBonAXej.mjs → NoPermissionsPage-RWPwNESA.mjs} +2 -2
  40. package/dist/admin/chunks/{NoPermissionsPage-lBonAXej.mjs.map → NoPermissionsPage-RWPwNESA.mjs.map} +1 -1
  41. package/dist/admin/chunks/{Preview-Bv3GKyWZ.js → Preview-CCjSV5Iu.js} +119 -200
  42. package/dist/admin/chunks/Preview-CCjSV5Iu.js.map +1 -0
  43. package/dist/admin/chunks/{Preview-Dh5Z7aGp.mjs → Preview-aVLT3LM_.mjs} +123 -204
  44. package/dist/admin/chunks/Preview-aVLT3LM_.mjs.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-CGBB9Dho.js → index-BA_JLxKS.js} +185 -121
  50. package/dist/admin/chunks/index-BA_JLxKS.js.map +1 -0
  51. package/dist/admin/chunks/{index-BkM_Z7pU.mjs → index-CIOT3ggy.mjs} +184 -123
  52. package/dist/admin/chunks/index-CIOT3ggy.mjs.map +1 -0
  53. package/dist/admin/chunks/{layout-Dbmjk7mC.js → layout-C510xcd6.js} +86 -13
  54. package/dist/admin/chunks/layout-C510xcd6.js.map +1 -0
  55. package/dist/admin/chunks/{layout-D8ji6QNJ.mjs → layout-DjT9cccU.mjs} +78 -5
  56. package/dist/admin/chunks/layout-DjT9cccU.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-jNJK8ESO.js → usePrev-BglKW7a4.js} +18 -2
  62. package/dist/admin/chunks/usePrev-BglKW7a4.js.map +1 -0
  63. package/dist/admin/chunks/{relations-Dv0FHoBS.mjs → usePrev-DAWUYIPh.mjs} +18 -4
  64. package/dist/admin/chunks/usePrev-DAWUYIPh.mjs.map +1 -0
  65. package/dist/admin/index.js +1 -1
  66. package/dist/admin/index.mjs +1 -1
  67. package/dist/admin/src/features/DocumentContext.d.ts +53 -0
  68. package/dist/admin/src/features/DocumentRBAC.d.ts +3 -2
  69. package/dist/admin/src/pages/EditView/components/DocumentActions.d.ts +1 -1
  70. package/dist/admin/src/pages/EditView/components/FormInputs/Relations/RelationModal.d.ts +8 -0
  71. package/dist/admin/src/pages/EditView/components/FormInputs/{Relations.d.ts → Relations/Relations.d.ts} +9 -4
  72. package/dist/admin/src/pages/EditView/components/FormLayout.d.ts +8 -3
  73. package/dist/admin/src/pages/EditView/components/InputRenderer.d.ts +7 -4
  74. package/dist/admin/src/preview/components/PreviewHeader.d.ts +1 -2
  75. package/dist/admin/src/preview/pages/Preview.d.ts +0 -2
  76. package/package.json +5 -5
  77. package/dist/admin/chunks/EditViewPage-B48r4DLX.js.map +0 -1
  78. package/dist/admin/chunks/EditViewPage-BpGtaOKG.mjs.map +0 -1
  79. package/dist/admin/chunks/History-BdsTgpW-.mjs.map +0 -1
  80. package/dist/admin/chunks/History-RLJ9Svg6.js.map +0 -1
  81. package/dist/admin/chunks/Input-BUVu6H9n.js.map +0 -1
  82. package/dist/admin/chunks/Input-CtIJ2J0c.mjs.map +0 -1
  83. package/dist/admin/chunks/Preview-Bv3GKyWZ.js.map +0 -1
  84. package/dist/admin/chunks/Preview-Dh5Z7aGp.mjs.map +0 -1
  85. package/dist/admin/chunks/Relations-DP5krCGC.mjs +0 -1291
  86. package/dist/admin/chunks/Relations-DP5krCGC.mjs.map +0 -1
  87. package/dist/admin/chunks/Relations-T8Cr0N9M.js +0 -1318
  88. package/dist/admin/chunks/Relations-T8Cr0N9M.js.map +0 -1
  89. package/dist/admin/chunks/index-BkM_Z7pU.mjs.map +0 -1
  90. package/dist/admin/chunks/index-CGBB9Dho.js.map +0 -1
  91. package/dist/admin/chunks/layout-D8ji6QNJ.mjs.map +0 -1
  92. package/dist/admin/chunks/layout-Dbmjk7mC.js.map +0 -1
  93. package/dist/admin/chunks/objects-C3EebVVe.js +0 -76
  94. package/dist/admin/chunks/objects-C3EebVVe.js.map +0 -1
  95. package/dist/admin/chunks/objects-wl73iEma.mjs +0 -73
  96. package/dist/admin/chunks/objects-wl73iEma.mjs.map +0 -1
  97. package/dist/admin/chunks/relations-Dv0FHoBS.mjs.map +0 -1
  98. package/dist/admin/chunks/relations-jNJK8ESO.js.map +0 -1
  99. package/dist/admin/chunks/useDragAndDrop-HYwNDExe.mjs.map +0 -1
  100. package/dist/admin/chunks/useDragAndDrop-gcqEJMnO.js.map +0 -1
  101. package/dist/admin/chunks/usePrev-Bjw2dhmq.mjs +0 -18
  102. package/dist/admin/chunks/usePrev-Bjw2dhmq.mjs.map +0 -1
  103. package/dist/admin/chunks/usePrev-DIYl-IAL.js +0 -21
  104. package/dist/admin/chunks/usePrev-DIYl-IAL.js.map +0 -1
  105. package/dist/admin/src/preview/components/PreviewContent.d.ts +0 -2
@@ -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-BkM_Z7pU.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-CIOT3ggy.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-DAWUYIPh.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-DP5krCGC.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-Dv0FHoBS.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';
@@ -3463,6 +3462,20 @@ const Initializer = ({ disabled, name, onClick })=>{
3463
3462
  });
3464
3463
  };
3465
3464
 
3465
+ /**
3466
+ * We use this component to wrap any individual component field in the Edit View,
3467
+ * this could be a component field in a dynamic zone, a component within a repeatable space,
3468
+ * or even nested components.
3469
+ *
3470
+ * We primarily need this to provide the component id to the components so that they can
3471
+ * correctly fetch their relations.
3472
+ */ const [ComponentProvider, useComponent] = createContext('ComponentContext', {
3473
+ id: undefined,
3474
+ level: -1,
3475
+ uid: undefined,
3476
+ type: undefined
3477
+ });
3478
+
3466
3479
  const AddComponentButton = ({ hasError, isDisabled, isOpen, children, onClick })=>{
3467
3480
  return /*#__PURE__*/ jsx(StyledButton, {
3468
3481
  type: "button",
@@ -3568,15 +3581,24 @@ const ComponentCategory = ({ category, components = [], variant = 'primary', onA
3568
3581
  const ResponsiveAccordionContent = styled(Accordion.Content)`
3569
3582
  container-type: inline-size;
3570
3583
  `;
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
- `;
3584
+ /**
3585
+ * TODO:
3586
+ * JSDOM cannot handle container queries.
3587
+ * This is a temporary workaround so that tests do not fail in the CI when jestdom throws an error
3588
+ * for failing to parse the stylesheet.
3589
+ */ const Grid = process.env.NODE_ENV !== 'test' ? styled(Box)`
3590
+ display: grid;
3591
+ grid-template-columns: repeat(auto-fill, 100%);
3592
+ grid-gap: 4px;
3593
+
3594
+ @container (min-width: ${()=>RESPONSIVE_CONTAINER_BREAKPOINTS.sm}) {
3595
+ grid-template-columns: repeat(auto-fill, 14rem);
3596
+ }
3597
+ ` : styled(Box)`
3598
+ display: grid;
3599
+ grid-template-columns: repeat(auto-fill, 100%);
3600
+ grid-gap: 4px;
3601
+ `;
3580
3602
  const ComponentBox = styled(Flex)`
3581
3603
  color: ${({ theme })=>theme.colors.neutral600};
3582
3604
  cursor: pointer;
@@ -3641,7 +3663,11 @@ const ComponentPicker = ({ dynamicComponentsByCategory = {}, isOpen, onClickAddC
3641
3663
  const DynamicComponent = ({ componentUid, disabled, index, name, onRemoveComponentClick, onMoveComponent, onGrabItem, onDropItem, onCancel, dynamicComponentsByCategory = {}, onAddComponent, children })=>{
3642
3664
  const { formatMessage } = useIntl();
3643
3665
  const formValues = useForm('DynamicComponent', (state)=>state.values);
3644
- const { edit: { components } } = useDocLayout();
3666
+ const documentMeta = useDocumentContext('DynamicComponent', (state)=>state.meta);
3667
+ const rootDocumentMeta = useDocumentContext('DynamicComponent', (state)=>state.rootDocumentMeta);
3668
+ const isRootDocument = rootDocumentMeta.model === documentMeta.model;
3669
+ const { edit: { components } } = useDocumentLayout(isRootDocument ? documentMeta.model : rootDocumentMeta.model);
3670
+ const document = useDocumentContext('DynamicComponent', (state)=>state.document);
3645
3671
  const title = React.useMemo(()=>{
3646
3672
  const { mainField } = components[componentUid]?.settings ?? {
3647
3673
  mainField: 'id'
@@ -3873,9 +3899,11 @@ const DynamicComponent = ({ componentUid, disabled, index, name, onRemoveCompone
3873
3899
  alignItems: "stretch",
3874
3900
  children: children ? children({
3875
3901
  ...fieldWithTranslatedLabel,
3902
+ document,
3876
3903
  name: fieldName
3877
3904
  }) : /*#__PURE__*/ jsx(MemoizedInputRenderer, {
3878
3905
  ...fieldWithTranslatedLabel,
3906
+ document: document,
3879
3907
  name: fieldName
3880
3908
  })
3881
3909
  }, fieldName);
@@ -3993,7 +4021,8 @@ const DynamicZone = ({ attribute, disabled: disabledProp, hint, label, labelActi
3993
4021
  const { max = Infinity, min = -Infinity } = attribute ?? {};
3994
4022
  const [addComponentIsOpen, setAddComponentIsOpen] = React.useState(false);
3995
4023
  const [liveText, setLiveText] = React.useState('');
3996
- const { components, isLoading } = useDoc();
4024
+ const document = useDocumentContext('DynamicZone', (state)=>state.document);
4025
+ const { components, isLoading } = document;
3997
4026
  const disabled = disabledProp || isLoading;
3998
4027
  const { addFieldRow, removeFieldRow, moveFieldRow } = useForm('DynamicZone', ({ addFieldRow, removeFieldRow, moveFieldRow })=>({
3999
4028
  addFieldRow,
@@ -4226,6 +4255,1230 @@ const NotAllowedInput = ({ hint, label, required, name })=>{
4226
4255
  });
4227
4256
  };
4228
4257
 
4258
+ function getCollectionType(url) {
4259
+ const regex = new RegExp(`(${COLLECTION_TYPES}|${SINGLE_TYPES})`);
4260
+ const match = url.match(regex);
4261
+ return match ? match[1] : undefined;
4262
+ }
4263
+ const CustomModalContent = styled(Modal.Content)`
4264
+ width: 90%;
4265
+ max-width: 100%;
4266
+ height: 90%;
4267
+ max-height: 100%;
4268
+ `;
4269
+ const [RelationModalProvider, useRelationModal] = createContext('RelationModal', {
4270
+ parentModified: false,
4271
+ depth: 0
4272
+ });
4273
+ const RelationModalForm = ({ relation, triggerButtonLabel })=>{
4274
+ const navigate = useNavigate();
4275
+ const { pathname, search } = useLocation();
4276
+ const { formatMessage } = useIntl();
4277
+ const [triggerRefetchDocument] = useLazyGetDocumentQuery();
4278
+ const currentDocument = useDocumentContext('RelationModalForm', (state)=>state.document);
4279
+ const rootDocumentMeta = useDocumentContext('RelationModalForm', (state)=>state.rootDocumentMeta);
4280
+ const currentDocumentMeta = useDocumentContext('RelationModalForm', (state)=>state.meta);
4281
+ const changeDocument = useDocumentContext('RelationModalForm', (state)=>state.changeDocument);
4282
+ const documentHistory = useDocumentContext('RelationModalForm', (state)=>state.documentHistory);
4283
+ const setDocumentHistory = useDocumentContext('RelationModalForm', (state)=>state.setDocumentHistory);
4284
+ const [isConfirmationOpen, setIsConfirmationOpen] = React.useState(false);
4285
+ const [actionPosition, setActionPosition] = React.useState('cancel');
4286
+ const [isModalOpen, setIsModalOpen] = React.useState(false);
4287
+ // NOTE: Not sure about this relation modal context, maybe we should move this to DocumentContext?
4288
+ // Get parent modal context if it exists
4289
+ const parentContext = useRelationModal('RelationModalForm', (state)=>state);
4290
+ // Get depth of nested modals
4291
+ const depth = parentContext ? parentContext.depth + 1 : 0;
4292
+ // Check if this is a nested modal
4293
+ const isNested = depth > 0;
4294
+ const addDocumentToHistory = (document)=>setDocumentHistory((prev)=>[
4295
+ ...prev,
4296
+ document
4297
+ ]);
4298
+ const getPreviousDocument = ()=>{
4299
+ if (documentHistory.length === 0) return undefined;
4300
+ const lastDocument = documentHistory[documentHistory.length - 1];
4301
+ return lastDocument;
4302
+ };
4303
+ const removeLastDocumentFromHistory = ()=>{
4304
+ setDocumentHistory((prev)=>[
4305
+ ...prev
4306
+ ].slice(0, prev.length - 1));
4307
+ };
4308
+ const handleToggleModal = ()=>{
4309
+ if (isModalOpen) {
4310
+ setIsModalOpen(false);
4311
+ const document = {
4312
+ collectionType: rootDocumentMeta.collectionType,
4313
+ model: rootDocumentMeta.model,
4314
+ documentId: rootDocumentMeta.documentId
4315
+ };
4316
+ // Change back to the root document
4317
+ changeDocument(document);
4318
+ // Reset the document history
4319
+ setDocumentHistory([]);
4320
+ // Reset action position
4321
+ setActionPosition('cancel');
4322
+ // Read from cache or refetch root document
4323
+ triggerRefetchDocument(document, // Favor the cache
4324
+ true);
4325
+ } else {
4326
+ changeDocument(relation);
4327
+ setIsModalOpen(true);
4328
+ }
4329
+ };
4330
+ const getFullPageLink = ()=>{
4331
+ const isSingleType = currentDocumentMeta.collectionType === SINGLE_TYPES;
4332
+ const queryParams = currentDocumentMeta.params?.locale ? `?plugins[i18n][locale]=${currentDocumentMeta.params.locale}` : '';
4333
+ return `/content-manager/${currentDocumentMeta.collectionType}/${currentDocumentMeta.model}${isSingleType ? '' : '/' + currentDocumentMeta.documentId}${queryParams}`;
4334
+ };
4335
+ const handleRedirection = ()=>{
4336
+ const editViewUrl = `${pathname}${search}`;
4337
+ const isRootDocumentUrl = editViewUrl.includes(getFullPageLink());
4338
+ if (isRootDocumentUrl) {
4339
+ handleToggleModal();
4340
+ } else {
4341
+ navigate(getFullPageLink());
4342
+ }
4343
+ };
4344
+ const handleConfirm = ()=>{
4345
+ if (actionPosition === 'navigate') {
4346
+ handleRedirection();
4347
+ } else if (actionPosition === 'back') {
4348
+ const previousRelation = getPreviousDocument();
4349
+ if (previousRelation) {
4350
+ removeLastDocumentFromHistory();
4351
+ changeDocument(previousRelation);
4352
+ }
4353
+ } else {
4354
+ handleToggleModal();
4355
+ }
4356
+ };
4357
+ return /*#__PURE__*/ jsx(Form, {
4358
+ method: "PUT",
4359
+ initialValues: currentDocument.getInitialFormValues(),
4360
+ validate: (values, options)=>{
4361
+ const yupSchema = createYupSchema(currentDocument.schema?.attributes, currentDocument.components, {
4362
+ status: currentDocument.document?.status,
4363
+ ...options
4364
+ });
4365
+ return yupSchema.validate(values, {
4366
+ abortEarly: false
4367
+ });
4368
+ },
4369
+ children: ({ modified, isSubmitting, resetForm })=>{
4370
+ // We don't count the root document, so history starts after 1
4371
+ const hasHistory = documentHistory.length > 1;
4372
+ return /*#__PURE__*/ jsxs(RelationModalProvider, {
4373
+ parentModified: modified,
4374
+ depth: depth,
4375
+ children: [
4376
+ /*#__PURE__*/ jsxs(Modal.Root, {
4377
+ open: isModalOpen,
4378
+ onOpenChange: ()=>{
4379
+ if (isModalOpen) {
4380
+ if (modified && !isSubmitting) {
4381
+ setIsConfirmationOpen(true);
4382
+ } else {
4383
+ handleToggleModal();
4384
+ }
4385
+ }
4386
+ },
4387
+ children: [
4388
+ /*#__PURE__*/ jsx(Modal.Trigger, {
4389
+ children: /*#__PURE__*/ jsx(Tooltip, {
4390
+ description: triggerButtonLabel,
4391
+ children: /*#__PURE__*/ jsx(CustomTextButton, {
4392
+ onClick: ()=>{
4393
+ // Check if parent modal has unsaved changes
4394
+ if (isNested && parentContext.parentModified) {
4395
+ setIsConfirmationOpen(true);
4396
+ // Return early to avoid opening the modal
4397
+ return;
4398
+ } else {
4399
+ if (modified && !isSubmitting) {
4400
+ setIsConfirmationOpen(true);
4401
+ } else {
4402
+ // Add current relation to history before opening a new one
4403
+ if (currentDocumentMeta && Object.keys(currentDocumentMeta).length > 0) {
4404
+ addDocumentToHistory(currentDocumentMeta);
4405
+ }
4406
+ handleToggleModal();
4407
+ }
4408
+ if (!isModalOpen) {
4409
+ setIsModalOpen(true);
4410
+ }
4411
+ }
4412
+ },
4413
+ children: triggerButtonLabel
4414
+ })
4415
+ })
4416
+ }),
4417
+ /*#__PURE__*/ jsxs(CustomModalContent, {
4418
+ children: [
4419
+ /*#__PURE__*/ jsx(Modal.Header, {
4420
+ gap: 2,
4421
+ children: /*#__PURE__*/ jsx(Flex, {
4422
+ justifyContent: "space-between",
4423
+ alignItems: "center",
4424
+ width: "100%",
4425
+ children: /*#__PURE__*/ jsxs(Flex, {
4426
+ gap: 2,
4427
+ children: [
4428
+ /*#__PURE__*/ jsx(IconButton, {
4429
+ withTooltip: false,
4430
+ label: "Back",
4431
+ variant: "ghost",
4432
+ disabled: !hasHistory,
4433
+ onClick: ()=>{
4434
+ setActionPosition('back');
4435
+ if (modified && !isSubmitting) {
4436
+ setIsConfirmationOpen(true);
4437
+ } else {
4438
+ const previousRelation = getPreviousDocument();
4439
+ if (previousRelation) {
4440
+ removeLastDocumentFromHistory();
4441
+ changeDocument(previousRelation);
4442
+ }
4443
+ }
4444
+ },
4445
+ marginRight: 1,
4446
+ children: /*#__PURE__*/ jsx(ArrowLeft, {})
4447
+ }),
4448
+ /*#__PURE__*/ jsx(Typography, {
4449
+ tag: "span",
4450
+ fontWeight: 600,
4451
+ children: formatMessage({
4452
+ id: 'content-manager.components.RelationInputModal.modal-title',
4453
+ defaultMessage: 'Edit a relation'
4454
+ })
4455
+ })
4456
+ ]
4457
+ })
4458
+ })
4459
+ }),
4460
+ /*#__PURE__*/ jsx(RelationModalBody, {
4461
+ children: /*#__PURE__*/ jsx(IconButton, {
4462
+ onClick: ()=>{
4463
+ setActionPosition('navigate');
4464
+ if (modified && !isSubmitting) {
4465
+ setIsConfirmationOpen(true);
4466
+ } else {
4467
+ navigate(getFullPageLink());
4468
+ }
4469
+ },
4470
+ variant: "tertiary",
4471
+ label: formatMessage({
4472
+ id: 'content-manager.components.RelationInputModal.button-fullpage',
4473
+ defaultMessage: 'Go to entry'
4474
+ }),
4475
+ children: /*#__PURE__*/ jsx(ArrowsOut, {})
4476
+ })
4477
+ }),
4478
+ /*#__PURE__*/ jsx(Modal.Footer, {
4479
+ children: /*#__PURE__*/ jsx(Button, {
4480
+ onClick: ()=>{
4481
+ if (modified && !isSubmitting) {
4482
+ setIsConfirmationOpen(true);
4483
+ } else {
4484
+ handleToggleModal();
4485
+ }
4486
+ },
4487
+ variant: "tertiary",
4488
+ children: formatMessage({
4489
+ id: 'app.components.Button.cancel',
4490
+ defaultMessage: 'Cancel'
4491
+ })
4492
+ })
4493
+ })
4494
+ ]
4495
+ })
4496
+ ]
4497
+ }),
4498
+ /*#__PURE__*/ jsx(Dialog.Root, {
4499
+ open: isConfirmationOpen,
4500
+ onOpenChange: setIsConfirmationOpen,
4501
+ children: /*#__PURE__*/ jsx(ConfirmDialog, {
4502
+ onConfirm: ()=>{
4503
+ handleConfirm();
4504
+ setIsConfirmationOpen(false);
4505
+ resetForm();
4506
+ },
4507
+ onCancel: ()=>{
4508
+ setIsConfirmationOpen(false);
4509
+ },
4510
+ variant: "danger",
4511
+ children: formatMessage({
4512
+ id: 'content-manager.components.RelationInputModal.confirmation-message',
4513
+ defaultMessage: 'Some changes were not saved. Are you sure you want to close this relation? All changes that were not saved will be lost.'
4514
+ })
4515
+ })
4516
+ })
4517
+ ]
4518
+ });
4519
+ }
4520
+ });
4521
+ };
4522
+ const CustomTextButton = styled(TextButton)`
4523
+ & > span {
4524
+ font-size: ${({ theme })=>theme.fontSizes[2]};
4525
+ }
4526
+ `;
4527
+ const RelationModalBody = ({ children })=>{
4528
+ const { formatMessage } = useIntl();
4529
+ const documentMeta = useDocumentContext('RelationModalBody', (state)=>state.meta);
4530
+ const documentResponse = useDocumentContext('RelationModalBody', (state)=>state.document);
4531
+ const onPreview = useDocumentContext('RelationModalBody', (state)=>state.onPreview);
4532
+ const documentLayoutResponse = useDocumentLayout(documentMeta.model);
4533
+ const plugins = useStrapiApp('RelationModalBody', (state)=>state.plugins);
4534
+ const initialValues = documentResponse.getInitialFormValues();
4535
+ const { permissions = [], isLoading: isLoadingPermissions, error } = useRBAC(PERMISSIONS.map((action)=>({
4536
+ action,
4537
+ subject: documentMeta.model
4538
+ })));
4539
+ const isLoading = isLoadingPermissions || documentLayoutResponse.isLoading || documentResponse.isLoading;
4540
+ if (isLoading && !documentResponse.document?.documentId) {
4541
+ return /*#__PURE__*/ jsx(Loader, {
4542
+ small: true,
4543
+ children: formatMessage({
4544
+ id: 'content-manager.ListViewTable.relation-loading',
4545
+ defaultMessage: 'Relations are loading'
4546
+ })
4547
+ });
4548
+ }
4549
+ if (error || !documentMeta.model || documentLayoutResponse.error || !documentResponse.document || !documentResponse.meta || !documentResponse.schema || !initialValues) {
4550
+ return /*#__PURE__*/ jsx(Flex, {
4551
+ alignItems: "center",
4552
+ height: "100%",
4553
+ justifyContent: "center",
4554
+ children: /*#__PURE__*/ jsx(EmptyStateLayout, {
4555
+ icon: /*#__PURE__*/ jsx(WarningCircle, {
4556
+ width: "16rem"
4557
+ }),
4558
+ content: formatMessage({
4559
+ id: 'anErrorOccurred',
4560
+ defaultMessage: 'Whoops! Something went wrong. Please, try again.'
4561
+ })
4562
+ })
4563
+ });
4564
+ }
4565
+ const documentTitle = documentResponse.getTitle(documentLayoutResponse.edit.settings.mainField);
4566
+ const hasDraftAndPublished = documentResponse.schema?.options?.draftAndPublish ?? false;
4567
+ const props = {
4568
+ activeTab: 'draft',
4569
+ collectionType: documentMeta.collectionType,
4570
+ model: documentMeta.model,
4571
+ documentId: documentMeta.documentId,
4572
+ document: documentResponse.document,
4573
+ meta: documentResponse.meta,
4574
+ onPreview
4575
+ };
4576
+ return /*#__PURE__*/ jsx(Modal.Body, {
4577
+ children: /*#__PURE__*/ jsxs(DocumentRBAC, {
4578
+ permissions: permissions,
4579
+ model: documentMeta.model,
4580
+ children: [
4581
+ /*#__PURE__*/ jsxs(Flex, {
4582
+ alignItems: "flex-start",
4583
+ direction: "column",
4584
+ gap: 2,
4585
+ children: [
4586
+ /*#__PURE__*/ jsxs(Flex, {
4587
+ width: "100%",
4588
+ justifyContent: "space-between",
4589
+ gap: 2,
4590
+ children: [
4591
+ /*#__PURE__*/ jsx(Typography, {
4592
+ tag: "h2",
4593
+ variant: "alpha",
4594
+ children: documentTitle
4595
+ }),
4596
+ /*#__PURE__*/ jsxs(Flex, {
4597
+ gap: 2,
4598
+ children: [
4599
+ children,
4600
+ /*#__PURE__*/ jsx(DescriptionComponentRenderer, {
4601
+ props: props,
4602
+ descriptions: plugins['content-manager'].apis.getDocumentActions('relation-modal'),
4603
+ children: (actions)=>{
4604
+ const filteredActions = actions.filter((action)=>{
4605
+ return [
4606
+ action.position
4607
+ ].flat().includes('relation-modal');
4608
+ });
4609
+ const [primaryAction, secondaryAction] = filteredActions;
4610
+ if (!primaryAction && !secondaryAction) return null;
4611
+ // Both actions are available when draft and publish enabled
4612
+ if (primaryAction && secondaryAction) {
4613
+ return /*#__PURE__*/ jsxs(Fragment, {
4614
+ children: [
4615
+ /*#__PURE__*/ jsx(DocumentActionButton, {
4616
+ ...secondaryAction,
4617
+ variant: secondaryAction.variant || 'secondary'
4618
+ }),
4619
+ /*#__PURE__*/ jsx(DocumentActionButton, {
4620
+ ...primaryAction,
4621
+ variant: primaryAction.variant || 'default'
4622
+ })
4623
+ ]
4624
+ });
4625
+ }
4626
+ // Otherwise we just have the save action
4627
+ return /*#__PURE__*/ jsx(DocumentActionButton, {
4628
+ ...primaryAction,
4629
+ variant: primaryAction.variant || 'secondary'
4630
+ });
4631
+ }
4632
+ })
4633
+ ]
4634
+ })
4635
+ ]
4636
+ }),
4637
+ hasDraftAndPublished ? /*#__PURE__*/ jsx(Box, {
4638
+ children: /*#__PURE__*/ jsx(DocumentStatus, {
4639
+ status: documentResponse.document?.status
4640
+ })
4641
+ }) : null
4642
+ ]
4643
+ }),
4644
+ /*#__PURE__*/ jsx(Flex, {
4645
+ flex: 1,
4646
+ overflow: "auto",
4647
+ alignItems: "stretch",
4648
+ paddingTop: 7,
4649
+ children: /*#__PURE__*/ jsx(Box, {
4650
+ overflow: "auto",
4651
+ flex: 1,
4652
+ children: /*#__PURE__*/ jsx(FormLayout, {
4653
+ layout: documentLayoutResponse.edit.layout,
4654
+ document: documentResponse,
4655
+ hasBackground: false
4656
+ })
4657
+ })
4658
+ })
4659
+ ]
4660
+ })
4661
+ });
4662
+ };
4663
+
4664
+ /**
4665
+ * Remove a relation, whether it's been already saved or not.
4666
+ * It's used both in RelationsList, where the "remove relation" button is, and in the input,
4667
+ * because we sometimes need to remove a previous relation when selecting a new one.
4668
+ */ function useHandleDisconnect(fieldName, consumerName) {
4669
+ const field = useField(fieldName);
4670
+ const removeFieldRow = useForm(consumerName, (state)=>state.removeFieldRow);
4671
+ const addFieldRow = useForm(consumerName, (state)=>state.addFieldRow);
4672
+ const handleDisconnect = (relation)=>{
4673
+ if (field.value && field.value.connect) {
4674
+ /**
4675
+ * A relation will exist in the `connect` array _if_ it has
4676
+ * been added without saving. In this case, we just remove it
4677
+ * from the connect array
4678
+ */ const indexOfRelationInConnectArray = field.value.connect.findIndex((rel)=>rel.id === relation.id);
4679
+ if (indexOfRelationInConnectArray >= 0) {
4680
+ removeFieldRow(`${fieldName}.connect`, indexOfRelationInConnectArray);
4681
+ return;
4682
+ }
4683
+ }
4684
+ addFieldRow(`${fieldName}.disconnect`, {
4685
+ id: relation.id,
4686
+ apiData: {
4687
+ id: relation.id,
4688
+ documentId: relation.documentId,
4689
+ locale: relation.locale
4690
+ }
4691
+ });
4692
+ };
4693
+ return handleDisconnect;
4694
+ }
4695
+ /* -------------------------------------------------------------------------------------------------
4696
+ * RelationsField
4697
+ * -----------------------------------------------------------------------------------------------*/ const RELATIONS_TO_DISPLAY = 5;
4698
+ const ONE_WAY_RELATIONS = [
4699
+ 'oneWay',
4700
+ 'oneToOne',
4701
+ 'manyToOne',
4702
+ 'oneToManyMorph',
4703
+ 'oneToOneMorph'
4704
+ ];
4705
+ /**
4706
+ * TODO: we get a rather ugly flash when we remove a single relation from the list leaving
4707
+ * no other relations when we press save. The initial relation re-renders, probably because
4708
+ * of the lag in the Form cleaning it's "disconnect" array, whilst our data has not been invalidated.
4709
+ *
4710
+ * Could we invalidate relation data on the document actions? Should we?
4711
+ */ /**
4712
+ * @internal
4713
+ * @description The relations field holds a lot of domain logic for handling relations which is rather complicated
4714
+ * At present we do not expose this to plugin developers, however, they are able to overwrite it themselves should
4715
+ * they wish to do so.
4716
+ */ const RelationsField = /*#__PURE__*/ React.forwardRef(({ disabled, label, ...props }, ref)=>{
4717
+ const currentDocumentMeta = useDocumentContext('RelationsField', (state)=>state.meta);
4718
+ const currentDocument = useDocumentContext('RelationsField', (state)=>state.document);
4719
+ const rootDocumentMeta = useDocumentContext('RelationsField', (state)=>state.rootDocumentMeta);
4720
+ const [currentPage, setCurrentPage] = React.useState(1);
4721
+ const isRootDocument = rootDocumentMeta.documentId === currentDocumentMeta.documentId;
4722
+ const documentMeta = isRootDocument ? rootDocumentMeta : currentDocumentMeta;
4723
+ // Use the documentId from the actual document, not the params (meta)
4724
+ const documentId = currentDocument.document?.documentId;
4725
+ const { formatMessage } = useIntl();
4726
+ const [{ query }] = useQueryParams();
4727
+ const params = documentMeta.params ?? buildValidParams(query);
4728
+ const isMorph = props.attribute.relation.toLowerCase().includes('morph');
4729
+ const isDisabled = isMorph || disabled;
4730
+ const { componentId, componentUID } = useComponent('RelationsField', ({ uid, id })=>({
4731
+ componentId: id,
4732
+ componentUID: uid
4733
+ }));
4734
+ const isSubmitting = useForm('RelationsList', (state)=>state.isSubmitting);
4735
+ React.useEffect(()=>{
4736
+ setCurrentPage(1);
4737
+ }, [
4738
+ isSubmitting
4739
+ ]);
4740
+ const component = componentUID && currentDocument.components[componentUID];
4741
+ /**
4742
+ * We'll always have a documentId in a created entry, so we look for a componentId first.
4743
+ * Same with `uid` and `documentModel`.
4744
+ */ const model = component ? component.uid : documentMeta.model;
4745
+ const id = component && componentId ? componentId.toString() : documentId;
4746
+ /**
4747
+ * The `name` prop is a complete path to the field, e.g. `field1.field2.field3`.
4748
+ * Where the above example would a nested field within two components, however
4749
+ * we only require the field on the component not the complete path since we query
4750
+ * individual components. Therefore we split the string and take the last item.
4751
+ */ const [targetField] = props.name.split('.').slice(-1);
4752
+ const schemaAttributes = component ? component.attributes ?? {} : currentDocument.schema?.attributes ?? {};
4753
+ /**
4754
+ * Confirm the target field is related to the current document.
4755
+ * Since relations can exist in a modal on top of the root document,
4756
+ * we need to ensure we are fetching relations for the correct document (root document vs related document),
4757
+ */ const isRelatedToCurrentDocument = Object.values(schemaAttributes).filter((attribute)=>attribute.type === 'relation' && 'target' in attribute && 'target' in props.attribute && attribute.target === props.attribute.target).length > 0;
4758
+ const { data, isLoading, isFetching } = useGetRelationsQuery({
4759
+ model,
4760
+ targetField,
4761
+ // below we don't run the query if there is no id.
4762
+ id,
4763
+ params: {
4764
+ ...params,
4765
+ pageSize: RELATIONS_TO_DISPLAY,
4766
+ page: currentPage
4767
+ }
4768
+ }, {
4769
+ refetchOnMountOrArgChange: true,
4770
+ skip: !id || !isRelatedToCurrentDocument,
4771
+ selectFromResult: (result)=>{
4772
+ return {
4773
+ ...result,
4774
+ data: {
4775
+ ...result.data,
4776
+ results: result.data?.results ? result.data.results : []
4777
+ }
4778
+ };
4779
+ }
4780
+ });
4781
+ const handleLoadMore = ()=>{
4782
+ setCurrentPage((prev)=>prev + 1);
4783
+ };
4784
+ const field = useField(props.name);
4785
+ const isFetchingMoreRelations = isLoading || isFetching;
4786
+ const realServerRelationsCount = 'pagination' in data && data.pagination ? data.pagination.total : 0;
4787
+ /**
4788
+ * Items that are already connected, but reordered would be in
4789
+ * this list, so to get an accurate figure, we remove them.
4790
+ */ const relationsConnected = (field.value?.connect ?? []).filter((rel)=>data.results.findIndex((relation)=>relation.id === rel.id) === -1).length ?? 0;
4791
+ const relationsDisconnected = field.value?.disconnect?.length ?? 0;
4792
+ const relationsCount = realServerRelationsCount + relationsConnected - relationsDisconnected;
4793
+ /**
4794
+ * This is it, the source of truth for reordering in conjunction with partial loading & updating
4795
+ * of relations. Relations on load are given __temp_key__ when fetched, because we don't want to
4796
+ * create brand new keys everytime the data updates, just keep adding them onto the newly loaded ones.
4797
+ */ const relations = React.useMemo(()=>{
4798
+ const ctx = {
4799
+ field: field.value,
4800
+ // @ts-expect-error – targetModel does exist on the attribute. But it's not typed.
4801
+ href: `../${COLLECTION_TYPES}/${props.attribute.targetModel}`,
4802
+ mainField: props.mainField
4803
+ };
4804
+ /**
4805
+ * Tidy up our data.
4806
+ */ const transformations = pipe$1(removeConnected(ctx), removeDisconnected(ctx), addLabelAndHref(ctx));
4807
+ const transformedRels = transformations([
4808
+ ...data.results
4809
+ ]);
4810
+ /**
4811
+ * THIS IS CRUCIAL. If you don't sort by the __temp_key__ which comes from fractional indexing
4812
+ * then the list will be in the wrong order.
4813
+ */ return [
4814
+ ...transformedRels,
4815
+ ...field.value?.connect ?? []
4816
+ ].sort((a, b)=>{
4817
+ if (a.__temp_key__ < b.__temp_key__) return -1;
4818
+ if (a.__temp_key__ > b.__temp_key__) return 1;
4819
+ return 0;
4820
+ });
4821
+ }, [
4822
+ data.results,
4823
+ field.value,
4824
+ // @ts-expect-error – targetModel does exist on the attribute. But it's not typed.
4825
+ props.attribute.targetModel,
4826
+ props.mainField
4827
+ ]);
4828
+ const handleDisconnect = useHandleDisconnect(props.name, 'RelationsField');
4829
+ const handleConnect = (relation)=>{
4830
+ const [lastItemInList] = relations.slice(-1);
4831
+ const item = {
4832
+ id: relation.id,
4833
+ apiData: {
4834
+ id: relation.id,
4835
+ documentId: relation.documentId,
4836
+ locale: relation.locale
4837
+ },
4838
+ status: relation.status,
4839
+ /**
4840
+ * If there's a last item, that's the first key we use to generate out next one.
4841
+ */ __temp_key__: generateNKeysBetween(lastItemInList?.__temp_key__ ?? null, null, 1)[0],
4842
+ // Fallback to `id` if there is no `mainField` value, which will overwrite the above `id` property with the exact same data.
4843
+ [props.mainField?.name ?? 'documentId']: relation[props.mainField?.name ?? 'documentId'],
4844
+ label: getRelationLabel(relation, props.mainField),
4845
+ // @ts-expect-error – targetModel does exist on the attribute, but it's not typed.
4846
+ href: `../${COLLECTION_TYPES}/${props.attribute.targetModel}/${relation.documentId}?${relation.locale ? `plugins[i18n][locale]=${relation.locale}` : ''}`
4847
+ };
4848
+ if (ONE_WAY_RELATIONS.includes(props.attribute.relation)) {
4849
+ // Remove any existing relation so they can be replaced with the new one
4850
+ field.value?.connect?.forEach(handleDisconnect);
4851
+ relations.forEach(handleDisconnect);
4852
+ field.onChange(`${props.name}.connect`, [
4853
+ item
4854
+ ]);
4855
+ } else {
4856
+ field.onChange(`${props.name}.connect`, [
4857
+ ...field.value?.connect ?? [],
4858
+ item
4859
+ ]);
4860
+ }
4861
+ };
4862
+ return /*#__PURE__*/ jsxs(Flex, {
4863
+ ref: ref,
4864
+ direction: "column",
4865
+ gap: 3,
4866
+ justifyContent: "space-between",
4867
+ alignItems: "stretch",
4868
+ wrap: "wrap",
4869
+ children: [
4870
+ /*#__PURE__*/ jsxs(StyledFlex, {
4871
+ direction: "column",
4872
+ alignItems: "start",
4873
+ gap: 2,
4874
+ width: "100%",
4875
+ children: [
4876
+ /*#__PURE__*/ jsx(RelationsInput, {
4877
+ disabled: isDisabled,
4878
+ // NOTE: we should not default to using the documentId if the component is being created (componentUID is undefined)
4879
+ id: componentUID && component ? componentId ? `${componentId}` : '' : documentId,
4880
+ label: `${label} ${relationsCount > 0 ? `(${relationsCount})` : ''}`,
4881
+ model: model,
4882
+ onChange: handleConnect,
4883
+ isRelatedToCurrentDocument: isRelatedToCurrentDocument,
4884
+ ...props
4885
+ }),
4886
+ 'pagination' in data && data.pagination && data.pagination.pageCount > data.pagination.page ? /*#__PURE__*/ jsx(TextButton, {
4887
+ disabled: isFetchingMoreRelations,
4888
+ onClick: handleLoadMore,
4889
+ loading: isFetchingMoreRelations,
4890
+ startIcon: /*#__PURE__*/ jsx(ArrowClockwise, {}),
4891
+ // prevent the label from line-wrapping
4892
+ shrink: 0,
4893
+ children: formatMessage({
4894
+ id: getTranslation('relation.loadMore'),
4895
+ defaultMessage: 'Load More'
4896
+ })
4897
+ }) : null
4898
+ ]
4899
+ }),
4900
+ /*#__PURE__*/ jsx(RelationsList, {
4901
+ data: relations,
4902
+ serverData: data.results,
4903
+ disabled: isDisabled,
4904
+ name: props.name,
4905
+ isLoading: isFetchingMoreRelations,
4906
+ relationType: props.attribute.relation,
4907
+ // @ts-expect-error – targetModel does exist on the attribute. But it's not typed.
4908
+ targetModel: props.attribute.targetModel
4909
+ })
4910
+ ]
4911
+ });
4912
+ });
4913
+ /**
4914
+ * TODO: this can be removed once we stop shipping Inputs with
4915
+ * labels wrapped round in DS@2.
4916
+ */ const StyledFlex = styled(Flex)`
4917
+ & > div {
4918
+ width: 100%;
4919
+ }
4920
+ `;
4921
+ /**
4922
+ * If it's in the connected array, it can get out of our data array,
4923
+ * we'll be putting it back in later and sorting it anyway.
4924
+ */ const removeConnected = ({ field })=>(relations)=>{
4925
+ return relations.filter((relation)=>{
4926
+ const connectedRelations = field?.connect ?? [];
4927
+ return connectedRelations.findIndex((rel)=>rel.id === relation.id) === -1;
4928
+ });
4929
+ };
4930
+ /**
4931
+ * @description Removes relations that are in the `disconnect` array of the field
4932
+ */ const removeDisconnected = ({ field })=>(relations)=>relations.filter((relation)=>{
4933
+ const disconnectedRelations = field?.disconnect ?? [];
4934
+ return disconnectedRelations.findIndex((rel)=>rel.id === relation.id) === -1;
4935
+ });
4936
+ /**
4937
+ * @description Adds a label and href to the relation object we use this to render
4938
+ * a better UI where we can link to the relation and display a human-readable label.
4939
+ */ const addLabelAndHref = ({ mainField, href })=>(relations)=>relations.map((relation)=>{
4940
+ return {
4941
+ ...relation,
4942
+ // Fallback to `id` if there is no `mainField` value, which will overwrite the above `documentId` property with the exact same data.
4943
+ [mainField?.name ?? 'documentId']: relation[mainField?.name ?? 'documentId'],
4944
+ label: getRelationLabel(relation, mainField),
4945
+ href: `${href}/${relation.documentId}?${relation.locale ? `plugins[i18n][locale]=${relation.locale}` : ''}`
4946
+ };
4947
+ });
4948
+ /**
4949
+ * @description Contains all the logic for the combobox that can search
4950
+ * for relations and then add them to the field's connect array.
4951
+ */ const RelationsInput = ({ hint, id, model, label, labelAction, name, mainField, placeholder, required, unique: _unique, 'aria-label': _ariaLabel, onChange, isRelatedToCurrentDocument, ...props })=>{
4952
+ const [textValue, setTextValue] = React.useState('');
4953
+ const [searchParams, setSearchParams] = React.useState({
4954
+ _q: '',
4955
+ page: 1
4956
+ });
4957
+ const { toggleNotification } = useNotification();
4958
+ const [{ query }] = useQueryParams();
4959
+ const currentDocumentMeta = useDocumentContext('RelationsInput', (state)=>state.meta);
4960
+ const rootDocumentMeta = useDocumentContext('RelationsInput', (state)=>state.rootDocumentMeta);
4961
+ const isRootDocument = rootDocumentMeta.documentId === currentDocumentMeta.documentId;
4962
+ const documentMeta = isRootDocument ? rootDocumentMeta : currentDocumentMeta;
4963
+ const { formatMessage } = useIntl();
4964
+ const fieldRef = useFocusInputField(name);
4965
+ const field = useField(name);
4966
+ const searchParamsDebounced = useDebounce(searchParams, 300);
4967
+ const [searchForTrigger, { data, isLoading }] = useLazySearchRelationsQuery();
4968
+ /**
4969
+ * Because we're using a lazy query, we need to trigger the search
4970
+ * when the component mounts and when the search params change.
4971
+ * We also need to trigger the search when the field value changes
4972
+ * so that we can filter out the relations that are already connected.
4973
+ */ React.useEffect(()=>{
4974
+ /**
4975
+ * The `name` prop is a complete path to the field, e.g. `field1.field2.field3`.
4976
+ * Where the above example would a nested field within two components, however
4977
+ * we only require the field on the component not the complete path since we query
4978
+ * individual components. Therefore we split the string and take the last item.
4979
+ */ const [targetField] = name.split('.').slice(-1);
4980
+ // Return early if there is no relation to the document
4981
+ if (!isRelatedToCurrentDocument) return;
4982
+ const params = documentMeta.params ?? buildValidParams(query);
4983
+ searchForTrigger({
4984
+ model,
4985
+ targetField,
4986
+ params: {
4987
+ ...params,
4988
+ id: id ?? '',
4989
+ pageSize: 10,
4990
+ idsToInclude: field.value?.disconnect?.map((rel)=>rel.id.toString()) ?? [],
4991
+ idsToOmit: field.value?.connect?.map((rel)=>rel.id.toString()) ?? [],
4992
+ ...searchParamsDebounced
4993
+ }
4994
+ });
4995
+ }, [
4996
+ field.value?.connect,
4997
+ field.value?.disconnect,
4998
+ id,
4999
+ model,
5000
+ name,
5001
+ query,
5002
+ searchForTrigger,
5003
+ searchParamsDebounced,
5004
+ isRelatedToCurrentDocument,
5005
+ documentMeta
5006
+ ]);
5007
+ const handleSearch = async (search)=>{
5008
+ setSearchParams((s)=>({
5009
+ ...s,
5010
+ _q: search,
5011
+ page: 1
5012
+ }));
5013
+ };
5014
+ const hasNextPage = data?.pagination ? data.pagination.page < data.pagination.pageCount : false;
5015
+ const options = data?.results ?? [];
5016
+ const handleChange = (relationId)=>{
5017
+ if (!relationId) {
5018
+ return;
5019
+ }
5020
+ const relation = options.find((opt)=>opt.id.toString() === relationId);
5021
+ if (!relation) {
5022
+ // This is very unlikely to happen, but it ensures we don't have any data for.
5023
+ 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.");
5024
+ toggleNotification({
5025
+ message: formatMessage({
5026
+ id: getTranslation('relation.error-adding-relation'),
5027
+ defaultMessage: 'An error occurred while trying to add the relation.'
5028
+ }),
5029
+ type: 'danger'
5030
+ });
5031
+ return;
5032
+ }
5033
+ /**
5034
+ * You need to give this relation a correct _temp_key_ but
5035
+ * this component doesn't know about those ones, you can't rely
5036
+ * on the connect array because that doesn't hold items that haven't
5037
+ * moved. So use a callback to fill in the gaps when connecting.
5038
+ *
5039
+ */ onChange(relation);
5040
+ };
5041
+ const handleLoadMore = ()=>{
5042
+ if (!data || !data.pagination) {
5043
+ return;
5044
+ } else if (data.pagination.page < data.pagination.pageCount) {
5045
+ setSearchParams((s)=>({
5046
+ ...s,
5047
+ page: s.page + 1
5048
+ }));
5049
+ }
5050
+ };
5051
+ React.useLayoutEffect(()=>{
5052
+ setTextValue('');
5053
+ }, [
5054
+ field.value
5055
+ ]);
5056
+ return /*#__PURE__*/ jsxs(Field.Root, {
5057
+ error: field.error,
5058
+ hint: hint,
5059
+ name: name,
5060
+ required: required,
5061
+ children: [
5062
+ /*#__PURE__*/ jsx(Field.Label, {
5063
+ action: labelAction,
5064
+ children: label
5065
+ }),
5066
+ /*#__PURE__*/ jsx(Combobox, {
5067
+ ref: fieldRef,
5068
+ name: name,
5069
+ autocomplete: "list",
5070
+ placeholder: placeholder || formatMessage({
5071
+ id: getTranslation('relation.add'),
5072
+ defaultMessage: 'Add relation'
5073
+ }),
5074
+ hasMoreItems: hasNextPage,
5075
+ loading: isLoading,
5076
+ onOpenChange: ()=>{
5077
+ handleSearch(textValue ?? '');
5078
+ },
5079
+ noOptionsMessage: ()=>formatMessage({
5080
+ id: getTranslation('relation.notAvailable'),
5081
+ defaultMessage: 'No relations available'
5082
+ }),
5083
+ loadingMessage: formatMessage({
5084
+ id: getTranslation('relation.isLoading'),
5085
+ defaultMessage: 'Relations are loading'
5086
+ }),
5087
+ onLoadMore: handleLoadMore,
5088
+ textValue: textValue,
5089
+ onChange: handleChange,
5090
+ onTextValueChange: (text)=>{
5091
+ setTextValue(text);
5092
+ },
5093
+ onInputChange: (event)=>{
5094
+ handleSearch(event.currentTarget.value);
5095
+ },
5096
+ ...props,
5097
+ children: options.map((opt)=>{
5098
+ const textValue = getRelationLabel(opt, mainField);
5099
+ return /*#__PURE__*/ jsx(ComboboxOption, {
5100
+ value: opt.id.toString(),
5101
+ textValue: textValue,
5102
+ children: /*#__PURE__*/ jsxs(Flex, {
5103
+ gap: 2,
5104
+ justifyContent: "space-between",
5105
+ children: [
5106
+ /*#__PURE__*/ jsx(Typography, {
5107
+ ellipsis: true,
5108
+ children: textValue
5109
+ }),
5110
+ opt.status ? /*#__PURE__*/ jsx(DocumentStatus, {
5111
+ status: opt.status
5112
+ }) : null
5113
+ ]
5114
+ })
5115
+ }, opt.id);
5116
+ })
5117
+ }),
5118
+ /*#__PURE__*/ jsx(Field.Error, {}),
5119
+ /*#__PURE__*/ jsx(Field.Hint, {})
5120
+ ]
5121
+ });
5122
+ };
5123
+ /* -------------------------------------------------------------------------------------------------
5124
+ * RelationsList
5125
+ * -----------------------------------------------------------------------------------------------*/ const RELATION_ITEM_HEIGHT = 50;
5126
+ const RELATION_GUTTER = 4;
5127
+ const RelationsList = ({ data, serverData, disabled, name, isLoading, relationType, targetModel })=>{
5128
+ const ariaDescriptionId = React.useId();
5129
+ const { formatMessage } = useIntl();
5130
+ const listRef = React.useRef(null);
5131
+ const outerListRef = React.useRef(null);
5132
+ const [overflow, setOverflow] = React.useState();
5133
+ const [liveText, setLiveText] = React.useState('');
5134
+ const field = useField(name);
5135
+ React.useEffect(()=>{
5136
+ if (data.length <= RELATIONS_TO_DISPLAY) {
5137
+ return setOverflow(undefined);
5138
+ }
5139
+ const handleNativeScroll = (e)=>{
5140
+ const el = e.target;
5141
+ const parentScrollContainerHeight = el.parentNode.scrollHeight;
5142
+ const maxScrollBottom = el.scrollHeight - el.scrollTop;
5143
+ if (el.scrollTop === 0) {
5144
+ return setOverflow('bottom');
5145
+ }
5146
+ if (maxScrollBottom === parentScrollContainerHeight) {
5147
+ return setOverflow('top');
5148
+ }
5149
+ return setOverflow('top-bottom');
5150
+ };
5151
+ const outerListRefCurrent = outerListRef?.current;
5152
+ if (!isLoading && data.length > 0 && outerListRefCurrent) {
5153
+ outerListRef.current.addEventListener('scroll', handleNativeScroll);
5154
+ }
5155
+ return ()=>{
5156
+ if (outerListRefCurrent) {
5157
+ outerListRefCurrent.removeEventListener('scroll', handleNativeScroll);
5158
+ }
5159
+ };
5160
+ }, [
5161
+ isLoading,
5162
+ data.length
5163
+ ]);
5164
+ const getItemPos = (index)=>`${index + 1} of ${data.length}`;
5165
+ const handleMoveItem = (newIndex, oldIndex)=>{
5166
+ const item = data[oldIndex];
5167
+ setLiveText(formatMessage({
5168
+ id: getTranslation('dnd.reorder'),
5169
+ defaultMessage: '{item}, moved. New position in list: {position}.'
5170
+ }, {
5171
+ item: item.label ?? item.documentId,
5172
+ position: getItemPos(newIndex)
5173
+ }));
5174
+ /**
5175
+ * Splicing mutates the array, so we need to create a new array
5176
+ */ const newData = [
5177
+ ...data
5178
+ ];
5179
+ const currentRow = data[oldIndex];
5180
+ const startKey = oldIndex > newIndex ? newData[newIndex - 1]?.__temp_key__ : newData[newIndex]?.__temp_key__;
5181
+ const endKey = oldIndex > newIndex ? newData[newIndex]?.__temp_key__ : newData[newIndex + 1]?.__temp_key__;
5182
+ /**
5183
+ * We're moving the relation between two other relations, so
5184
+ * we need to generate a new key that keeps the order
5185
+ */ const [newKey] = generateNKeysBetween(startKey, endKey, 1);
5186
+ newData.splice(oldIndex, 1);
5187
+ newData.splice(newIndex, 0, {
5188
+ ...currentRow,
5189
+ __temp_key__: newKey
5190
+ });
5191
+ /**
5192
+ * Now we diff against the server to understand what's different so we
5193
+ * can keep the connect array nice and tidy. It also needs reversing because
5194
+ * we reverse the relations from the server in the first place.
5195
+ */ const connectedRelations = newData.reduce((acc, relation, currentIndex, array)=>{
5196
+ const relationOnServer = serverData.find((oldRelation)=>oldRelation.id === relation.id);
5197
+ const relationInFront = array[currentIndex + 1];
5198
+ if (!relationOnServer || relationOnServer.__temp_key__ !== relation.__temp_key__) {
5199
+ const position = relationInFront ? {
5200
+ before: relationInFront.documentId,
5201
+ locale: relationInFront.locale,
5202
+ status: 'publishedAt' in relationInFront && relationInFront.publishedAt ? 'published' : 'draft'
5203
+ } : {
5204
+ end: true
5205
+ };
5206
+ const relationWithPosition = {
5207
+ ...relation,
5208
+ ...{
5209
+ apiData: {
5210
+ id: relation.id,
5211
+ documentId: relation.documentId,
5212
+ locale: relation.locale,
5213
+ position
5214
+ }
5215
+ }
5216
+ };
5217
+ return [
5218
+ ...acc,
5219
+ relationWithPosition
5220
+ ];
5221
+ }
5222
+ return acc;
5223
+ }, []).toReversed();
5224
+ field.onChange(`${name}.connect`, connectedRelations);
5225
+ };
5226
+ const handleGrabItem = (index)=>{
5227
+ const item = data[index];
5228
+ setLiveText(formatMessage({
5229
+ id: getTranslation('dnd.grab-item'),
5230
+ defaultMessage: `{item}, grabbed. Current position in list: {position}. Press up and down arrow to change position, Spacebar to drop, Escape to cancel.`
5231
+ }, {
5232
+ item: item.label ?? item.documentId,
5233
+ position: getItemPos(index)
5234
+ }));
5235
+ };
5236
+ const handleDropItem = (index)=>{
5237
+ const { href: _href, label, ...item } = data[index];
5238
+ setLiveText(formatMessage({
5239
+ id: getTranslation('dnd.drop-item'),
5240
+ defaultMessage: `{item}, dropped. Final position in list: {position}.`
5241
+ }, {
5242
+ item: label ?? item.documentId,
5243
+ position: getItemPos(index)
5244
+ }));
5245
+ };
5246
+ const handleCancel = (index)=>{
5247
+ const item = data[index];
5248
+ setLiveText(formatMessage({
5249
+ id: getTranslation('dnd.cancel-item'),
5250
+ defaultMessage: '{item}, dropped. Re-order cancelled.'
5251
+ }, {
5252
+ item: item.label ?? item.documentId
5253
+ }));
5254
+ };
5255
+ const handleDisconnect = useHandleDisconnect(name, 'RelationsList');
5256
+ /**
5257
+ * These relation types will only ever have one item
5258
+ * in their list, so you can't reorder a single item!
5259
+ */ const canReorder = !ONE_WAY_RELATIONS.includes(relationType);
5260
+ 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);
5261
+ return /*#__PURE__*/ jsxs(ShadowBox, {
5262
+ $overflowDirection: overflow,
5263
+ children: [
5264
+ /*#__PURE__*/ jsx(VisuallyHidden, {
5265
+ id: ariaDescriptionId,
5266
+ children: formatMessage({
5267
+ id: getTranslation('dnd.instructions'),
5268
+ defaultMessage: `Press spacebar to grab and re-order`
5269
+ })
5270
+ }),
5271
+ /*#__PURE__*/ jsx(VisuallyHidden, {
5272
+ "aria-live": "assertive",
5273
+ children: liveText
5274
+ }),
5275
+ /*#__PURE__*/ jsx(FixedSizeList, {
5276
+ height: dynamicListHeight,
5277
+ ref: listRef,
5278
+ outerRef: outerListRef,
5279
+ itemCount: data.length,
5280
+ itemSize: RELATION_ITEM_HEIGHT + RELATION_GUTTER,
5281
+ itemData: {
5282
+ ariaDescribedBy: ariaDescriptionId,
5283
+ canDrag: canReorder,
5284
+ disabled,
5285
+ handleCancel,
5286
+ handleDropItem,
5287
+ handleGrabItem,
5288
+ handleMoveItem,
5289
+ name,
5290
+ handleDisconnect,
5291
+ relations: data,
5292
+ targetModel
5293
+ },
5294
+ itemKey: (index)=>data[index].id,
5295
+ innerElementType: "ol",
5296
+ children: ListItem
5297
+ })
5298
+ ]
5299
+ });
5300
+ };
5301
+ const ShadowBox = styled(Box)`
5302
+ position: relative;
5303
+ overflow: hidden;
5304
+ flex: 1;
5305
+
5306
+ &:before,
5307
+ &:after {
5308
+ position: absolute;
5309
+ width: 100%;
5310
+ height: 4px;
5311
+ z-index: 1;
5312
+ }
5313
+
5314
+ &:before {
5315
+ /* TODO: as for DS Table component we would need this to be handled by the DS theme */
5316
+ content: '';
5317
+ background: linear-gradient(rgba(3, 3, 5, 0.2) 0%, rgba(0, 0, 0, 0) 100%);
5318
+ top: 0;
5319
+ opacity: ${({ $overflowDirection })=>$overflowDirection === 'top-bottom' || $overflowDirection === 'top' ? 1 : 0};
5320
+ transition: opacity 0.2s ease-in-out;
5321
+ }
5322
+
5323
+ &:after {
5324
+ /* TODO: as for DS Table component we would need this to be handled by the DS theme */
5325
+ content: '';
5326
+ background: linear-gradient(0deg, rgba(3, 3, 5, 0.2) 0%, rgba(0, 0, 0, 0) 100%);
5327
+ bottom: 0;
5328
+ opacity: ${({ $overflowDirection })=>$overflowDirection === 'top-bottom' || $overflowDirection === 'bottom' ? 1 : 0};
5329
+ transition: opacity 0.2s ease-in-out;
5330
+ }
5331
+ `;
5332
+ const ListItem = ({ data, index, style })=>{
5333
+ const { ariaDescribedBy, canDrag = false, disabled = false, handleCancel, handleDisconnect, handleDropItem, handleGrabItem, handleMoveItem, name, relations, targetModel } = data;
5334
+ const { formatMessage } = useIntl();
5335
+ const { href, id, label, status, documentId, apiData, locale } = relations[index];
5336
+ const [{ handlerId, isDragging, handleKeyDown }, relationRef, dropRef, dragRef, dragPreviewRef] = useDragAndDrop(canDrag && !disabled, {
5337
+ type: `${ItemTypes.RELATION}_${name}`,
5338
+ index,
5339
+ item: {
5340
+ displayedValue: label,
5341
+ status,
5342
+ id: id,
5343
+ index
5344
+ },
5345
+ onMoveItem: handleMoveItem,
5346
+ onDropItem: handleDropItem,
5347
+ onGrabItem: handleGrabItem,
5348
+ onCancel: handleCancel,
5349
+ dropSensitivity: DROP_SENSITIVITY.REGULAR
5350
+ });
5351
+ const composedRefs = useComposedRefs(relationRef, dragRef);
5352
+ React.useEffect(()=>{
5353
+ dragPreviewRef(getEmptyImage());
5354
+ }, [
5355
+ dragPreviewRef
5356
+ ]);
5357
+ return /*#__PURE__*/ jsx(Box, {
5358
+ style: style,
5359
+ tag: "li",
5360
+ ref: dropRef,
5361
+ "aria-describedby": ariaDescribedBy,
5362
+ cursor: canDrag ? 'all-scroll' : 'default',
5363
+ children: isDragging ? /*#__PURE__*/ jsx(RelationItemPlaceholder, {}) : /*#__PURE__*/ jsxs(Flex, {
5364
+ paddingTop: 2,
5365
+ paddingBottom: 2,
5366
+ paddingLeft: canDrag ? 2 : 4,
5367
+ paddingRight: 4,
5368
+ hasRadius: true,
5369
+ borderColor: "neutral200",
5370
+ background: disabled ? 'neutral150' : 'neutral0',
5371
+ justifyContent: "space-between",
5372
+ ref: composedRefs,
5373
+ "data-handler-id": handlerId,
5374
+ children: [
5375
+ /*#__PURE__*/ jsxs(FlexWrapper, {
5376
+ gap: 1,
5377
+ children: [
5378
+ canDrag ? /*#__PURE__*/ jsx(IconButton, {
5379
+ tag: "div",
5380
+ role: "button",
5381
+ tabIndex: 0,
5382
+ withTooltip: false,
5383
+ label: formatMessage({
5384
+ id: getTranslation('components.RelationInput.icon-button-aria-label'),
5385
+ defaultMessage: 'Drag'
5386
+ }),
5387
+ variant: "ghost",
5388
+ onKeyDown: handleKeyDown,
5389
+ disabled: disabled,
5390
+ children: /*#__PURE__*/ jsx(Drag, {})
5391
+ }) : null,
5392
+ /*#__PURE__*/ jsxs(Flex, {
5393
+ width: "100%",
5394
+ minWidth: 0,
5395
+ justifyContent: "space-between",
5396
+ children: [
5397
+ /*#__PURE__*/ jsx(Box, {
5398
+ minWidth: 0,
5399
+ paddingTop: 1,
5400
+ paddingBottom: 1,
5401
+ paddingRight: 4,
5402
+ children: /*#__PURE__*/ jsx(RelationModalForm, {
5403
+ triggerButtonLabel: label,
5404
+ relation: {
5405
+ documentId: documentId ?? apiData?.documentId,
5406
+ model: targetModel,
5407
+ collectionType: getCollectionType(href),
5408
+ params: {
5409
+ locale: locale || apiData?.locale || null
5410
+ }
5411
+ }
5412
+ })
5413
+ }),
5414
+ status ? /*#__PURE__*/ jsx(DocumentStatus, {
5415
+ status: status
5416
+ }) : null
5417
+ ]
5418
+ })
5419
+ ]
5420
+ }),
5421
+ /*#__PURE__*/ jsx(Box, {
5422
+ paddingLeft: 4,
5423
+ children: /*#__PURE__*/ jsx(IconButton, {
5424
+ onClick: ()=>handleDisconnect(relations[index]),
5425
+ disabled: disabled,
5426
+ label: formatMessage({
5427
+ id: getTranslation('relation.disconnect'),
5428
+ defaultMessage: 'Remove'
5429
+ }),
5430
+ variant: "ghost",
5431
+ size: "S",
5432
+ children: /*#__PURE__*/ jsx(Cross, {})
5433
+ })
5434
+ })
5435
+ ]
5436
+ })
5437
+ });
5438
+ };
5439
+ const FlexWrapper = styled(Flex)`
5440
+ width: 100%;
5441
+ /* Used to prevent endAction to be pushed out of container */
5442
+ min-width: 0;
5443
+
5444
+ & > div[role='button'] {
5445
+ cursor: all-scroll;
5446
+ }
5447
+ `;
5448
+ const DisconnectButton = styled.button`
5449
+ svg path {
5450
+ fill: ${({ theme, disabled })=>disabled ? theme.colors.neutral600 : theme.colors.neutral500};
5451
+ }
5452
+
5453
+ &:hover svg path,
5454
+ &:focus svg path {
5455
+ fill: ${({ theme, disabled })=>!disabled && theme.colors.neutral600};
5456
+ }
5457
+ `;
5458
+ const LinkEllipsis = styled(Link$2)`
5459
+ display: block;
5460
+
5461
+ & > span {
5462
+ white-space: nowrap;
5463
+ overflow: hidden;
5464
+ text-overflow: ellipsis;
5465
+ display: block;
5466
+ }
5467
+ `;
5468
+ const RelationItemPlaceholder = ()=>/*#__PURE__*/ jsx(Box, {
5469
+ paddingTop: 2,
5470
+ paddingBottom: 2,
5471
+ paddingLeft: 4,
5472
+ paddingRight: 4,
5473
+ hasRadius: true,
5474
+ borderStyle: "dashed",
5475
+ borderColor: "primary600",
5476
+ borderWidth: "1px",
5477
+ background: "primary100",
5478
+ height: `calc(100% - ${RELATION_GUTTER}px)`
5479
+ });
5480
+ const MemoizedRelationsField = /*#__PURE__*/ React.memo(RelationsField);
5481
+
4229
5482
  const uidApi = contentManagerApi.injectEndpoints({
4230
5483
  endpoints: (builder)=>({
4231
5484
  getDefaultUID: builder.query({
@@ -4467,7 +5720,7 @@ const UIDInput = /*#__PURE__*/ React.forwardRef(({ hint, label, labelAction, nam
4467
5720
  onMouseLeave: ()=>setShowRegenerate(false),
4468
5721
  children: isLoading ? /*#__PURE__*/ jsx(LoadingWrapper, {
4469
5722
  "data-testid": "loading-wrapper",
4470
- children: /*#__PURE__*/ jsx(Loader, {})
5723
+ children: /*#__PURE__*/ jsx(Loader$1, {})
4471
5724
  }) : /*#__PURE__*/ jsx(ArrowClockwise, {})
4472
5725
  })
4473
5726
  ]
@@ -6401,17 +7654,19 @@ const MemoizedWysiwyg = /*#__PURE__*/ React.memo(Wysiwyg);
6401
7654
  * specifically to be used in the EditView of the content-manager this understands
6402
7655
  * the complete EditFieldLayout and will handle RBAC conditions and rendering CM specific
6403
7656
  * 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);
7657
+ */ const InputRenderer = ({ visible, hint: providedHint, document, ...props })=>{
7658
+ const { model: rootModel } = useDoc();
7659
+ const documentLayout = useDocumentLayout(document.schema?.uid ?? rootModel);
7660
+ const collectionType = document.schema?.kind === 'collectionType' ? 'collection-types' : 'single-types';
6407
7661
  const isInDynamicZone = useDynamicZone('isInDynamicZone', (state)=>state.isInDynamicZone);
7662
+ const isFormDisabled = useForm('InputRenderer', (state)=>state.disabled);
6408
7663
  const canCreateFields = useDocumentRBAC('InputRenderer', (rbac)=>rbac.canCreateFields);
6409
7664
  const canReadFields = useDocumentRBAC('InputRenderer', (rbac)=>rbac.canReadFields);
6410
7665
  const canUpdateFields = useDocumentRBAC('InputRenderer', (rbac)=>rbac.canUpdateFields);
6411
7666
  const canUserAction = useDocumentRBAC('InputRenderer', (rbac)=>rbac.canUserAction);
6412
- let idToCheck = id;
7667
+ let idToCheck = document.document?.documentId;
6413
7668
  if (collectionType === SINGLE_TYPES) {
6414
- idToCheck = document?.documentId;
7669
+ idToCheck = document?.document?.documentId;
6415
7670
  }
6416
7671
  const editableFields = idToCheck ? canUpdateFields : canCreateFields;
6417
7672
  const readableFields = idToCheck ? canReadFields : canCreateFields;
@@ -6425,7 +7680,7 @@ const MemoizedWysiwyg = /*#__PURE__*/ React.memo(Wysiwyg);
6425
7680
  props.attribute.customField
6426
7681
  ] : undefined);
6427
7682
  const hint = useFieldHint(providedHint, props.attribute);
6428
- const { edit: { components } } = useDocLayout();
7683
+ const components = documentLayout.edit.components;
6429
7684
  // We pass field in case of Custom Fields to keep backward compatibility
6430
7685
  const field = useField(props.name);
6431
7686
  if (!visible) {
@@ -6502,13 +7757,6 @@ const MemoizedWysiwyg = /*#__PURE__*/ React.memo(Wysiwyg);
6502
7757
  disabled: fieldIsDisabled
6503
7758
  });
6504
7759
  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
7760
  return /*#__PURE__*/ jsx(MemoizedRelationsField, {
6513
7761
  ...props,
6514
7762
  hint: hint,
@@ -6606,7 +7854,7 @@ const getMinMax = (attribute)=>{
6606
7854
  };
6607
7855
  }
6608
7856
  };
6609
- const MemoizedInputRenderer = /*#__PURE__*/ memo(InputRenderer);
7857
+ const MemoizedInputRenderer = /*#__PURE__*/ React.memo(InputRenderer);
6610
7858
 
6611
7859
  const RESPONSIVE_CONTAINER_BREAKPOINTS = {
6612
7860
  sm: '27.5rem'
@@ -6614,16 +7862,22 @@ const RESPONSIVE_CONTAINER_BREAKPOINTS = {
6614
7862
  const ResponsiveGridRoot = styled(Grid$1.Root)`
6615
7863
  container-type: inline-size;
6616
7864
  `;
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 })=>{
7865
+ const ResponsiveGridItem = /**
7866
+ * TODO:
7867
+ * JSDOM cannot handle container queries.
7868
+ * This is a temporary workaround so that tests do not fail in the CI when jestdom throws an error
7869
+ * for failing to parse the stylesheet.
7870
+ */ process.env.NODE_ENV !== 'test' ? styled(Grid$1.Item)`
7871
+ grid-column: span 12;
7872
+ @container (min-width: ${RESPONSIVE_CONTAINER_BREAKPOINTS.sm}) {
7873
+ ${({ col })=>col && `grid-column: span ${col};`}
7874
+ }
7875
+ ` : styled(Grid$1.Item)`
7876
+ grid-column: span 12;
7877
+ `;
7878
+ const FormLayout = ({ layout, document, hasBackground = true })=>{
6625
7879
  const { formatMessage } = useIntl();
6626
- const { model } = useDoc();
7880
+ const model = document.schema?.modelName;
6627
7881
  return /*#__PURE__*/ jsx(Flex, {
6628
7882
  direction: "column",
6629
7883
  alignItems: "stretch",
@@ -6648,13 +7902,14 @@ const FormLayout = ({ layout, hasBackground = false })=>{
6648
7902
  direction: "column",
6649
7903
  alignItems: "stretch",
6650
7904
  children: /*#__PURE__*/ jsx(MemoizedInputRenderer, {
6651
- ...fieldWithTranslatedLabel
7905
+ ...fieldWithTranslatedLabel,
7906
+ document: document
6652
7907
  })
6653
7908
  })
6654
7909
  }, field.name);
6655
7910
  }
6656
7911
  return /*#__PURE__*/ jsx(Box, {
6657
- ...!hasBackground && {
7912
+ ...hasBackground && {
6658
7913
  padding: 6,
6659
7914
  borderColor: 'neutral150',
6660
7915
  background: 'neutral0',
@@ -6682,7 +7937,8 @@ const FormLayout = ({ layout, hasBackground = false })=>{
6682
7937
  direction: "column",
6683
7938
  alignItems: "stretch",
6684
7939
  children: /*#__PURE__*/ jsx(MemoizedInputRenderer, {
6685
- ...fieldWithTranslatedLabel
7940
+ ...fieldWithTranslatedLabel,
7941
+ document: document
6686
7942
  })
6687
7943
  }, field.name);
6688
7944
  })
@@ -6698,6 +7954,7 @@ const NonRepeatableComponent = ({ attribute, name, children, layout })=>{
6698
7954
  const { value } = useField(name);
6699
7955
  const level = useComponent('NonRepeatableComponent', (state)=>state.level);
6700
7956
  const isNested = level > 0;
7957
+ const currentDocument = useDocumentContext('NonRepeatableComponent', (state)=>state.document);
6701
7958
  return /*#__PURE__*/ jsx(ComponentProvider, {
6702
7959
  id: value?.id,
6703
7960
  uid: attribute.component,
@@ -6738,7 +7995,8 @@ const NonRepeatableComponent = ({ attribute, name, children, layout })=>{
6738
7995
  children: children({
6739
7996
  ...field,
6740
7997
  label: translatedLabel,
6741
- name: completeFieldName
7998
+ name: completeFieldName,
7999
+ document: currentDocument
6742
8000
  })
6743
8001
  }, completeFieldName);
6744
8002
  })
@@ -6756,7 +8014,8 @@ const RepeatableComponent = ({ attribute, disabled, name, mainField, children, l
6756
8014
  const search = React.useMemo(()=>new URLSearchParams(searchString), [
6757
8015
  searchString
6758
8016
  ]);
6759
- const { components } = useDoc();
8017
+ const currentDocument = useDocumentContext('RepeatableComponent', (state)=>state.document);
8018
+ const components = currentDocument.components;
6760
8019
  const { value = [], error, rawError } = useField(name);
6761
8020
  const addFieldRow = useForm('RepeatableComponent', (state)=>state.addFieldRow);
6762
8021
  const moveFieldRow = useForm('RepeatableComponent', (state)=>state.moveFieldRow);
@@ -6959,7 +8218,8 @@ const RepeatableComponent = ({ attribute, disabled, name, mainField, children, l
6959
8218
  children: children({
6960
8219
  ...field,
6961
8220
  label: translatedLabel,
6962
- name: completeFieldName
8221
+ name: completeFieldName,
8222
+ document: currentDocument
6963
8223
  })
6964
8224
  }, completeFieldName);
6965
8225
  })
@@ -7121,7 +8381,7 @@ const ComponentInput = ({ label, required, name, attribute, disabled, labelActio
7121
8381
  const { formatMessage } = useIntl();
7122
8382
  const field = useField(name);
7123
8383
  const showResetComponent = !attribute.repeatable && field.value && !disabled;
7124
- const { components } = useDoc();
8384
+ const components = useDocumentContext('ComponentInput', (state)=>state.document.components);
7125
8385
  const handleInitialisationClick = ()=>{
7126
8386
  const schema = components[attribute.component];
7127
8387
  const form = createDefaultForm(schema, components);
@@ -7186,5 +8446,5 @@ const ComponentInput = ({ label, required, name, attribute, disabled, labelActio
7186
8446
  };
7187
8447
  const MemoizedComponentInput = /*#__PURE__*/ React.memo(ComponentInput);
7188
8448
 
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-CtIJ2J0c.mjs.map
8449
+ 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 };
8450
+ //# sourceMappingURL=Input-D6obstp0.mjs.map