cozy-sharing 33.0.0 → 33.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,12 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [33.0.1](https://github.com/cozy/cozy-libs/compare/cozy-sharing@33.0.0...cozy-sharing@33.0.1) (2026-06-02)
7
+
8
+ ### Bug Fixes
9
+
10
+ - Disable new contact creation for shared drives forms ([08abf8a](https://github.com/cozy/cozy-libs/commit/08abf8a21a8d37db190866d68a73744b8bcbf0aa))
11
+
6
12
  # [33.0.0](https://github.com/cozy/cozy-libs/compare/cozy-sharing@32.3.2...cozy-sharing@33.0.0) (2026-06-02)
7
13
 
8
14
  ### Bug Fixes
@@ -258,7 +258,8 @@ var FederatedFolderModalContent = function FederatedFolderModalContent(_ref) {
258
258
  pendingRecipients: pendingRecipients,
259
259
  onPendingRecipientsChange: setPendingRecipients,
260
260
  selectedOption: selectedOption,
261
- onSelectedOptionChange: setSelectedOption
261
+ onSelectedOptionChange: setSelectedOption,
262
+ enableCreateContact: true
262
263
  })), /*#__PURE__*/React.createElement(WhoHasAccess, {
263
264
  isOwner: true,
264
265
  isSharedDrive: true,
@@ -8,6 +8,7 @@ import Chip from 'cozy-ui/transpiled/react/Chips';
8
8
  import { Spinner } from 'cozy-ui/transpiled/react/Spinner';
9
9
  import { GroupAvatar } from './Avatar/GroupAvatar';
10
10
  import ContactSuggestion from './ContactSuggestion';
11
+ import { isContactToBeCreated } from '../helpers/contacts';
11
12
  import { extractEmails, validateEmail } from '../helpers/email';
12
13
  import { getDisplayName, getInitials, Contact, Group } from '../models';
13
14
  var styles = {
@@ -31,6 +32,7 @@ var ShareAutocomplete = function ShareAutocomplete(_ref) {
31
32
  recipients = _ref.recipients,
32
33
  onPick = _ref.onPick,
33
34
  onRemove = _ref.onRemove,
35
+ enableCreateContact = _ref.enableCreateContact,
34
36
  placeholder = _ref.placeholder,
35
37
  endAdornment = _ref.endAdornment;
36
38
 
@@ -67,11 +69,17 @@ var ShareAutocomplete = function ShareAutocomplete(_ref) {
67
69
  var onSuggestionsFetchRequested = function onSuggestionsFetchRequested(_ref2) {
68
70
  var value = _ref2.value;
69
71
  var computedSuggestions = computeSuggestions(value);
70
- var newSuggestions = computedSuggestions.length === 0 && validateEmail(value) ? [{
71
- email: value,
72
- _type: Contact.doctype
73
- }] : computedSuggestions;
74
- setSuggestions(newSuggestions);
72
+
73
+ if (computedSuggestions.length > 0) {
74
+ setSuggestions(computedSuggestions);
75
+ } else if (enableCreateContact && validateEmail(value)) {
76
+ setSuggestions([{
77
+ email: value,
78
+ _type: Contact.doctype
79
+ }]);
80
+ } else {
81
+ setSuggestions([]);
82
+ }
75
83
  };
76
84
 
77
85
  var onSuggestionsClearRequested = function onSuggestionsClearRequested() {
@@ -142,6 +150,7 @@ var ShareAutocomplete = function ShareAutocomplete(_ref) {
142
150
  var onAutosuggestPick = function onAutosuggestPick(value) {
143
151
  var _autosuggestRef$curre;
144
152
 
153
+ if (isContactToBeCreated(value) && !enableCreateContact) return;
145
154
  onPick(value);
146
155
  setInputValue('');
147
156
  autosuggestRef === null || autosuggestRef === void 0 || (_autosuggestRef$curre = autosuggestRef.current) === null || _autosuggestRef$curre === void 0 || (_autosuggestRef$curre = _autosuggestRef$curre.input) === null || _autosuggestRef$curre === void 0 || _autosuggestRef$curre.focus();
@@ -241,6 +250,7 @@ ShareAutocomplete.propTypes = {
241
250
  recipients: PropTypes.array.isRequired,
242
251
  onPick: PropTypes.func.isRequired,
243
252
  onRemove: PropTypes.func.isRequired,
253
+ enableCreateContact: PropTypes.bool,
244
254
  placeholder: PropTypes.string,
245
255
  endAdornment: PropTypes.node
246
256
  };
@@ -14,14 +14,17 @@ describe('ShareAutosuggest', function () {
14
14
  var onPick = _ref.onPick,
15
15
  onRemove = _ref.onRemove,
16
16
  _ref$loading = _ref.loading,
17
- loading = _ref$loading === void 0 ? false : _ref$loading;
17
+ loading = _ref$loading === void 0 ? false : _ref$loading,
18
+ _ref$enableCreateCont = _ref.enableCreateContact,
19
+ enableCreateContact = _ref$enableCreateCont === void 0 ? true : _ref$enableCreateCont;
18
20
  render( /*#__PURE__*/React.createElement(AppLike, null, /*#__PURE__*/React.createElement(ShareAutosuggest, {
19
21
  loading: loading,
20
22
  contactsAndGroups: [],
21
23
  placeholder: "myPlaceHolder",
22
24
  onPick: onPick,
23
25
  recipients: [],
24
- onRemove: onRemove
26
+ onRemove: onRemove,
27
+ enableCreateContact: enableCreateContact
25
28
  })));
26
29
  };
27
30
 
@@ -78,6 +81,27 @@ describe('ShareAutosuggest', function () {
78
81
  email: 'quentin.valmori@cozycloud.cc'
79
82
  });
80
83
  });
84
+ it('should not call onPick for new contacts when enableCreateContact is false', function () {
85
+ var onPick = jest.fn();
86
+ var onRemove = jest.fn();
87
+ setup({
88
+ onPick: onPick,
89
+ onRemove: onRemove,
90
+ enableCreateContact: false
91
+ });
92
+ var inputNode = screen.getByPlaceholderText('myPlaceHolder');
93
+ fireEvent.change(inputNode, {
94
+ target: {
95
+ value: 'new@cozycloud.cc'
96
+ }
97
+ });
98
+ fireEvent.keyPress(inputNode, {
99
+ key: 'Enter',
100
+ keyCode: 13,
101
+ charCode: 13
102
+ });
103
+ expect(onPick).not.toHaveBeenCalled();
104
+ });
81
105
  it('should show loading indicator when loading prop is true (autoFocus triggers onFocus on mount)', function () {
82
106
  var onPick = jest.fn();
83
107
  var onRemove = jest.fn();
@@ -33,6 +33,7 @@ export var ShareByEmail = function ShareByEmail(_ref) {
33
33
  onPendingRecipientsChange = _ref.onPendingRecipientsChange,
34
34
  selectedOption = _ref.selectedOption,
35
35
  onSelectedOptionChange = _ref.onSelectedOptionChange,
36
+ enableCreateContact = _ref.enableCreateContact,
36
37
  sharing = _ref.sharing;
37
38
 
38
39
  var _useI18n = useI18n(),
@@ -71,6 +72,7 @@ export var ShareByEmail = function ShareByEmail(_ref) {
71
72
  placeholder: pendingRecipients.length === 0 ? t("".concat(documentType, ".share.shareByEmail.emailPlaceholder")) : '',
72
73
  onPick: onRecipientPick,
73
74
  onRemove: onRecipientRemove,
75
+ enableCreateContact: enableCreateContact,
74
76
  currentRecipients: currentRecipients,
75
77
  recipients: pendingRecipients,
76
78
  endAdornment: /*#__PURE__*/React.createElement(ShareTypeSelect, {
@@ -88,6 +90,7 @@ ShareByEmail.propTypes = {
88
90
  onPendingRecipientsChange: PropTypes.func.isRequired,
89
91
  selectedOption: PropTypes.oneOf(['readWrite', 'readOnly']).isRequired,
90
92
  onSelectedOptionChange: PropTypes.func.isRequired,
93
+ enableCreateContact: PropTypes.bool,
91
94
  sharing: PropTypes.object
92
95
  };
93
96
  export default ShareByEmail;
@@ -25,7 +25,8 @@ describe('ShareByEmail (controlled)', function () {
25
25
  pendingRecipients: [],
26
26
  onPendingRecipientsChange: onPendingRecipientsChange,
27
27
  selectedOption: 'readWrite',
28
- onSelectedOptionChange: onSelectedOptionChange
28
+ onSelectedOptionChange: onSelectedOptionChange,
29
+ enableCreateContact: true
29
30
  }, overrides);
30
31
 
31
32
  var utils = render( /*#__PURE__*/React.createElement(AppLike, null, /*#__PURE__*/React.createElement(ShareByEmail, props)));
@@ -91,7 +91,8 @@ var SharingContent = function SharingContent(_ref) {
91
91
  pendingRecipients: pendingRecipients,
92
92
  onPendingRecipientsChange: onPendingRecipientsChange,
93
93
  selectedOption: selectedOption,
94
- onSelectedOptionChange: onSelectedOptionChange
94
+ onSelectedOptionChange: onSelectedOptionChange,
95
+ enableCreateContact: true
95
96
  })), showWhoHasAccess && /*#__PURE__*/React.createElement(WhoHasAccess, {
96
97
  document: document,
97
98
  documentType: documentType,
@@ -39,11 +39,11 @@ jest.mock('../hooks/useSharingContext', function () {
39
39
  };
40
40
  });
41
41
  jest.mock('../helpers/contacts', function () {
42
- return {
42
+ return _objectSpread(_objectSpread({}, jest.requireActual('../helpers/contacts')), {}, {
43
43
  getOrCreateFromArray: jest.fn(function (client, recipients) {
44
44
  return Promise.resolve(recipients);
45
45
  })
46
- };
46
+ });
47
47
  });
48
48
  describe('ShareDialogCozyToCozy', function () {
49
49
  var client = createMockClient({});
@@ -28,6 +28,7 @@ var ShareRecipientsInput = function ShareRecipientsInput(_ref) {
28
28
  placeholder = _ref.placeholder,
29
29
  onPick = _ref.onPick,
30
30
  onRemove = _ref.onRemove,
31
+ enableCreateContact = _ref.enableCreateContact,
31
32
  disabled = _ref.disabled,
32
33
  endAdornment = _ref.endAdornment;
33
34
  var reachableContactsQuery = buildReachableContactsQuery();
@@ -82,6 +83,7 @@ var ShareRecipientsInput = function ShareRecipientsInput(_ref) {
82
83
  recipients: recipients,
83
84
  onPick: onPick,
84
85
  onRemove: onRemove,
86
+ enableCreateContact: enableCreateContact,
85
87
  placeholder: placeholder,
86
88
  endAdornment: endAdornment
87
89
  });
@@ -93,6 +95,7 @@ ShareRecipientsInput.propTypes = {
93
95
  placeholder: PropTypes.string,
94
96
  onPick: PropTypes.func.isRequired,
95
97
  onRemove: PropTypes.func.isRequired,
98
+ enableCreateContact: PropTypes.bool,
96
99
  disabled: PropTypes.bool,
97
100
  endAdornment: PropTypes.node
98
101
  };
@@ -102,4 +102,16 @@ export var getContactsFromGroupId = function getContactsFromGroupId() {
102
102
  return group._id === groupId;
103
103
  });
104
104
  });
105
+ };
106
+ /**
107
+ * Returns true when a contact does not exist. A contact without
108
+ * an `_id` is always a placeholder that must be created before sharing.
109
+ *
110
+ * Used to decide whether the autosuggest input should allow creating
111
+ * a new contact on the fly or should block the pick (depending on the
112
+ * `enableCreateContact` flag).
113
+ */
114
+
115
+ export var isContactToBeCreated = function isContactToBeCreated(contact) {
116
+ return !contact._id;
105
117
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-sharing",
3
- "version": "33.0.0",
3
+ "version": "33.0.1",
4
4
  "description": "Provides sharing login for React applications.",
5
5
  "main": "dist/index.js",
6
6
  "author": "Cozy",
@@ -83,5 +83,5 @@
83
83
  "sideEffects": [
84
84
  "*.css"
85
85
  ],
86
- "gitHead": "6276bf5263e77e71944feb505a0aad085e770166"
86
+ "gitHead": "546412325d385a075b531e7c735a0082d6864040"
87
87
  }
@@ -206,6 +206,7 @@ const FederatedFolderModalContent = ({
206
206
  onPendingRecipientsChange={setPendingRecipients}
207
207
  selectedOption={selectedOption}
208
208
  onSelectedOptionChange={setSelectedOption}
209
+ enableCreateContact
209
210
  />
210
211
  </div>
211
212
  <WhoHasAccess
@@ -8,6 +8,7 @@ import { Spinner } from 'cozy-ui/transpiled/react/Spinner'
8
8
 
9
9
  import { GroupAvatar } from './Avatar/GroupAvatar'
10
10
  import ContactSuggestion from './ContactSuggestion'
11
+ import { isContactToBeCreated } from '../helpers/contacts'
11
12
  import { extractEmails, validateEmail } from '../helpers/email'
12
13
  import { getDisplayName, getInitials, Contact, Group } from '../models'
13
14
  import styles from '../styles/autosuggest.styl'
@@ -25,6 +26,7 @@ const ShareAutocomplete = ({
25
26
  recipients,
26
27
  onPick,
27
28
  onRemove,
29
+ enableCreateContact,
28
30
  placeholder,
29
31
  endAdornment
30
32
  }) => {
@@ -53,17 +55,18 @@ const ShareAutocomplete = ({
53
55
  const onSuggestionsFetchRequested = ({ value }) => {
54
56
  const computedSuggestions = computeSuggestions(value)
55
57
 
56
- const newSuggestions =
57
- computedSuggestions.length === 0 && validateEmail(value)
58
- ? [
59
- {
60
- email: value,
61
- _type: Contact.doctype
62
- }
63
- ]
64
- : computedSuggestions
65
-
66
- setSuggestions(newSuggestions)
58
+ if (computedSuggestions.length > 0) {
59
+ setSuggestions(computedSuggestions)
60
+ } else if (enableCreateContact && validateEmail(value)) {
61
+ setSuggestions([
62
+ {
63
+ email: value,
64
+ _type: Contact.doctype
65
+ }
66
+ ])
67
+ } else {
68
+ setSuggestions([])
69
+ }
67
70
  }
68
71
 
69
72
  const onSuggestionsClearRequested = () => {
@@ -130,6 +133,8 @@ const ShareAutocomplete = ({
130
133
  }
131
134
 
132
135
  const onAutosuggestPick = value => {
136
+ if (isContactToBeCreated(value) && !enableCreateContact) return
137
+
133
138
  onPick(value)
134
139
  setInputValue('')
135
140
  autosuggestRef?.current?.input?.focus()
@@ -228,6 +233,7 @@ ShareAutocomplete.propTypes = {
228
233
  recipients: PropTypes.array.isRequired,
229
234
  onPick: PropTypes.func.isRequired,
230
235
  onRemove: PropTypes.func.isRequired,
236
+ enableCreateContact: PropTypes.bool,
231
237
  placeholder: PropTypes.string,
232
238
  endAdornment: PropTypes.node
233
239
  }
@@ -9,7 +9,12 @@ jest.mock('cozy-ui/transpiled/react/Spinner', () => ({
9
9
  }))
10
10
 
11
11
  describe('ShareAutosuggest', () => {
12
- const setup = ({ onPick, onRemove, loading = false }) => {
12
+ const setup = ({
13
+ onPick,
14
+ onRemove,
15
+ loading = false,
16
+ enableCreateContact = true
17
+ }) => {
13
18
  render(
14
19
  <AppLike>
15
20
  <ShareAutosuggest
@@ -19,6 +24,7 @@ describe('ShareAutosuggest', () => {
19
24
  onPick={onPick}
20
25
  recipients={[]}
21
26
  onRemove={onRemove}
27
+ enableCreateContact={enableCreateContact}
22
28
  />
23
29
  </AppLike>
24
30
  )
@@ -51,6 +57,19 @@ describe('ShareAutosuggest', () => {
51
57
  })
52
58
  })
53
59
 
60
+ it('should not call onPick for new contacts when enableCreateContact is false', () => {
61
+ const onPick = jest.fn()
62
+ const onRemove = jest.fn()
63
+
64
+ setup({ onPick, onRemove, enableCreateContact: false })
65
+
66
+ const inputNode = screen.getByPlaceholderText('myPlaceHolder')
67
+
68
+ fireEvent.change(inputNode, { target: { value: 'new@cozycloud.cc' } })
69
+ fireEvent.keyPress(inputNode, { key: 'Enter', keyCode: 13, charCode: 13 })
70
+ expect(onPick).not.toHaveBeenCalled()
71
+ })
72
+
54
73
  it('should show loading indicator when loading prop is true (autoFocus triggers onFocus on mount)', () => {
55
74
  const onPick = jest.fn()
56
75
  const onRemove = jest.fn()
@@ -21,6 +21,7 @@ export const ShareByEmail = ({
21
21
  onPendingRecipientsChange,
22
22
  selectedOption,
23
23
  onSelectedOptionChange,
24
+ enableCreateContact,
24
25
  sharing
25
26
  }) => {
26
27
  const { t } = useI18n()
@@ -66,6 +67,7 @@ export const ShareByEmail = ({
66
67
  }
67
68
  onPick={onRecipientPick}
68
69
  onRemove={onRecipientRemove}
70
+ enableCreateContact={enableCreateContact}
69
71
  currentRecipients={currentRecipients}
70
72
  recipients={pendingRecipients}
71
73
  endAdornment={
@@ -89,6 +91,7 @@ ShareByEmail.propTypes = {
89
91
  onPendingRecipientsChange: PropTypes.func.isRequired,
90
92
  selectedOption: PropTypes.oneOf(['readWrite', 'readOnly']).isRequired,
91
93
  onSelectedOptionChange: PropTypes.func.isRequired,
94
+ enableCreateContact: PropTypes.bool,
92
95
  sharing: PropTypes.object
93
96
  }
94
97
 
@@ -17,6 +17,7 @@ describe('ShareByEmail (controlled)', () => {
17
17
  onPendingRecipientsChange,
18
18
  selectedOption: 'readWrite',
19
19
  onSelectedOptionChange,
20
+ enableCreateContact: true,
20
21
  ...overrides
21
22
  }
22
23
  const utils = render(
@@ -79,6 +79,7 @@ const SharingContent = ({
79
79
  onPendingRecipientsChange={onPendingRecipientsChange}
80
80
  selectedOption={selectedOption}
81
81
  onSelectedOptionChange={onSelectedOptionChange}
82
+ enableCreateContact
82
83
  />
83
84
  )}
84
85
  </div>
@@ -21,6 +21,7 @@ jest.mock('../hooks/useSharingContext', () => ({
21
21
  }))
22
22
 
23
23
  jest.mock('../helpers/contacts', () => ({
24
+ ...jest.requireActual('../helpers/contacts'),
24
25
  getOrCreateFromArray: jest.fn((client, recipients) =>
25
26
  Promise.resolve(recipients)
26
27
  )
@@ -25,6 +25,7 @@ const ShareRecipientsInput = ({
25
25
  placeholder,
26
26
  onPick,
27
27
  onRemove,
28
+ enableCreateContact,
28
29
  disabled,
29
30
  endAdornment
30
31
  }) => {
@@ -114,6 +115,7 @@ const ShareRecipientsInput = ({
114
115
  recipients={recipients}
115
116
  onPick={onPick}
116
117
  onRemove={onRemove}
118
+ enableCreateContact={enableCreateContact}
117
119
  placeholder={placeholder}
118
120
  endAdornment={endAdornment}
119
121
  />
@@ -126,6 +128,7 @@ ShareRecipientsInput.propTypes = {
126
128
  placeholder: PropTypes.string,
127
129
  onPick: PropTypes.func.isRequired,
128
130
  onRemove: PropTypes.func.isRequired,
131
+ enableCreateContact: PropTypes.bool,
129
132
  disabled: PropTypes.bool,
130
133
  endAdornment: PropTypes.node
131
134
  }
@@ -47,3 +47,15 @@ export const getContactsFromGroupId = (contacts = [], groupId) => {
47
47
  )
48
48
  })
49
49
  }
50
+
51
+ /**
52
+ * Returns true when a contact does not exist. A contact without
53
+ * an `_id` is always a placeholder that must be created before sharing.
54
+ *
55
+ * Used to decide whether the autosuggest input should allow creating
56
+ * a new contact on the fly or should block the pick (depending on the
57
+ * `enableCreateContact` flag).
58
+ */
59
+ export const isContactToBeCreated = contact => {
60
+ return !contact._id
61
+ }