decap-cms-widget-list 3.5.0 → 3.6.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/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "decap-cms-widget-list",
3
3
  "description": "Widget for editing lists in Decap CMS.",
4
- "version": "3.5.0",
4
+ "version": "3.6.1",
5
5
  "homepage": "https://www.decapcms.org/docs/widgets/#list",
6
- "repository": "https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-list",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-list"
9
+ },
7
10
  "bugs": "https://github.com/decaporg/decap-cms/issues",
8
11
  "module": "dist/esm/index.js",
9
12
  "main": "dist/decap-cms-widget-list.js",
@@ -15,27 +18,28 @@
15
18
  "object"
16
19
  ],
17
20
  "sideEffects": false,
18
- "scripts": {
19
- "develop": "npm run build:esm -- --watch",
20
- "build": "cross-env NODE_ENV=production webpack",
21
- "build:esm": "cross-env NODE_ENV=esm babel src --out-dir dist/esm --ignore \"**/__tests__\" --root-mode upward"
22
- },
23
21
  "dependencies": {
24
22
  "@dnd-kit/core": "^6.0.8",
25
23
  "@dnd-kit/modifiers": "^6.0.1",
26
- "@dnd-kit/sortable": "^7.0.2"
24
+ "@dnd-kit/sortable": "^7.0.2",
25
+ "@dnd-kit/utilities": "^3.2.2"
27
26
  },
28
27
  "peerDependencies": {
29
28
  "@emotion/react": "^11.11.1",
30
29
  "@emotion/styled": "^11.11.0",
31
- "decap-cms-lib-widgets": "^3.0.0",
32
- "decap-cms-ui-default": "^3.0.0",
33
- "decap-cms-widget-object": "^3.0.0",
34
- "immutable": "^3.7.6",
30
+ "immutable": "^4.3.9",
35
31
  "lodash": "^4.17.11",
36
32
  "prop-types": "^15.7.2",
37
33
  "react": "^19.1.0",
38
- "react-immutable-proptypes": "^2.1.0"
34
+ "react-immutable-proptypes": "^2.1.0",
35
+ "uuid": "^8.3.2",
36
+ "decap-cms-lib-widgets": "3.4.1",
37
+ "decap-cms-ui-default": "3.9.1",
38
+ "decap-cms-widget-object": "3.7.1"
39
39
  },
40
- "gitHead": "13afe6686b32f7c03b4819d343d787c63f13664f"
41
- }
40
+ "scripts": {
41
+ "develop": "pnpm run build:esm --watch",
42
+ "build": "cross-env NODE_ENV=production webpack",
43
+ "build:esm": "cross-env NODE_ENV=esm babel src --out-dir dist/esm --ignore \"**/__tests__\" --root-mode upward"
44
+ }
45
+ }
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import { Component } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import ImmutablePropTypes from 'react-immutable-proptypes';
4
4
  import styled from '@emotion/styled';
@@ -7,7 +7,6 @@ import { List, Map, fromJS } from 'immutable';
7
7
  import partial from 'lodash/partial';
8
8
  import isEmpty from 'lodash/isEmpty';
9
9
  import uniqueId from 'lodash/uniqueId';
10
- import { v4 as uuid } from 'uuid';
11
10
  import DecapCmsWidgetObject from 'decap-cms-widget-object';
