@manuscripts/body-editor 3.12.1 → 3.12.3

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 (67) hide show
  1. package/dist/cjs/components/affiliations/AffiliationItem.js +7 -11
  2. package/dist/cjs/components/affiliations/AffiliationList.js +3 -5
  3. package/dist/cjs/components/affiliations/AffiliationsModal.js +13 -4
  4. package/dist/cjs/components/authors/AuthorDetailsForm.js +44 -34
  5. package/dist/cjs/components/authors/AuthorList.js +4 -2
  6. package/dist/cjs/components/authors/AuthorsModal.js +10 -3
  7. package/dist/cjs/components/authors/DraggableAuthor.js +7 -11
  8. package/dist/cjs/components/awards/AwardForm.js +108 -100
  9. package/dist/cjs/components/hooks/use-debounce.js +15 -0
  10. package/dist/cjs/components/modal-drawer/GenericDrawerGroup.js +1 -0
  11. package/dist/cjs/components/references/CitationEditor.js +6 -7
  12. package/dist/cjs/components/references/ImportBibliographyForm.js +12 -3
  13. package/dist/cjs/components/references/ReferenceForm/PersonDropDown.js +1 -1
  14. package/dist/cjs/components/references/ReferenceForm/styled-components.js +12 -3
  15. package/dist/cjs/components/references/ReferenceSearch.js +2 -2
  16. package/dist/cjs/components/references/ReferenceSearchResults.js +58 -11
  17. package/dist/cjs/components/references/ReferenceSearchSection.js +3 -11
  18. package/dist/cjs/components/references/ReferencesModal.js +5 -4
  19. package/dist/cjs/components/toolbar/InsertTableDialog.js +11 -3
  20. package/dist/cjs/components/views/CrossReferenceItems.js +8 -14
  21. package/dist/cjs/components/views/FootnotesSelector.js +6 -6
  22. package/dist/cjs/components/views/InsertSpecialCharacter.js +24 -10
  23. package/dist/cjs/components/views/LinkForm.js +8 -1
  24. package/dist/cjs/lib/normalize.js +8 -6
  25. package/dist/cjs/lib/popper.js +4 -2
  26. package/dist/cjs/versions.js +1 -1
  27. package/dist/cjs/views/cross_reference_editable.js +1 -0
  28. package/dist/cjs/views/equation_editable.js +6 -0
  29. package/dist/cjs/views/inline_equation_editable.js +6 -0
  30. package/dist/cjs/views/inline_footnote.js +1 -0
  31. package/dist/es/components/affiliations/AffiliationItem.js +8 -12
  32. package/dist/es/components/affiliations/AffiliationList.js +3 -5
  33. package/dist/es/components/affiliations/AffiliationsModal.js +14 -5
  34. package/dist/es/components/authors/AuthorDetailsForm.js +43 -33
  35. package/dist/es/components/authors/AuthorList.js +4 -2
  36. package/dist/es/components/authors/AuthorsModal.js +11 -4
  37. package/dist/es/components/authors/DraggableAuthor.js +8 -12
  38. package/dist/es/components/awards/AwardForm.js +110 -102
  39. package/dist/es/components/hooks/use-debounce.js +15 -1
  40. package/dist/es/components/modal-drawer/GenericDrawerGroup.js +2 -1
  41. package/dist/es/components/references/CitationEditor.js +7 -8
  42. package/dist/es/components/references/ImportBibliographyForm.js +14 -5
  43. package/dist/es/components/references/ReferenceForm/PersonDropDown.js +1 -1
  44. package/dist/es/components/references/ReferenceForm/styled-components.js +13 -4
  45. package/dist/es/components/references/ReferenceSearch.js +3 -3
  46. package/dist/es/components/references/ReferenceSearchResults.js +26 -12
  47. package/dist/es/components/references/ReferenceSearchSection.js +4 -12
  48. package/dist/es/components/references/ReferencesModal.js +6 -5
  49. package/dist/es/components/toolbar/InsertTableDialog.js +11 -3
  50. package/dist/es/components/views/CrossReferenceItems.js +9 -15
  51. package/dist/es/components/views/FootnotesSelector.js +7 -7
  52. package/dist/es/components/views/InsertSpecialCharacter.js +25 -11
  53. package/dist/es/components/views/LinkForm.js +9 -2
  54. package/dist/es/lib/normalize.js +8 -6
  55. package/dist/es/lib/popper.js +4 -2
  56. package/dist/es/versions.js +1 -1
  57. package/dist/es/views/cross_reference_editable.js +1 -0
  58. package/dist/es/views/equation_editable.js +6 -0
  59. package/dist/es/views/inline_equation_editable.js +6 -0
  60. package/dist/es/views/inline_footnote.js +1 -0
  61. package/dist/types/components/authors/AuthorDetailsForm.d.ts +2 -2
  62. package/dist/types/components/hooks/use-debounce.d.ts +1 -0
  63. package/dist/types/components/references/ReferenceForm/styled-components.d.ts +11 -1
  64. package/dist/types/components/references/ReferenceSearchResults.d.ts +20 -2
  65. package/dist/types/versions.d.ts +1 -1
  66. package/package.json +2 -2
  67. package/styles/popper.css +2 -0
