newspack-components 4.1.0 → 4.3.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.
Files changed (135) hide show
  1. package/colors/colors.module.scss +1 -1
  2. package/dist/cjs/action-card/index.js +21 -12
  3. package/dist/cjs/action-card/style.scss +32 -14
  4. package/dist/cjs/autocomplete-tokenfield/index.js +2 -2
  5. package/dist/cjs/autocomplete-with-latest-posts/index.js +289 -0
  6. package/dist/cjs/autocomplete-with-latest-posts/style.scss +150 -0
  7. package/dist/cjs/badge/index.js +32 -0
  8. package/dist/cjs/badge/style.scss +36 -0
  9. package/dist/cjs/button/index.js +2 -0
  10. package/dist/cjs/card/core-card.js +141 -0
  11. package/dist/cjs/card/index.js +33 -0
  12. package/dist/cjs/card/style-core.scss +227 -0
  13. package/dist/cjs/card/style.scss +2 -2
  14. package/dist/cjs/card-settings-group/index.js +49 -0
  15. package/dist/cjs/card-settings-group/style.scss +33 -0
  16. package/dist/cjs/card-sortable-list/index.js +390 -0
  17. package/dist/cjs/card-sortable-list/style.scss +48 -0
  18. package/dist/cjs/confirm-dialog/index.js +59 -0
  19. package/dist/cjs/divider/index.js +42 -0
  20. package/dist/cjs/divider/style.scss +51 -0
  21. package/dist/cjs/form-token-field/style.scss +0 -5
  22. package/dist/cjs/grid/index.js +2 -1
  23. package/dist/cjs/grid/style.scss +63 -9
  24. package/dist/cjs/index.js +42 -0
  25. package/dist/cjs/modal/index.js +11 -4
  26. package/dist/cjs/modal/style.scss +43 -13
  27. package/dist/cjs/newspack-icon/style.scss +5 -3
  28. package/dist/cjs/popover/style.scss +1 -1
  29. package/dist/cjs/section-header/index.js +76 -17
  30. package/dist/cjs/section-header/style.scss +109 -4
  31. package/dist/cjs/style.scss +2 -0
  32. package/dist/cjs/waiting/index.js +3 -1
  33. package/dist/cjs/waiting/style.scss +4 -0
  34. package/dist/cjs/web-preview/index.js +98 -39
  35. package/dist/cjs/web-preview/style.scss +20 -53
  36. package/dist/cjs/with-wizard/index.js +1 -1
  37. package/dist/cjs/with-wizard/style.scss +24 -6
  38. package/dist/cjs/with-wizard-screen/style.scss +39 -10
  39. package/dist/cjs/wizard/components/WizardSnackbar.js +70 -0
  40. package/dist/cjs/wizard/components/style.scss +48 -0
  41. package/dist/cjs/wizard/index.js +97 -16
  42. package/dist/cjs/wizard/store/index.js +46 -0
  43. package/dist/esm/action-card/index.js +21 -12
  44. package/dist/esm/action-card/style.scss +32 -14
  45. package/dist/esm/autocomplete-tokenfield/index.js +2 -2
  46. package/dist/esm/autocomplete-with-latest-posts/index.js +281 -0
  47. package/dist/esm/autocomplete-with-latest-posts/style.scss +150 -0
  48. package/dist/esm/badge/index.js +24 -0
  49. package/dist/esm/badge/style.scss +36 -0
  50. package/dist/esm/button/index.js +2 -0
  51. package/dist/esm/card/core-card.js +133 -0
  52. package/dist/esm/card/index.js +33 -0
  53. package/dist/esm/card/style-core.scss +227 -0
  54. package/dist/esm/card/style.scss +2 -2
  55. package/dist/esm/card-settings-group/index.js +42 -0
  56. package/dist/esm/card-settings-group/style.scss +33 -0
  57. package/dist/esm/card-sortable-list/index.js +381 -0
  58. package/dist/esm/card-sortable-list/style.scss +48 -0
  59. package/dist/esm/confirm-dialog/index.js +49 -0
  60. package/dist/esm/divider/index.js +34 -0
  61. package/dist/esm/divider/style.scss +51 -0
  62. package/dist/esm/form-token-field/style.scss +0 -5
  63. package/dist/esm/grid/index.js +2 -1
  64. package/dist/esm/grid/style.scss +63 -9
  65. package/dist/esm/index.js +6 -0
  66. package/dist/esm/modal/index.js +11 -4
  67. package/dist/esm/modal/style.scss +43 -13
  68. package/dist/esm/newspack-icon/style.scss +5 -3
  69. package/dist/esm/popover/style.scss +1 -1
  70. package/dist/esm/section-header/index.js +75 -18
  71. package/dist/esm/section-header/style.scss +109 -4
  72. package/dist/esm/style.scss +2 -0
  73. package/dist/esm/waiting/index.js +3 -1
  74. package/dist/esm/waiting/style.scss +4 -0
  75. package/dist/esm/web-preview/index.js +101 -42
  76. package/dist/esm/web-preview/style.scss +20 -53
  77. package/dist/esm/with-wizard/index.js +1 -1
  78. package/dist/esm/with-wizard/style.scss +24 -6
  79. package/dist/esm/with-wizard-screen/style.scss +39 -10
  80. package/dist/esm/wizard/components/WizardSnackbar.js +64 -0
  81. package/dist/esm/wizard/components/style.scss +48 -0
  82. package/dist/esm/wizard/index.js +101 -20
  83. package/dist/esm/wizard/store/index.js +46 -0
  84. package/package.json +5 -6
  85. package/shared/author-context.js +26 -0
  86. package/shared/hooks/use-coauthors.js +220 -0
  87. package/shared/hooks/use-coauthors.test.js +624 -0
  88. package/shared/hooks/use-custom-byline.js +47 -0
  89. package/shared/hooks/use-custom-byline.test.js +133 -0
  90. package/shared/scss/_mixins.scss +46 -0
  91. package/src/action-card/action-card.d.ts +7 -3
  92. package/src/action-card/index.js +24 -11
  93. package/src/action-card/style.scss +32 -14
  94. package/src/autocomplete-tokenfield/index.js +2 -2
  95. package/src/autocomplete-with-latest-posts/index.js +290 -0
  96. package/src/autocomplete-with-latest-posts/style.scss +150 -0
  97. package/src/badge/index.tsx +24 -0
  98. package/src/badge/style.scss +36 -0
  99. package/src/button/index.tsx +3 -2
  100. package/src/card/core-card.js +147 -0
  101. package/src/card/index.js +39 -1
  102. package/src/card/style-core.scss +227 -0
  103. package/src/card/style.scss +2 -2
  104. package/src/card-settings-group/README.md +43 -0
  105. package/src/card-settings-group/index.tsx +53 -0
  106. package/src/card-settings-group/style.scss +33 -0
  107. package/src/card-sortable-list/README.md +51 -0
  108. package/src/card-sortable-list/index.tsx +412 -0
  109. package/src/card-sortable-list/style.scss +48 -0
  110. package/src/confirm-dialog/index.tsx +64 -0
  111. package/src/divider/README.md +25 -0
  112. package/src/divider/index.js +31 -0
  113. package/src/divider/style.scss +51 -0
  114. package/src/form-token-field/style.scss +0 -5
  115. package/src/grid/index.tsx +2 -1
  116. package/src/grid/style.scss +63 -9
  117. package/src/index.js +6 -0
  118. package/src/modal/index.js +12 -3
  119. package/src/modal/style.scss +43 -13
  120. package/src/newspack-icon/style.scss +5 -3
  121. package/src/popover/style.scss +1 -1
  122. package/src/section-header/index.js +72 -13
  123. package/src/section-header/style.scss +109 -4
  124. package/src/style.scss +2 -0
  125. package/src/waiting/index.js +2 -1
  126. package/src/waiting/style.scss +4 -0
  127. package/src/web-preview/index.js +90 -35
  128. package/src/web-preview/style.scss +20 -53
  129. package/src/with-wizard/index.js +1 -1
  130. package/src/with-wizard/style.scss +24 -6
  131. package/src/with-wizard-screen/style.scss +39 -10
  132. package/src/wizard/components/WizardSnackbar.js +58 -0
  133. package/src/wizard/components/style.scss +48 -0
  134. package/src/wizard/index.js +108 -14
  135. package/src/wizard/store/index.js +26 -0