12
11
  import {
13
12
  DndContext,
@@ -172,7 +171,7 @@ function LabelComponent({ field, isActive, hasErrors, uniqueFieldId, isFieldOpti
172
171
  );
173
172
  }
174
173
 
175
- export default class ListControl extends React.Component {
174
+ export default class ListControl extends Component {
176
175
  childRefs = {};
177
176
 
178
177
  static propTypes = {
@@ -211,7 +210,7 @@ export default class ListControl extends React.Component {
211
210
  const { field, value } = props;
212
211
  const listCollapsed = field.get('collapsed', true);
213
212
  const itemsCollapsed = (value && Array(value.size).fill(listCollapsed)) || [];
214
- const keys = (value && Array.from({ length: value.size }, () => uuid())) || [];
213
+ const keys = (value && Array.from({ length: value.size }, () => crypto.randomUUID())) || [];
215
214
 
216
215
  this.state = {
217
216
  listCollapsed,
@@ -353,7 +352,7 @@ export default class ListControl extends React.Component {
353
352
  const { value, onChange, field } = this.props;
354
353
  const addToTop = field.get('add_to_top', false);
355
354
 
356
- const itemKey = uuid();
355
+ const itemKey = crypto.randomUUID();
357
356
  this.setState({
358
357
  itemsCollapsed: addToTop
359
358
  ? [false, ...this.state.itemsCollapsed]
@@ -420,20 +419,32 @@ export default class ListControl extends React.Component {
420
419
  getObjectValue = idx => this.props.value.get(idx) || Map();
421
420
 
422
421
  handleChangeFor(index) {
422
+ const key = this.state.keys[index];
423
+
423
424
  return (f, newValue, newMetadata) => {
424
425
  const { value, metadata, onChange, field } = this.props;
426
+
427
+ // Resolve the item's position when the change fires rather than when this
428
+ // handler was created. If an item has been removed or moved in between,
429
+ // `index` now points at a different item, or past the end of the list.
430
+ const currentIndex = this.state.keys.indexOf(key);
431
+
432
+ if (currentIndex === -1) {
433
+ return;
434
+ }
435
+
425
436
  const collectionName = field.get('name');
426
437
  const listFieldObjectWidget = field.getIn(['field', 'widget']) === 'object';
427
438
  const withNameKey =
428
439
  this.getValueType() !== valueTypes.SINGLE ||
429
440
  (this.getValueType() === valueTypes.SINGLE && listFieldObjectWidget);
430
441
  const newObjectValue = withNameKey
431
- ? this.getObjectValue(index).set(f.get('name'), newValue)
442
+ ? this.getObjectValue(currentIndex).set(f.get('name'), newValue)
432
443
  : newValue;
433
444
  const parsedMetadata = {
434
445
  [collectionName]: Object.assign(metadata ? metadata.toJS() : {}, newMetadata || {}),
435
446
  };
436
- onChange(value.set(index, newObjectValue), parsedMetadata);
447
+ onChange(value.set(currentIndex, newObjectValue), parsedMetadata);
437
448
  };
438
449
  }
439
450
 
@@ -1,4 +1,3 @@
1
- import React from 'react';
2
1
  import { fireEvent, render } from '@testing-library/react';
3
2
  import { fromJS } from 'immutable';
4
3
 
@@ -34,7 +33,6 @@ jest.mock('decap-cms-ui-default', () => {
34
33
  ListItemTopBar,
35
34
  };
36
35
  });
37
- jest.mock('uuid');
38
36
 
39
37
  describe('ListControl', () => {
40
38
  const props = {
@@ -63,9 +61,8 @@ describe('ListControl', () => {
63
61
 
64
62
  beforeEach(() => {
65
63
  jest.clearAllMocks();
66
- const uuid = require('uuid');
67
64
  let id = 0;
68
- uuid.v4.mockImplementation(() => {
65
+ global.crypto.randomUUID = jest.fn(() => {
69
66
  return id++;
70
67
  });
71
68
  });
@@ -789,4 +786,77 @@ describe('ListControl', () => {
789
786
  listControl.validate();
790
787
  expect(props.onValidateObject).toHaveBeenCalledWith('forID', []);
791
788
  });
789
+
790
+ const typedField = fromJS({
791
+ name: 'list',
792
+ label: 'List',
793
+ types: [
794
+ {
795
+ name: 'type_one',
796
+ widget: 'object',
797
+ fields: [{ name: 'text', widget: 'markdown', label: 'Text' }],
798
+ },
799
+ ],
800
+ });
801
+
802
+ it('should apply a change to the item it was made on after an earlier item is removed', () => {
803
+ const value = fromJS([
804
+ { type: 'type_one', text: 'one' },
805
+ { type: 'type_one', text: 'two' },
806
+ { type: 'type_one', text: 'three' },
807
+ ]);
808
+
809
+ let control;
810
+ const { getAllByText, rerender } = render(
811
+ <ListControl ref={ref => (control = ref)} {...props} field={typedField} value={value} />,
812
+ );
813
+
814
+ const changeThirdItem = control.handleChangeFor(2);
815
+
816
+ fireEvent.click(getAllByText('Remove')[0]);
817
+ const afterRemoval = props.onChange.mock.calls[0][0];
818
+ rerender(
819
+ <ListControl
820
+ ref={ref => (control = ref)}
821
+ {...props}
822
+ field={typedField}
823
+ value={afterRemoval}
824
+ />,
825
+ );
826
+
827
+ changeThirdItem(fromJS({ name: 'text' }), 'three edited');
828
+
829
+ const written = props.onChange.mock.calls[1][0].toJS();
830
+ expect(written).toHaveLength(2);
831
+ expect(written[1]).toEqual({ type: 'type_one', text: 'three edited' });
832
+ });
833
+
834
+ it('should discard a change made on an item that has since been removed', () => {
835
+ const value = fromJS([
836
+ { type: 'type_one', text: 'one' },
837
+ { type: 'type_one', text: 'two' },
838
+ ]);
839
+
840
+ let control;
841
+ const { getAllByText, rerender } = render(
842
+ <ListControl ref={ref => (control = ref)} {...props} field={typedField} value={value} />,
843
+ );
844
+
845
+ const changeSecondItem = control.handleChangeFor(1);
846
+
847
+ fireEvent.click(getAllByText('Remove')[1]);
848
+ const afterRemoval = props.onChange.mock.calls[0][0];
849
+ rerender(
850
+ <ListControl
851
+ ref={ref => (control = ref)}
852
+ {...props}
853
+ field={typedField}
854
+ value={afterRemoval}
855
+ />,
856
+ );
857
+
858
+ changeSecondItem(fromJS({ name: 'text' }), 'text for a removed item');
859
+
860
+ expect(props.onChange).toHaveBeenCalledTimes(1);
861
+ });
792
862
  });
@@ -162,6 +162,7 @@ exports[`ListControl should add to list when add button is clicked 1`] = `
162
162
  class="emotion-3 emotion-4"
163
163
  >
164
164
  <button
165
+ aria-label="editor.editorWidgets.object.collapse"
165
166
  class="emotion-5 emotion-6"
166
167
  data-testid="expand-button"
167
168
  >
@@ -414,6 +415,7 @@ exports[`ListControl should remove from list when remove button is clicked 1`] =
414
415
  class="emotion-3 emotion-4"
415
416
  >
416
417
  <button
418
+ aria-label="editor.editorWidgets.object.collapse"
417
419
  class="emotion-5 emotion-6"
418
420
  data-testid="expand-button"
419
421
  >
@@ -696,6 +698,7 @@ exports[`ListControl should remove from list when remove button is clicked 2`] =
696
698
  class="emotion-3 emotion-4"
697
699
  >
698
700
  <button
701
+ aria-label="editor.editorWidgets.object.collapse"
699
702
  class="emotion-5 emotion-6"
700
703
  data-testid="expand-button"
701
704
  >
@@ -961,6 +964,7 @@ exports[`ListControl should render list with fields with collapse = "false" and
961
964
  class="emotion-3 emotion-4"
962
965
  >
963
966
  <button
967
+ aria-label="editor.editorWidgets.object.collapse"
964
968
  class="emotion-5 emotion-6"
965
969
  data-testid="expand-button"
966
970
  >
@@ -1243,6 +1247,7 @@ exports[`ListControl should render list with fields with collapse = "false" and
1243
1247
  class="emotion-3 emotion-4"
1244
1248
  >
1245
1249
  <button
1250
+ aria-label="editor.editorWidgets.object.collapse"
1246
1251
  class="emotion-5 emotion-6"
1247
1252
  data-testid="expand-button"
1248
1253
  >
@@ -1525,6 +1530,7 @@ exports[`ListControl should render list with fields with default collapse ("true
1525
1530
  class="emotion-3 emotion-4"
1526
1531
  >
1527
1532
  <button
1533
+ aria-label="editor.editorWidgets.object.expand"
1528
1534
  class="emotion-5 emotion-6"
1529
1535
  data-testid="expand-button"
1530
1536
  >
@@ -1790,6 +1796,7 @@ exports[`ListControl should render list with fields with default collapse ("true
1790
1796
  class="emotion-3 emotion-4"
1791
1797
  >
1792
1798
  <button
1799
+ aria-label="editor.editorWidgets.object.expand"
1793
1800
  class="emotion-5 emotion-6"
1794
1801
  data-testid="expand-button"
1795
1802
  >
@@ -1993,6 +2000,7 @@ exports[`ListControl should render list with nested object 1`] = `
1993
2000
  class="emotion-3 emotion-4"
1994
2001
  >
1995
2002
  <button
2003
+ aria-label="editor.editorWidgets.object.expand"
1996
2004
  class="emotion-5 emotion-6"
1997
2005
  data-testid="expand-button"
1998
2006
  >
@@ -2279,6 +2287,7 @@ exports[`ListControl should render list with nested object with collapse = false
2279
2287
  class="emotion-3 emotion-4"
2280
2288
  >
2281
2289
  <button
2290
+ aria-label="editor.editorWidgets.object.collapse"
2282
2291
  class="emotion-5 emotion-6"
2283
2292
  data-testid="expand-button"
2284
2293
  >
package/LICENSE DELETED
@@ -1,22 +0,0 @@
1
- Copyright (c) 2016 Netlify <decap@p-m.si>
2
-
3
- MIT License
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining
6
- a copy of this software and associated documentation files (the
7
- "Software"), to deal in the Software without restriction, including
8
- without limitation the rights to use, copy, modify, merge, publish,
9
- distribute, sublicense, and/or sell copies of the Software, and to
10
- permit persons to whom the Software is furnished to do so, subject to
11
- the following conditions:
12
-
13
- The above copyright notice and this permission notice shall be
14
- included in all copies or substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.