newspack-components 4.5.0 → 4.6.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 +40 -0
- package/DEVELOPMENT.md +1 -1
- package/dist/cjs/accordion/index.js +43 -26
- package/dist/cjs/accordion/index.test.js +80 -0
- package/dist/cjs/accordion/style.scss +25 -21
- package/dist/cjs/action-card/style.scss +0 -22
- package/dist/cjs/breadcrumbs/index.js +80 -0
- package/dist/cjs/breadcrumbs/index.test.js +109 -0
- package/dist/cjs/breadcrumbs/style.scss +41 -0
- package/dist/cjs/card/core-card.js +8 -4
- package/dist/cjs/card/index.js +1 -1
- package/dist/cjs/card/style-core.scss +14 -0
- package/dist/cjs/card/style.scss +0 -9
- package/dist/cjs/card-feature/index.js +25 -13
- package/dist/cjs/card-settings-group/index.js +3 -0
- package/dist/cjs/confirm-dialog/index.js +34 -8
- package/dist/cjs/confirm-dialog/index.test.js +173 -0
- package/dist/cjs/divider/style.scss +4 -2
- package/dist/cjs/grid/style.scss +5 -0
- package/dist/cjs/handoff/index.js +8 -5
- package/dist/cjs/index.js +34 -1
- package/dist/cjs/integration-icon/index.js +40 -0
- package/dist/cjs/integration-icon/style.scss +42 -0
- package/dist/cjs/integration-icons/index.js +21 -2
- package/dist/cjs/modal/style.scss +4 -0
- package/dist/cjs/page/index.js +121 -0
- package/dist/cjs/page/index.test.js +69 -0
- package/dist/cjs/page/style.scss +86 -0
- package/dist/cjs/route-match.js +59 -0
- package/dist/cjs/route-match.test.js +77 -0
- package/dist/cjs/section-header/index.js +8 -1
- package/dist/cjs/section-header/style.scss +11 -11
- package/dist/cjs/select-control/GroupedSelectControl.js +1 -1
- package/dist/cjs/select-control/style.scss +4 -0
- package/dist/cjs/style.scss +1 -4
- package/dist/cjs/tabbed-navigation/index.js +188 -40
- package/dist/cjs/tabbed-navigation/index.test.js +299 -0
- package/dist/cjs/tabbed-navigation/style.scss +77 -57
- package/dist/cjs/with-wizard/index.js +34 -45
- package/dist/cjs/with-wizard-screen/index.js +82 -41
- package/dist/cjs/with-wizard-screen/index.test.js +50 -0
- package/dist/cjs/with-wizard-screen/style.scss +15 -118
- package/dist/cjs/wizard/breadcrumbs-select.js +70 -0
- package/dist/cjs/wizard/breadcrumbs-select.test.js +200 -0
- package/dist/cjs/wizard/components/WizardError.js +8 -4
- package/dist/cjs/wizard/index.js +162 -141
- package/dist/esm/accordion/index.js +42 -26
- package/dist/esm/accordion/index.test.js +79 -0
- package/dist/esm/accordion/style.scss +25 -21
- package/dist/esm/action-card/style.scss +0 -22
- package/dist/esm/breadcrumbs/index.js +75 -0
- package/dist/esm/breadcrumbs/index.test.js +105 -0
- package/dist/esm/breadcrumbs/style.scss +41 -0
- package/dist/esm/card/core-card.js +8 -4
- package/dist/esm/card/index.js +1 -1
- package/dist/esm/card/style-core.scss +14 -0
- package/dist/esm/card/style.scss +0 -9
- package/dist/esm/card-feature/index.js +25 -13
- package/dist/esm/card-settings-group/index.js +3 -0
- package/dist/esm/confirm-dialog/index.js +34 -8
- package/dist/esm/confirm-dialog/index.test.js +173 -0
- package/dist/esm/divider/style.scss +4 -2
- package/dist/esm/grid/style.scss +5 -0
- package/dist/esm/handoff/index.js +7 -5
- package/dist/esm/index.js +5 -1
- package/dist/esm/integration-icon/index.js +34 -0
- package/dist/esm/integration-icon/style.scss +42 -0
- package/dist/esm/integration-icons/index.js +22 -1
- package/dist/esm/modal/style.scss +4 -0
- package/dist/esm/page/index.js +113 -0
- package/dist/esm/page/index.test.js +65 -0
- package/dist/esm/page/style.scss +86 -0
- package/dist/esm/route-match.js +51 -0
- package/dist/esm/route-match.test.js +74 -0
- package/dist/esm/section-header/index.js +8 -1
- package/dist/esm/section-header/style.scss +11 -11
- package/dist/esm/select-control/GroupedSelectControl.js +1 -1
- package/dist/esm/select-control/style.scss +4 -0
- package/dist/esm/style.scss +1 -4
- package/dist/esm/tabbed-navigation/index.js +192 -38
- package/dist/esm/tabbed-navigation/index.test.js +296 -0
- package/dist/esm/tabbed-navigation/style.scss +77 -57
- package/dist/esm/with-wizard/index.js +33 -45
- package/dist/esm/with-wizard-screen/index.js +78 -41
- package/dist/esm/with-wizard-screen/index.test.js +46 -0
- package/dist/esm/with-wizard-screen/style.scss +15 -118
- package/dist/esm/wizard/breadcrumbs-select.js +63 -0
- package/dist/esm/wizard/breadcrumbs-select.test.js +197 -0
- package/dist/esm/wizard/components/WizardError.js +7 -5
- package/dist/esm/wizard/index.js +165 -144
- package/package.json +10 -2
- package/src/accordion/index.js +27 -15
- package/src/accordion/index.test.js +62 -0
- package/src/accordion/style.scss +25 -21
- package/src/action-card/style.scss +0 -22
- package/src/breadcrumbs/index.js +63 -0
- package/src/breadcrumbs/index.test.js +59 -0
- package/src/breadcrumbs/style.scss +41 -0
- package/src/card/core-card.js +11 -6
- package/src/card/index.js +0 -1
- package/src/card/style-core.scss +14 -0
- package/src/card/style.scss +0 -9
- package/src/card-feature/README.md +3 -3
- package/src/card-feature/index.tsx +37 -21
- package/src/card-settings-group/index.tsx +4 -0
- package/src/card-sortable-list/index.tsx +1 -1
- package/src/confirm-dialog/index.test.js +142 -0
- package/src/confirm-dialog/index.tsx +34 -7
- package/src/divider/style.scss +4 -2
- package/src/grid/style.scss +5 -0
- package/src/handoff/index.js +4 -3
- package/src/index.js +5 -1
- package/src/integration-icon/index.tsx +35 -0
- package/src/integration-icon/style.scss +42 -0
- package/src/integration-icons/index.js +23 -0
- package/src/modal/style.scss +4 -0
- package/src/page/index.js +89 -0
- package/src/page/index.test.js +43 -0
- package/src/page/style.scss +86 -0
- package/src/route-match.js +47 -0
- package/src/route-match.test.js +50 -0
- package/src/section-header/index.js +7 -3
- package/src/section-header/style.scss +11 -11
- package/src/select-control/GroupedSelectControl.js +1 -1
- package/src/select-control/style.scss +4 -0
- package/src/style.scss +1 -4
- package/src/tabbed-navigation/index.js +181 -34
- package/src/tabbed-navigation/index.test.js +228 -0
- package/src/tabbed-navigation/style.scss +77 -57
- package/src/with-wizard/index.js +18 -39
- package/src/with-wizard-screen/index.js +70 -35
- package/src/with-wizard-screen/index.test.js +36 -0
- package/src/with-wizard-screen/style.scss +15 -118
- package/src/wizard/breadcrumbs-select.js +51 -0
- package/src/wizard/breadcrumbs-select.test.js +124 -0
- package/src/wizard/components/WizardError.js +4 -3
- package/src/wizard/index.js +160 -146
- package/tsconfig.json +7 -0
|
@@ -4,70 +4,218 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
|
|
|
4
4
|
Object.defineProperty(exports, "__esModule", {
|
|
5
5
|
value: true
|
|
6
6
|
});
|
|
7
|
-
exports["default"] = void 0;
|
|
7
|
+
exports.isItemActive = exports["default"] = void 0;
|
|
8
|
+
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
9
|
+
var _i18n = require("@wordpress/i18n");
|
|
10
|
+
var _ui = require("@wordpress/ui");
|
|
8
11
|
var _classnames = _interopRequireDefault(require("classnames"));
|
|
9
|
-
var _findIndex = _interopRequireDefault(require("lodash/findIndex"));
|
|
10
12
|
var _router = _interopRequireDefault(require("../proxied-imports/router"));
|
|
13
|
+
var _routeMatch = require("../route-match");
|
|
11
14
|
require("./style.scss");
|
|
12
15
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
13
|
-
|
|
16
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
17
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2["default"])(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } /**
|
|
18
|
+
* WordPress dependencies.
|
|
19
|
+
*/ /**
|
|
14
20
|
* External dependencies.
|
|
21
|
+
*/ /**
|
|
22
|
+
* Internal dependencies.
|
|
15
23
|
*/
|
|
24
|
+
var useHistory = _router["default"].useHistory,
|
|
25
|
+
useLocation = _router["default"].useLocation;
|
|
26
|
+
var getItemValue = function getItemValue(item, index) {
|
|
27
|
+
return item.path || item.href || "item-".concat(index);
|
|
28
|
+
};
|
|
29
|
+
var isItemActive = exports.isItemActive = function isItemActive(item, pathname) {
|
|
30
|
+
if (item.selected) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
if (null === pathname) {
|
|
34
|
+
return Boolean(item.href) && window.location.href === item.href;
|
|
35
|
+
}
|
|
36
|
+
return (0, _routeMatch.matchesRoute)(item, pathname);
|
|
37
|
+
};
|
|
16
38
|
|
|
17
39
|
/**
|
|
18
|
-
*
|
|
40
|
+
* Default layout when `Page` doesn't inject one: bar above content.
|
|
19
41
|
*/
|
|
42
|
+
var defaultRenderShell = function defaultRenderShell(bar, content) {
|
|
43
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
44
|
+
children: [bar, content]
|
|
45
|
+
});
|
|
46
|
+
};
|
|
20
47
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Router-driven tabs: a real WAI-ARIA tabs widget. The routed page content
|
|
50
|
+
* renders inside the active tab's `Tabs.Panel`, so each tab's `aria-controls`
|
|
51
|
+
* points at the panel that actually contains its content — or, when no visible
|
|
52
|
+
* tab owns the route, as a sibling of the panels (see `unownedContent` below).
|
|
53
|
+
*/
|
|
54
|
+
var RoutedTabbedNavigation = function RoutedTabbedNavigation(_ref) {
|
|
24
55
|
var items = _ref.items,
|
|
25
56
|
className = _ref.className,
|
|
26
57
|
disableUpcoming = _ref.disableUpcoming,
|
|
58
|
+
_ref$content = _ref.content,
|
|
59
|
+
content = _ref$content === void 0 ? null : _ref$content,
|
|
60
|
+
_ref$renderShell = _ref.renderShell,
|
|
61
|
+
renderShell = _ref$renderShell === void 0 ? defaultRenderShell : _ref$renderShell,
|
|
27
62
|
_ref$children = _ref.children,
|
|
28
63
|
children = _ref$children === void 0 ? null : _ref$children;
|
|
64
|
+
var history = useHistory();
|
|
65
|
+
var _useLocation = useLocation(),
|
|
66
|
+
pathname = _useLocation.pathname;
|
|
29
67
|
var displayedItems = items.filter(function (item) {
|
|
30
68
|
return !item.isHiddenInTabbedNavigation;
|
|
31
69
|
});
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
|
|
70
|
+
|
|
71
|
+
// Most-specific match wins, so a tab is never outranked by a sibling that
|
|
72
|
+
// happens to prefix-match the same pathname.
|
|
73
|
+
var activeIndex = displayedItems.reduce(function (best, item, index) {
|
|
74
|
+
var _item$path, _displayedItems$best$;
|
|
75
|
+
if (!isItemActive(item, pathname)) {
|
|
76
|
+
return best;
|
|
38
77
|
}
|
|
39
|
-
if (
|
|
40
|
-
return
|
|
41
|
-
if (path.endsWith('*')) {
|
|
42
|
-
var basePath = path.slice(0, -1);
|
|
43
|
-
return pathname.startsWith(basePath);
|
|
44
|
-
}
|
|
45
|
-
return item.activeTabPaths.includes(pathname);
|
|
46
|
-
});
|
|
78
|
+
if (best === -1 || (((_item$path = item.path) === null || _item$path === void 0 ? void 0 : _item$path.length) || 0) > (((_displayedItems$best$ = displayedItems[best].path) === null || _displayedItems$best$ === void 0 ? void 0 : _displayedItems$best$.length) || 0)) {
|
|
79
|
+
return index;
|
|
47
80
|
}
|
|
48
|
-
return
|
|
49
|
-
}
|
|
50
|
-
|
|
81
|
+
return best;
|
|
82
|
+
}, -1);
|
|
83
|
+
var activeValue = activeIndex > -1 ? getItemValue(displayedItems[activeIndex], activeIndex) : null;
|
|
84
|
+
var onTabClick = function onTabClick(event, item) {
|
|
85
|
+
// Modified clicks (new tab/window) keep native anchor behavior. Middle
|
|
86
|
+
// clicks never reach onClick (they dispatch auxclick), so the anchor's
|
|
87
|
+
// href handles those natively too.
|
|
88
|
+
if (event.defaultPrevented || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// Route through the history object so navigation guards registered via
|
|
92
|
+
// history.block() (unsaved-changes prompts) are consulted.
|
|
93
|
+
event.preventDefault();
|
|
94
|
+
if (item.path !== pathname) {
|
|
95
|
+
history.push(item.path);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
var bar = /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
|
|
51
99
|
className: (0, _classnames["default"])('newspack-tabbed-navigation', className),
|
|
52
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(
|
|
100
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_ui.Tabs.List, {
|
|
101
|
+
activateOnFocus: false,
|
|
53
102
|
children: displayedItems.map(function (item, index) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
103
|
+
var isDisabled = disableUpcoming && index > activeIndex;
|
|
104
|
+
var href = item.path ? "#".concat(item.path) : item.href;
|
|
105
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_ui.Tabs.Tab, {
|
|
106
|
+
value: getItemValue(item, index),
|
|
107
|
+
disabled: isDisabled,
|
|
108
|
+
nativeButton: false,
|
|
109
|
+
render:
|
|
110
|
+
/*#__PURE__*/
|
|
111
|
+
// eslint-disable-next-line jsx-a11y/anchor-has-content, jsx-a11y/anchor-is-valid -- content is supplied via the Tab children through @wordpress/ui's render prop, and disabled tabs intentionally drop the href.
|
|
112
|
+
(0, _jsxRuntime.jsx)("a", {
|
|
113
|
+
href: isDisabled ? undefined : href,
|
|
114
|
+
onClick: isDisabled || !item.path ? undefined : function (event) {
|
|
115
|
+
return onTabClick(event, item);
|
|
116
|
+
}
|
|
117
|
+
}),
|
|
118
|
+
children: item.label
|
|
119
|
+
}, getItemValue(item, index));
|
|
69
120
|
})
|
|
70
121
|
}), children]
|
|
71
122
|
});
|
|
123
|
+
|
|
124
|
+
// Panels must stay 1:1 with the tabs above (@wordpress/ui validates the
|
|
125
|
+
// pairing in development builds); only the active panel holds the content.
|
|
126
|
+
var panels = displayedItems.map(function (item, index) {
|
|
127
|
+
var value = getItemValue(item, index);
|
|
128
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_ui.Tabs.Panel, {
|
|
129
|
+
value: value,
|
|
130
|
+
tabIndex: -1,
|
|
131
|
+
className: "newspack-tabbed-navigation__panel",
|
|
132
|
+
children: value === activeValue ? content : null
|
|
133
|
+
}, value);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// A route no visible tab owns — a hidden sub-view (an edit screen), or a path
|
|
137
|
+
// with no matching tab at all — still has to render its content, outside the
|
|
138
|
+
// panels: with no active tab there is no panel entitled to hold it. Without
|
|
139
|
+
// this the page body is blank, and because the router's own fallback (the
|
|
140
|
+
// wizard's trailing `<Redirect>`) lives inside that content, it never mounts
|
|
141
|
+
// to correct the route either.
|
|
142
|
+
//
|
|
143
|
+
// This renders without the panels' `__panel` class, which is styleless today.
|
|
144
|
+
// Keep it that way: styling `__panel` would silently indent/pad owned routes
|
|
145
|
+
// only, and the divergence would be hard to trace back to here.
|
|
146
|
+
var unownedContent = null === activeValue ? content : null;
|
|
147
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_ui.Tabs.Root, {
|
|
148
|
+
value: activeValue,
|
|
149
|
+
className: "newspack-tabbed-navigation__root",
|
|
150
|
+
children: renderShell(bar, /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
151
|
+
children: [panels, unownedContent]
|
|
152
|
+
}))
|
|
153
|
+
});
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Link-driven "tabs": plain navigation for items that trigger full page loads
|
|
158
|
+
* (e.g. the PHP-rendered wizards admin header). These are not tabs in the ARIA
|
|
159
|
+
* sense — there is no in-page panel to control — so they render as a `nav` of
|
|
160
|
+
* links with `aria-current`, styled to match the tabs widget.
|
|
161
|
+
*/
|
|
162
|
+
var LinkTabbedNavigation = function LinkTabbedNavigation(_ref2) {
|
|
163
|
+
var items = _ref2.items,
|
|
164
|
+
className = _ref2.className,
|
|
165
|
+
_ref2$content = _ref2.content,
|
|
166
|
+
content = _ref2$content === void 0 ? null : _ref2$content,
|
|
167
|
+
_ref2$renderShell = _ref2.renderShell,
|
|
168
|
+
renderShell = _ref2$renderShell === void 0 ? defaultRenderShell : _ref2$renderShell,
|
|
169
|
+
_ref2$children = _ref2.children,
|
|
170
|
+
children = _ref2$children === void 0 ? null : _ref2$children;
|
|
171
|
+
var displayedItems = items.filter(function (item) {
|
|
172
|
+
return !item.isHiddenInTabbedNavigation;
|
|
173
|
+
});
|
|
174
|
+
var bar = /*#__PURE__*/(0, _jsxRuntime.jsxs)("nav", {
|
|
175
|
+
className: (0, _classnames["default"])('newspack-tabbed-navigation', 'newspack-tabbed-navigation--links', className),
|
|
176
|
+
"aria-label": (0, _i18n.__)('Secondary navigation', 'newspack-plugin'),
|
|
177
|
+
children: [displayedItems.map(function (item, index) {
|
|
178
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)("a", {
|
|
179
|
+
href: item.href,
|
|
180
|
+
className: "newspack-tabbed-navigation__link",
|
|
181
|
+
"aria-current": isItemActive(item, null) ? 'page' : undefined,
|
|
182
|
+
children: item.label
|
|
183
|
+
}, getItemValue(item, index));
|
|
184
|
+
}), children]
|
|
185
|
+
});
|
|
186
|
+
return renderShell(bar, content);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Tabbed navigation over `{ label, path? | href?, exact?, activeTabPaths?, selected?, isHiddenInTabbedNavigation? }` items.
|
|
191
|
+
*
|
|
192
|
+
* Items with router `path`s render as a real tabs widget whose active panel
|
|
193
|
+
* contains `content`; href-only items render as plain navigation links.
|
|
194
|
+
*
|
|
195
|
+
* When no visible tab owns the current route — a hidden sub-view, or an unknown
|
|
196
|
+
* URL — `content` renders as a sibling of the panels with no tab selected. That
|
|
197
|
+
* is a safety net, not the preferred shape: for a route that is conceptually a
|
|
198
|
+
* sub-view of a tab, give that tab an `activeTabPaths` entry (e.g.
|
|
199
|
+
* `activeTabPaths: [ '/edit/*' ]`) so it stays selected and its panel keeps
|
|
200
|
+
* owning the content.
|
|
201
|
+
*
|
|
202
|
+
* `renderShell( bar, content )` is a layout injection point used by `Page` to
|
|
203
|
+
* place the bar inside its sticky header region while the content flows below.
|
|
204
|
+
*
|
|
205
|
+
* @param {Object} props
|
|
206
|
+
* @param {Array} props.items Navigation items.
|
|
207
|
+
* @param {boolean} [props.disableUpcoming] Disable tabs after the active one (setup flows).
|
|
208
|
+
* @param {*} [props.content] Page content, rendered inside the active tab's panel — or
|
|
209
|
+
* as a sibling of the panels when no visible tab owns the route.
|
|
210
|
+
* @param {*} [props.renderShell] Layout callback `( bar, content ) => element`.
|
|
211
|
+
* @param {string} [props.className]
|
|
212
|
+
* @param {*} [props.children] Extra elements rendered inside the bar (e.g. error notices).
|
|
213
|
+
* @return {JSX.Element} Tabbed navigation.
|
|
214
|
+
*/
|
|
215
|
+
var TabbedNavigation = function TabbedNavigation(props) {
|
|
216
|
+
var hasRoutedItems = props.items.some(function (item) {
|
|
217
|
+
return item.path;
|
|
218
|
+
});
|
|
219
|
+
return hasRoutedItems ? /*#__PURE__*/(0, _jsxRuntime.jsx)(RoutedTabbedNavigation, _objectSpread({}, props)) : /*#__PURE__*/(0, _jsxRuntime.jsx)(LinkTabbedNavigation, _objectSpread({}, props));
|
|
72
220
|
};
|
|
73
221
|
var _default = exports["default"] = TabbedNavigation;
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
5
|
+
var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
|
|
6
|
+
var _react = require("@testing-library/react");
|
|
7
|
+
var _reactRouterDom = require("react-router-dom");
|
|
8
|
+
var _index = _interopRequireWildcard(require("./index"));
|
|
9
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
10
|
+
var _excluded = ["initialEntries"];
|
|
11
|
+
/**
|
|
12
|
+
* External dependencies.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Internal dependencies.
|
|
16
|
+
*/
|
|
17
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, "default": e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
|
|
18
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
19
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2["default"])(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
20
|
+
var HistoryGrabber = function HistoryGrabber(_ref) {
|
|
21
|
+
var historyRef = _ref.historyRef;
|
|
22
|
+
historyRef.current = (0, _reactRouterDom.useHistory)();
|
|
23
|
+
return null;
|
|
24
|
+
};
|
|
25
|
+
var ITEMS = [{
|
|
26
|
+
label: 'Stories',
|
|
27
|
+
path: '/stories'
|
|
28
|
+
}, {
|
|
29
|
+
label: 'Budgets',
|
|
30
|
+
path: '/budgets'
|
|
31
|
+
}, {
|
|
32
|
+
label: 'Sites',
|
|
33
|
+
path: '/sites'
|
|
34
|
+
}];
|
|
35
|
+
var renderTabs = function renderTabs() {
|
|
36
|
+
var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
|
|
37
|
+
_ref2$initialEntries = _ref2.initialEntries,
|
|
38
|
+
initialEntries = _ref2$initialEntries === void 0 ? ['/stories'] : _ref2$initialEntries,
|
|
39
|
+
props = (0, _objectWithoutProperties2["default"])(_ref2, _excluded);
|
|
40
|
+
var historyRef = {
|
|
41
|
+
current: null
|
|
42
|
+
};
|
|
43
|
+
(0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactRouterDom.MemoryRouter, {
|
|
44
|
+
initialEntries: initialEntries,
|
|
45
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(HistoryGrabber, {
|
|
46
|
+
historyRef: historyRef
|
|
47
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_index["default"], _objectSpread({
|
|
48
|
+
items: ITEMS,
|
|
49
|
+
content: /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
|
|
50
|
+
children: "Routed content"
|
|
51
|
+
})
|
|
52
|
+
}, props))]
|
|
53
|
+
}));
|
|
54
|
+
return historyRef.current;
|
|
55
|
+
};
|
|
56
|
+
var getTab = function getTab(name) {
|
|
57
|
+
return _react.screen.getByRole('tab', {
|
|
58
|
+
name: name
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
describe('isItemActive', function () {
|
|
62
|
+
it('treats an explicitly selected item as active regardless of pathname', function () {
|
|
63
|
+
expect((0, _index.isItemActive)({
|
|
64
|
+
selected: true,
|
|
65
|
+
path: '/other'
|
|
66
|
+
}, '/current')).toBe(true);
|
|
67
|
+
expect((0, _index.isItemActive)({
|
|
68
|
+
selected: true
|
|
69
|
+
}, null)).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
describe('outside a router (pathname is null)', function () {
|
|
72
|
+
afterEach(function () {
|
|
73
|
+
delete window.location;
|
|
74
|
+
window.location = new URL('http://localhost/');
|
|
75
|
+
});
|
|
76
|
+
it('is active when the href matches the current URL', function () {
|
|
77
|
+
delete window.location;
|
|
78
|
+
window.location = new URL('http://example.com/wp-admin/admin.php?page=ads');
|
|
79
|
+
expect((0, _index.isItemActive)({
|
|
80
|
+
href: 'http://example.com/wp-admin/admin.php?page=ads'
|
|
81
|
+
}, null)).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
it('is inactive when the href does not match', function () {
|
|
84
|
+
delete window.location;
|
|
85
|
+
window.location = new URL('http://example.com/wp-admin/admin.php?page=ads');
|
|
86
|
+
expect((0, _index.isItemActive)({
|
|
87
|
+
href: 'http://example.com/wp-admin/admin.php?page=other'
|
|
88
|
+
}, null)).toBe(false);
|
|
89
|
+
});
|
|
90
|
+
it('is inactive when the item has no href', function () {
|
|
91
|
+
expect((0, _index.isItemActive)({
|
|
92
|
+
path: '/ads'
|
|
93
|
+
}, null)).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
describe('inside a router (delegates to the shared route matcher)', function () {
|
|
97
|
+
it('matches a path as a prefix by default', function () {
|
|
98
|
+
expect((0, _index.isItemActive)({
|
|
99
|
+
path: '/stories'
|
|
100
|
+
}, '/stories')).toBe(true);
|
|
101
|
+
expect((0, _index.isItemActive)({
|
|
102
|
+
path: '/stories'
|
|
103
|
+
}, '/stories/new')).toBe(true);
|
|
104
|
+
expect((0, _index.isItemActive)({
|
|
105
|
+
path: '/stories'
|
|
106
|
+
}, '/budgets')).toBe(false);
|
|
107
|
+
});
|
|
108
|
+
it('matches exactly when the item opts in via exact', function () {
|
|
109
|
+
expect((0, _index.isItemActive)({
|
|
110
|
+
path: '/stories',
|
|
111
|
+
exact: true
|
|
112
|
+
}, '/stories/new')).toBe(false);
|
|
113
|
+
});
|
|
114
|
+
it('keeps the parent tab active on a hidden subpage via wildcard', function () {
|
|
115
|
+
var item = {
|
|
116
|
+
path: '/additional-brands',
|
|
117
|
+
activeTabPaths: ['/additional-brands/*']
|
|
118
|
+
};
|
|
119
|
+
expect((0, _index.isItemActive)(item, '/additional-brands/new')).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
describe('TabbedNavigation with routed items', function () {
|
|
124
|
+
it('renders the routed content inside the active tab panel', function () {
|
|
125
|
+
renderTabs({
|
|
126
|
+
initialEntries: ['/budgets']
|
|
127
|
+
});
|
|
128
|
+
expect(getTab('Budgets')).toHaveAttribute('aria-selected', 'true');
|
|
129
|
+
var content = _react.screen.getByText('Routed content');
|
|
130
|
+
var panel = content.closest('[role="tabpanel"]');
|
|
131
|
+
expect(panel).not.toBeNull();
|
|
132
|
+
expect(getTab('Budgets')).toHaveAttribute('aria-controls', panel.id);
|
|
133
|
+
});
|
|
134
|
+
it('keeps the tab active and the content in its panel on a nested route', function () {
|
|
135
|
+
renderTabs({
|
|
136
|
+
initialEntries: ['/stories/new']
|
|
137
|
+
});
|
|
138
|
+
expect(getTab('Stories')).toHaveAttribute('aria-selected', 'true');
|
|
139
|
+
expect(_react.screen.getByText('Routed content').closest('[role="tabpanel"]')).not.toBeNull();
|
|
140
|
+
});
|
|
141
|
+
it('prefers the most specific tab when paths nest', function () {
|
|
142
|
+
(0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactRouterDom.MemoryRouter, {
|
|
143
|
+
initialEntries: ['/stories/new'],
|
|
144
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_index["default"], {
|
|
145
|
+
items: [{
|
|
146
|
+
label: 'Stories',
|
|
147
|
+
path: '/stories'
|
|
148
|
+
}, {
|
|
149
|
+
label: 'New story',
|
|
150
|
+
path: '/stories/new'
|
|
151
|
+
}]
|
|
152
|
+
})
|
|
153
|
+
}));
|
|
154
|
+
expect(getTab('New story')).toHaveAttribute('aria-selected', 'true');
|
|
155
|
+
expect(getTab('Stories')).toHaveAttribute('aria-selected', 'false');
|
|
156
|
+
});
|
|
157
|
+
it('navigates through history.push on click', function () {
|
|
158
|
+
var history = renderTabs();
|
|
159
|
+
_react.fireEvent.click(getTab('Sites'));
|
|
160
|
+
expect(history.location.pathname).toBe('/sites');
|
|
161
|
+
expect(getTab('Sites')).toHaveAttribute('aria-selected', 'true');
|
|
162
|
+
});
|
|
163
|
+
it('leaves modified clicks to the browser', function () {
|
|
164
|
+
var history = renderTabs();
|
|
165
|
+
_react.fireEvent.click(getTab('Sites'), {
|
|
166
|
+
metaKey: true
|
|
167
|
+
});
|
|
168
|
+
expect(history.location.pathname).toBe('/stories');
|
|
169
|
+
});
|
|
170
|
+
it('consults history.block guards before navigating', function () {
|
|
171
|
+
var history = renderTabs();
|
|
172
|
+
var unblock = history.block(function () {
|
|
173
|
+
return false;
|
|
174
|
+
});
|
|
175
|
+
_react.fireEvent.click(getTab('Sites'));
|
|
176
|
+
expect(history.location.pathname).toBe('/stories');
|
|
177
|
+
expect(getTab('Stories')).toHaveAttribute('aria-selected', 'true');
|
|
178
|
+
unblock();
|
|
179
|
+
});
|
|
180
|
+
it('disables tabs after the active one with disableUpcoming', function () {
|
|
181
|
+
renderTabs({
|
|
182
|
+
initialEntries: ['/budgets'],
|
|
183
|
+
disableUpcoming: true
|
|
184
|
+
});
|
|
185
|
+
expect(getTab('Stories')).toHaveAttribute('href');
|
|
186
|
+
expect(getTab('Sites')).not.toHaveAttribute('href');
|
|
187
|
+
expect(getTab('Sites')).toHaveAttribute('aria-disabled', 'true');
|
|
188
|
+
_react.fireEvent.click(getTab('Sites'));
|
|
189
|
+
expect(getTab('Budgets')).toHaveAttribute('aria-selected', 'true');
|
|
190
|
+
});
|
|
191
|
+
it('disables every tab with disableUpcoming when no route matches', function () {
|
|
192
|
+
renderTabs({
|
|
193
|
+
initialEntries: ['/unknown'],
|
|
194
|
+
disableUpcoming: true
|
|
195
|
+
});
|
|
196
|
+
ITEMS.forEach(function (_ref3) {
|
|
197
|
+
var label = _ref3.label;
|
|
198
|
+
expect(getTab(label)).toHaveAttribute('aria-disabled', 'true');
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
it('renders the content outside the panels when no tab owns the route', function () {
|
|
202
|
+
// A route no tab matches — an edit screen registered as hidden, or a stale
|
|
203
|
+
// URL. The content must still mount, or the page body is blank.
|
|
204
|
+
renderTabs({
|
|
205
|
+
initialEntries: ['/edit/123']
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Assert positively: with nothing selected the panels unmount entirely, so
|
|
209
|
+
// a `closest( '[role=tabpanel]' )` check alone would pass even if the
|
|
210
|
+
// content rendered somewhere wrong. Exactly one copy also guards the mutual
|
|
211
|
+
// exclusivity with the in-panel render.
|
|
212
|
+
var matches = _react.screen.getAllByText('Routed content');
|
|
213
|
+
expect(matches).toHaveLength(1);
|
|
214
|
+
expect(matches[0].closest('.newspack-tabbed-navigation__root')).not.toBeNull();
|
|
215
|
+
expect(matches[0].closest('[role="tabpanel"]')).toBeNull();
|
|
216
|
+
ITEMS.forEach(function (_ref4) {
|
|
217
|
+
var label = _ref4.label;
|
|
218
|
+
expect(getTab(label)).toHaveAttribute('aria-selected', 'false');
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
it('renders the content when the only matching item is hidden from the bar', function () {
|
|
222
|
+
// The wizard routes hidden sections but filters them out of the bar, so the
|
|
223
|
+
// item exists and yet can never own the route.
|
|
224
|
+
(0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactRouterDom.MemoryRouter, {
|
|
225
|
+
initialEntries: ['/edit/123'],
|
|
226
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_index["default"], {
|
|
227
|
+
items: [].concat(ITEMS, [{
|
|
228
|
+
label: 'Edit',
|
|
229
|
+
path: '/edit/:id',
|
|
230
|
+
isHiddenInTabbedNavigation: true
|
|
231
|
+
}]),
|
|
232
|
+
content: /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
|
|
233
|
+
children: "Routed content"
|
|
234
|
+
})
|
|
235
|
+
})
|
|
236
|
+
}));
|
|
237
|
+
expect(_react.screen.getAllByText('Routed content')).toHaveLength(1);
|
|
238
|
+
expect(_react.screen.queryByRole('tab', {
|
|
239
|
+
name: 'Edit'
|
|
240
|
+
})).not.toBeInTheDocument();
|
|
241
|
+
});
|
|
242
|
+
it('renders unowned content even when disableUpcoming disables every tab', function () {
|
|
243
|
+
// The setup wizard's Welcome and Completed screens are hidden from the bar
|
|
244
|
+
// AND use disableUpcoming, so `index > activeIndex` disables every tab. The
|
|
245
|
+
// content still has to render — this is the case that left onboarding blank.
|
|
246
|
+
renderTabs({
|
|
247
|
+
initialEntries: ['/unowned'],
|
|
248
|
+
disableUpcoming: true
|
|
249
|
+
});
|
|
250
|
+
expect(_react.screen.getAllByText('Routed content')).toHaveLength(1);
|
|
251
|
+
ITEMS.forEach(function (_ref5) {
|
|
252
|
+
var label = _ref5.label;
|
|
253
|
+
expect(getTab(label)).toHaveAttribute('aria-disabled', 'true');
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
it('mounts unowned content so a router fallback inside it still runs', function () {
|
|
257
|
+
// The wizard's trailing <Redirect> lives inside this content, so dropping
|
|
258
|
+
// it would strand the route instead of correcting it.
|
|
259
|
+
renderTabs({
|
|
260
|
+
initialEntries: ['/unknown'],
|
|
261
|
+
content: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactRouterDom.Redirect, {
|
|
262
|
+
to: "/stories"
|
|
263
|
+
})
|
|
264
|
+
});
|
|
265
|
+
expect(getTab('Stories')).toHaveAttribute('aria-selected', 'true');
|
|
266
|
+
});
|
|
267
|
+
it('navigates when the active tab changes so panels follow the route', function () {
|
|
268
|
+
var history = renderTabs();
|
|
269
|
+
(0, _react.act)(function () {
|
|
270
|
+
return history.push('/budgets');
|
|
271
|
+
});
|
|
272
|
+
expect(getTab('Budgets')).toHaveAttribute('aria-selected', 'true');
|
|
273
|
+
expect(_react.screen.getByText('Routed content').closest('[role="tabpanel"]').id).toBe(getTab('Budgets').getAttribute('aria-controls'));
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
describe('TabbedNavigation with href-only items', function () {
|
|
277
|
+
var LINK_ITEMS = [{
|
|
278
|
+
label: 'Newsletters',
|
|
279
|
+
href: 'http://example.com/wp-admin/admin.php?page=newsletters',
|
|
280
|
+
selected: true
|
|
281
|
+
}, {
|
|
282
|
+
label: 'Ads',
|
|
283
|
+
href: 'http://example.com/wp-admin/admin.php?page=ads'
|
|
284
|
+
}];
|
|
285
|
+
it('renders plain navigation links instead of a tabs widget', function () {
|
|
286
|
+
(0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_index["default"], {
|
|
287
|
+
items: LINK_ITEMS
|
|
288
|
+
}));
|
|
289
|
+
expect(_react.screen.queryByRole('tab')).not.toBeInTheDocument();
|
|
290
|
+
expect(_react.screen.getByRole('navigation')).toBeInTheDocument();
|
|
291
|
+
var active = _react.screen.getByRole('link', {
|
|
292
|
+
name: 'Newsletters'
|
|
293
|
+
});
|
|
294
|
+
expect(active).toHaveAttribute('aria-current', 'page');
|
|
295
|
+
expect(_react.screen.getByRole('link', {
|
|
296
|
+
name: 'Ads'
|
|
297
|
+
})).not.toHaveAttribute('aria-current');
|
|
298
|
+
});
|
|
299
|
+
});
|