@@ -6,7 +6,7 @@
6
6
  @use "sass:color";
7
7
 
8
8
  @function to-rgb($color) {
9
- @return color.red($color) + ", " + color.green($color) + ", " + color.blue($color);
9
+ @return color.channel($color, "red", $space: rgb) + ", " + color.channel($color, "green", $space: rgb) + ", " + color.channel($color, "blue", $space: rgb);
10
10
  }
11
11
 
12
12
  // Primary: Hex
@@ -36,13 +36,14 @@ var _jsxRuntime = require("react/jsx-runtime");
36
36
  * @return {JSX.Element} ActionCard component.
37
37
  */const ActionCard = ({
38
38
  badge,
39
- badgeLevel = 'info',
39
+ badgeLevel,
40
40
  className,
41
41
  checkbox,
42
42
  children,
43
43
  collapse,
44
44
  disabled,
45
45
  title,
46
+ heading = 2,
46
47
  description,
47
48
  handoff,
48
49
  editLink,
@@ -67,11 +68,13 @@ var _jsxRuntime = require("react/jsx-runtime");
67
68
  titleLink,
68
69
  toggleChecked = false,
69
70
  toggleOnChange,
71
+ togglePosition = 'leading',
70
72
  hasGreyHeader,
71
73
  hasWhiteHeader,
72
74
  noBorder,
73
75
  isPending,
74
76
  expandable = false,
77
+ isExpanded,
75
78
  isButtonEnabled = false,
76
79
  // Draggable props. All are required to enable drag sorting.
77
80
  draggable = false,
@@ -79,10 +82,15 @@ var _jsxRuntime = require("react/jsx-runtime");
79
82
  dragWrapperRef,
80
83
  onDragCallback
81
84
  }) => {
82
- const [expanded, setExpanded] = (0, _element.useState)(false);
85
+ const [expanded, setExpanded] = (0, _element.useState)(Boolean(isExpanded));
83
86
  const [dragging, setDragging] = (0, _element.useState)(false);
84
87
  const [targetIndex, setTargetIndex] = (0, _element.useState)(null);
85
88
  const [dragRef, setDragRef] = (0, _element.useState)(null);
89
+ (0, _element.useEffect)(() => {
90
+ if (typeof isExpanded === 'boolean') {
91
+ setExpanded(isExpanded);
92
+ }
93
+ }, [isExpanded]);
86
94
  (0, _element.useEffect)(() => {
87
95
  if (dragWrapperRef && !dragRef) {
88
96
  setDragRef(dragWrapperRef);
@@ -104,16 +112,19 @@ var _jsxRuntime = require("react/jsx-runtime");
104
112
  onClick: () => toggleOnChange(!toggleChecked),
105
113
  tabIndex: '0'
106
114
  } : {};
115
+ const togglePositionClass = togglePosition === 'trailing' ? 'is-toggle-trailing' : 'is-toggle-leading';
107
116
  const hasInternalLink = href && href.indexOf('http') !== 0;
108
117
  const isDisplayingSecondaryAction = secondaryActionText && onSecondaryActionClick;
109
118
  const badges = !Array.isArray(badge) && badge ? [badge] : badge;
110
- const Component = () => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
119
+ const HeadingTag = `h${heading}`;
120
+ const cardContent = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
111
121
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
112
122
  className: "newspack-action-card__region newspack-action-card__region-top",
113
123
  children: [toggleOnChange && /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.ToggleControl, {
114
124
  checked: toggleChecked,
115
125
  onChange: toggleOnChange,
116
- disabled: disabled
126
+ disabled: disabled,
127
+ className: togglePositionClass
117
128
  }), image && !toggleOnChange && /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
118
129
  className: "newspack-action-card__region newspack-action-card__region-left",
119
130
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)("a", {
@@ -137,7 +148,7 @@ var _jsxRuntime = require("react/jsx-runtime");
137
148
  columns: 1,
138
149
  gutter: 8,
139
150
  noMargin: true,
140
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)("h2", {
151
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(HeadingTag, {
141
152
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)("span", {
142
153
  className: "newspack-action-card__title",
143
154
  ...titleProps,
@@ -214,10 +225,10 @@ var _jsxRuntime = require("react/jsx-runtime");
214
225
  isWarning: true,
215
226
  rawHTML: notificationHTML
216
227
  })]
217
- }), children && (expandable && expanded || !expandable) && /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
228
+ }), children && (expandable && expanded || !expandable) ? /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
218
229
  className: "newspack-action-card__region-children",
219
230
  children: children
220
- })]
231
+ }) : null]
221
232
  });