@@ -40,7 +40,7 @@ exports.AffiliationItem = void 0;
40
40
  const style_guide_1 = require("@manuscripts/style-guide");
41
41
  const react_1 = __importStar(require("react"));
42
42
  const styled_components_1 = __importDefault(require("styled-components"));
43
- const AffiliationContainer = styled_components_1.default.div `
43
+ const AffiliationContainer = (0, style_guide_1.withNavigableListItem)(styled_components_1.default.div `
44
44
  padding: ${(props) => props.theme.grid.unit * 2}px 0
45
45
  ${(props) => props.theme.grid.unit * 2}px;
46
46
  display: flex;
@@ -58,7 +58,7 @@ const AffiliationContainer = styled_components_1.default.div `
58
58
  &.active {
59
59
  border-color: ${(props) => props.theme.colors.border.primary};
60
60
  }
61
- `;
61
+ `);
62
62
  const AffiliationName = styled_components_1.default.div `
63
63
  margin-left: 12px;
64
64
  flex: 1;
@@ -82,15 +82,11 @@ const AffiliationBox = styled_components_1.default.div `
82
82
  flex-direction: column;
83
83
  margin-left: 4px;
84
84
  `;
85
- const RemoveButton = styled_components_1.default.div `
86
- display: flex;
87
- align-items: center;
88
- margin-right: 8px;
85
+ const RemoveButton = (0, styled_components_1.default)(style_guide_1.SecondaryIconButton) `
89
86
  svg {
90
- cursor: pointer;
91
- }
92
- .icon_element {
93
- fill: #6e6e6e;
87
+ path[fill='white'] {
88
+ fill: white;
89
+ }
94
90
  }
95
91
  `;
96
92
  const AffiliationItem = ({ affiliation, isSelected, onClick, onDelete, showSuccessIcon, }) => {
@@ -114,7 +110,7 @@ const AffiliationItem = ({ affiliation, isSelected, onClick, onDelete, showSucce
114
110
  affiliation.country ? ', ' : '')),
115
111
  affiliation.country && react_1.default.createElement(react_1.default.Fragment, null, affiliation.country))),
116
112
  showSuccessIcon && (react_1.default.createElement(style_guide_1.CrclTickAnimation, { size: 36, style: { position: 'absolute', left: '10px' } })),
117
- isSelected && (react_1.default.createElement(RemoveButton, { onClick: () => onDelete(), "data-tooltip-content": "Delete" },
113
+ isSelected && (react_1.default.createElement(RemoveButton, { size: 13, onClick: () => onDelete(), "data-tooltip-content": "Delete" },
118
114
  react_1.default.createElement(style_guide_1.DeleteIcon, { fill: '#6E6E6E' })))));
119
115
  };
120
116
  exports.AffiliationItem = AffiliationItem;
@@ -22,18 +22,16 @@ exports.AffiliationList = void 0;
22
22
  const react_1 = __importDefault(require("react"));
23
23
  const styled_components_1 = __importDefault(require("styled-components"));
24
24
  const AffiliationItem_1 = require("./AffiliationItem");
25
- const AffiliationListContainer = styled_components_1.default.div `
25
+ const style_guide_1 = require("@manuscripts/style-guide");
26
+ const AffiliationListContainer = (0, style_guide_1.withListNavigation)(styled_components_1.default.div `
26
27
  flex: 1;
27
28
  overflow-y: visible;
28
- margin-left: 16px;
29
- `;
29
+ `);
30
30
  const AffiliationListTitle = styled_components_1.default.h2 `
31
31
  color: #6e6e6e;
32
32
  font-size: 18px;
33
33
  font-weight: 400;
34
34
  line-height: 24px;
35
- margin-left: 14px;
36
- margin-top: 20px;
37
35
  `;
38
36
  const AffiliationList = ({ affiliation, affiliations, onSelect, onDelete, lastSavedAffiliationId, }) => {
39
37
  return (react_1.default.createElement(react_1.default.Fragment, null,
@@ -367,7 +367,9 @@ const AffiliationsModal = ({ authors: $authors, affiliations: $affiliations, aff
367
367
  react_1.default.createElement(AffiliationList_1.AffiliationList, { affiliation: selection, affiliations: affiliations, onSelect: handleSelect, onDelete: handleShowDeleteDialog, lastSavedAffiliationId: savedAffiliationId }))),
368
368
  react_1.default.createElement(style_guide_1.ScrollableModalContent, { "data-cy": "affiliations-modal-content" }, selection ? (react_1.default.createElement(react_1.default.Fragment, null,
369
369
  react_1.default.createElement(AffiliationTabs, null,
370
- react_1.default.createElement(ModalFormActions_1.ModalFormActions, { type: 'affiliation', form: 'affiliation-form', onDelete: handleDeleteAffiliation, showingDeleteDialog: showingDeleteDialog, showDeleteDialog: handleShowDeleteDialog, newEntity: newAffiliation, isDisableSave: isDisableSave }),
370
+ react_1.default.createElement(ModalFormActions_1.ModalFormActions, { type: 'affiliation', form: 'affiliation-form', onDelete: handleDeleteAffiliation, showingDeleteDialog: showingDeleteDialog &&
371
+ !(showConfirmationDialog ||
372
+ showRequiredFieldConfirmationDialog), showDeleteDialog: handleShowDeleteDialog, newEntity: newAffiliation, isDisableSave: isDisableSave }),
371
373
  react_1.default.createElement(style_guide_1.InspectorTabList, null,
372
374
  react_1.default.createElement(style_guide_1.InspectorTab, null, "Details"),
373
375
  react_1.default.createElement(style_guide_1.InspectorTab, null, "Authors")),
@@ -403,9 +405,15 @@ function createEmptyAffiliation(priority) {
403
405
  };
404
406
  }
405
407
  const StyledSidebarContent = (0, styled_components_1.default)(style_guide_1.SidebarContent) `
406
- padding: 0;
408
+ padding: 8px;
407
409
  `;
408
- const AddAffiliationButton = styled_components_1.default.div `
410
+ const AddAffiliationButton = styled_components_1.default.button `
411
+ background: none;
412
+ border: none;
413
+ margin: 0;
414
+ font: inherit;
415
+ color: inherit;
416
+ width: 100%;
409
417
  display: flex;
410
418
  align-items: center;
411
419
  padding: 12px 8px 12px 12px;
@@ -416,6 +424,7 @@ const AddAffiliationButton = styled_components_1.default.div `
416
424
  border-left: 0;
417
425
  border-right: 0;
418
426
  }
