@wordpress/block-editor 15.10.1-next.ba3aee3a2.0 → 15.10.1-next.v.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 (159) hide show
  1. package/build/components/block-bindings/attribute-control.cjs +1 -1
  2. package/build/components/block-bindings/attribute-control.cjs.map +1 -1
  3. package/build/components/block-bindings/source-fields-list.cjs +1 -1
  4. package/build/components/block-bindings/source-fields-list.cjs.map +1 -1
  5. package/build/components/block-tools/index.cjs +82 -70
  6. package/build/components/block-tools/index.cjs.map +2 -2
  7. package/build/components/block-visibility/block-visibility-info.cjs +0 -59
  8. package/build/components/block-visibility/block-visibility-info.cjs.map +3 -3
  9. package/build/components/block-visibility/constants.cjs +10 -5
  10. package/build/components/block-visibility/constants.cjs.map +2 -2
  11. package/build/components/block-visibility/index.cjs +13 -5
  12. package/build/components/block-visibility/index.cjs.map +3 -3
  13. package/build/components/block-visibility/modal.cjs +397 -0
  14. package/build/components/block-visibility/modal.cjs.map +7 -0
  15. package/build/components/block-visibility/toolbar.cjs +1 -1
  16. package/build/components/block-visibility/toolbar.cjs.map +2 -2
  17. package/build/components/block-visibility/use-block-visibility.cjs +13 -17
  18. package/build/components/block-visibility/use-block-visibility.cjs.map +2 -2
  19. package/build/components/block-visibility/utils.cjs +81 -0
  20. package/build/components/block-visibility/utils.cjs.map +7 -0
  21. package/build/components/block-visibility/viewport-menu-item.cjs +61 -0
  22. package/build/components/block-visibility/viewport-menu-item.cjs.map +7 -0
  23. package/build/components/block-visibility/viewport-toolbar.cjs +89 -0
  24. package/build/components/block-visibility/viewport-toolbar.cjs.map +7 -0
  25. package/build/components/inner-blocks/use-inner-block-template-sync.cjs +1 -1
  26. package/build/components/inner-blocks/use-inner-block-template-sync.cjs.map +1 -1
  27. package/build/components/inserter/menu.cjs +6 -2
  28. package/build/components/inserter/menu.cjs.map +2 -2
  29. package/build/components/list-view/block-select-button.cjs +2 -2
  30. package/build/components/list-view/block-select-button.cjs.map +2 -2
  31. package/build/components/list-view/block.cjs +39 -22
  32. package/build/components/list-view/block.cjs.map +2 -2
  33. package/build/components/rich-text/index.cjs +1 -1
  34. package/build/components/rich-text/index.cjs.map +1 -1
  35. package/build/components/url-input/index.cjs +2 -0
  36. package/build/components/url-input/index.cjs.map +2 -2
  37. package/build/components/use-block-commands/index.cjs +1 -1
  38. package/build/components/use-block-commands/index.cjs.map +2 -2
  39. package/build/hooks/block-fields/index.cjs +75 -166
  40. package/build/hooks/block-fields/index.cjs.map +2 -2
  41. package/build/hooks/block-fields/link/index.cjs +13 -23
  42. package/build/hooks/block-fields/link/index.cjs.map +2 -2
  43. package/build/hooks/block-fields/media/index.cjs +32 -58
  44. package/build/hooks/block-fields/media/index.cjs.map +2 -2
  45. package/build/hooks/block-fields/rich-text/index.cjs +1 -5
  46. package/build/hooks/block-fields/rich-text/index.cjs.map +2 -2
  47. package/build/hooks/cross-origin-isolation.cjs +102 -0
  48. package/build/hooks/cross-origin-isolation.cjs.map +7 -0
  49. package/build/hooks/index.cjs +1 -0
  50. package/build/hooks/index.cjs.map +2 -2
  51. package/build/layouts/flex.cjs +6 -2
  52. package/build/layouts/flex.cjs.map +2 -2
  53. package/build/store/private-selectors.cjs +33 -1
  54. package/build/store/private-selectors.cjs.map +3 -3
  55. package/build/store/reducer.cjs +1 -1
  56. package/build/store/reducer.cjs.map +1 -1
  57. package/build/store/selectors.cjs +7 -8
  58. package/build/store/selectors.cjs.map +2 -2
  59. package/build-module/components/block-bindings/attribute-control.mjs +1 -1
  60. package/build-module/components/block-bindings/attribute-control.mjs.map +1 -1
  61. package/build-module/components/block-bindings/source-fields-list.mjs +1 -1
  62. package/build-module/components/block-bindings/source-fields-list.mjs.map +1 -1
  63. package/build-module/components/block-tools/index.mjs +85 -73
  64. package/build-module/components/block-tools/index.mjs.map +2 -2
  65. package/build-module/components/block-visibility/block-visibility-info.mjs +0 -59
  66. package/build-module/components/block-visibility/block-visibility-info.mjs.map +3 -3
  67. package/build-module/components/block-visibility/constants.mjs +8 -4
  68. package/build-module/components/block-visibility/constants.mjs.map +2 -2
  69. package/build-module/components/block-visibility/index.mjs +13 -6
  70. package/build-module/components/block-visibility/index.mjs.map +2 -2
  71. package/build-module/components/block-visibility/modal.mjs +384 -0
  72. package/build-module/components/block-visibility/modal.mjs.map +7 -0
  73. package/build-module/components/block-visibility/toolbar.mjs +1 -1
  74. package/build-module/components/block-visibility/toolbar.mjs.map +2 -2
  75. package/build-module/components/block-visibility/use-block-visibility.mjs +13 -13
  76. package/build-module/components/block-visibility/use-block-visibility.mjs.map +2 -2
  77. package/build-module/components/block-visibility/utils.mjs +55 -0
  78. package/build-module/components/block-visibility/utils.mjs.map +7 -0
  79. package/build-module/components/block-visibility/viewport-menu-item.mjs +40 -0
  80. package/build-module/components/block-visibility/viewport-menu-item.mjs.map +7 -0
  81. package/build-module/components/block-visibility/viewport-toolbar.mjs +68 -0
  82. package/build-module/components/block-visibility/viewport-toolbar.mjs.map +7 -0
  83. package/build-module/components/inner-blocks/use-inner-block-template-sync.mjs +1 -1
  84. package/build-module/components/inner-blocks/use-inner-block-template-sync.mjs.map +1 -1
  85. package/build-module/components/inserter/menu.mjs +6 -2
  86. package/build-module/components/inserter/menu.mjs.map +2 -2
  87. package/build-module/components/list-view/block-select-button.mjs +2 -2
  88. package/build-module/components/list-view/block-select-button.mjs.map +2 -2
  89. package/build-module/components/list-view/block.mjs +39 -22
  90. package/build-module/components/list-view/block.mjs.map +2 -2
  91. package/build-module/components/rich-text/index.mjs +1 -1
  92. package/build-module/components/rich-text/index.mjs.map +1 -1
  93. package/build-module/components/url-input/index.mjs +2 -0
  94. package/build-module/components/url-input/index.mjs.map +2 -2
  95. package/build-module/components/use-block-commands/index.mjs +1 -1
  96. package/build-module/components/use-block-commands/index.mjs.map +2 -2
  97. package/build-module/hooks/block-fields/index.mjs +75 -166
  98. package/build-module/hooks/block-fields/index.mjs.map +2 -2
  99. package/build-module/hooks/block-fields/link/index.mjs +13 -23
  100. package/build-module/hooks/block-fields/link/index.mjs.map +2 -2
  101. package/build-module/hooks/block-fields/media/index.mjs +32 -58
  102. package/build-module/hooks/block-fields/media/index.mjs.map +2 -2
  103. package/build-module/hooks/block-fields/rich-text/index.mjs +1 -5
  104. package/build-module/hooks/block-fields/rich-text/index.mjs.map +2 -2
  105. package/build-module/hooks/cross-origin-isolation.mjs +100 -0
  106. package/build-module/hooks/cross-origin-isolation.mjs.map +7 -0
  107. package/build-module/hooks/index.mjs +1 -0
  108. package/build-module/hooks/index.mjs.map +2 -2
  109. package/build-module/layouts/flex.mjs +6 -2
  110. package/build-module/layouts/flex.mjs.map +2 -2
  111. package/build-module/store/private-selectors.mjs +34 -1
  112. package/build-module/store/private-selectors.mjs.map +2 -2
  113. package/build-module/store/reducer.mjs +1 -1
  114. package/build-module/store/reducer.mjs.map +1 -1
  115. package/build-module/store/selectors.mjs +7 -8
  116. package/build-module/store/selectors.mjs.map +2 -2
  117. package/build-style/content-rtl.css +4 -1
  118. package/build-style/content.css +4 -1
  119. package/build-style/style-rtl.css +48 -0
  120. package/build-style/style.css +48 -0
  121. package/package.json +39 -39
  122. package/src/components/block-bindings/attribute-control.js +1 -1
  123. package/src/components/block-bindings/source-fields-list.js +1 -1
  124. package/src/components/block-list/content.scss +4 -1
  125. package/src/components/block-tools/index.js +45 -33
  126. package/src/components/block-visibility/block-visibility-info.js +0 -1
  127. package/src/components/block-visibility/constants.js +7 -3
  128. package/src/components/block-visibility/index.js +21 -3
  129. package/src/components/block-visibility/modal.js +358 -0
  130. package/src/components/block-visibility/style.scss +58 -0
  131. package/src/components/block-visibility/test/use-block-visibility.js +12 -56
  132. package/src/components/block-visibility/test/utils.js +266 -0
  133. package/src/components/block-visibility/toolbar.js +1 -1
  134. package/src/components/block-visibility/use-block-visibility.js +18 -21
  135. package/src/components/block-visibility/utils.js +95 -0
  136. package/src/components/block-visibility/viewport-menu-item.js +42 -0
  137. package/src/components/block-visibility/viewport-toolbar.js +88 -0
  138. package/src/components/inner-blocks/use-inner-block-template-sync.js +1 -1
  139. package/src/components/inserter/menu.js +6 -2
  140. package/src/components/list-view/block-select-button.js +2 -2
  141. package/src/components/list-view/block.js +47 -25
  142. package/src/components/rich-text/index.js +1 -1
  143. package/src/components/url-input/index.js +2 -0
  144. package/src/components/use-block-commands/index.js +4 -3
  145. package/src/hooks/block-fields/index.js +104 -224
  146. package/src/hooks/block-fields/link/index.js +13 -39
  147. package/src/hooks/block-fields/media/index.js +31 -90
  148. package/src/hooks/block-fields/rich-text/index.js +1 -5
  149. package/src/hooks/block-fields/styles.scss +2 -0
  150. package/src/hooks/cross-origin-isolation.js +143 -0
  151. package/src/hooks/index.js +1 -0
  152. package/src/layouts/flex.js +8 -3
  153. package/src/layouts/test/flex.js +53 -0
  154. package/src/store/private-selectors.js +64 -1
  155. package/src/store/reducer.js +1 -1
  156. package/src/store/selectors.js +7 -9
  157. package/src/store/test/private-selectors.js +80 -0
  158. package/src/style.scss +1 -0
  159. package/src/components/block-visibility/styles.scss +0 -10
