newspack-components 4.10.0-alpha.1 → 4.11.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.
- package/CHANGELOG.md +10 -3
- package/DEVELOPMENT.md +1 -0
- package/dist/cjs/card-sortable-list/index.js +6 -3
- package/dist/cjs/card-sortable-list/index.test.js +21 -0
- package/dist/cjs/card-sortable-list/style.scss +8 -0
- package/dist/cjs/index.js +7 -0
- package/dist/cjs/reorder-modal/index.js +521 -0
- package/dist/cjs/reorder-modal/index.test.js +1223 -0
- package/dist/cjs/reorder-modal/style.scss +114 -0
- package/dist/esm/card-sortable-list/index.js +5 -2
- package/dist/esm/card-sortable-list/index.test.js +21 -0
- package/dist/esm/card-sortable-list/style.scss +8 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/reorder-modal/index.js +514 -0
- package/dist/esm/reorder-modal/index.test.js +1224 -0
- package/dist/esm/reorder-modal/style.scss +114 -0
- package/package.json +3 -3
- package/src/card-sortable-list/index.test.js +13 -0
- package/src/card-sortable-list/index.tsx +4 -1
- package/src/card-sortable-list/style.scss +8 -0
- package/src/index.js +1 -0
- package/src/reorder-modal/index.js +484 -0
- package/src/reorder-modal/index.test.js +566 -0
- package/src/reorder-modal/style.scss +114 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
# newspack-components [4.
|
|
1
|
+
# newspack-components [4.11.0](https://github.com/Automattic/newspack-workspace/compare/newspack-components@4.10.0...newspack-components@4.11.0) (2026-10-14)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **newsletters:** show premium lists in the subscription form ([#1291](https://github.com/Automattic/newspack-workspace/issues/1291)) ([456d762](https://github.com/Automattic/newspack-workspace/commit/456d762ba465aafc6e5be08ed47836acafaad1a6))
|
|
7
|
+
|
|
8
|
+
# newspack-components [4.10.0](https://github.com/Automattic/newspack-workspace/compare/newspack-components@4.9.2...newspack-components@4.10.0) (2026-10-12)
|
|
2
9
|
|
|
3
10
|
|
|
4
11
|
### Bug Fixes
|
|
@@ -23,8 +30,8 @@
|
|
|
23
30
|
|
|
24
31
|
### Dependencies
|
|
25
32
|
|
|
26
|
-
* **newspack-icons:** upgraded to 1.2.0
|
|
27
|
-
* **newspack-scripts:** upgraded to 5.12.0
|
|
33
|
+
* **newspack-icons:** upgraded to 1.2.0
|
|
34
|
+
* **newspack-scripts:** upgraded to 5.12.0
|
|
28
35
|
|
|
29
36
|
## newspack-components [4.9.2](https://github.com/Automattic/newspack-workspace/compare/newspack-components@4.9.1...newspack-components@4.9.2) (2026-09-28)
|
|
30
37
|
|
package/DEVELOPMENT.md
CHANGED
|
@@ -93,6 +93,7 @@ When building a screen, use the **spacing scale** (8px unit: 16, 24, 32, 48, 64)
|
|
|
93
93
|
- **`FormTokenField`** - Token input field for tags/categories; prefer the Newspack component over the core `FormTokenField` because it also supports a `description` prop for help text like other controls.
|
|
94
94
|
- **`ImageUpload`** - Image upload and selection component
|
|
95
95
|
- **`RadioControl`** - Radio button group control
|
|
96
|
+
- **`ReorderModal`** - Modal for putting a short list of items in order, by dragging or with move buttons; applies on Save
|
|
96
97
|
- **`TextControl`** - Text input with Newspack styling and required field support
|
|
97
98
|
|
|
98
99
|
### Content Components
|
|
@@ -11,7 +11,7 @@ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/sli
|
|
|
11
11
|
var _components = require("@wordpress/components");
|
|
12
12
|
var _element = require("@wordpress/element");
|
|
13
13
|
var _ui = require("@wordpress/ui");
|
|
14
|
-
var
|
|
14
|
+
var _card = _interopRequireDefault(require("../card"));
|
|
15
15
|
require("./style.scss");
|
|
16
16
|
var _classnames = _interopRequireDefault(require("classnames"));
|
|
17
17
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
@@ -388,7 +388,7 @@ var CardSortableList = function CardSortableList(_ref) {
|
|
|
388
388
|
var _item$badge, _item$badge$intent;
|
|
389
389
|
var onDraggableStart = _ref2.onDraggableStart,
|
|
390
390
|
onDraggableEnd = _ref2.onDraggableEnd;
|
|
391
|
-
return /*#__PURE__*/(0, _jsxRuntime.jsx)(
|
|
391
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_card["default"], {
|
|
392
392
|
isSmall: true,
|
|
393
393
|
draggable: true,
|
|
394
394
|
onDragStart: onDraggableStart,
|
|
@@ -413,7 +413,10 @@ var CardSortableList = function CardSortableList(_ref) {
|
|
|
413
413
|
}, item.onToggleChange !== undefined && {
|
|
414
414
|
actionType: 'toggle',
|
|
415
415
|
isActive: item.toggleChecked,
|
|
416
|
-
onToggle: item.onToggleChange
|
|
416
|
+
onToggle: item.onToggleChange,
|
|
417
|
+
// Names the toggle for assistive tech. The card hides this
|
|
418
|
+
// label, since the header already shows the title.
|
|
419
|
+
title: item.title
|
|
417
420
|
}), item.actions !== undefined && {
|
|
418
421
|
actions: item.actions
|
|
419
422
|
})
|
|
@@ -47,4 +47,25 @@ describe('CardSortableList', function () {
|
|
|
47
47
|
// An unguarded Badge would still emit an empty span, so assert on element children.
|
|
48
48
|
expect(container.querySelector('h3').children).toHaveLength(0);
|
|
49
49
|
});
|
|
50
|
+
it('names each toggle after its item', function () {
|
|
51
|
+
(0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_["default"], {
|
|
52
|
+
items: [{
|
|
53
|
+
id: 1,
|
|
54
|
+
title: 'Homepage',
|
|
55
|
+
toggleChecked: true,
|
|
56
|
+
onToggleChange: function onToggleChange() {}
|
|
57
|
+
}, {
|
|
58
|
+
id: 2,
|
|
59
|
+
title: 'About',
|
|
60
|
+
toggleChecked: false,
|
|
61
|
+
onToggleChange: function onToggleChange() {}
|
|
62
|
+
}]
|
|
63
|
+
}));
|
|
64
|
+
expect(_react.screen.getByRole('checkbox', {
|
|
65
|
+
name: 'Homepage'
|
|
66
|
+
})).toBeChecked();
|
|
67
|
+
expect(_react.screen.getByRole('checkbox', {
|
|
68
|
+
name: 'About'
|
|
69
|
+
})).not.toBeChecked();
|
|
70
|
+
});
|
|
50
71
|
});
|
|
@@ -6,6 +6,14 @@ $drop-duration: 400ms; /* must match DROP_ANIMATION_DURATION in index.tsx */
|
|
|
6
6
|
&__item {
|
|
7
7
|
cursor: grab;
|
|
8
8
|
|
|
9
|
+
/*
|
|
10
|
+
* Keep a card's title in the case it was written in. The block editor
|
|
11
|
+
* sidebar uppercases every h3 (`.interface-complementary-area h3`).
|
|
12
|
+
*/
|
|
13
|
+
.newspack-card--core__header-content h3 {
|
|
14
|
+
text-transform: none;
|
|
15
|
+
}
|
|
16
|
+
|
|
9
17
|
/*
|
|
10
18
|
* Animate items sliding to fill/make space around the dragged item.
|
|
11
19
|
*/
|
package/dist/cjs/index.js
CHANGED
|
@@ -250,6 +250,12 @@ Object.defineProperty(exports, "RadioControl", {
|
|
|
250
250
|
return _radioControl["default"];
|
|
251
251
|
}
|
|
252
252
|
});
|
|
253
|
+
Object.defineProperty(exports, "ReorderModal", {
|
|
254
|
+
enumerable: true,
|
|
255
|
+
get: function get() {
|
|
256
|
+
return _reorderModal["default"];
|
|
257
|
+
}
|
|
258
|
+
});
|
|
253
259
|
Object.defineProperty(exports, "Router", {
|
|
254
260
|
enumerable: true,
|
|
255
261
|
get: function get() {
|
|
@@ -436,6 +442,7 @@ var _pluginToggle = _interopRequireDefault(require("./plugin-toggle"));
|
|
|
436
442
|
var _popover = _interopRequireDefault(require("./popover"));
|
|
437
443
|
var _progressBar = _interopRequireDefault(require("./progress-bar"));
|
|
438
444
|
var _radioControl = _interopRequireDefault(require("./radio-control"));
|
|
445
|
+
var _reorderModal = _interopRequireDefault(require("./reorder-modal"));
|
|
439
446
|
var _sectionHeader = _interopRequireDefault(require("./section-header"));
|
|
440
447
|
var _selectControl = _interopRequireDefault(require("./select-control"));
|
|
441
448
|
var _settings = _interopRequireDefault(require("./settings"));
|
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.moveItem = exports.findOverlay = exports["default"] = void 0;
|
|
8
|
+
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
|
9
|
+
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
|
|
10
|
+
var _classnames = _interopRequireDefault(require("classnames"));
|
|
11
|
+
var _i18n = require("@wordpress/i18n");
|
|
12
|
+
var _a11y = require("@wordpress/a11y");
|
|
13
|
+
var _element = require("@wordpress/element");
|
|
14
|
+
var _compose = require("@wordpress/compose");
|
|
15
|
+
var _components = require("@wordpress/components");
|
|
16
|
+
var _icons = require("@wordpress/icons");
|
|
17
|
+
var _ui = require("@wordpress/ui");
|
|
18
|
+
require("./style.scss");
|
|
19
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
20
|
+
/**
|
|
21
|
+
* External dependencies
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* WordPress dependencies
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Internal dependencies
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
var moveItem = exports.moveItem = function moveItem(items, from, to) {
|
|
33
|
+
if (from === to || from < 0 || to < 0 || from >= items.length || to >= items.length) {
|
|
34
|
+
return items;
|
|
35
|
+
}
|
|
36
|
+
var next = (0, _toConsumableArray2["default"])(items);
|
|
37
|
+
var _next$splice = next.splice(from, 1),
|
|
38
|
+
_next$splice2 = (0, _slicedToArray2["default"])(_next$splice, 1),
|
|
39
|
+
moved = _next$splice2[0];
|
|
40
|
+
next.splice(to, 0, moved);
|
|
41
|
+
return next;
|
|
42
|
+
};
|
|
43
|
+
var OVERLAY_SELECTOR = '.components-modal__screen-overlay';
|
|
44
|
+
|
|
45
|
+
// The overlay carries the dismissal handling and the drop target, and `Modal`
|
|
46
|
+
// forwards its ref to it today. Deriving it from whatever node arrives keeps
|
|
47
|
+
// both working if core ever forwards that ref somewhere else.
|
|
48
|
+
var findOverlay = exports.findOverlay = function findOverlay(node) {
|
|
49
|
+
var _node$closest, _node$querySelector, _node$ownerDocument;
|
|
50
|
+
return (node === null || node === void 0 || (_node$closest = node.closest) === null || _node$closest === void 0 ? void 0 : _node$closest.call(node, OVERLAY_SELECTOR)) || (node === null || node === void 0 || (_node$querySelector = node.querySelector) === null || _node$querySelector === void 0 ? void 0 : _node$querySelector.call(node, OVERLAY_SELECTOR)) || (node === null || node === void 0 || (_node$ownerDocument = node.ownerDocument) === null || _node$ownerDocument === void 0 ? void 0 : _node$ownerDocument.querySelector(OVERLAY_SELECTOR)) || null;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Disabled controls stay focusable here, so they report `aria-disabled` rather
|
|
54
|
+
// than the native attribute.
|
|
55
|
+
var isAvailable = function isAvailable(button) {
|
|
56
|
+
return !!button && !button.matches(':disabled, [aria-disabled="true"]');
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Speech input matches on the visible label, so it has to open the accessible
|
|
60
|
+
// name. Composing both from one translated string keeps that true in every locale.
|
|
61
|
+
var nameWithTitle = function nameWithTitle(label, title) {
|
|
62
|
+
return (0, _i18n.sprintf)(/* translators: keep %1$s first: it repeats the button's visible label, which speech input matches on. 1: the control's visible label. 2: title of the content being moved. */
|
|
63
|
+
(0, _i18n.__)('%1$s: %2$s', 'newspack-plugin'), label, title);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Put a short list of items in order and, optionally, choose which are on.
|
|
68
|
+
* Changes apply on Save.
|
|
69
|
+
*
|
|
70
|
+
* @param {Object} props Component props.
|
|
71
|
+
* @param {string} props.title Modal title.
|
|
72
|
+
* @param {Array} props.ids Item IDs, in their current order.
|
|
73
|
+
* @param {Function} props.fetchItems Resolves the IDs to `{ value, label, badge? }`, where `badge` is `{ label, tooltip? }`.
|
|
74
|
+
* @param {Function} props.onSave Called with the new order and, when `enabledIds` is given, the enabled IDs: those
|
|
75
|
+
* on, in that order, then any from `enabledIds` the modal had no row for, kept as they were.
|
|
76
|
+
* @param {Function} props.onClose Called when the modal closes without saving.
|
|
77
|
+
* @param {Array} props.enabledIds Optional. Gives each row an on/off toggle, starting on for these IDs. Save is held while none is on.
|
|
78
|
+
* @param {string} props.description Optional. Introduces the list, above it.
|
|
79
|
+
* @param {string} props.noneOnHelp Why Save is held while no toggle is on.
|
|
80
|
+
*/
|
|
81
|
+
var _ReorderModal = function ReorderModal(_ref) {
|
|
82
|
+
var title = _ref.title,
|
|
83
|
+
ids = _ref.ids,
|
|
84
|
+
fetchItems = _ref.fetchItems,
|
|
85
|
+
onSave = _ref.onSave,
|
|
86
|
+
onClose = _ref.onClose,
|
|
87
|
+
enabledIds = _ref.enabledIds,
|
|
88
|
+
description = _ref.description,
|
|
89
|
+
_ref$noneOnHelp = _ref.noneOnHelp,
|
|
90
|
+
noneOnHelp = _ref$noneOnHelp === void 0 ? (0, _i18n.__)('Turn on at least one item to save.', 'newspack-plugin') : _ref$noneOnHelp;
|
|
91
|
+
var hasToggles = Array.isArray(enabledIds);
|
|
92
|
+
var instanceId = (0, _compose.useInstanceId)(_ReorderModal, 'newspack-reorder-modal');
|
|
93
|
+
var moveUpLabel = (0, _i18n.__)('Move Up', 'newspack-plugin');
|
|
94
|
+
var moveDownLabel = (0, _i18n.__)('Move Down', 'newspack-plugin');
|
|
95
|
+
var _useState = (0, _element.useState)(null),
|
|
96
|
+
_useState2 = (0, _slicedToArray2["default"])(_useState, 2),
|
|
97
|
+
items = _useState2[0],
|
|
98
|
+
setItems = _useState2[1];
|
|
99
|
+
var _useState3 = (0, _element.useState)(null),
|
|
100
|
+
_useState4 = (0, _slicedToArray2["default"])(_useState3, 2),
|
|
101
|
+
dragIndex = _useState4[0],
|
|
102
|
+
setDragIndex = _useState4[1];
|
|
103
|
+
var _useState5 = (0, _element.useState)(false),
|
|
104
|
+
_useState6 = (0, _slicedToArray2["default"])(_useState5, 2),
|
|
105
|
+
hasFetchError = _useState6[0],
|
|
106
|
+
setHasFetchError = _useState6[1];
|
|
107
|
+
var _useState7 = (0, _element.useState)(false),
|
|
108
|
+
_useState8 = (0, _slicedToArray2["default"])(_useState7, 2),
|
|
109
|
+
isConfirmingDiscard = _useState8[0],
|
|
110
|
+
setIsConfirmingDiscard = _useState8[1];
|
|
111
|
+
var listRef = (0, _element.useRef)(null);
|
|
112
|
+
var overlayRef = (0, _element.useRef)(null);
|
|
113
|
+
var pendingFocus = (0, _element.useRef)(null);
|
|
114
|
+
var closeRef = (0, _element.useRef)(null);
|
|
115
|
+
var preDragItems = (0, _element.useRef)(null);
|
|
116
|
+
var dragBlocked = (0, _element.useRef)(false);
|
|
117
|
+
var captureOverlay = (0, _element.useCallback)(function (node) {
|
|
118
|
+
overlayRef.current = findOverlay(node);
|
|
119
|
+
}, []);
|
|
120
|
+
|
|
121
|
+
// The order the modal opened with. Snapshotted so a change to `ids` from
|
|
122
|
+
// elsewhere in the editor cannot desynchronise it from `items`.
|
|
123
|
+
var openedWith = (0, _element.useRef)(ids);
|
|
124
|
+
var openedEnabled = (0, _element.useRef)(hasToggles ? enabledIds : []);
|
|
125
|
+
var _useState9 = (0, _element.useState)(function () {
|
|
126
|
+
return new Set(openedEnabled.current);
|
|
127
|
+
}),
|
|
128
|
+
_useState0 = (0, _slicedToArray2["default"])(_useState9, 2),
|
|
129
|
+
enabled = _useState0[0],
|
|
130
|
+
setEnabled = _useState0[1];
|
|
131
|
+
var isReordered = !!items && items.some(function (item, index) {
|
|
132
|
+
return item.id !== openedWith.current[index];
|
|
133
|
+
});
|
|
134
|
+
var isToggled = enabled.size !== openedEnabled.current.length || openedEnabled.current.some(function (id) {
|
|
135
|
+
return !enabled.has(id);
|
|
136
|
+
});
|
|
137
|
+
var isDirty = isReordered || hasToggles && isToggled;
|
|
138
|
+
// Enabled IDs without a row can't be switched off here, so Save keeps them.
|
|
139
|
+
var enabledWithoutRow = openedEnabled.current.filter(function (id) {
|
|
140
|
+
return !openedWith.current.includes(id);
|
|
141
|
+
});
|
|
142
|
+
var isNoneOn = hasToggles && 0 === enabled.size && 0 === enabledWithoutRow.length;
|
|
143
|
+
var toggleItem = function toggleItem(id) {
|
|
144
|
+
return setEnabled(function (current) {
|
|
145
|
+
var next = new Set(current);
|
|
146
|
+
if (next.has(id)) {
|
|
147
|
+
next["delete"](id);
|
|
148
|
+
} else {
|
|
149
|
+
next.add(id);
|
|
150
|
+
}
|
|
151
|
+
return next;
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
(0, _element.useEffect)(function () {
|
|
155
|
+
var cancelled = false;
|
|
156
|
+
var openIds = openedWith.current;
|
|
157
|
+
var withLabels = function withLabels(labels, badges) {
|
|
158
|
+
return openIds.map(function (id) {
|
|
159
|
+
return {
|
|
160
|
+
id: id,
|
|
161
|
+
label: labels[id] || (0, _i18n.__)('(no title)', 'newspack-plugin'),
|
|
162
|
+
badge: badges[id]
|
|
163
|
+
};
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
fetchItems(openIds).then(function (results) {
|
|
167
|
+
if (cancelled) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
var labels = {};
|
|
171
|
+
var badges = {};
|
|
172
|
+
results.forEach(function (_ref2) {
|
|
173
|
+
var value = _ref2.value,
|
|
174
|
+
label = _ref2.label,
|
|
175
|
+
badge = _ref2.badge;
|
|
176
|
+
labels[value] = label;
|
|
177
|
+
badges[value] = badge;
|
|
178
|
+
});
|
|
179
|
+
var loaded = withLabels(labels, badges);
|
|
180
|
+
setItems(loaded);
|
|
181
|
+
(0, _a11y.speak)((0, _i18n.sprintf)(/* translators: %d: number of items in the list. */
|
|
182
|
+
(0, _i18n._n)('%d item ready to reorder.', '%d items ready to reorder.', loaded.length, 'newspack-plugin'), loaded.length));
|
|
183
|
+
})["catch"](function (error) {
|
|
184
|
+
// eslint-disable-next-line no-console
|
|
185
|
+
console.error('Newspack: could not prepare the items to reorder.', error);
|
|
186
|
+
if (!cancelled) {
|
|
187
|
+
setHasFetchError(true);
|
|
188
|
+
// Only to leave the loading state: the error view lists nothing.
|
|
189
|
+
setItems([]);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
return function () {
|
|
193
|
+
cancelled = true;
|
|
194
|
+
};
|
|
195
|
+
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
196
|
+
|
|
197
|
+
// A chevron that reaches an end of the list stops accepting moves, so send
|
|
198
|
+
// focus to the row's other chevron.
|
|
199
|
+
(0, _element.useEffect)(function () {
|
|
200
|
+
var pending = pendingFocus.current;
|
|
201
|
+
pendingFocus.current = null;
|
|
202
|
+
if (!pending || !listRef.current) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
var row = listRef.current.querySelector("[data-item-id=\"".concat(pending.id, "\"]"));
|
|
206
|
+
if (!row) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
var preferred = row.querySelector("[data-direction=\"".concat(pending.direction, "\"]"));
|
|
210
|
+
var fallback = row.querySelector("[data-direction=\"".concat('up' === pending.direction ? 'down' : 'up', "\"]"));
|
|
211
|
+
if (isAvailable(preferred)) {
|
|
212
|
+
preferred.focus();
|
|
213
|
+
} else if (isAvailable(fallback)) {
|
|
214
|
+
fallback.focus();
|
|
215
|
+
}
|
|
216
|
+
}, [items]);
|
|
217
|
+
var moveTo = function moveTo(from, to, direction) {
|
|
218
|
+
var next = moveItem(items, from, to);
|
|
219
|
+
if (next === items) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
pendingFocus.current = {
|
|
223
|
+
id: items[from].id,
|
|
224
|
+
direction: direction
|
|
225
|
+
};
|
|
226
|
+
setItems(next);
|
|
227
|
+
(0, _a11y.speak)((0, _i18n.sprintf)(/* translators: 1: title of the moved item. 2: its new position. 3: total number of items. */
|
|
228
|
+
(0, _i18n.__)('%1$s moved to position %2$d of %3$d.', 'newspack-plugin'), items[from].label, to + 1, items.length));
|
|
229
|
+
};
|
|
230
|
+
var requestClose = function requestClose() {
|
|
231
|
+
if (isDirty) {
|
|
232
|
+
setIsConfirmingDiscard(true);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
onClose();
|
|
236
|
+
};
|
|
237
|
+
(0, _element.useEffect)(function () {
|
|
238
|
+
closeRef.current = requestClose;
|
|
239
|
+
}, [requestClose]);
|
|
240
|
+
var handleKeyDown = function handleKeyDown(event) {
|
|
241
|
+
if ('Escape' !== event.key || event.defaultPrevented) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
event.preventDefault();
|
|
245
|
+
requestClose();
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
// `Modal` plays its exit animation before it reports a close request, which
|
|
249
|
+
// would fade the modal out and back in whenever the confirmation vetoes one.
|
|
250
|
+
// Its own dismissal handlers are off, so drive the overlay press here.
|
|
251
|
+
(0, _element.useEffect)(function () {
|
|
252
|
+
var overlay = overlayRef.current;
|
|
253
|
+
if (!overlay) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
var pressedOverlay = false;
|
|
257
|
+
var onPointerDown = function onPointerDown(event) {
|
|
258
|
+
pressedOverlay = event.target === overlay;
|
|
259
|
+
if (pressedOverlay) {
|
|
260
|
+
event.preventDefault();
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
var onPointerUp = function onPointerUp(event) {
|
|
264
|
+
var dismisses = pressedOverlay && 0 === event.button && event.target === overlay;
|
|
265
|
+
pressedOverlay = false;
|
|
266
|
+
if (dismisses) {
|
|
267
|
+
closeRef.current();
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
overlay.addEventListener('pointerdown', onPointerDown);
|
|
271
|
+
overlay.addEventListener('pointerup', onPointerUp);
|
|
272
|
+
return function () {
|
|
273
|
+
overlay.removeEventListener('pointerdown', onPointerDown);
|
|
274
|
+
overlay.removeEventListener('pointerup', onPointerUp);
|
|
275
|
+
};
|
|
276
|
+
}, []);
|
|
277
|
+
|
|
278
|
+
// Rows are the only drop targets, and the frame's own padding is not one, so a
|
|
279
|
+
// release a few pixels wide of the list would report a cancelled drag and undo
|
|
280
|
+
// the reorder. Accepting the drop across the whole dialog leaves "cancelled"
|
|
281
|
+
// meaning only Escape or a release outside it.
|
|
282
|
+
(0, _element.useEffect)(function () {
|
|
283
|
+
var _overlayRef$current;
|
|
284
|
+
var frame = (_overlayRef$current = overlayRef.current) === null || _overlayRef$current === void 0 ? void 0 : _overlayRef$current.querySelector('.components-modal__frame');
|
|
285
|
+
if (!frame) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
var accept = function accept(event) {
|
|
289
|
+
return event.preventDefault();
|
|
290
|
+
};
|
|
291
|
+
frame.addEventListener('dragover', accept);
|
|
292
|
+
frame.addEventListener('drop', accept);
|
|
293
|
+
return function () {
|
|
294
|
+
frame.removeEventListener('dragover', accept);
|
|
295
|
+
frame.removeEventListener('drop', accept);
|
|
296
|
+
};
|
|
297
|
+
}, []);
|
|
298
|
+
var handleDragOver = function handleDragOver(event, index) {
|
|
299
|
+
event.preventDefault();
|
|
300
|
+
if (null === dragIndex) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
event.dataTransfer.dropEffect = 'move';
|
|
304
|
+
if (dragIndex === index) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
setItems(moveItem(items, dragIndex, index));
|
|
308
|
+
setDragIndex(index);
|
|
309
|
+
};
|
|
310
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
311
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Modal, {
|
|
312
|
+
ref: captureOverlay,
|
|
313
|
+
title: title,
|
|
314
|
+
onRequestClose: requestClose,
|
|
315
|
+
onKeyDown: handleKeyDown,
|
|
316
|
+
isDismissible: false,
|
|
317
|
+
shouldCloseOnEsc: false,
|
|
318
|
+
shouldCloseOnClickOutside: false,
|
|
319
|
+
headerActions: /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Button, {
|
|
320
|
+
size: "compact",
|
|
321
|
+
icon: _icons.close,
|
|
322
|
+
label: (0, _i18n.__)('Close', 'newspack-plugin'),
|
|
323
|
+
onClick: requestClose
|
|
324
|
+
}),
|
|
325
|
+
size: "medium",
|
|
326
|
+
className: "newspack-reorder-modal",
|
|
327
|
+
aria: {
|
|
328
|
+
describedby: description ? "".concat(instanceId, "-description") : undefined
|
|
329
|
+
},
|
|
330
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
|
|
331
|
+
className: "newspack-reorder-modal__body",
|
|
332
|
+
"aria-busy": !items,
|
|
333
|
+
children: [description && /*#__PURE__*/(0, _jsxRuntime.jsx)("p", {
|
|
334
|
+
id: "".concat(instanceId, "-description"),
|
|
335
|
+
className: "newspack-reorder-modal__description",
|
|
336
|
+
children: description
|
|
337
|
+
}), !items && /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
|
|
338
|
+
className: "newspack-reorder-modal__loading",
|
|
339
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Spinner, {}), /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
|
|
340
|
+
className: "screen-reader-text",
|
|
341
|
+
children: (0, _i18n.__)('Loading content…', 'newspack-plugin')
|
|
342
|
+
})]
|
|
343
|
+
}), items && hasFetchError && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
344
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Notice, {
|
|
345
|
+
status: "error",
|
|
346
|
+
isDismissible: false,
|
|
347
|
+
className: "newspack-reorder-modal__notice",
|
|
348
|
+
children: (0, _i18n.__)('The content could not be loaded, so the order cannot be saved.', 'newspack-plugin')
|
|
349
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
|
|
350
|
+
className: "newspack-reorder-modal__footer",
|
|
351
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Button, {
|
|
352
|
+
variant: "tertiary",
|
|
353
|
+
onClick: requestClose,
|
|
354
|
+
children: (0, _i18n.__)('Cancel', 'newspack-plugin')
|
|
355
|
+
})
|
|
356
|
+
})]
|
|
357
|
+
}), items && !hasFetchError && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
358
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("ul", {
|
|
359
|
+
className: "newspack-reorder-modal__list",
|
|
360
|
+
role: "list",
|
|
361
|
+
ref: listRef,
|
|
362
|
+
children: items.map(function (item, index) {
|
|
363
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_ui.Card.Root, {
|
|
364
|
+
render: /*#__PURE__*/(0, _jsxRuntime.jsx)("li", {}),
|
|
365
|
+
"data-item-id": item.id,
|
|
366
|
+
className: (0, _classnames["default"])('newspack-reorder-modal__item', {
|
|
367
|
+
'is-dragging': dragIndex === index,
|
|
368
|
+
'is-off': hasToggles && !enabled.has(item.id)
|
|
369
|
+
}),
|
|
370
|
+
draggable: true
|
|
371
|
+
// `dragstart` always fires at the draggable row, never at the
|
|
372
|
+
// chevron inside it, so the grab point is recorded separately.
|
|
373
|
+
,
|
|
374
|
+
onPointerDown: function onPointerDown(event) {
|
|
375
|
+
var _event$target$closest, _event$target;
|
|
376
|
+
dragBlocked.current = !!((_event$target$closest = (_event$target = event.target).closest) !== null && _event$target$closest !== void 0 && _event$target$closest.call(_event$target, 'button, input'));
|
|
377
|
+
},
|
|
378
|
+
onDragStart: function onDragStart(event) {
|
|
379
|
+
if (dragBlocked.current) {
|
|
380
|
+
event.preventDefault();
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
preDragItems.current = items;
|
|
384
|
+
// Firefox and Safari will not start a drag until the payload is set.
|
|
385
|
+
event.dataTransfer.setData('text/plain', String(item.id));
|
|
386
|
+
event.dataTransfer.effectAllowed = 'move';
|
|
387
|
+
setDragIndex(index);
|
|
388
|
+
},
|
|
389
|
+
onDragOver: function onDragOver(event) {
|
|
390
|
+
return handleDragOver(event, index);
|
|
391
|
+
},
|
|
392
|
+
onDragEnd: function onDragEnd(event) {
|
|
393
|
+
// A cancelled drag reports no drop effect: put the order back.
|
|
394
|
+
if ('none' === event.dataTransfer.dropEffect && preDragItems.current) {
|
|
395
|
+
setItems(preDragItems.current);
|
|
396
|
+
}
|
|
397
|
+
preDragItems.current = null;
|
|
398
|
+
setDragIndex(null);
|
|
399
|
+
},
|
|
400
|
+
onDrop: function onDrop(event) {
|
|
401
|
+
return event.preventDefault();
|
|
402
|
+
},
|
|
403
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
|
|
404
|
+
className: "newspack-reorder-modal__grip",
|
|
405
|
+
"aria-hidden": "true",
|
|
406
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.Icon, {
|
|
407
|
+
icon: _icons.dragHandle
|
|
408
|
+
})
|
|
409
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)("span", {
|
|
410
|
+
className: "newspack-reorder-modal__move",
|
|
411
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Button, {
|
|
412
|
+
icon: _icons.chevronUp,
|
|
413
|
+
size: "small",
|
|
414
|
+
"data-direction": "up",
|
|
415
|
+
disabled: 0 === index,
|
|
416
|
+
accessibleWhenDisabled: true,
|
|
417
|
+
label: moveUpLabel,
|
|
418
|
+
"aria-label": nameWithTitle(moveUpLabel, item.label),
|
|
419
|
+
onClick: function onClick() {
|
|
420
|
+
return moveTo(index, index - 1, 'up');
|
|
421
|
+
}
|
|
422
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Button, {
|
|
423
|
+
icon: _icons.chevronDown,
|
|
424
|
+
size: "small",
|
|
425
|
+
"data-direction": "down",
|
|
426
|
+
disabled: index === items.length - 1,
|
|
427
|
+
accessibleWhenDisabled: true,
|
|
428
|
+
label: moveDownLabel,
|
|
429
|
+
"aria-label": nameWithTitle(moveDownLabel, item.label),
|
|
430
|
+
onClick: function onClick() {
|
|
431
|
+
return moveTo(index, index + 1, 'down');
|
|
432
|
+
}
|
|
433
|
+
})]
|
|
434
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_ui.Card.Content, {
|
|
435
|
+
className: "newspack-reorder-modal__content",
|
|
436
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
|
|
437
|
+
className: "newspack-reorder-modal__title",
|
|
438
|
+
title: item.label,
|
|
439
|
+
children: item.label
|
|
440
|
+
}), item.badge && /*#__PURE__*/(0, _jsxRuntime.jsxs)("span", {
|
|
441
|
+
className: "newspack-reorder-modal__badge",
|
|
442
|
+
children: [item.badge.tooltip ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Tooltip, {
|
|
443
|
+
text: item.badge.tooltip,
|
|
444
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
|
|
445
|
+
"aria-hidden": "true",
|
|
446
|
+
tabIndex: -1,
|
|
447
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_ui.Badge, {
|
|
448
|
+
intent: "draft",
|
|
449
|
+
children: item.badge.label
|
|
450
|
+
})
|
|
451
|
+
})
|
|
452
|
+
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
|
|
453
|
+
"aria-hidden": "true",
|
|
454
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_ui.Badge, {
|
|
455
|
+
intent: "draft",
|
|
456
|
+
children: item.badge.label
|
|
457
|
+
})
|
|
458
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.VisuallyHidden, {
|
|
459
|
+
id: "".concat(instanceId, "-badge-").concat(item.id),
|
|
460
|
+
children: item.badge.tooltip ? "".concat(item.badge.label, ": ").concat(item.badge.tooltip) : item.badge.label
|
|
461
|
+
})]
|
|
462
|
+
})]
|
|
463
|
+
}), hasToggles && /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
|
|
464
|
+
className: "newspack-reorder-modal__toggle",
|
|
465
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.FormToggle, {
|
|
466
|
+
checked: enabled.has(item.id),
|
|
467
|
+
onChange: function onChange() {
|
|
468
|
+
return toggleItem(item.id);
|
|
469
|
+
},
|
|
470
|
+
"aria-label": item.label,
|
|
471
|
+
"aria-describedby": item.badge ? "".concat(instanceId, "-badge-").concat(item.id) : undefined
|
|
472
|
+
})
|
|
473
|
+
})]
|
|
474
|
+
}, item.id);
|
|
475
|
+
})
|
|
476
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
|
|
477
|
+
className: "newspack-reorder-modal__footer",
|
|
478
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Button, {
|
|
479
|
+
variant: "tertiary",
|
|
480
|
+
onClick: requestClose,
|
|
481
|
+
children: (0, _i18n.__)('Cancel', 'newspack-plugin')
|
|
482
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Button, {
|
|
483
|
+
variant: "primary",
|
|
484
|
+
disabled: !isDirty || isNoneOn,
|
|
485
|
+
accessibleWhenDisabled: true
|
|
486
|
+
// A button with visible children shows a tooltip only when asked and labelled.
|
|
487
|
+
,
|
|
488
|
+
label: (0, _i18n.__)('Save', 'newspack-plugin'),
|
|
489
|
+
showTooltip: isDirty && isNoneOn,
|
|
490
|
+
description: isDirty && isNoneOn ? noneOnHelp : undefined,
|
|
491
|
+
onClick: function onClick() {
|
|
492
|
+
var order = items.map(function (item) {
|
|
493
|
+
return item.id;
|
|
494
|
+
});
|
|
495
|
+
if (hasToggles) {
|
|
496
|
+
onSave(order, [].concat((0, _toConsumableArray2["default"])(order.filter(function (id) {
|
|
497
|
+
return enabled.has(id);
|
|
498
|
+
})), (0, _toConsumableArray2["default"])(enabledWithoutRow)));
|
|
499
|
+
} else {
|
|
500
|
+
onSave(order);
|
|
501
|
+
}
|
|
502
|
+
},
|
|
503
|
+
children: (0, _i18n.__)('Save', 'newspack-plugin')
|
|
504
|
+
})]
|
|
505
|
+
})]
|
|
506
|
+
})]
|
|
507
|
+
})
|
|
508
|
+
}), isConfirmingDiscard && /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.__experimentalConfirmDialog, {
|
|
509
|
+
isOpen: true,
|
|
510
|
+
contentLabel: hasToggles ? (0, _i18n.__)('Discard the changes?', 'newspack-plugin') : (0, _i18n.__)('Discard the new order?', 'newspack-plugin'),
|
|
511
|
+
confirmButtonText: (0, _i18n.__)('Discard', 'newspack-plugin'),
|
|
512
|
+
cancelButtonText: (0, _i18n.__)('Keep editing', 'newspack-plugin'),
|
|
513
|
+
onConfirm: onClose,
|
|
514
|
+
onCancel: function onCancel() {
|
|
515
|
+
return setIsConfirmingDiscard(false);
|
|
516
|
+
},
|
|
517
|
+
children: hasToggles ? (0, _i18n.__)('The changes you made will be lost.', 'newspack-plugin') : (0, _i18n.__)('The order you set will be lost.', 'newspack-plugin')
|
|
518
|
+
})]
|
|
519
|
+
});
|
|
520
|
+
};
|
|
521
|
+
var _default = exports["default"] = _ReorderModal;
|