427
+ ${style_guide_1.outlineStyle}
419
428
  `;
420
429
  const ActionTitle = styled_components_1.default.div `
421
430
  padding-left: ${(props) => props.theme.grid.unit * 2}px;
@@ -423,7 +432,7 @@ const ActionTitle = styled_components_1.default.div `
423
432
  const AffiliationTabs = (0, styled_components_1.default)(style_guide_1.InspectorTabs) `
424
433
  position: relative;
425
434
  `;
426
- const AffiliationTabPanel = (0, styled_components_1.default)(style_guide_1.InspectorTabPanel).attrs({ unmount: false }) `
435
+ const AffiliationTabPanel = (0, styled_components_1.default)(style_guide_1.InspectorTabPanel).attrs({ tabIndex: -1, unmount: false }) `
427
436
  margin-top: ${(props) => props.theme.grid.unit * 4}px;
428
437
  `;
429
438
  const StyledModalBody = (0, styled_components_1.default)(style_guide_1.ModalBody) `
@@ -51,30 +51,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
51
51
  return (mod && mod.__esModule) ? mod : { "default": mod };
52
52
  };
53
53
  Object.defineProperty(exports, "__esModule", { value: true });
54
- exports.AuthorDetailsForm = exports.CheckboxContainer = exports.Fieldset = void 0;
54
+ exports.CheckboxContainer = exports.Fieldset = exports.AuthorDetailsForm = void 0;
55
55
  const style_guide_1 = require("@manuscripts/style-guide");
56
56
  const formik_1 = require("formik");
57
57
  const react_1 = __importStar(require("react"));
58
58
  const styled_components_1 = __importDefault(require("styled-components"));
59
+ const normalize_1 = require("../../lib/normalize");
59
60
  const ChangeHandlingForm_1 = require("../ChangeHandlingForm");
60
- exports.Fieldset = styled_components_1.default.fieldset `
61
- padding: 0;
62
- margin: 0;
63
- border: none;
64
- `;
65
- const OrcidContainer = styled_components_1.default.div `
66
- margin: 16px 0 0;
67
- `;
68
- const TextFieldWithError = (0, styled_components_1.default)(style_guide_1.TextField) `
69
- &:required::placeholder {
70
- color: ${(props) => props.theme.colors.text.error};
71
- }
72
- `;
73
- exports.CheckboxContainer = styled_components_1.default.div `
74
- display: flex;
75
- align-items: center;
76
- gap: 32px;
77
- `;
61
+ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
62
+ const ORCID_URL_REGEX = /^https:\/\/orcid\.org\/\d{4}-\d{4}-\d{4}-\d{3}[0-9Xx]\/?$/;
63
+ const ORCID_INPUT_PATTERN = ORCID_URL_REGEX.source.slice(1, -1);
78
64
  const AuthorDetailsForm = ({ values, onChange, onSave, actionsRef, isEmailRequired, selectedAffiliations, selectedCreditRoles, authorFormRef, }) => {
79
65
  const formRef = (0, react_1.useRef)(null);
80
66
  (0, react_1.useEffect)(() => {
@@ -96,17 +82,22 @@ const AuthorDetailsForm = ({ values, onChange, onSave, actionsRef, isEmailRequir
96
82
  }
97
83
  const validateAuthor = (values) => {
98
84
  const errors = {};
99
- if (isEmailRequired && !values.email) {
85
+ const email = values.email?.trim();
86
+ if (isEmailRequired && !email) {
100
87
  errors.email = 'Email address is required';
101
88
  }
102
- else if (values.email &&
103
- !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(values.email)) {
89
+ else if (email && !EMAIL_REGEX.test(email)) {
104
90
  errors.email = 'Invalid email address';
105
91
  }
92
+ const orcid = values.ORCID?.trim();
93
+ if (orcid && !ORCID_URL_REGEX.test(orcid)) {
94
+ errors.ORCID =
95
+ 'Please enter a valid ORCID URL: https://orcid.org/xxxx-xxxx-xxxx-xxxx';
96
+ }
106
97
  return errors;
107
98
  };
108
- return (react_1.default.createElement(formik_1.Formik, { initialValues: values, onSubmit: onSave, enableReinitialize: true, validateOnChange: true, innerRef: formRef, validate: validateAuthor }, (formik) => {
109
- return (react_1.default.createElement(ChangeHandlingForm_1.ChangeHandlingForm, { onChange: onChange, id: "author-details-form", formRef: authorFormRef, noValidate: true },
99
+ return (react_1.default.createElement(formik_1.Formik, { initialValues: values, onSubmit: (submitted) => onSave((0, normalize_1.normalizeAuthor)(submitted)), enableReinitialize: true, validateOnChange: true, innerRef: formRef, validate: validateAuthor }, (formik) => {
100
+ return (react_1.default.createElement(ChangeHandlingForm_1.ChangeHandlingForm, { onChange: (next) => onChange((0, normalize_1.normalizeAuthor)(next)), id: "author-details-form", formRef: authorFormRef, noValidate: true },
110
101
  react_1.default.createElement(style_guide_1.FormRow, null,
111
102
  react_1.default.createElement(formik_1.Field, { name: 'prefix' }, (props) => (react_1.default.createElement(react_1.default.Fragment, null,
112
103
  react_1.default.createElement(style_guide_1.Label, { htmlFor: "prefix" }, "Prefix"),
@@ -134,16 +125,35 @@ const AuthorDetailsForm = ({ values, onChange, onSave, actionsRef, isEmailRequir
134
125
  react_1.default.createElement(TextFieldWithError, { id: 'email', type: "email", required: isEmailRequired, ...props.field, error: hasError }),
135
126
  hasError && (react_1.default.createElement(style_guide_1.InputErrorText, null, (0, formik_1.getIn)(formik.errors, 'email')))));
136
127
  })),
137
- react_1.default.createElement(exports.CheckboxContainer, null,
138
- react_1.default.createElement(style_guide_1.CheckboxLabel, null,
139
- react_1.default.createElement(formik_1.Field, { name: 'isCorresponding' }, (props) => (react_1.default.createElement(style_guide_1.CheckboxField, { id: 'isCorresponding', checked: props.field.value, ...props.field }))),
140
- react_1.default.createElement(style_guide_1.LabelText, null, "Corresponding Author"))),
141
- react_1.default.createElement(OrcidContainer, null,
142
- react_1.default.createElement(style_guide_1.FormRow, null,
143
- react_1.default.createElement(style_guide_1.Label, { htmlFor: "orcid" }, "ORCID"),
144
- react_1.default.createElement(formik_1.Field, { name: 'ORCID', type: 'text' }, (props) => (react_1.default.createElement(react_1.default.Fragment, null,
145
- react_1.default.createElement(style_guide_1.Label, { htmlFor: "orcid", className: "sr-only" }, "ORCID"),
146
- react_1.default.createElement(style_guide_1.TextField, { id: 'orcid', placeholder: 'https://orcid.org/...', pattern: "https://orcid\\.org/\\d{4}-\\d{4}-\\d{4}-\\d{3}[0-9Xx]", title: "Please enter a valid ORCID URL format: https://orcid.org/xxxx-xxxx-xxxx-xxxx", ...props.field }))))))));
128
+ react_1.default.createElement(style_guide_1.FormRow, null,
129
+ react_1.default.createElement(exports.CheckboxContainer, { "data-cy": "corresponding-author-container" },
130
+ react_1.default.createElement(style_guide_1.CheckboxLabel, null,
131
+ react_1.default.createElement(formik_1.Field, { name: 'isCorresponding' }, (props) => (react_1.default.createElement(style_guide_1.CheckboxField, { id: 'isCorresponding', checked: props.field.value, ...props.field }))),
132
+ react_1.default.createElement(style_guide_1.LabelText, null, "Corresponding Author")))),
133
+ react_1.default.createElement(style_guide_1.FormRow, null,
134
+ react_1.default.createElement(formik_1.Field, { name: 'ORCID', type: 'text' }, (props) => {
135
+ const hasError = (0, formik_1.getIn)(formik.touched, 'ORCID') &&
136
+ (0, formik_1.getIn)(formik.errors, 'ORCID');
137
+ return (react_1.default.createElement(react_1.default.Fragment, null,
138
+ react_1.default.createElement(style_guide_1.Label, { htmlFor: "orcid" }, "ORCID"),
139
+ react_1.default.createElement(TextFieldWithError, { id: 'orcid', type: "url", placeholder: 'https://orcid.org/...', ...props.field, pattern: ORCID_INPUT_PATTERN, title: "Please enter a valid ORCID URL: https://orcid.org/xxxx-xxxx-xxxx-xxxx", error: hasError }),
140
+ hasError && (react_1.default.createElement(style_guide_1.InputErrorText, null, (0, formik_1.getIn)(formik.errors, 'ORCID')))));
141
+ }))));
147
142
  }));
148
143
  };
149
144
  exports.AuthorDetailsForm = AuthorDetailsForm;
145
+ exports.Fieldset = styled_components_1.default.fieldset `
146
+ padding: 0;
147
+ margin: 0;
148
+ border: none;
149
+ `;
150
+ const TextFieldWithError = (0, styled_components_1.default)(style_guide_1.TextField) `
151
+ &:required::placeholder {
152
+ color: ${(props) => props.theme.colors.text.error};
153
+ }
154
+ `;
155
+ exports.CheckboxContainer = styled_components_1.default.div `
156
+ display: flex;
157
+ align-items: center;
158
+ gap: 32px;
159
+ `;
@@ -24,10 +24,12 @@ const react_dnd_1 = require("react-dnd");
24
24
  const react_dnd_html5_backend_1 = require("react-dnd-html5-backend");
25
25
  const styled_components_1 = __importDefault(require("styled-components"));
26
26
  const DraggableAuthor_1 = require("./DraggableAuthor");
27
- const AuthorListContainer = styled_components_1.default.div `
27
+ const style_guide_1 = require("@manuscripts/style-guide");
28
+ const AuthorListContainer = (0, style_guide_1.withListNavigation)(styled_components_1.default.div `
28
29
  flex: 1;
29
30
  overflow-y: visible;
30
- `;
31
+ padding: 0 6px;
32
+ `);
31
33
  const AuthorListTitle = styled_components_1.default.h2 `
32
34
  color: #6e6e6e;
33
35
  font-size: 18px;
@@ -386,7 +386,13 @@ function createEmptyAuthor(priority) {
386
386
  prefix: '',
387
387
  };
388
388
  }