222
233
  if (draggable && dragRef?.current && typeof dragIndex === 'number' && onDragCallback && id) {
223
234
  let wrapperRect = dragRef.current.getBoundingClientRect();
@@ -288,9 +299,7 @@ var _jsxRuntime = require("react/jsx-runtime");
288
299
  onDragStart: onDraggableStart,
289
300
  onDragEnd: onDraggableEnd,
290
301
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.Icon, {
291
- icon: _icons.dragHandle,
292
- height: 18,
293
- width: 18
302
+ icon: _icons.dragHandle
294
303
  })
295
304
  }), /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
296
305
  className: "movers",
@@ -306,7 +315,7 @@ var _jsxRuntime = require("react/jsx-runtime");
306
315
  label: (0, _i18n.__)('Move one position down', 'newspack-plugin')
307
316
  })]
308
317
  })]
309
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(Component, {})]
318
+ }), cardContent]
310
319
  })
311
320
  })
312
321
  });
@@ -316,7 +325,7 @@ var _jsxRuntime = require("react/jsx-runtime");
316
325
  onClick: simple && onClick,
317
326
  id: id !== null && id !== void 0 ? id : null,
318
327
  noBorder: noBorder,
319
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(Component, {})
328
+ children: cardContent
320
329
  });
321
330
  };