@@ -24,9 +24,9 @@ import { useInspectorPopoverPlacement } from '../use-inspector-popover-placement
24
24
  import { getMediaSelectKey } from '../../../store/private-keys';
25
25
  import { store as blockEditorStore } from '../../../store';
26
26
 
27
- function MediaThumbnail( { data, field, attachment } ) {
28
- const config = field.config || {};
29
- const { allowedTypes = [], multiple = false } = config;
27
+ function MediaThumbnail( { data, field, attachment, config } ) {
28
+ const { fieldDef } = config;
29
+ const { allowedTypes = [], multiple = false } = fieldDef.args || {};
30
30
 
31
31
  if ( multiple ) {
32
32
  return 'todo multiple';
@@ -53,7 +53,7 @@ function MediaThumbnail( { data, field, attachment } ) {
53
53
  const value = field.getValue( { item: data } );
54
54
  const url = value?.url;
55
55
 
56
- if ( url ) {
56
+ if ( allowedTypes[ 0 ] === 'image' && url ) {
57
57
  return (
58
58
  <div className="block-editor-content-only-controls__media-thumbnail">
59
59
  <img alt="" width={ 24 } height={ 24 } src={ url } />
@@ -85,15 +85,8 @@ export default function Media( { data, field, onChange, config = {} } ) {
85
85
  isControl: true,
86
86
  } );
87
87
  const value = field.getValue( { item: data } );
88
- const { allowedTypes = [], multiple = false } = field.config || {};
89
88
  const { fieldDef } = config;
90
- const updateAttributes = ( newFieldValue ) => {
91
- const mappedChanges = field.setValue( {
92
- item: data,
93
- value: newFieldValue,
94
- } );
95
- onChange( mappedChanges );
96
- };
89
+ const { allowedTypes = [], multiple = false } = fieldDef.args || {};
97
90
 
98
91
  // Check if featured image is supported by checking if it's in the mapping
99
92
  const hasFeaturedImageSupport =
@@ -152,102 +145,49 @@ export default function Media( { data, field, onChange, config = {} } ) {
152
145
 
153
146
  if ( fieldDef?.mapping ) {
154
147
  Object.keys( fieldDef.mapping ).forEach( ( key ) => {
155
- if (
156
- key === 'id' ||
157
- key === 'src' ||
158
- key === 'url'
159
- ) {
160
- resetValue[ key ] = undefined;
161
- } else if ( key === 'caption' || key === 'alt' ) {
162
- resetValue[ key ] = '';
163
- }
148
+ resetValue[ key ] = undefined;
164
149
  } );
165
150
  }
166
151
 
167
- // Turn off featured image when resetting (only if it's in the mapping)
168
- if ( hasFeaturedImageSupport ) {
169
- resetValue.featuredImage = false;
170
- }
171
-
172
- // Merge with existing value to preserve other field properties
173
- updateAttributes( { ...value, ...resetValue } );
152
+ onChange(
153
+ field.setValue( {
154
+ item: data,
155
+ value: resetValue,
156
+ } )
157
+ );
174
158
  } }
175
159
  { ...( hasFeaturedImageSupport && {
176
160
  useFeaturedImage: !! value?.featuredImage,
177
161
  onToggleFeaturedImage: () => {
178
- updateAttributes( {
179
- ...value,
180
- featuredImage: ! value?.featuredImage,
181
- } );
162
+ onChange(
163
+ field.setValue( {
164
+ item: data,
165
+ value: {
166
+ featuredImage: ! value?.featuredImage,
167
+ },
168
+ } )
169
+ );
182
170
  },
183
171
  } ) }
184
172
  onSelect={ ( selectedMedia ) => {
185
173
  if ( selectedMedia.id && selectedMedia.url ) {
186
- // Determine mediaType from MIME type, not from object type
187
- let mediaType = 'image'; // default
188
- if ( selectedMedia.mime_type ) {
189
- if (
190
- selectedMedia.mime_type.startsWith( 'video/' )
191
- ) {
192
- mediaType = 'video';
193
- } else if (
194
- selectedMedia.mime_type.startsWith( 'audio/' )
195
- ) {
196
- mediaType = 'audio';
197
- }
198
- }
199
-
200
174
  // Build new value dynamically based on what's in the mapping
201
- const newValue = {};
202
-
203
- // Iterate over mapping keys and set values for supported properties
204
- if ( fieldDef?.mapping ) {
205
- Object.keys( fieldDef.mapping ).forEach(
206
- ( key ) => {
207
- if ( key === 'id' ) {
208
- newValue[ key ] = selectedMedia.id;
209
- } else if (
210
- key === 'src' ||
211
- key === 'url'
212
- ) {
213
- newValue[ key ] = selectedMedia.url;
214
- } else if ( key === 'type' ) {
215
- newValue[ key ] = mediaType;
216
- } else if (
217
- key === 'link' &&
218
- selectedMedia.link
219
- ) {
220
- newValue[ key ] = selectedMedia.link;
221
- } else if (
222
- key === 'caption' &&
223
- ! value?.caption &&
224
- selectedMedia.caption
225
- ) {
226
- newValue[ key ] = selectedMedia.caption;
227
- } else if (
228
- key === 'alt' &&
229
- ! value?.alt &&
230
- selectedMedia.alt
231
- ) {
232
- newValue[ key ] = selectedMedia.alt;
233
- } else if (
234
- key === 'poster' &&
235
- selectedMedia.poster
236
- ) {
237
- newValue[ key ] = selectedMedia.poster;
238
- }
239
- }
240
- );
241
- }
175
+ const newValue = {
176
+ ...selectedMedia,
177
+ mediaType: selectedMedia.media_type,
178
+ };
242
179
 
243
180
  // Turn off featured image when manually selecting media
244
181
  if ( hasFeaturedImageSupport ) {
245
182
  newValue.featuredImage = false;
246
183
  }
247
184
 
248
- // Merge with existing value to preserve other field properties
249
- const finalValue = { ...value, ...newValue };
250
- updateAttributes( finalValue );
185
+ onChange(
186
+ field.setValue( {
187
+ item: data,
188
+ value: newValue,
189
+ } )
190
+ );
251
191
  }
252
192
  } }
253
193
  renderToggle={ ( buttonProps ) => (
@@ -268,6 +208,7 @@ export default function Media( { data, field, onChange, config = {} } ) {
268
208
  attachment={ attachment }
269
209
  field={ field }
270
210
  data={ data }
211
+ config={ config }
271
212
  />
272
213
  <span className="block-editor-content-only-controls__media-title">
273
214
  {
@@ -33,10 +33,6 @@ export default function RichTextControl( {
33
33
  const attrValue = field.getValue( { item: data } );
34
34
  const fieldConfig = field.config || {};
35
35
  const { clientId } = config;
36
- const updateAttributes = ( html ) => {
37
- const mappedChanges = field.setValue( { item: data, value: html } );
38
- onChange( mappedChanges );
39
- };
40
36
  const [ selection, setSelection ] = useState( {
41
37
  start: undefined,
42
38
  end: undefined,
@@ -107,7 +103,7 @@ export default function RichTextControl( {
107
103
  } = useRichText( {
108
104
  value: attrValue,
109
105
  onChange( html, { __unstableFormats, __unstableText } ) {
110
- updateAttributes( html );
106
+ onChange( field.setValue( { item: data, value: html } ) );
111
107
  Object.values( changeHandlers ).forEach( ( changeHandler ) => {
112
108
  changeHandler( __unstableFormats, __unstableText );
113
109
  } );
@@ -30,4 +30,6 @@
30
30
 
31
31
  .block-editor-block-fields__header-title {
32
32
  flex: 1;
33
+ // Override the default margin on a h2 element.
34
+ margin: 0 !important;
33
35
  }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { addFilter } from '@wordpress/hooks';
5
+ import { createHigherOrderComponent } from '@wordpress/compose';
6
+
7
+ /**
8
+ * Adds crossorigin and credentialless attributes to elements as needed.
9
+ *
10
+ * @param {Element} el The element to modify.
11
+ */
12
+ function addCrossOriginAttributes( el ) {
13
+ // Add the crossorigin attribute if missing.
14
+ if ( ! el.hasAttribute( 'crossorigin' ) ) {
15
+ el.setAttribute( 'crossorigin', 'anonymous' );
16
+ }
17
+
18
+ // For iframes, add the credentialless attribute.
19
+ if ( el.nodeName === 'IFRAME' && ! el.hasAttribute( 'credentialless' ) ) {
20
+ // Do not modify the iframed editor canvas.
21
+ if ( el.getAttribute( 'src' )?.startsWith( 'blob:' ) ) {
22
+ return;
23
+ }
24
+
25
+ el.setAttribute( 'credentialless', '' );
26
+
27
+ // Reload the iframe to ensure the new attribute is taken into account.
28
+ const origSrc = el.getAttribute( 'src' ) || '';
29
+ el.setAttribute( 'src', '' );
30
+ el.setAttribute( 'src', origSrc );
31
+ }
32
+ }
33
+
34
+ // Only add the mutation observer if the site is cross-origin isolated.
35
+ if ( window.crossOriginIsolated ) {
36
+ /*
37
+ * Detects dynamically added DOM nodes that are missing the `crossorigin` attribute.
38
+ */
39
+ const observer = new window.MutationObserver( ( mutations ) => {
40
+ mutations.forEach( ( mutation ) => {
41
+ [ mutation.addedNodes, mutation.target ].forEach( ( value ) => {
42
+ const nodes =
43
+ value instanceof window.NodeList ? value : [ value ];
44
+ nodes.forEach( ( node ) => {
45
+ const el = node;
46
+
47
+ if ( ! el.querySelectorAll ) {
48
+ // Most likely a text node.
49
+ return;
50
+ }
51
+
52
+ el.querySelectorAll(
53
+ 'img,source,script,video,link,iframe'
54
+ ).forEach( ( v ) => {
55
+ addCrossOriginAttributes( v );
56
+ } );
57
+
58
+ if ( el.nodeName === 'IFRAME' ) {
59
+ const iframeNode = el;
60
+
61
+ /*
62
+ * Sandboxed iframes should not get modified. For example embedding a tweet served in a sandboxed
63
+ * iframe, the tweet itself would not be modified.
64
+ */
65
+ const isEmbedSandboxIframe =
66
+ iframeNode.classList.contains(
67
+ 'components-sandbox'
68
+ );
69
+
70
+ if ( ! isEmbedSandboxIframe ) {
71
+ iframeNode.addEventListener( 'load', () => {
72
+ if ( iframeNode.contentDocument ) {
73
+ observer.observe(
74
+ iframeNode.contentDocument,
75
+ {
76
+ childList: true,
77
+ attributes: true,
78
+ subtree: true,
79
+ }
80
+ );
81
+ }
82
+ } );
83
+ }
84
+ }
85
+
86
+ if (
87
+ [
88
+ 'IMG',
89
+ 'SOURCE',
90
+ 'SCRIPT',
91
+ 'VIDEO',
92
+ 'LINK',
93
+ 'IFRAME',
94
+ ].includes( el.nodeName )
95
+ ) {
96
+ addCrossOriginAttributes( el );
97
+ }
98
+ } );
99
+ } );
100
+ } );
101
+ } );
102
+
103
+ observer.observe( document.body, {
104
+ childList: true,
105
+ attributes: true,
106
+ subtree: true,
107
+ } );
108
+ }
109
+
110
+ // Only apply the embed preview filter when cross-origin isolated.
111
+ if ( window.crossOriginIsolated ) {
112
+ const supportsCredentialless =
113
+ 'credentialless' in window.HTMLIFrameElement.prototype;
114
+
115
+ const disableEmbedPreviews = createHigherOrderComponent(
116
+ ( BlockEdit ) => ( props ) => {
117
+ if ( 'core/embed' !== props.name ) {
118
+ return <BlockEdit { ...props } />;
119
+ }
120
+
121
+ // List of embeds that do not support a preview is from packages/block-library/src/embed/variations.js.
122
+ const previewable =
123
+ supportsCredentialless &&
124
+ ! [ 'facebook', 'smugmug' ].includes(
125
+ props.attributes.providerNameSlug
126
+ );
127
+
128
+ return (
129
+ <BlockEdit
130
+ { ...props }
131
+ attributes={ { ...props.attributes, previewable } }
132
+ />
133
+ );
134
+ },
135
+ 'withDisabledEmbedPreview'
136
+ );
137
+
138
+ addFilter(
139
+ 'editor.BlockEdit',
140
+ 'media-experiments/disable-embed-previews',
141
+ disableEmbedPreviews
142
+ );
143
+ }
@@ -7,6 +7,7 @@ import {
7
7
  createBlockSaveFilter,
8
8
  } from './utils';
9
9
  import './compat';
10
+ import './cross-origin-isolation';
10
11
  import align from './align';
11
12
  import background from './background';
12
13
  import './lock';
@@ -71,8 +71,11 @@ export default {
71
71
  onChange,
72
72
  layoutBlockSupport = {},
73
73
  } ) {
74
- const { allowOrientation = true, allowJustification = true } =
75
- layoutBlockSupport;
74
+ const {
75
+ allowOrientation = true,
76
+ allowJustification = true,
77
+ allowWrap = true,
78
+ } = layoutBlockSupport;
76
79
  return (
77
80
  <>
78
81
  <Flex>
@@ -93,7 +96,9 @@ export default {
93
96
  </FlexItem>
94
97
  ) }
95
98
  </Flex>
96
- <FlexWrapControl layout={ layout } onChange={ onChange } />
99
+ { allowWrap && (
100
+ <FlexWrapControl layout={ layout } onChange={ onChange } />
101
+ ) }
97
102
  </>
98
103
  );
99
104
  },
@@ -1,8 +1,15 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { render, screen } from '@testing-library/react';
5
+
1
6
  /**
2
7
  * Internal dependencies
3
8
  */
4
9
  import flex from '../flex';
5
10
 
11
+ const FlexLayoutInspectorControls = flex.inspectorControls;
12
+
6
13
  describe( 'getLayoutStyle', () => {
7
14
  it( 'should return an empty string if no non-default params are provided', () => {
8
15
  const expected = '';
@@ -19,3 +26,49 @@ describe( 'getLayoutStyle', () => {
19
26
  expect( result ).toBe( expected );
20
27
  } );
21
28
  } );
29
+
30
+ describe( 'FlexLayoutInspectorControls', () => {
31
+ it( 'should render the wrap toggle by default', () => {
32
+ render(
33
+ <FlexLayoutInspectorControls layout={ {} } onChange={ jest.fn() } />
34
+ );
35
+
36
+ expect(
37
+ screen.getByRole( 'checkbox', {
38
+ name: 'Allow to wrap to multiple lines',
39
+ } )
40
+ ).toBeInTheDocument();
41
+ } );
42
+
43
+ it( 'should render the wrap toggle when allowWrap is true', () => {
44
+ render(
45
+ <FlexLayoutInspectorControls
46
+ layout={ {} }
47
+ onChange={ jest.fn() }
48
+ layoutBlockSupport={ { allowWrap: true } }
49
+ />
50
+ );
51
+
52
+ expect(
53
+ screen.getByRole( 'checkbox', {
54
+ name: 'Allow to wrap to multiple lines',
55
+ } )
56
+ ).toBeInTheDocument();
57
+ } );
58
+
59
+ it( 'should not render the wrap toggle when allowWrap is false', () => {
60
+ render(
61
+ <FlexLayoutInspectorControls
62
+ layout={ {} }
63
+ onChange={ jest.fn() }
64
+ layoutBlockSupport={ { allowWrap: false } }
65
+ />
66
+ );
67
+
68
+ expect(
69
+ screen.queryByRole( 'checkbox', {
70
+ name: 'Allow to wrap to multiple lines',
71
+ } )
72
+ ).not.toBeInTheDocument();
73
+ } );
74
+ } );
@@ -38,6 +38,10 @@ import {
38
38
  isIsolatedEditorKey,
39
39
  deviceTypeKey,
40
40
  } from './private-keys';
41
+ import {
42
+ BLOCK_VISIBILITY_VIEWPORT_ENTRIES,
43
+ BLOCK_VISIBILITY_VIEWPORTS,
44
+ } from '../components/block-visibility/constants';
41
45
 
42
46
  const { isContentBlock } = unlock( blocksPrivateApis );
43
47
 
@@ -730,7 +734,8 @@ export const isBlockHidden = ( state, clientId ) => {
730
734
  // Only apply when a device is explicitly selected.
731
735
  if ( typeof blockVisibility === 'object' && blockVisibility !== null ) {
732
736
  const settings = getSettings( state );
733
- const viewportType = settings[ deviceTypeKey ] ?? 'Desktop';
737
+ const viewportType =
738
+ settings[ deviceTypeKey ] ?? BLOCK_VISIBILITY_VIEWPORTS.desktop.key;
734
739
  const viewportKey = viewportType.toLowerCase();
735
740
  return blockVisibility?.[ viewportKey ] === false;
736
741
  }
@@ -738,6 +743,64 @@ export const isBlockHidden = ( state, clientId ) => {
738
743
  return false;
739
744
  };
740
745
 
746
+ /**
747
+ * Returns true if any of the provided blocks are hidden.
748
+ *
749
+ * @param {Object} state Global application state.
750
+ * @param {Array} clientIds Array of block client IDs to check.
751
+ * @return {boolean} Whether any block is hidden.
752
+ */
753
+ export const areBlocksHidden = ( state, clientIds ) => {
754
+ if ( ! clientIds || clientIds.length === 0 ) {
755
+ return false;
756
+ }
757
+ return clientIds.some( ( clientId ) => isBlockHidden( state, clientId ) );
758
+ };
759
+
760
+ /**
761
+ * Checks if at least one block in an array is hidden according to viewport visibility metadata.
762
+ *
763
+ * This is typically used to determine if the block visibility button should be shown in the toolbar.
764
+ * TODO: This is temporary for now. Later the UI will
765
+ * want to know where exactly the block is hidden, e.g., to display icons or other things.
766
+ *
767
+ * A block is considered hidden if:
768
+ * - Its `blockVisibility` metadata is `false` (hidden everywhere), or
769
+ * - Any viewport is set to `false`
770
+ *
771
+ * @param {Object} state Global application state.
772
+ * @param {Array} clientIds Array of block client IDs to check.
773
+ * @return {boolean} `true` if at least one block meets the visibility criteria, `false` otherwise.
774
+ */
775
+ export const areBlocksHiddenAnywhere = ( state, clientIds ) => {
776
+ if ( ! clientIds?.length ) {
777
+ return false;
778
+ }
779
+ return clientIds.some( ( clientId ) => {
780
+ if ( ! clientId ) {
781
+ return false;
782
+ }
783
+
784
+ const attributes = state.blocks.attributes.get( clientId );
785
+ const blockVisibility = attributes?.metadata?.blockVisibility;
786
+
787
+ // If explicitly hidden everywhere (false), return true.
788
+ if ( typeof blockVisibility === 'boolean' ) {
789
+ return blockVisibility === false;
790
+ }
791
+
792
+ // If not an object, block is not hidden in any viewport.
793
+ if ( 'object' !== typeof blockVisibility ) {
794
+ return false;
795
+ }
796
+
797
+ // Check viewport-specific visibility.
798
+ return BLOCK_VISIBILITY_VIEWPORT_ENTRIES.some(
799
+ ( [ , { key } ] ) => blockVisibility?.[ key ] === false
800
+ );
801
+ } );
802
+ };
803
+
741
804
  /**
742
805
  * Returns true if there is a spotlighted block.
743
806
  *
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import fastDeepEqual from 'fast-deep-equal/es6';
4
+ import fastDeepEqual from 'fast-deep-equal/es6/index.js';
5
5
 
6
6
  /**
7
7
  * WordPress dependencies
@@ -2154,24 +2154,22 @@ const buildBlockTypeItem =
2154
2154
  'inserter'
2155
2155
  );
2156
2156
  const blockVariations = getBlockVariations( blockType.name, 'block' );
2157
- // Combine inserter and block variations. Block-scope variations without
2158
- // inserter scope are searchable via slash commands but hidden from browse.
2159
- const inserterVariationNames = new Set(
2160
- inserterVariations.map( ( variation ) => variation.name )
2161
- );
2162
2157
  const allVariations = [
2163
2158
  ...inserterVariations,
2159
+ // Built-in heading level variations have block scope but allow
2160
+ // insertion via slash inserter.
2161
+ // See https://github.com/WordPress/gutenberg/issues/74233.
2164
2162
  ...blockVariations
2165
2163
  .filter(
2166
2164
  ( variation ) =>
2167
- ! inserterVariationNames.has( variation.name )
2165
+ blockType.name === 'core/heading' &&
2166
+ [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ].includes(
2167
+ variation.name
2168
+ )
2168
2169
  )
2169
2170
  .map( ( variation ) => ( {
2170
2171
  ...variation,
2171
2172
  isSearchOnly: true,
2172
- // Block-scope `isDefault` is for the placeholder picker,
2173
- // not for the inserter, so don't carry it over.
2174
- isDefault: false,
2175
2173
  } ) ),
2176
2174
  ];
2177
2175
  return {
@@ -20,6 +20,7 @@ import {
20
20
  isRemoveLockedBlock,
21
21
  isLockedBlock,
22
22
  isBlockHidden,
23
+ areBlocksHiddenAnywhere,
23
24
  } from '../private-selectors';
24
25
  import { getBlockEditingMode } from '../selectors';
25
26
  import { deviceTypeKey } from '../private-keys';
@@ -1244,4 +1245,83 @@ describe( 'private selectors', () => {
1244
1245
  expect( result ).toBe( false );
1245
1246
  } );
1246
1247
  } );
1248
+
1249
+ describe( 'areBlocksHiddenAnywhere', () => {
1250
+ it( 'should return false when clientIds array is empty', () => {
1251
+ const state = {
1252
+ blocks: {
1253
+ attributes: new Map(),
1254
+ },
1255
+ };
1256
+ expect( areBlocksHiddenAnywhere( state, [] ) ).toBe( false );
1257
+ expect( areBlocksHiddenAnywhere( state, null ) ).toBe( false );
1258
+ expect( areBlocksHiddenAnywhere( state, undefined ) ).toBe( false );
1259
+ } );
1260
+
1261
+ it( 'should return false when no blocks are hidden', () => {
1262
+ const state = {
1263
+ blocks: {
1264
+ attributes: new Map( [
1265
+ [ 'block-1', { metadata: { blockVisibility: true } } ],
1266
+ [ 'block-2', { metadata: {} } ],
1267
+ ] ),
1268
+ },
1269
+ };
1270
+ expect(
1271
+ areBlocksHiddenAnywhere( state, [ 'block-1', 'block-2' ] )
1272
+ ).toBe( false );
1273
+ } );
1274
+
1275
+ it( 'should return true when a block is hidden everywhere', () => {
1276
+ const state = {
1277
+ blocks: {
1278
+ attributes: new Map( [
1279
+ [ 'block-1', { metadata: { blockVisibility: false } } ],
1280
+ [ 'block-2', { metadata: { blockVisibility: true } } ],
1281
+ ] ),
1282
+ },
1283
+ };
1284
+ expect(
1285
+ areBlocksHiddenAnywhere( state, [ 'block-1', 'block-2' ] )
1286
+ ).toBe( true );
1287
+ } );
1288
+
1289
+ it( 'should return true when a block is hidden in any viewport', () => {
1290
+ const state = {
1291
+ blocks: {
1292
+ attributes: new Map( [
1293
+ [
1294
+ 'block-1',
1295
+ {
1296
+ metadata: {
1297
+ blockVisibility: {
1298
+ mobile: false,
1299
+ tablet: true,
1300
+ },
1301
+ },
1302
+ },
1303
+ ],
1304
+ [ 'block-2', { metadata: { blockVisibility: true } } ],
1305
+ ] ),
1306
+ },
1307
+ };
1308
+ expect(
1309
+ areBlocksHiddenAnywhere( state, [ 'block-1', 'block-2' ] )
1310
+ ).toBe( true );
1311
+ } );
1312
+
1313
+ it( 'should return false when clientId is null or undefined', () => {
1314
+ const state = {
1315
+ blocks: {
1316
+ attributes: new Map( [
1317
+ [ 'block-1', { metadata: { blockVisibility: false } } ],
1318
+ ] ),
1319
+ },
1320
+ };
1321
+ expect( areBlocksHiddenAnywhere( state, [ null ] ) ).toBe( false );
1322
+ expect( areBlocksHiddenAnywhere( state, [ undefined ] ) ).toBe(
1323
+ false
1324
+ );
1325
+ } );
1326
+ } );
1247
1327
  } );
package/src/style.scss CHANGED
@@ -67,3 +67,4 @@
67
67
  @use "./components/block-toolbar/style.scss" as *;
68
68
  @use "./components/inserter/style.scss" as *;
69
69
  @use "./components/spacing-sizes-control/style.scss" as *;
70
+ @use "./components/block-visibility/style.scss" as *;
@@ -1,10 +0,0 @@
1
- @use "@wordpress/base-styles/variables" as *;
2
-
3
- .block-editor-block-visibility-info {
4
- padding-top: $grid-unit-05;
5
- padding-bottom: $grid-unit-05;
6
- margin: 0 $grid-unit-20 $grid-unit-20;
7
- display: flex;
8
- align-items: center;
9
- justify-content: start;
10
- }