389
- const AddAuthorButton = styled_components_1.default.div `
389
+ const AddAuthorButton = styled_components_1.default.button `
390
+ background: none;
391
+ border: none;
392
+ margin: 0;
393
+ font: inherit;
394
+ color: inherit;
395
+ width: 100%;
390
396
  display: flex;
391
397
  align-items: center;
392
398
  padding: 12px 8px 12px 12px;
@@ -397,6 +403,7 @@ const AddAuthorButton = styled_components_1.default.div `
397
403
  border-left: 0;
398
404
  border-right: 0;
399
405
  }
406
+ ${style_guide_1.outlineStyle}
400
407
  `;
401
408
  const ActionTitle = styled_components_1.default.div `
402
409
  padding-left: ${(props) => props.theme.grid.unit * 2}px;
@@ -404,11 +411,11 @@ const ActionTitle = styled_components_1.default.div `
404
411
  const AuthorTabs = (0, styled_components_1.default)(style_guide_1.InspectorTabs) `
405
412
  position: relative;
406
413
  `;
407
- const AuthorTabPanel = (0, styled_components_1.default)(style_guide_1.InspectorTabPanel).attrs({ unmount: false }) `
414
+ const AuthorTabPanel = (0, styled_components_1.default)(style_guide_1.InspectorTabPanel).attrs({ tabIndex: -1, unmount: false }) `
408
415
  margin-top: ${(props) => props.theme.grid.unit * 4}px;
