@wordpress/block-editor 15.12.1-next.v.0 → 15.12.2-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.
- package/CHANGELOG.md +6 -0
- package/build/components/block-allowed-blocks/modal.cjs +1 -1
- package/build/components/block-allowed-blocks/modal.cjs.map +2 -2
- package/build/components/block-removal-warning-modal/index.cjs +30 -5
- package/build/components/block-removal-warning-modal/index.cjs.map +3 -3
- package/build/components/block-visibility/use-block-visibility.cjs +14 -29
- package/build/components/block-visibility/use-block-visibility.cjs.map +2 -2
- package/build/components/global-styles/hooks.cjs +7 -0
- package/build/components/global-styles/hooks.cjs.map +2 -2
- package/build/components/global-styles/typography-panel.cjs +71 -3
- package/build/components/global-styles/typography-panel.cjs.map +3 -3
- package/build/components/grid/grid-visualizer.cjs +49 -13
- package/build/components/grid/grid-visualizer.cjs.map +2 -2
- package/build/components/iframe/index.cjs +3 -1
- package/build/components/iframe/index.cjs.map +2 -2
- package/build/components/iframe/use-scale-canvas.cjs +1 -0
- package/build/components/iframe/use-scale-canvas.cjs.map +2 -2
- package/build/components/link-control/index.cjs +73 -2
- package/build/components/link-control/index.cjs.map +3 -3
- package/build/components/link-control/is-url-like.cjs +15 -3
- package/build/components/link-control/is-url-like.cjs.map +2 -2
- package/build/components/link-control/search-input.cjs +4 -1
- package/build/components/link-control/search-input.cjs.map +2 -2
- package/build/components/link-control/use-search-handler.cjs +1 -1
- package/build/components/link-control/use-search-handler.cjs.map +2 -2
- package/build/components/provider/use-block-sync.cjs +60 -8
- package/build/components/provider/use-block-sync.cjs.map +2 -2
- package/build/components/text-indent-control/index.cjs +121 -0
- package/build/components/text-indent-control/index.cjs.map +7 -0
- package/build/components/url-input/index.cjs +22 -2
- package/build/components/url-input/index.cjs.map +3 -3
- package/build/components/url-popover/image-url-input-ui.cjs +1 -1
- package/build/components/url-popover/image-url-input-ui.cjs.map +2 -2
- package/build/components/writing-flow/use-arrow-nav.cjs +0 -3
- package/build/components/writing-flow/use-arrow-nav.cjs.map +2 -2
- package/build/hooks/aria-label.cjs +2 -1
- package/build/hooks/aria-label.cjs.map +2 -2
- package/build/hooks/grid-visualizer.cjs +20 -4
- package/build/hooks/grid-visualizer.cjs.map +2 -2
- package/build/hooks/layout-child.cjs +8 -3
- package/build/hooks/layout-child.cjs.map +2 -2
- package/build/hooks/typography.cjs +2 -0
- package/build/hooks/typography.cjs.map +2 -2
- package/build/hooks/utils.cjs +4 -0
- package/build/hooks/utils.cjs.map +2 -2
- package/build/store/actions.cjs +2 -2
- package/build/store/actions.cjs.map +2 -2
- package/build-module/components/block-allowed-blocks/modal.mjs +2 -2
- package/build-module/components/block-allowed-blocks/modal.mjs.map +2 -2
- package/build-module/components/block-removal-warning-modal/index.mjs +34 -7
- package/build-module/components/block-removal-warning-modal/index.mjs.map +2 -2
- package/build-module/components/block-visibility/use-block-visibility.mjs +14 -29
- package/build-module/components/block-visibility/use-block-visibility.mjs.map +2 -2
- package/build-module/components/global-styles/hooks.mjs +7 -0
- package/build-module/components/global-styles/hooks.mjs.map +2 -2
- package/build-module/components/global-styles/typography-panel.mjs +73 -4
- package/build-module/components/global-styles/typography-panel.mjs.map +2 -2
- package/build-module/components/grid/grid-visualizer.mjs +50 -14
- package/build-module/components/grid/grid-visualizer.mjs.map +2 -2
- package/build-module/components/iframe/index.mjs +9 -2
- package/build-module/components/iframe/index.mjs.map +2 -2
- package/build-module/components/iframe/use-scale-canvas.mjs +1 -0
- package/build-module/components/iframe/use-scale-canvas.mjs.map +2 -2
- package/build-module/components/link-control/index.mjs +74 -3
- package/build-module/components/link-control/index.mjs.map +2 -2
- package/build-module/components/link-control/is-url-like.mjs +10 -3
- package/build-module/components/link-control/is-url-like.mjs.map +2 -2
- package/build-module/components/link-control/search-input.mjs +4 -1
- package/build-module/components/link-control/search-input.mjs.map +2 -2
- package/build-module/components/link-control/use-search-handler.mjs +2 -2
- package/build-module/components/link-control/use-search-handler.mjs.map +2 -2
- package/build-module/components/provider/use-block-sync.mjs +60 -8
- package/build-module/components/provider/use-block-sync.mjs.map +2 -2
- package/build-module/components/text-indent-control/index.mjs +110 -0
- package/build-module/components/text-indent-control/index.mjs.map +7 -0
- package/build-module/components/url-input/index.mjs +24 -4
- package/build-module/components/url-input/index.mjs.map +2 -2
- package/build-module/components/url-popover/image-url-input-ui.mjs +2 -2
- package/build-module/components/url-popover/image-url-input-ui.mjs.map +2 -2
- package/build-module/components/writing-flow/use-arrow-nav.mjs +0 -3
- package/build-module/components/writing-flow/use-arrow-nav.mjs.map +2 -2
- package/build-module/hooks/aria-label.mjs +2 -1
- package/build-module/hooks/aria-label.mjs.map +2 -2
- package/build-module/hooks/grid-visualizer.mjs +20 -4
- package/build-module/hooks/grid-visualizer.mjs.map +2 -2
- package/build-module/hooks/layout-child.mjs +8 -3
- package/build-module/hooks/layout-child.mjs.map +2 -2
- package/build-module/hooks/typography.mjs +2 -0
- package/build-module/hooks/typography.mjs.map +2 -2
- package/build-module/hooks/utils.mjs +4 -0
- package/build-module/hooks/utils.mjs.map +2 -2
- package/build-module/store/actions.mjs +2 -2
- package/build-module/store/actions.mjs.map +2 -2
- package/package.json +39 -39
- package/src/components/block-allowed-blocks/modal.js +2 -2
- package/src/components/block-removal-warning-modal/index.js +55 -19
- package/src/components/block-switcher/block-transformations-menu.native.js +1 -0
- package/src/components/block-toolbar/test/__snapshots__/block-toolbar-menu.native.js.snap +4 -6
- package/src/components/block-toolbar/test/block-toolbar-menu.native.js +2 -2
- package/src/components/block-visibility/use-block-visibility.js +17 -32
- package/src/components/global-styles/hooks.js +10 -0
- package/src/components/global-styles/typography-panel.js +78 -1
- package/src/components/grid/grid-visualizer.js +58 -12
- package/src/components/iframe/index.js +12 -2
- package/src/components/iframe/use-scale-canvas.js +1 -0
- package/src/components/inserter/menu.native.js +1 -0
- package/src/components/link-control/index.js +160 -3
- package/src/components/link-control/is-url-like.js +43 -8
- package/src/components/link-control/search-input.js +7 -0
- package/src/components/link-control/test/index.js +260 -0
- package/src/components/link-control/test/is-url-like.js +49 -1
- package/src/components/link-control/use-search-handler.js +2 -2
- package/src/components/provider/test/use-block-sync.js +105 -0
- package/src/components/provider/use-block-sync.js +118 -9
- package/src/components/text-indent-control/index.js +138 -0
- package/src/components/url-input/index.js +21 -2
- package/src/components/url-popover/image-url-input-ui.js +2 -2
- package/src/components/writing-flow/use-arrow-nav.js +0 -4
- package/src/hooks/aria-label.js +9 -1
- package/src/hooks/grid-visualizer.js +53 -33
- package/src/hooks/layout-child.js +6 -0
- package/src/hooks/typography.js +2 -0
- package/src/hooks/utils.js +4 -0
- package/src/store/actions.js +8 -6
|
@@ -566,4 +566,109 @@ describe( 'useBlockSync hook', () => {
|
|
|
566
566
|
} )
|
|
567
567
|
);
|
|
568
568
|
} );
|
|
569
|
+
|
|
570
|
+
it( 'preserves external client IDs in onChange callback for inner block controllers', async () => {
|
|
571
|
+
const originalClientId = 'original-external-id';
|
|
572
|
+
const innerBlockClientId = 'inner-external-id';
|
|
573
|
+
const onChange = jest.fn();
|
|
574
|
+
const onInput = jest.fn();
|
|
575
|
+
const replaceInnerBlocks = jest.spyOn(
|
|
576
|
+
blockEditorActions,
|
|
577
|
+
'replaceInnerBlocks'
|
|
578
|
+
);
|
|
579
|
+
|
|
580
|
+
// Blocks with specific external client IDs
|
|
581
|
+
const controlledBlocks = [
|
|
582
|
+
{
|
|
583
|
+
name: 'test/test-block',
|
|
584
|
+
clientId: originalClientId,
|
|
585
|
+
innerBlocks: [
|
|
586
|
+
{
|
|
587
|
+
name: 'test/test-block',
|
|
588
|
+
clientId: innerBlockClientId,
|
|
589
|
+
innerBlocks: [],
|
|
590
|
+
attributes: { foo: 10 },
|
|
591
|
+
},
|
|
592
|
+
],
|
|
593
|
+
attributes: { foo: 1 },
|
|
594
|
+
},
|
|
595
|
+
];
|
|
596
|
+
|
|
597
|
+
let registry;
|
|
598
|
+
const setRegistry = ( reg ) => {
|
|
599
|
+
registry = reg;
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
render(
|
|
603
|
+
<TestWrapper
|
|
604
|
+
setRegistry={ setRegistry }
|
|
605
|
+
value={ controlledBlocks }
|
|
606
|
+
onChange={ onChange }
|
|
607
|
+
onInput={ onInput }
|
|
608
|
+
/>
|
|
609
|
+
);
|
|
610
|
+
|
|
611
|
+
// For the root case (no clientId), blocks are not cloned
|
|
612
|
+
// So the external IDs should be preserved as-is
|
|
613
|
+
expect( replaceInnerBlocks ).not.toHaveBeenCalled();
|
|
614
|
+
|
|
615
|
+
onChange.mockClear();
|
|
616
|
+
onInput.mockClear();
|
|
617
|
+
|
|
618
|
+
registry
|
|
619
|
+
.dispatch( blockEditorStore )
|
|
620
|
+
.updateBlockAttributes( originalClientId, { foo: 2 } );
|
|
621
|
+
|
|
622
|
+
// The onChange callback should receive blocks with the same external IDs
|
|
623
|
+
expect( onChange ).toHaveBeenCalledWith(
|
|
624
|
+
expect.arrayContaining( [
|
|
625
|
+
expect.objectContaining( {
|
|
626
|
+
clientId: originalClientId,
|
|
627
|
+
attributes: { foo: 2 },
|
|
628
|
+
innerBlocks: expect.arrayContaining( [
|
|
629
|
+
expect.objectContaining( {
|
|
630
|
+
clientId: innerBlockClientId,
|
|
631
|
+
} ),
|
|
632
|
+
] ),
|
|
633
|
+
} ),
|
|
634
|
+
] ),
|
|
635
|
+
expect.objectContaining( {
|
|
636
|
+
selection: expect.any( Object ),
|
|
637
|
+
} )
|
|
638
|
+
);
|
|
639
|
+
} );
|
|
640
|
+
|
|
641
|
+
it( 'clones blocks with new internal IDs for inner block controllers', async () => {
|
|
642
|
+
const originalClientId = 'original-external-id';
|
|
643
|
+
const replaceInnerBlocks = jest.spyOn(
|
|
644
|
+
blockEditorActions,
|
|
645
|
+
'replaceInnerBlocks'
|
|
646
|
+
);
|
|
647
|
+
|
|
648
|
+
// Blocks with specific external client IDs
|
|
649
|
+
const controlledBlocks = [
|
|
650
|
+
{
|
|
651
|
+
name: 'test/test-block',
|
|
652
|
+
clientId: originalClientId,
|
|
653
|
+
innerBlocks: [],
|
|
654
|
+
attributes: { foo: 1 },
|
|
655
|
+
},
|
|
656
|
+
];
|
|
657
|
+
|
|
658
|
+
render(
|
|
659
|
+
<TestWrapper
|
|
660
|
+
clientId="test-controller"
|
|
661
|
+
value={ controlledBlocks }
|
|
662
|
+
onChange={ jest.fn() }
|
|
663
|
+
onInput={ jest.fn() }
|
|
664
|
+
/>
|
|
665
|
+
);
|
|
666
|
+
|
|
667
|
+
// replaceInnerBlocks should have been called with cloned blocks
|
|
668
|
+
expect( replaceInnerBlocks ).toHaveBeenCalled();
|
|
669
|
+
const replacedBlocks = replaceInnerBlocks.mock.calls[ 0 ][ 1 ];
|
|
670
|
+
|
|
671
|
+
// The internal IDs should be different from the external IDs (due to cloning)
|
|
672
|
+
expect( replacedBlocks[ 0 ].clientId ).not.toBe( originalClientId );
|
|
673
|
+
} );
|
|
569
674
|
} );
|
|
@@ -12,6 +12,87 @@ import { store as blockEditorStore } from '../../store';
|
|
|
12
12
|
|
|
13
13
|
const noop = () => {};
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Clones a block and its inner blocks, building a bidirectional mapping
|
|
17
|
+
* between external (original) and internal (cloned) client IDs.
|
|
18
|
+
*
|
|
19
|
+
* This allows the block editor to use unique internal IDs while preserving
|
|
20
|
+
* stable external IDs for features like real-time collaboration.
|
|
21
|
+
*
|
|
22
|
+
* @param {Object} block The block to clone.
|
|
23
|
+
* @param {Object} mapping The mapping object with externalToInternal and internalToExternal Maps.
|
|
24
|
+
* @return {Object} The cloned block with a new clientId.
|
|
25
|
+
*/
|
|
26
|
+
function cloneBlockWithMapping( block, mapping ) {
|
|
27
|
+
const clonedBlock = cloneBlock( block );
|
|
28
|
+
|
|
29
|
+
// Build bidirectional mapping
|
|
30
|
+
mapping.externalToInternal.set( block.clientId, clonedBlock.clientId );
|
|
31
|
+
mapping.internalToExternal.set( clonedBlock.clientId, block.clientId );
|
|
32
|
+
|
|
33
|
+
// Recursively map inner blocks
|
|
34
|
+
if ( block.innerBlocks?.length ) {
|
|
35
|
+
clonedBlock.innerBlocks = block.innerBlocks.map( ( innerBlock ) => {
|
|
36
|
+
const clonedInner = cloneBlockWithMapping( innerBlock, mapping );
|
|
37
|
+
// The clonedBlock already has cloned inner blocks from cloneBlock(),
|
|
38
|
+
// but we need to use our mapped versions to maintain the mapping.
|
|
39
|
+
return clonedInner;
|
|
40
|
+
} );
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return clonedBlock;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Restores external (original) client IDs on blocks before passing them
|
|
48
|
+
* to onChange/onInput callbacks.
|
|
49
|
+
*
|
|
50
|
+
* @param {Object[]} blocks The blocks with internal client IDs.
|
|
51
|
+
* @param {Object} mapping The mapping object with internalToExternal Map.
|
|
52
|
+
* @return {Object[]} Blocks with external client IDs restored.
|
|
53
|
+
*/
|
|
54
|
+
function restoreExternalIds( blocks, mapping ) {
|
|
55
|
+
return blocks.map( ( block ) => {
|
|
56
|
+
const externalId = mapping.internalToExternal.get( block.clientId );
|
|
57
|
+
return {
|
|
58
|
+
...block,
|
|
59
|
+
// Use external ID if available, otherwise keep internal ID (for new blocks)
|
|
60
|
+
clientId: externalId ?? block.clientId,
|
|
61
|
+
innerBlocks: restoreExternalIds( block.innerBlocks, mapping ),
|
|
62
|
+
};
|
|
63
|
+
} );
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Restores external client IDs in selection state.
|
|
68
|
+
*
|
|
69
|
+
* @param {Object} selection The selection state with internal client IDs.
|
|
70
|
+
* @param {Object} mapping The mapping object with internalToExternal Map.
|
|
71
|
+
* @return {Object} Selection state with external client IDs.
|
|
72
|
+
*/
|
|
73
|
+
function restoreSelectionIds( selection, mapping ) {
|
|
74
|
+
const { selectionStart, selectionEnd, initialPosition } = selection;
|
|
75
|
+
|
|
76
|
+
const restoreClientId = ( selectionState ) => {
|
|
77
|
+
if ( ! selectionState?.clientId ) {
|
|
78
|
+
return selectionState;
|
|
79
|
+
}
|
|
80
|
+
const externalId = mapping.internalToExternal.get(
|
|
81
|
+
selectionState.clientId
|
|
82
|
+
);
|
|
83
|
+
return {
|
|
84
|
+
...selectionState,
|
|
85
|
+
clientId: externalId ?? selectionState.clientId,
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
selectionStart: restoreClientId( selectionStart ),
|
|
91
|
+
selectionEnd: restoreClientId( selectionEnd ),
|
|
92
|
+
initialPosition,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
15
96
|
/**
|
|
16
97
|
* A function to call when the block value has been updated in the block-editor
|
|
17
98
|
* store.
|
|
@@ -94,6 +175,13 @@ export default function useBlockSync( {
|
|
|
94
175
|
const pendingChangesRef = useRef( { incoming: null, outgoing: [] } );
|
|
95
176
|
const subscribedRef = useRef( false );
|
|
96
177
|
|
|
178
|
+
// Mapping between external (original) and internal (cloned) client IDs.
|
|
179
|
+
// This allows stable external IDs while using unique internal IDs.
|
|
180
|
+
const idMappingRef = useRef( {
|
|
181
|
+
externalToInternal: new Map(),
|
|
182
|
+
internalToExternal: new Map(),
|
|
183
|
+
} );
|
|
184
|
+
|
|
97
185
|
const setControlledBlocks = () => {
|
|
98
186
|
if ( ! controlledBlocks ) {
|
|
99
187
|
return;
|
|
@@ -110,8 +198,14 @@ export default function useBlockSync( {
|
|
|
110
198
|
// before the actual blocks get set properly in state.
|
|
111
199
|
registry.batch( () => {
|
|
112
200
|
setHasControlledInnerBlocks( clientId, true );
|
|
201
|
+
|
|
202
|
+
// Clear previous mappings and build new ones during cloning.
|
|
203
|
+
// This ensures the mapping stays in sync with the current blocks.
|
|
204
|
+
idMappingRef.current.externalToInternal.clear();
|
|
205
|
+
idMappingRef.current.internalToExternal.clear();
|
|
206
|
+
|
|
113
207
|
const storeBlocks = controlledBlocks.map( ( block ) =>
|
|
114
|
-
|
|
208
|
+
cloneBlockWithMapping( block, idMappingRef.current )
|
|
115
209
|
);
|
|
116
210
|
if ( subscribedRef.current ) {
|
|
117
211
|
pendingChangesRef.current.incoming = storeBlocks;
|
|
@@ -262,24 +356,39 @@ export default function useBlockSync( {
|
|
|
262
356
|
|
|
263
357
|
if ( areBlocksDifferent || didPersistenceChange ) {
|
|
264
358
|
isPersistent = newIsPersistent;
|
|
359
|
+
|
|
360
|
+
// For inner block controllers (clientId is set), restore external IDs
|
|
361
|
+
// before passing blocks to the parent. This maintains stable external
|
|
362
|
+
// IDs for features like real-time collaboration while using unique
|
|
363
|
+
// internal IDs in the block-editor store.
|
|
364
|
+
const blocksForParent = clientId
|
|
365
|
+
? restoreExternalIds( blocks, idMappingRef.current )
|
|
366
|
+
: blocks;
|
|
367
|
+
|
|
368
|
+
const selection = {
|
|
369
|
+
selectionStart: getSelectionStart(),
|
|
370
|
+
selectionEnd: getSelectionEnd(),
|
|
371
|
+
initialPosition: getSelectedBlocksInitialCaretPosition(),
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
// Also restore external IDs in selection state for inner block controllers.
|
|
375
|
+
const selectionForParent = clientId
|
|
376
|
+
? restoreSelectionIds( selection, idMappingRef.current )
|
|
377
|
+
: selection;
|
|
378
|
+
|
|
265
379
|
// We know that onChange/onInput will update controlledBlocks.
|
|
266
380
|
// We need to be aware that it was caused by an outgoing change
|
|
267
381
|
// so that we do not treat it as an incoming change later on,
|
|
268
382
|
// which would cause a block reset.
|
|
269
|
-
pendingChangesRef.current.outgoing.push(
|
|
383
|
+
pendingChangesRef.current.outgoing.push( blocksForParent );
|
|
270
384
|
|
|
271
385
|
// Inform the controlling entity that changes have been made to
|
|
272
386
|
// the block-editor store they should be aware about.
|
|
273
387
|
const updateParent = isPersistent
|
|
274
388
|
? onChangeRef.current
|
|
275
389
|
: onInputRef.current;
|
|
276
|
-
updateParent(
|
|
277
|
-
selection:
|
|
278
|
-
selectionStart: getSelectionStart(),
|
|
279
|
-
selectionEnd: getSelectionEnd(),
|
|
280
|
-
initialPosition:
|
|
281
|
-
getSelectedBlocksInitialCaretPosition(),
|
|
282
|
-
},
|
|
390
|
+
updateParent( blocksForParent, {
|
|
391
|
+
selection: selectionForParent,
|
|
283
392
|
} );
|
|
284
393
|
}
|
|
285
394
|
previousAreBlocksDifferent = areBlocksDifferent;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import {
|
|
5
|
+
__experimentalUnitControl as UnitControl,
|
|
6
|
+
__experimentalUseCustomUnits as useCustomUnits,
|
|
7
|
+
__experimentalParseQuantityAndUnitFromRawValue as parseQuantityAndUnitFromRawValue,
|
|
8
|
+
__experimentalView as View,
|
|
9
|
+
RangeControl,
|
|
10
|
+
__experimentalSpacer as Spacer,
|
|
11
|
+
Flex,
|
|
12
|
+
FlexItem,
|
|
13
|
+
BaseControl,
|
|
14
|
+
} from '@wordpress/components';
|
|
15
|
+
import { __ } from '@wordpress/i18n';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Internal dependencies
|
|
19
|
+
*/
|
|
20
|
+
import { useSettings } from '../../components/use-settings';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Control for line text indent.
|
|
24
|
+
*
|
|
25
|
+
* @param {Object} props Component props.
|
|
26
|
+
* @param {boolean} props.__next40pxDefaultSize Start opting into the larger default height that will become the default size in a future version.
|
|
27
|
+
* @param {string} props.value Currently selected text indent.
|
|
28
|
+
* @param {Function} props.onChange Handles change in text indent selection.
|
|
29
|
+
* @param {string|number|undefined} props.__unstableInputWidth Input width to pass through to inner UnitControl. Should be a valid CSS value.
|
|
30
|
+
* @param {boolean} props.withSlider Whether to show the slider control.
|
|
31
|
+
* @param {boolean} props.hasBottomMargin Whether to add bottom margin below the control.
|
|
32
|
+
* @param {string} props.help Help text to display below the control.
|
|
33
|
+
*
|
|
34
|
+
* @return {Element} Text indent control.
|
|
35
|
+
*/
|
|
36
|
+
export default function TextIndentControl( {
|
|
37
|
+
__next40pxDefaultSize = false,
|
|
38
|
+
value,
|
|
39
|
+
onChange,
|
|
40
|
+
__unstableInputWidth = '60px',
|
|
41
|
+
withSlider = false,
|
|
42
|
+
hasBottomMargin = false,
|
|
43
|
+
help,
|
|
44
|
+
...otherProps
|
|
45
|
+
} ) {
|
|
46
|
+
const [ availableUnits ] = useSettings( 'spacing.units' );
|
|
47
|
+
const units = useCustomUnits( {
|
|
48
|
+
availableUnits: availableUnits || [
|
|
49
|
+
'px',
|
|
50
|
+
'em',
|
|
51
|
+
'rem',
|
|
52
|
+
'ch',
|
|
53
|
+
'%',
|
|
54
|
+
'vw',
|
|
55
|
+
'vh',
|
|
56
|
+
],
|
|
57
|
+
defaultValues: { px: 16, em: 2, rem: 2, ch: 2 },
|
|
58
|
+
} );
|
|
59
|
+
|
|
60
|
+
const [ valueQuantity, valueUnit ] = parseQuantityAndUnitFromRawValue(
|
|
61
|
+
value,
|
|
62
|
+
units
|
|
63
|
+
);
|
|
64
|
+
const isValueUnitRelative =
|
|
65
|
+
!! valueUnit &&
|
|
66
|
+
[ 'em', 'rem', '%', 'ch', 'vw', 'vh' ].includes( valueUnit );
|
|
67
|
+
|
|
68
|
+
if ( ! withSlider ) {
|
|
69
|
+
return (
|
|
70
|
+
<UnitControl
|
|
71
|
+
__next40pxDefaultSize={ __next40pxDefaultSize }
|
|
72
|
+
__shouldNotWarnDeprecated36pxSize
|
|
73
|
+
{ ...otherProps }
|
|
74
|
+
label={ __( 'Line indent' ) }
|
|
75
|
+
value={ value }
|
|
76
|
+
__unstableInputWidth={ __unstableInputWidth }
|
|
77
|
+
units={ units }
|
|
78
|
+
onChange={ onChange }
|
|
79
|
+
help={ help }
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<View style={ hasBottomMargin ? { marginBottom: 12 } : undefined }>
|
|
86
|
+
<BaseControl.VisualLabel>
|
|
87
|
+
{ __( 'Line indent' ) }
|
|
88
|
+
</BaseControl.VisualLabel>
|
|
89
|
+
<Flex>
|
|
90
|
+
<FlexItem isBlock>
|
|
91
|
+
<UnitControl
|
|
92
|
+
__next40pxDefaultSize={ __next40pxDefaultSize }
|
|
93
|
+
__shouldNotWarnDeprecated36pxSize
|
|
94
|
+
label={ __( 'Line indent' ) }
|
|
95
|
+
labelPosition="top"
|
|
96
|
+
hideLabelFromVision
|
|
97
|
+
value={ value }
|
|
98
|
+
onChange={ onChange }
|
|
99
|
+
size={ otherProps.size }
|
|
100
|
+
units={ units }
|
|
101
|
+
__unstableInputWidth={ __unstableInputWidth }
|
|
102
|
+
min={ 0 }
|
|
103
|
+
/>
|
|
104
|
+
</FlexItem>
|
|
105
|
+
{ withSlider && (
|
|
106
|
+
<FlexItem isBlock>
|
|
107
|
+
<Spacer marginX={ 2 } marginBottom={ 0 }>
|
|
108
|
+
<RangeControl
|
|
109
|
+
__next40pxDefaultSize={ __next40pxDefaultSize }
|
|
110
|
+
__shouldNotWarnDeprecated36pxSize
|
|
111
|
+
label={ __( 'Line indent' ) }
|
|
112
|
+
hideLabelFromVision
|
|
113
|
+
value={ valueQuantity }
|
|
114
|
+
withInputField={ false }
|
|
115
|
+
onChange={ ( newValue ) => {
|
|
116
|
+
if ( newValue === undefined ) {
|
|
117
|
+
onChange?.( undefined );
|
|
118
|
+
} else {
|
|
119
|
+
onChange?.(
|
|
120
|
+
newValue + ( valueUnit ?? 'px' )
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
} }
|
|
124
|
+
min={ 0 }
|
|
125
|
+
max={ isValueUnitRelative ? 10 : 100 }
|
|
126
|
+
step={ isValueUnitRelative ? 0.1 : 1 }
|
|
127
|
+
initialPosition={ 0 }
|
|
128
|
+
/>
|
|
129
|
+
</Spacer>
|
|
130
|
+
</FlexItem>
|
|
131
|
+
) }
|
|
132
|
+
</Flex>
|
|
133
|
+
{ help && (
|
|
134
|
+
<p className="components-base-control__help">{ help }</p>
|
|
135
|
+
) }
|
|
136
|
+
</View>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
@@ -12,10 +12,10 @@ import { UP, DOWN, ENTER, TAB } from '@wordpress/keycodes';
|
|
|
12
12
|
import {
|
|
13
13
|
BaseControl,
|
|
14
14
|
Button,
|
|
15
|
-
__experimentalInputControl as InputControl,
|
|
16
15
|
Spinner,
|
|
17
16
|
withSpokenMessages,
|
|
18
17
|
Popover,
|
|
18
|
+
privateApis as componentsPrivateApis,
|
|
19
19
|
} from '@wordpress/components';
|
|
20
20
|
import {
|
|
21
21
|
compose,
|
|
@@ -30,6 +30,9 @@ import { isURL } from '@wordpress/url';
|
|
|
30
30
|
* Internal dependencies
|
|
31
31
|
*/
|
|
32
32
|
import { store as blockEditorStore } from '../../store';
|
|
33
|
+
import { unlock } from '../../lock-unlock';
|
|
34
|
+
|
|
35
|
+
const { ValidatedInputControl } = unlock( componentsPrivateApis );
|
|
33
36
|
|
|
34
37
|
/**
|
|
35
38
|
* Whether the argument is a function.
|
|
@@ -426,6 +429,8 @@ class URLInput extends Component {
|
|
|
426
429
|
hideLabelFromVision = false,
|
|
427
430
|
help = null,
|
|
428
431
|
disabled = false,
|
|
432
|
+
customValidity,
|
|
433
|
+
markWhenOptional,
|
|
429
434
|
} = this.props;
|
|
430
435
|
|
|
431
436
|
const {
|
|
@@ -473,13 +478,27 @@ class URLInput extends Component {
|
|
|
473
478
|
help,
|
|
474
479
|
};
|
|
475
480
|
|
|
481
|
+
const validationProps = {
|
|
482
|
+
customValidity,
|
|
483
|
+
// Suppress the "(Required)" indicator in the label.
|
|
484
|
+
// The field is still required for validation, but the indicator
|
|
485
|
+
// can be hidden when markWhenOptional is set to true.
|
|
486
|
+
...( markWhenOptional !== undefined && {
|
|
487
|
+
markWhenOptional,
|
|
488
|
+
} ),
|
|
489
|
+
};
|
|
490
|
+
|
|
476
491
|
if ( renderControl ) {
|
|
477
492
|
return renderControl( controlProps, inputProps, loading );
|
|
478
493
|
}
|
|
479
494
|
|
|
480
495
|
return (
|
|
481
496
|
<BaseControl { ...controlProps }>
|
|
482
|
-
<
|
|
497
|
+
<ValidatedInputControl
|
|
498
|
+
{ ...inputProps }
|
|
499
|
+
{ ...validationProps }
|
|
500
|
+
__next40pxDefaultSize
|
|
501
|
+
/>
|
|
483
502
|
{ loading && <Spinner /> }
|
|
484
503
|
</BaseControl>
|
|
485
504
|
);
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
fullscreen,
|
|
28
28
|
linkOff,
|
|
29
29
|
} from '@wordpress/icons';
|
|
30
|
-
import {
|
|
30
|
+
import { prependHTTPS } from '@wordpress/url';
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Internal dependencies
|
|
@@ -156,7 +156,7 @@ const ImageURLInputUI = ( {
|
|
|
156
156
|
)?.linkDestination || LINK_DESTINATION_CUSTOM;
|
|
157
157
|
|
|
158
158
|
onChangeUrl( {
|
|
159
|
-
href:
|
|
159
|
+
href: prependHTTPS( urlInput ),
|
|
160
160
|
linkDestination: selectedDestination,
|
|
161
161
|
lightbox: { enabled: false },
|
|
162
162
|
} );
|
|
@@ -114,10 +114,6 @@ export function getClosestTabbable(
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
function isTabCandidate( node ) {
|
|
117
|
-
if ( node.closest( '[inert]' ) ) {
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
117
|
// Skip if there's only one child that is content editable (and thus a
|
|
122
118
|
// better candidate).
|
|
123
119
|
if (
|
package/src/hooks/aria-label.js
CHANGED
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
import { addFilter } from '@wordpress/hooks';
|
|
5
5
|
import { hasBlockSupport } from '@wordpress/blocks';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Internal dependencies
|
|
9
|
+
*/
|
|
10
|
+
import { shouldSkipSerialization } from './utils';
|
|
11
|
+
|
|
7
12
|
/**
|
|
8
13
|
* Filters registered block settings, extending attributes with ariaLabel using aria-label
|
|
9
14
|
* of the first node.
|
|
@@ -42,7 +47,10 @@ export function addAttribute( settings ) {
|
|
|
42
47
|
* @return {Object} Filtered props applied to save element.
|
|
43
48
|
*/
|
|
44
49
|
export function addSaveProps( extraProps, blockType, attributes ) {
|
|
45
|
-
if (
|
|
50
|
+
if (
|
|
51
|
+
hasBlockSupport( blockType, 'ariaLabel' ) &&
|
|
52
|
+
! shouldSkipSerialization( blockType, 'ariaLabel', 'ariaLabel' )
|
|
53
|
+
) {
|
|
46
54
|
extraProps[ 'aria-label' ] =
|
|
47
55
|
attributes.ariaLabel === '' ? null : attributes.ariaLabel;
|
|
48
56
|
}
|
|
@@ -10,6 +10,7 @@ import { useSelect } from '@wordpress/data';
|
|
|
10
10
|
*/
|
|
11
11
|
import { GridVisualizer, useGridLayoutSync } from '../components/grid';
|
|
12
12
|
import { store as blockEditorStore } from '../store';
|
|
13
|
+
import { unlock } from '../lock-unlock';
|
|
13
14
|
import useBlockVisibility from '../components/block-visibility/use-block-visibility';
|
|
14
15
|
import { deviceTypeKey } from '../store/private-keys';
|
|
15
16
|
import { BLOCK_VISIBILITY_VIEWPORTS } from '../components/block-visibility/constants';
|
|
@@ -19,40 +20,54 @@ function GridLayoutSync( props ) {
|
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
function GridTools( { clientId, layout } ) {
|
|
22
|
-
const { isVisible, blockVisibility, deviceType } =
|
|
23
|
-
(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
23
|
+
const { isVisible, blockVisibility, deviceType, isAnyAncestorHidden } =
|
|
24
|
+
useSelect(
|
|
25
|
+
( select ) => {
|
|
26
|
+
const {
|
|
27
|
+
isBlockSelected,
|
|
28
|
+
hasSelectedInnerBlock,
|
|
29
|
+
isDraggingBlocks,
|
|
30
|
+
getTemplateLock,
|
|
31
|
+
getBlockEditingMode,
|
|
32
|
+
getBlockAttributes,
|
|
33
|
+
getSettings,
|
|
34
|
+
} = select( blockEditorStore );
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
// These calls are purposely ordered from least expensive to most expensive.
|
|
37
|
+
// Hides the visualizer in cases where the user is not or cannot interact with it.
|
|
38
|
+
// Also hide if a child block is selected, because layout-child.js will render
|
|
39
|
+
// the visualizer in that case (with proper childGridClientId handling).
|
|
40
|
+
if (
|
|
41
|
+
( ! isDraggingBlocks() && ! isBlockSelected( clientId ) ) ||
|
|
42
|
+
getTemplateLock( clientId ) ||
|
|
43
|
+
getBlockEditingMode( clientId ) !== 'default' ||
|
|
44
|
+
hasSelectedInnerBlock( clientId )
|
|
45
|
+
) {
|
|
46
|
+
return { isVisible: false };
|
|
47
|
+
}
|
|
42
48
|
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
const { isBlockParentHiddenAtViewport } = unlock(
|
|
50
|
+
select( blockEditorStore )
|
|
51
|
+
);
|
|
45
52
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
deviceType:
|
|
53
|
+
const attributes = getBlockAttributes( clientId );
|
|
54
|
+
const settings = getSettings();
|
|
55
|
+
const currentDeviceType =
|
|
50
56
|
settings?.[ deviceTypeKey ]?.toLowerCase() ||
|
|
51
|
-
BLOCK_VISIBILITY_VIEWPORTS.desktop.value
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
BLOCK_VISIBILITY_VIEWPORTS.desktop.value;
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
isVisible: true,
|
|
61
|
+
blockVisibility: attributes?.metadata?.blockVisibility,
|
|
62
|
+
deviceType: currentDeviceType,
|
|
63
|
+
isAnyAncestorHidden: isBlockParentHiddenAtViewport(
|
|
64
|
+
clientId,
|
|
65
|
+
currentDeviceType
|
|
66
|
+
),
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
[ clientId ]
|
|
70
|
+
);
|
|
56
71
|
|
|
57
72
|
const { isBlockCurrentlyHidden } = useBlockVisibility( {
|
|
58
73
|
blockVisibility,
|
|
@@ -62,9 +77,14 @@ function GridTools( { clientId, layout } ) {
|
|
|
62
77
|
return (
|
|
63
78
|
<>
|
|
64
79
|
<GridLayoutSync clientId={ clientId } />
|
|
65
|
-
{ isVisible &&
|
|
66
|
-
|
|
67
|
-
|
|
80
|
+
{ isVisible &&
|
|
81
|
+
! isBlockCurrentlyHidden &&
|
|
82
|
+
! isAnyAncestorHidden && (
|
|
83
|
+
<GridVisualizer
|
|
84
|
+
clientId={ clientId }
|
|
85
|
+
parentLayout={ layout }
|
|
86
|
+
/>
|
|
87
|
+
) }
|
|
68
88
|
</>
|
|
69
89
|
);
|
|
70
90
|
}
|
|
@@ -208,6 +208,7 @@ function GridTools( {
|
|
|
208
208
|
parentBlockVisibility,
|
|
209
209
|
blockBlockVisibility,
|
|
210
210
|
deviceType,
|
|
211
|
+
isChildBlockAGrid,
|
|
211
212
|
} = useSelect(
|
|
212
213
|
( select ) => {
|
|
213
214
|
const {
|
|
@@ -244,6 +245,8 @@ function GridTools( {
|
|
|
244
245
|
deviceType:
|
|
245
246
|
settings?.[ deviceTypeKey ]?.toLowerCase() ||
|
|
246
247
|
BLOCK_VISIBILITY_VIEWPORTS.desktop.value,
|
|
248
|
+
// Check if the selected child block is itself a grid.
|
|
249
|
+
isChildBlockAGrid: blockAttributes?.layout?.type === 'grid',
|
|
247
250
|
};
|
|
248
251
|
},
|
|
249
252
|
[ clientId ]
|
|
@@ -264,6 +267,8 @@ function GridTools( {
|
|
|
264
267
|
// Use useState() instead of useRef() so that GridItemResizer updates when ref is set.
|
|
265
268
|
const [ resizerBounds, setResizerBounds ] = useState();
|
|
266
269
|
|
|
270
|
+
const childGridClientId = isChildBlockAGrid ? clientId : undefined;
|
|
271
|
+
|
|
267
272
|
if ( ! isVisible || isParentBlockCurrentlyHidden ) {
|
|
268
273
|
return null;
|
|
269
274
|
}
|
|
@@ -288,6 +293,7 @@ function GridTools( {
|
|
|
288
293
|
clientId={ rootClientId }
|
|
289
294
|
contentRef={ setResizerBounds }
|
|
290
295
|
parentLayout={ parentLayout }
|
|
296
|
+
childGridClientId={ childGridClientId }
|
|
291
297
|
/>
|
|
292
298
|
{ showResizer && (
|
|
293
299
|
<GridItemResizer
|
package/src/hooks/typography.js
CHANGED
|
@@ -31,6 +31,7 @@ function omit( object, keys ) {
|
|
|
31
31
|
const LETTER_SPACING_SUPPORT_KEY = 'typography.__experimentalLetterSpacing';
|
|
32
32
|
const TEXT_TRANSFORM_SUPPORT_KEY = 'typography.__experimentalTextTransform';
|
|
33
33
|
const TEXT_DECORATION_SUPPORT_KEY = 'typography.__experimentalTextDecoration';
|
|
34
|
+
const TEXT_INDENT_SUPPORT_KEY = 'typography.textIndent';
|
|
34
35
|
const TEXT_COLUMNS_SUPPORT_KEY = 'typography.textColumns';
|
|
35
36
|
const FONT_STYLE_SUPPORT_KEY = 'typography.__experimentalFontStyle';
|
|
36
37
|
const FONT_WEIGHT_SUPPORT_KEY = 'typography.__experimentalFontWeight';
|
|
@@ -45,6 +46,7 @@ export const TYPOGRAPHY_SUPPORT_KEYS = [
|
|
|
45
46
|
TEXT_ALIGN_SUPPORT_KEY,
|
|
46
47
|
TEXT_COLUMNS_SUPPORT_KEY,
|
|
47
48
|
TEXT_DECORATION_SUPPORT_KEY,
|
|
49
|
+
TEXT_INDENT_SUPPORT_KEY,
|
|
48
50
|
WRITING_MODE_SUPPORT_KEY,
|
|
49
51
|
TEXT_TRANSFORM_SUPPORT_KEY,
|
|
50
52
|
LETTER_SPACING_SUPPORT_KEY,
|