322
331
  var _default = exports.default = ActionCard;
@@ -55,7 +55,7 @@
55
55
  .components-toggle-control {
56
56
  margin-bottom: 16px;
57
57
 
58
- @media screen and ( min-width: 744px ) {
58
+ @media screen and (min-width: 744px) {
59
59
  margin-bottom: 0;
60
60
  margin-right: 16px;
61
61
  }
@@ -77,10 +77,6 @@
77
77
  padding: 0 4px;
78
78
  }
79
79
 
80
- & + & {
81
- margin-top: -16px;
82
- }
83
-
84
80
  &.mv0 {
85
81
  border-left-color: wp-colors.$gray-300;
86
82
  border-radius: 0;
@@ -122,7 +118,7 @@
122
118
  .components-toggle-control {
123
119
  margin-bottom: 16px;
124
120
 
125
- @media screen and ( min-width: 744px ) {
121
+ @media screen and (min-width: 744px) {
126
122
  margin-bottom: 0;
127
123
  margin-right: 16px;
128
124
  }
@@ -132,7 +128,7 @@
132
128
  .newspack-action-card__region-left {
133
129
  margin-bottom: 16px;
134
130
 
135
- @media screen and ( min-width: 744px ) {
131
+ @media screen and (min-width: 744px) {
136
132
  margin-bottom: 0;
137
133
  margin-right: 16px;
138
134
  }
@@ -252,7 +248,7 @@
252
248
  padding: 24px;
253
249
  display: block;
254
250
 
255
- @media screen and ( min-width: 744px ) {
251
+ @media screen and (min-width: 744px) {
256
252
  display: flex;
257
253
  }
258
254
 
@@ -261,9 +257,19 @@
261
257
  margin-bottom: 24px;
262
258
  margin-top: 0;
263
259
 
264
- @media screen and ( min-width: 744px ) {
260
+ @media screen and (min-width: 744px) {
265
261
  margin-bottom: 0;
266
262
  margin-right: 24px;
263
+
264
+ &.is-toggle-trailing {
265
+ margin-left: 24px;
266
+ margin-right: 0;
267
+ order: 999;
268
+ }
269
+ }
270
+
271
+ label {
272
+ display: none;
267
273
  }
268
274
 
269
275
  .components-base-control__field {
@@ -308,7 +314,7 @@
308
314
  .newspack-action-card__region-left {
309
315
  margin-bottom: 24px;
310
316
 
311
- @media screen and ( min-width: 744px ) {
317
+ @media screen and (min-width: 744px) {
312
318
  margin-bottom: 0;
313
319
  margin-right: 24px;
314
320
  }
@@ -335,7 +341,7 @@
335
341
  margin-top: 24px;
336
342
  text-align: right;
337
343
 
338
- @media screen and ( min-width: 744px ) {
344
+ @media screen and (min-width: 744px) {
339
345
  align-items: flex-end;
340
346
  flex-direction: column;
341
347
  margin-left: 24px;
@@ -437,13 +443,21 @@
437
443
  text-decoration: inherit;
438
444
  }
439
445
 
440
- .newspack-action-card__region-children {
441
- border-top: 1px solid wp-colors.$gray-100;
446
+ > .newspack-action-card__region-children {
447
+ border-top: 1px solid wp-colors.$gray-300;
442
448
  padding-top: 24px;
443
449
 
444
450
  + .newspack-action-card__region-children {
445
451
  border-top: none;
446
452
  }
453
+
454
+ > * + * {
455
+ margin-top: 24px !important;
456
+ }
457
+
458
+ .newspack-action-card__region-children:has(> div:not(:empty)) {
459
+ margin-top: 16px;
460
+ }
447
461
  }
448
462
 
449
463
  &.is-medium .newspack-action-card__region-children {
@@ -473,6 +487,11 @@
473
487
  margin-left: 8px;
474
488
  }
475
489
  }
490
+
491
+ hr:not(.newspack-divider) {
492
+ background: wp-colors.$gray-100;
493
+ margin: 0;
494
+ }
476
495
  }
477
496
 
478
497
  .newspack-card {
@@ -557,7 +576,6 @@
557
576
  }
558
577
 
559
578
  .newspack-action-card__region-top {
560
- flex: 1 1 auto;
561
579
  padding: 8px 16px 8px 0 !important;
562
580
  flex: 1;
563
581
  }
@@ -143,7 +143,7 @@ var _jsxRuntime = require("react/jsx-runtime");
143
143
  Object.keys(validValues).forEach(key => {
144
144
  if (validValues[key] === label) {
145
145
  // Preserve numeric or string type of values. Object.keys will convert numbers to strings.
146
- const value = isNaN(parseInt(key)) ? key.toString() : parseInt(key);
146
+ const value = /^\d+$/.test(key.toString()) ? parseInt(key) : key.toString();
147
147
  acc.push({
148
148
  value,
149
149
  label
@@ -153,7 +153,7 @@ var _jsxRuntime = require("react/jsx-runtime");
153
153
  return acc;
154
154
  }, []);
155
155
  }
156
- return labels.map(label => Object.keys(validValues).map(key => isNaN(parseInt(key)) ? key.toString() : parseInt(key)).find(key => validValues[key] === label));
156
+ return labels.map(label => Object.keys(validValues).map(key => /^\d+$/.test(key.toString()) ? parseInt(key) : key.toString()).find(key => validValues[key] === label));
157
157
  }
158
158
 
159
159
  /**
@@ -0,0 +1,289 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = void 0;
8
+ var _debounce = _interopRequireDefault(require("lodash/debounce"));
9
+ var _apiFetch = _interopRequireDefault(require("@wordpress/api-fetch"));
10
+ var _components = require("@wordpress/components");
11
+ var _htmlEntities = require("@wordpress/html-entities");
12
+ var _element = require("@wordpress/element");
13
+ var _i18n = require("@wordpress/i18n");
14
+ var _url = require("@wordpress/url");
15
+ require("./style.scss");
16
+ var _jsxRuntime = require("react/jsx-runtime");
17
+ /**
18
+ * External dependencies
19
+ */
20
+
21
+ /**
22
+ * WordPress dependencies
23
+ */
24
+
25
+ const AutocompleteWithLatestPosts = ({
26
+ fetchSuggestions = false,
27
+ // If passed, will use this function to fetch suggestions data.
28
+ help = (0, _i18n.__)('Begin typing search term, click autocomplete result to select.', 'newspack-plugin'),
29
+ hideHelp = true,
30
+ // If true, all help text will be hidden.
31
+ hideFormTokenHelp = true,
32
+ // If true, hides FormTokenField's built-in help text.
33
+ label = (0, _i18n.__)('Search', 'newspack-plugin'),
34
+ maxItemsToSuggest = 0,
35
+ // If passed, will be used to determine "load more" state. Necessary if you want "load more" functionality when using a custom `fetchSuggestions` function.
36
+ multiSelect = false,
37
+ // If true, component can select multiple values at once.
38
+ onChange = false,
39
+ // Function to call when selections change.
40
+ selectedItems = [],
41
+ // Array of saved items.
42
+ suggestionsToFetch = 20 // Number of suggestions to fetch per query.
43
+ }) => {
44
+ const [isLoading, setIsLoading] = (0, _element.useState)(true);
45
+ const [isLoadingMore, setIsLoadingMore] = (0, _element.useState)(false);
46
+ const [suggestions, setSuggestions] = (0, _element.useState)([]);
47
+ const [maxSuggestions, setMaxSuggestions] = (0, _element.useState)(0);
48
+ const [searchSuggestions, setSearchSuggestions] = (0, _element.useState)([]);
49
+ const [validValues, setValidValues] = (0, _element.useState)({});
50
+ const [isSearching, setIsSearching] = (0, _element.useState)(false);
51
+ // Leaving this in here so we can expand to use different post types in the future; right now it defaults to 'post'.
52
+ const postTypeToSearch = 'post';
53
+ const classNames = ['newspack-autocomplete-with-latest-posts'];
54
+ if (hideFormTokenHelp) {
55
+ classNames.push('hide-form-token-help');
56
+ }
57
+ if (hideHelp) {
58
+ classNames.push('hide-help');
59
+ }
60
+
61
+ /**
62
+ * Debounced function to update search suggestions.
63
+ */
64
+ const debouncedUpdateSuggestions = (0, _debounce.default)(input => {
65
+ if (!input || input.length < 2) {
66
+ setSearchSuggestions([]);
67
+ setIsSearching(false);
68
+ return;
69
+ }
70
+ setIsSearching(true);
71
+ handleFetchSuggestions(input, 0, postTypeToSearch).then(_suggestions => {
72
+ setSearchSuggestions(_suggestions);
73
+ // Update valid values for token conversion
74
+ const newValidValues = {
75
+ ...validValues
76
+ };
77
+ _suggestions.forEach(suggestion => {
78
+ newValidValues[suggestion.value] = suggestion.label;
79
+ });
80
+ setValidValues(newValidValues);
81
+ }).finally(() => setIsSearching(false));
82
+ }, 500);
83
+
84
+ /**
85
+ * Fetch recent posts to show as suggestions.
86
+ */
87
+ (0, _element.useEffect)(() => {
88
+ setIsLoading(true);
89
+ handleFetchSuggestions(null, 0, postTypeToSearch).then(_suggestions => {
90
+ if (0 < _suggestions.length) {
91
+ setSuggestions(_suggestions);
92
+ // Update valid values for token conversion
93
+ const newValidValues = {
94
+ ...validValues
95
+ };
96
+ _suggestions.forEach(suggestion => {
97
+ newValidValues[suggestion.value] = suggestion.label;
98
+ });
99
+ setValidValues(newValidValues);
100
+ }
101
+ }).finally(() => setIsLoading(false));
102
+ }, []);
103
+
104
+ /**
105
+ * Fetch more suggestions.
106
+ */
107
+ (0, _element.useEffect)(() => {
108
+ if (isLoadingMore) {
109
+ handleFetchSuggestions(null, suggestions.length, postTypeToSearch).then(_suggestions => {
110
+ if (0 < _suggestions.length) {
111
+ setSuggestions(suggestions.concat(_suggestions));
112
+ }
113
+ }).finally(() => setIsLoadingMore(false));
114
+ }
115
+ }, [isLoadingMore]);
116
+
117
+ /**
118
+ * If passed a `fetchSuggestions` prop, use that, otherwise, build it based on the selected post type.
119
+ */
120
+ const handleFetchSuggestions = fetchSuggestions ? fetchSuggestions : async (search = null, offset = 0, searchSlug = null) => {
121
+ const postTypeSlug = searchSlug || postTypeToSearch;
122
+ const endpoint = 'post' === postTypeSlug || 'page' === postTypeSlug ? postTypeSlug + 's' // Default post type endpoints are plural.
123
+ : postTypeSlug; // Custom post type endpoints are singular.
124
+ const response = await (0, _apiFetch.default)({
125
+ parse: false,
126
+ path: (0, _url.addQueryArgs)('/wp/v2/' + endpoint, {
127
+ search,
128
+ offset,
129
+ per_page: suggestionsToFetch,
130
+ _fields: 'id,title'
131
+ })
132
+ });
133
+ const total = parseInt(response.headers.get('x-wp-total') || 0);
134
+ const posts = await response.json();
135
+ setMaxSuggestions(total);
136
+
137
+ // Format suggestions for FormTokenField display.
138
+ return posts.reduce((acc, post) => {
139
+ acc.push({
140
+ value: parseInt(post.id),
141
+ label: (0, _htmlEntities.decodeEntities)(post?.title.rendered) || (0, _i18n.__)('(no title)', 'newspack-plugin')
142
+ });
143
+ return acc;
144
+ }, []);
145
+ };
146
+
147
+ /**
148
+ * Get labels for token values.
149
+ */
150
+ const getLabelsForValues = values => {
151
+ return values.reduce((accumulator, value) => {
152
+ if (!value) {
153
+ return accumulator;
154
+ }
155
+ if (value.label) {
156
+ return [...accumulator, value.label];
157
+ }
158
+ return validValues[value] ? [...accumulator, validValues[value]] : accumulator;
159
+ }, []);
160
+ };
161
+
162
+ /**
163
+ * Get values for token labels.
164
+ */
165
+ const getValuesForLabels = labels => {
166
+ // eslint-disable-next-line @typescript-eslint/no-shadow
167
+ return labels.reduce((acc, label) => {
168
+ Object.keys(validValues).forEach(key => {
169
+ if (validValues[key] === label) {
170
+ const value = isNaN(parseInt(key)) ? key.toString() : parseInt(key);
171
+ acc.push({
172
+ value,
173
+ label
174
+ });
175
+ }
176
+ });
177
+ return acc;
178
+ }, []);
179
+ };
180
+
181
+ /**
182
+ * Handle FormTokenField onChange.
183
+ */
184
+ const handleTokenChange = tokenStrings => {
185
+ const newSelections = getValuesForLabels(tokenStrings);
186
+
187
+ // If only allowing one selection, just return the one selected item.
188
+ if (!multiSelect) {
189
+ const newSelection = newSelections[newSelections.length - 1]; // Get the last selected item
190
+ if (newSelection) {
191
+ return onChange([{
192
+ ...newSelection,
193
+ postType: postTypeToSearch
194
+ }]);
195
+ }
196
+ return onChange([]);
197
+ }
198
+
199
+ // For multi-select, include currently selected post type in selection results.
200
+ onChange(newSelections.map(selection => ({
201
+ ...selection,
202
+ postType: postTypeToSearch
203
+ })));
204
+ };
205
+
206
+ /**
207
+ * Get tokens for FormTokenField.
208
+ */
209
+ const getTokens = () => {
210
+ return getLabelsForValues(selectedItems);
211
+ };
212
+
213
+ /**
214
+ * Render a single suggestion object that can be clicked to select it immediately.
215
+ *
216
+ * @param {Object} suggestion Suggestion object with value and label keys.
217
+ */
218
+ const renderSuggestion = suggestion => {
219
+ if (multiSelect) {
220
+ const isSelected = !!selectedItems.find(_selection => parseInt(_selection.value) === parseInt(suggestion.value) && _selection.label === suggestion.label);
221
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.CheckboxControl, {
222
+ checked: isSelected,
223
+ onChange: () => {
224
+ // For multi-select, we need to toggle the selection
225
+ const currentTokens = getTokens();
226
+ const suggestionLabel = suggestion.label;
227
+ if (isSelected) {
228
+ // Remove the suggestion
229
+ const newTokens = currentTokens.filter(token => token !== suggestionLabel);
230
+ handleTokenChange(newTokens);
231
+ } else {
232
+ // Add the suggestion
233
+ handleTokenChange([...currentTokens, suggestionLabel]);
234
+ }
235
+ },
236
+ label: suggestion.label
237
+ }, suggestion.value);
238
+ }
239
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Button, {
240
+ isLink: true,
241
+ onClick: () => handleTokenChange([...getTokens(), suggestion.label]),
242
+ children: suggestion.label
243
+ }, suggestion.value);
244
+ };
245
+
246
+ /**
247
+ * Render a list of suggestions that can be clicked to select instead of searching by title.
248
+ */
249
+ const renderSuggestions = () => {
250
+ if (0 === suggestions.length) {
251
+ return null;
252
+ }
253
+ const className = multiSelect ? 'newspack-autocomplete-with-latest-posts__search-suggestions-multiselect' : 'newspack-autocomplete-with-latest-posts__search-suggestions';
254
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
255
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
256
+ className: "newspack-autocomplete-with-latest-posts__search-suggestions-container",
257
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("p", {
258
+ className: "newspack-autocomplete-with-suggestions__label",
259
+ children: (0, _i18n.__)('Latest Posts', 'newspack-plugin')
260
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
261
+ className: className,
262
+ children: [suggestions.map(renderSuggestion), suggestions.length < (maxItemsToSuggest || maxSuggestions) && /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Button, {
263
+ disabled: isLoadingMore,
264
+ isSecondary: true,
265
+ onClick: () => setIsLoadingMore(true),
266
+ children: isLoadingMore ? (0, _i18n.__)('Loading…', 'newspack-plugin') : (0, _i18n.__)('Load more', 'newspack-plugin')
267
+ })]
268
+ })]
269
+ })
270
+ });
271
+ };
272
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
273
+ className: classNames.join(' '),
274
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
275
+ className: "newspack-autocomplete-with-latest-posts__input-container",
276
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_components.FormTokenField, {
277
+ value: getTokens(),
278
+ suggestions: searchSuggestions.map(suggestion => suggestion.label),
279
+ onChange: handleTokenChange,
280
+ onInputChange: debouncedUpdateSuggestions,
281
+ label: label,
282
+ help: !hideHelp && help,
283
+ __next40pxDefaultSize: true,
284
+ __nextHasNoMarginBottom: true
285
+ }), (isLoading || isSearching) && /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Spinner, {})]
286
+ }), renderSuggestions()]
287
+ });
288
+ };
289
+ var _default = exports.default = AutocompleteWithLatestPosts;