409
416
  `;
410
417
  const StyledSidebarContent = (0, styled_components_1.default)(style_guide_1.SidebarContent) `
411
- padding: 0;
418
+ padding: 8px;
412
419
  `;
413
420
  const StyledModalBody = (0, styled_components_1.default)(style_guide_1.ModalBody) `
414
421
  position: relative;
@@ -58,7 +58,7 @@ const react_dnd_1 = require("react-dnd");
58
58
  const styled_components_1 = __importDefault(require("styled-components"));
59
59
  const authors_1 = require("../../lib/authors");
60
60
  const dnd_1 = require("../../lib/dnd");
61
- const AuthorContainer = styled_components_1.default.div `
61
+ const AuthorContainer = (0, style_guide_1.withNavigableListItem)(styled_components_1.default.div `
62
62
  padding: ${(props) => props.theme.grid.unit * 2}px 0
63
63
  ${(props) => props.theme.grid.unit * 2}px;
64
64
  display: flex;
@@ -92,7 +92,7 @@ const AuthorContainer = styled_components_1.default.div `
92
92
  cursor: grabbing;
93
93
  border-bottom-color: ${(props) => props.theme.colors.brand.dark};
94
94
  }
95
- `;
95
+ `);
96
96
  const AvatarContainer = styled_components_1.default.div `
97
97
  display: inline-flex;
98
98
  position: relative;
@@ -125,15 +125,11 @@ const DragHandle = (0, styled_components_1.default)(style_guide_1.DraggableIcon)
125
125
  margin-left: -4px;
126
126
  margin-right: -12px;
127
127
  `;
128
- const RemoveButton = styled_components_1.default.div `
129
- display: flex;
130
- align-items: center;
131
- margin-right: 8px;
128
+ const RemoveButton = (0, styled_components_1.default)(style_guide_1.SecondaryIconButton) `
132
129
  svg {
133
- cursor: pointer;
134
- }
135
- .icon_element {
136
- fill: #6e6e6e;
130
+ path[fill='white'] {
131
+ fill: white;
132
+ }
137
133
  }
138
134
  `;
139
135
  const StyledCrclTickAnimation = (0, styled_components_1.default)(style_guide_1.CrclTickAnimation) `
@@ -201,6 +197,6 @@ exports.DraggableAuthor = react_1.default.memo(({ author, isSelected, onClick, o
201
197
  react_1.default.createElement(AuthorNotes, { "data-cy": "author-notes" }, author.isCorresponding && (react_1.default.createElement(AuthorBadge, null,
202
198
  react_1.default.createElement(style_guide_1.CorrespondingAuthorIcon, null))))),
203
199
  react_1.default.createElement(AuthorName, { "data-cy": "author-name" }, (0, authors_1.authorLabel)(author)),
204
- isSelected && (react_1.default.createElement(RemoveButton, { onClick: () => onDelete(), "data-tooltip-content": "Delete" },
200
+ isSelected && (react_1.default.createElement(RemoveButton, { size: 13, onClick: () => onDelete(), "data-tooltip-content": "Delete" },
205
201
  react_1.default.createElement(style_guide_1.DeleteIcon, { fill: '#6E6E6E' })))));
206
202
  });
@@ -55,55 +55,46 @@ exports.AwardForm = void 0;
55
55
  const style_guide_1 = require("@manuscripts/style-guide");
56
56
  const formik_1 = require("formik");
57
57
  const react_1 = __importStar(require("react"));
58
- const styled_components_1 = __importDefault(require("styled-components"));
58
+ const styled_components_1 = __importStar(require("styled-components"));
59
59
  const ChangeHandlingForm_1 = require("../ChangeHandlingForm");
60
- const use_debounce_1 = require("../hooks/use-debounce");
60
+ const react_select_1 = require("react-select");
61
+ const async_creatable_1 = __importDefault(require("react-select/async-creatable"));
62
+ const use_debounce_1 = __importDefault(require("../hooks/use-debounce"));
63
+ const loadOptions = async (inputValue) => {
64
+ try {
65
+ const formattedQuery = inputValue.replace(/\s+/g, '+');
66
+ const response = await fetch(`https://api.crossref.org/funders?query=${encodeURIComponent(formattedQuery)}`);
67
+ if (!response.ok) {
68
+ console.log(`HTTP error! status: ${response.status}`);
69
+ return [];
70
+ }
71
+ const data = await response.json();
72
+ const funderOptions = data.message.items.map((funder) => ({
73
+ value: funder.name,
74
+ label: funder.name,
75
+ }));
76
+ return funderOptions.sort((a, b) => a.label.localeCompare(b.label));
77
+ }
78
+ catch (e) {
79
+ console.error('Error fetching funders:', e);
80
+ return [];
81
+ }
82
+ };
61
83
  const AwardForm = ({ values, onSave, onCancel, onChange, }) => {
62
- const [funders, setFunders] = (0, react_1.useState)([]);
63
- const [isLoading, setIsLoading] = (0, react_1.useState)(false);
64
- const [searchQuery, setSearchQuery] = (0, react_1.useState)('');
65
84
  const formRef = (0, react_1.useRef)(null);
66
85
  const primaryButtonText = values.source ? 'Update funder' : 'Add funder';
67
- const debouncedSearchQuery = (0, use_debounce_1.useDebounce)(searchQuery, 300);
68
- (0, react_1.useEffect)(() => {
69
- const searchFunders = async () => {
70
- const query = debouncedSearchQuery;
71
- if (!query) {
72
- setFunders([]);
73
- return;
74
- }
75
- setIsLoading(true);
76
- try {
77
- const formattedQuery = query.replace(/\s+/g, '+');
78
- const response = await fetch(`https://api.crossref.org/funders?query=${encodeURIComponent(formattedQuery)}`);
79
- if (!response.ok) {
80
- throw new Error(`HTTP error! status: ${response.status}`);
81
- }
82
- const data = await response.json();
83
- const funderOptions = data.message.items.map((funder) => ({
84
- value: funder.name,
85
- label: funder.name,
86
- }));
87
- funderOptions.sort((a, b) => a.label.localeCompare(b.label));
88
- setFunders(funderOptions);
89
- }
90
- catch (error) {
91
- console.error('Error fetching funders:', error);
92
- setFunders([]);
93
- }
94
- finally {
95
- setIsLoading(false);
96
- }
97
- };
98
- searchFunders();
99
- }, [debouncedSearchQuery]);
100
- const handleFunderSearch = (event) => {
101
- setSearchQuery(event.target.value);
102
- };
86
+ const themes = (0, styled_components_1.useTheme)();
87
+ const customStyles = (0, react_1.useMemo)(() => getCustomStyles(themes), [themes]);
88
+ const handleOnLoadFunders = (0, use_debounce_1.default)(async (inputValue) => loadOptions(inputValue), 300);
103
89
  const handleCancel = () => {
104
90
  formRef.current?.resetForm();
105
91
  onCancel();
106
92
  };
93
+ const handleInputChange = (value, action) => {
94
+ if (action.action === 'input-change') {
95
+ formRef.current?.setFieldValue('source', value);
96
+ }
97
+ };
107
98
  const validate = (values) => {
108
99
  const errors = {};
109
100
  if (!values.source) {
@@ -119,18 +110,14 @@ const AwardForm = ({ values, onSave, onCancel, onChange, }) => {
119
110
  react_1.default.createElement(formik_1.Field, { type: "hidden", name: "id" }),
120
111
  react_1.default.createElement(style_guide_1.FormRow, null,
121
112
  react_1.default.createElement(style_guide_1.Label, { htmlFor: 'source' }, "Funder name"),
122
- react_1.default.createElement(SearchContainer, null,
123
- react_1.default.createElement(SearchIconContainer, null,
124
- react_1.default.createElement(style_guide_1.SearchIcon, null)),
125
- react_1.default.createElement(formik_1.Field, { name: "source" }, (props) => (react_1.default.createElement(StyledTextField, { id: "source", placeholder: "Search for funder...", onChange: (e) => {
126
- props.field.onChange(e);
127
- handleFunderSearch(e);
128
- }, value: props.field.value || '' }))),
129
- isLoading && react_1.default.createElement(LoadingText, null, "Loading..."),
130
- funders.length > 0 && (react_1.default.createElement(SearchResults, null, funders.map((funder) => (react_1.default.createElement(SearchResultItem, { key: funder.value, onClick: () => {
131
- formik.setFieldValue('source', funder.value);
132
- setFunders([]);
133
- } }, funder.label)))))),
113
+ react_1.default.createElement(formik_1.Field, { name: "source" }, (props) => (react_1.default.createElement(async_creatable_1.default, { inputId: 'source', loadOptions: handleOnLoadFunders, onInputChange: handleInputChange, autoFocus: true, onChange: (option) => option &&
114
+ formRef.current?.setFieldValue('source', option.value), inputValue: props.field.value, value: null, isValidNewOption: () => false, components: {
115
+ Input,
116
+ Control,
117
+ Option,
118
+ DropdownIndicator: null,
119
+ IndicatorSeparator: null,
120
+ }, styles: customStyles, placeholder: "Search for funder..." }))),
134
121
  formik.errors.source && formik.touched.source && (react_1.default.createElement(ErrorText, null, formik.errors.source))),
135
122
  react_1.default.createElement(style_guide_1.FormRow, null,
136
123
  react_1.default.createElement(style_guide_1.Label, { htmlFor: 'code' }, "Grant number"),
@@ -141,69 +128,90 @@ const AwardForm = ({ values, onSave, onCancel, onChange, }) => {
141
128
  react_1.default.createElement(style_guide_1.Label, { htmlFor: 'recipient' }, "Recipient name"),
142
129
  react_1.default.createElement(formik_1.Field, { name: "recipient" }, (props) => (react_1.default.createElement(style_guide_1.TextField, { id: "recipient", placeholder: "Enter full name", ...props.field })))),
143
130
  react_1.default.createElement(style_guide_1.FormRow, null,
144
- react_1.default.createElement(style_guide_1.FormActionsBar, null,
131
+ react_1.default.createElement(FormActions, null,
145
132
  react_1.default.createElement(style_guide_1.SecondaryButton, { onClick: handleCancel }, "Cancel"),
146
133
  react_1.default.createElement(style_guide_1.PrimaryButton, { type: "submit", disabled: !formik.dirty || formik.isSubmitting }, primaryButtonText)))));
147
134
  }));
148
135
  };
149
136
  exports.AwardForm = AwardForm;
150
- const SearchContainer = styled_components_1.default.div `
151
- position: relative;
152
- width: 100%;
153
- `;
154
137
  const SearchIconContainer = styled_components_1.default.span `
155
138
  display: flex;
156
- left: ${(props) => props.theme.grid.unit * 4}px;
157
- position: absolute;
158
- top: 50%;
159
- transform: translateY(-50%);
160
- z-index: 2;
139
+ align-items: center;
140
+ padding-left: ${(props) => props.theme.grid.unit * 4}px;
141
+ padding-right: ${(props) => props.theme.grid.unit * 2}px;
161
142
 
162
143
  path {
163
- stroke: ${(props) => props.theme.colors.text.primary};
144
+ stroke: ${(props) => props.isFocused
145
+ ? props.theme.colors.brand.medium
146
+ : props.theme.colors.text.primary};
164
147
  }
148
+ `;
149
+ const OptionWrapper = styled_components_1.default.div `
150
+ padding-left: ${(props) => props.theme.grid.unit * 4}px;
151
+ padding-top: ${(props) => props.theme.grid.unit * 2}px;
152
+ padding-bottom: ${(props) => props.theme.grid.unit * 2}px;
165
153
 
166
- ${SearchContainer}:hover &,
167
- ${SearchContainer}:focus-within & {
168
- path {
169
- stroke: ${(props) => props.theme.colors.brand.medium};
154
+ background-color: ${(props) => {
155
+ if (props.selected) {
156
+ return props.theme.colors.background.selected;
170
157
  }
171
- }
172
- `;
173
- const StyledTextField = (0, styled_components_1.default)(style_guide_1.TextField) `
174
- padding-left: ${(props) => props.theme.grid.unit * 11}px;
175
- &:hover,
176
- &:focus {
177
- background-color: ${(props) => props.theme.colors.background.fifth};
178
- }
179
- `;
180
- const SearchResults = styled_components_1.default.div `
181
- position: absolute;
182
- top: 100%;
183
- left: 0;
184
- right: 0;
185
- max-height: 200px;
186
- overflow-y: auto;
187
- background: white;
188
- border: 1px solid ${(props) => props.theme.colors.border.secondary};
189
- border-radius: 4px;
190
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
191
- z-index: 1000;
192
- `;
193
- const SearchResultItem = styled_components_1.default.div `
194
- padding: 8px 12px;
195
- cursor: pointer;
158
+ if (props.focused) {
159
+ return props.theme.colors.background.fifth;
160
+ }
161
+ return 'transparent';
162
+ }};
163
+
196
164
  &:hover {
197
165
  background-color: ${(props) => props.theme.colors.background.fifth};
198
166
  }
199
167
  `;
200
- const LoadingText = styled_components_1.default.div `
201
- position: absolute;
202
- right: 12px;
203
- top: 50%;
204
- transform: translateY(-50%);
205
- color: ${(props) => props.theme.colors.text.secondary};
206
- font-size: ${(props) => props.theme.font.size.small};
168
+ const Option = ({ innerProps, innerRef, data, isFocused, isSelected, }) => {
169
+ return (react_1.default.createElement(OptionWrapper, { ...innerProps, ref: innerRef, focused: isFocused, selected: isSelected }, data.label));
170
+ };
171
+ const Control = ({ children, ...props }) => {
172
+ return (react_1.default.createElement(react_select_1.components.Control, { ...props },
173
+ react_1.default.createElement(SearchIconContainer, { isFocused: props.isFocused },
174
+ react_1.default.createElement(style_guide_1.SearchIcon, null)),
175
+ children));
176
+ };
177
+ const Input = (props) => (react_1.default.createElement(react_select_1.components.Input, { ...props, isHidden: false }));
178
+ const getCustomStyles = (theme) => ({
179
+ control: (provided, state) => ({
180
+ ...provided,
181
+ minHeight: '40px',
182
+ borderRadius: '4px',
183
+ border: `${state.isFocused ? '2px' : '1px'} solid ${state.isFocused ? theme.colors.brand.default : theme.colors.border.secondary}`,
184
+ backgroundColor: state.isFocused || state.menuIsOpen
185
+ ? theme.colors.background.fifth
186
+ : theme.colors.background.primary,
187
+ boxShadow: 'none',
188
+ cursor: 'text',
189
+ transition: 'background-color 0.2s',
190
+ '&:hover': {
191
+ backgroundColor: theme.colors.background.fifth,
192
+ },
193
+ }),
194
+ input: (provided) => ({
195
+ ...provided,
196
+ margin: 0,
197
+ padding: 0,
198
+ color: theme.colors.text.primary,
199
+ fontSize: theme.font.size.normal,
200
+ fontFamily: theme.font.family.sans,
201
+ }),
202
+ placeholder: (provided, state) => ({
203
+ ...provided,
204
+ margin: 0,
205
+ fontStyle: 'italic',
206
+ color: state.isFocused ? '#c9c9c9' : theme.colors.text.secondary,
207
+ fontSize: theme.font.size.medium,
208
+ fontFamily: theme.font.family.sans,
209
+ fontWeight: theme.font.weight.normal,
210
+ lineHeight: theme.font.lineHeight.large,
211
+ }),
212
+ });
213
+ const FormActions = (0, styled_components_1.default)(style_guide_1.ButtonGroup) `
214
+ margin: ${(props) => props.theme.grid.unit * 2}px;
207
215
  `;
208
216
  const ErrorText = styled_components_1.default.div `
209
217
  color: ${(props) => props.theme.colors.text.error};
@@ -16,6 +16,7 @@
16
16
  */
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.useDebounce = void 0;
19
+ exports.default = useDebounced;
19
20
  const react_1 = require("react");
20
21
  const useDebounce = (value, delay) => {
21
22
  const [debouncedValue, setDebouncedValue] = (0, react_1.useState)(value);
@@ -30,3 +31,17 @@ const useDebounce = (value, delay) => {
30
31
  return debouncedValue;
31
32
  };
32
33
  exports.useDebounce = useDebounce;
34
+ function useDebounced(fn, delay) {
35
+ const timeoutRef = (0, react_1.useRef)(null);
36
+ return (0, react_1.useCallback)((arg) => {
37
+ const timeout = timeoutRef.current;
38
+ if (timeout) {
39
+ clearTimeout(timeout);
40
+ }
41
+ return new Promise((resolve) => {
42
+ timeoutRef.current = setTimeout(async () => {
43
+ resolve(await fn(arg));
44
+ }, delay);
45
+ });
46
+ }, [fn, delay]);
47
+ }
@@ -50,4 +50,5 @@ exports.AssignButton = styled_components_1.default.button `
50
50
  &:hover {
51
51
  opacity: 0.8;
52
52
  }
53
+ ${style_guide_1.outlineStyle}
53
54
  `;