Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce Block type level lock control #32457

Merged
merged 17 commits into from Sep 10, 2021
Merged

Introduce Block type level lock control #32457

merged 17 commits into from Sep 10, 2021

Conversation

senadir
Copy link
Contributor

@senadir senadir commented Jun 4, 2021

Parts of #29864

Description

This PR introduces parts of #29864 mainly, it adds:

  • Attribute level support for locking a block from being moved/removed.
  • Hides the block moving arrows and delete button if a block is locked.

The PR also adds checks at the datastore level for both moving and removing blocks.

API definition

When defining a block, the value is set at the attributes level.

attributes: {
	lock: {
		type: 'object',
		default: {
			move: true,
			remove: true,	
		},
	},
},

This would lock the block ability to be moved or removed.

How has this been tested?

  • Modify any block attributes to include moving or removing.
  • modify any innerBlocks templateLock to be insert, all, or false to see the all the possible combinations.
  • Try to move or remove the block, it should respect your choices.

This table shows the intersect of conditions between templateLock and lock

For removing:

lock.remove\templateLock "all" "insert" false
true can't Remove can't Remove can't Remove
false can Remove can Remove can Remove
undefined can't Remove can't Remove can Remove

For moving:

lock.move\templateLock "all" "insert" false
true can't Move can't Move can't Move
false can Move can Move can Move
undefined can't Move can Move can Move

Screenshots

In this example. the parent block is locked to all, the first block "Contact information" has locking disabled for move and remove, which means it can be moved or removed.
https://user-images.githubusercontent.com/6165348/120816789-fc87af80-c548-11eb-821e-227083a9badf.mov

Use case

There are several valid uses cases outlined in #29864
Another use case that we're building for is having a Checkout Block with different blocks that act as fundamental steps, we don't want people to delete or move those steps since they're fundamental and their order is also important, but we want to allow people to select them, access settings, and insert blocks in between them.

@senadir senadir self-assigned this Jun 4, 2021
@senadir senadir added [Feature] Block API API that allows to express the block paradigm. [Feature] Nested / Inner Blocks Anything related to the experience of nested/inner blocks inside a larger container, like Group or P [Package] Block editor /packages/block-editor [Type] Enhancement A suggestion for improvement. Needs Dev Note Requires a developer note for a major WordPress release cycle labels Jun 4, 2021
Comment on lines +178 to +184
{ canRemove && (
<MenuGroup>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I switched the order here between MenuGroup and isLocked (now canRemove) because when removing was disabled, there would be an empty cell there.
image

return ( clientIds, rootClientId ) => {
return {
return function* ( clientIds, rootClientId ) {
const canMoveBlocks = yield controls.select(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure how would mixed blocks behave, so I follow what was done with duplication. If you have several blocks selected and one of them is locked (can't be removed or moved) then the whole action is prevented.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be the right approach 👍

@github-actions
Copy link

github-actions bot commented Jun 4, 2021

Size Change: +3.25 kB (0%)

Total Size: 1.04 MB

Filename Size Change
build/block-editor/index.min.js 120 kB +1.12 kB (+1%)
build/block-editor/style-rtl.css 13.8 kB +21 B (0%)
build/block-editor/style.css 13.8 kB +22 B (0%)
build/block-library/blocks/button/style-rtl.css 600 B -5 B (-1%)
build/block-library/blocks/button/style.css 600 B -4 B (-1%)
build/block-library/blocks/columns/editor-rtl.css 206 B +12 B (+6%) 🔍
build/block-library/blocks/columns/editor.css 205 B +12 B (+6%) 🔍
build/block-library/blocks/columns/style-rtl.css 497 B +23 B (+5%) 🔍
build/block-library/blocks/columns/style.css 496 B +21 B (+4%)
build/block-library/blocks/embed/style-rtl.css 417 B +17 B (+4%)
build/block-library/blocks/embed/style.css 417 B +17 B (+4%)
build/block-library/blocks/gallery/editor-rtl.css 927 B +48 B (+5%) 🔍
build/block-library/blocks/gallery/editor.css 934 B +58 B (+7%) 🔍
build/block-library/blocks/gallery/style-rtl.css 1.6 kB -15 B (-1%)
build/block-library/blocks/gallery/style.css 1.59 kB -13 B (-1%)
build/block-library/blocks/heading/style-rtl.css 114 B +38 B (+50%) 🆘
build/block-library/blocks/heading/style.css 114 B +38 B (+50%) 🆘
build/block-library/blocks/list/style-rtl.css 94 B +31 B (+49%) 🚨
build/block-library/blocks/list/style.css 94 B +31 B (+49%) 🚨
build/block-library/blocks/navigation/editor-rtl.css 1.72 kB +18 B (+1%)
build/block-library/blocks/navigation/editor.css 1.72 kB +17 B (+1%)
build/block-library/blocks/navigation/style-rtl.css 1.42 kB -45 B (-3%)
build/block-library/blocks/navigation/style.css 1.41 kB -43 B (-3%)
build/block-library/blocks/paragraph/style-rtl.css 261 B +13 B (+5%) 🔍
build/block-library/blocks/paragraph/style.css 261 B +13 B (+5%) 🔍
build/block-library/blocks/pullquote/style-rtl.css 378 B +17 B (+5%) 🔍
build/block-library/blocks/pullquote/style.css 378 B +18 B (+5%) 🔍
build/block-library/blocks/query-pagination/style-rtl.css 239 B +71 B (+42%) 🚨
build/block-library/blocks/query-pagination/style.css 236 B +68 B (+40%) 🚨
build/block-library/blocks/quote/style-rtl.css 187 B +18 B (+11%) ⚠️
build/block-library/blocks/quote/style.css 187 B +18 B (+11%) ⚠️
build/block-library/blocks/social-links/style-rtl.css 1.3 kB -29 B (-2%)
build/block-library/blocks/social-links/style.css 1.3 kB -29 B (-2%)
build/block-library/common-rtl.css 853 B -437 B (-34%) 🎉
build/block-library/common.css 849 B -439 B (-34%) 🎉
build/block-library/editor-rtl.css 9.54 kB -376 B (-4%)
build/block-library/editor.css 9.52 kB -379 B (-4%)
build/block-library/index.min.js 151 kB +719 B (0%)
build/block-library/style-rtl.css 10.2 kB -410 B (-4%)
build/block-library/style.css 10.2 kB -409 B (-4%)
build/blocks/index.min.js 46.9 kB +91 B (0%)
build/components/index.min.js 209 kB +30 B (0%)
build/components/style-rtl.css 15.8 kB +39 B (0%)
build/components/style.css 15.8 kB +38 B (0%)
build/core-data/index.min.js 12.3 kB -180 B (-1%)
build/customize-widgets/index.min.js 11.1 kB +22 B (0%)
build/dom/index.min.js 4.53 kB +3 B (0%)
build/edit-navigation/index.min.js 14.9 kB +1.31 kB (+10%) ⚠️
build/edit-navigation/style-rtl.css 3.37 kB +228 B (+7%) 🔍
build/edit-navigation/style.css 3.37 kB +229 B (+7%) 🔍
build/edit-post/index.min.js 29 kB +35 B (0%)
build/edit-post/style-rtl.css 7.2 kB +6 B (0%)
build/edit-post/style.css 7.2 kB +6 B (0%)
build/edit-site/index.min.js 26.6 kB +350 B (+1%)
build/edit-widgets/index.min.js 16.1 kB +7 B (0%)
build/editor/index.min.js 37.7 kB +22 B (0%)
build/rich-text/index.min.js 10.6 kB -8 B (0%)
build/widgets/index.min.js 7.27 kB +902 B (+14%) ⚠️
build/widgets/style-rtl.css 1.17 kB +126 B (+12%) ⚠️
build/widgets/style.css 1.18 kB +125 B (+12%) ⚠️
ℹ️ View Unchanged
Filename Size
build/a11y/index.min.js 931 B
build/admin-manifest/index.min.js 1.09 kB
build/annotations/index.min.js 2.7 kB
build/api-fetch/index.min.js 2.19 kB
build/autop/index.min.js 2.08 kB
build/blob/index.min.js 459 B
build/block-directory/index.min.js 6.2 kB
build/block-directory/style-rtl.css 1.01 kB
build/block-directory/style.css 1.01 kB
build/block-editor/default-editor-styles-rtl.css 378 B
build/block-editor/default-editor-styles.css 378 B
build/block-library/blocks/archives/editor-rtl.css 61 B
build/block-library/blocks/archives/editor.css 60 B
build/block-library/blocks/archives/style-rtl.css 65 B
build/block-library/blocks/archives/style.css 65 B
build/block-library/blocks/audio/editor-rtl.css 58 B
build/block-library/blocks/audio/editor.css 58 B
build/block-library/blocks/audio/style-rtl.css 111 B
build/block-library/blocks/audio/style.css 111 B
build/block-library/blocks/audio/theme-rtl.css 125 B
build/block-library/blocks/audio/theme.css 125 B
build/block-library/blocks/block/editor-rtl.css 161 B
build/block-library/blocks/block/editor.css 161 B
build/block-library/blocks/button/editor-rtl.css 474 B
build/block-library/blocks/button/editor.css 474 B
build/block-library/blocks/buttons/editor-rtl.css 315 B
build/block-library/blocks/buttons/editor.css 315 B
build/block-library/blocks/buttons/style-rtl.css 370 B
build/block-library/blocks/buttons/style.css 370 B
build/block-library/blocks/calendar/style-rtl.css 207 B
build/block-library/blocks/calendar/style.css 207 B
build/block-library/blocks/categories/editor-rtl.css 84 B
build/block-library/blocks/categories/editor.css 83 B
build/block-library/blocks/categories/style-rtl.css 79 B
build/block-library/blocks/categories/style.css 79 B
build/block-library/blocks/code/style-rtl.css 90 B
build/block-library/blocks/code/style.css 90 B
build/block-library/blocks/code/theme-rtl.css 131 B
build/block-library/blocks/code/theme.css 131 B
build/block-library/blocks/cover/editor-rtl.css 666 B
build/block-library/blocks/cover/editor.css 670 B
build/block-library/blocks/cover/style-rtl.css 1.23 kB
build/block-library/blocks/cover/style.css 1.23 kB
build/block-library/blocks/embed/editor-rtl.css 488 B
build/block-library/blocks/embed/editor.css 488 B
build/block-library/blocks/embed/theme-rtl.css 124 B
build/block-library/blocks/embed/theme.css 124 B
build/block-library/blocks/file/editor-rtl.css 300 B
build/block-library/blocks/file/editor.css 300 B
build/block-library/blocks/file/style-rtl.css 255 B
build/block-library/blocks/file/style.css 255 B
build/block-library/blocks/file/view.min.js 322 B
build/block-library/blocks/freeform/editor-rtl.css 2.44 kB
build/block-library/blocks/freeform/editor.css 2.44 kB
build/block-library/blocks/gallery/theme-rtl.css 122 B
build/block-library/blocks/gallery/theme.css 122 B
build/block-library/blocks/group/editor-rtl.css 159 B
build/block-library/blocks/group/editor.css 159 B
build/block-library/blocks/group/style-rtl.css 57 B
build/block-library/blocks/group/style.css 57 B
build/block-library/blocks/group/theme-rtl.css 70 B
build/block-library/blocks/group/theme.css 70 B
build/block-library/blocks/home-link/style-rtl.css 247 B
build/block-library/blocks/home-link/style.css 247 B
build/block-library/blocks/html/editor-rtl.css 283 B
build/block-library/blocks/html/editor.css 284 B
build/block-library/blocks/image/editor-rtl.css 728 B
build/block-library/blocks/image/editor.css 728 B
build/block-library/blocks/image/style-rtl.css 482 B
build/block-library/blocks/image/style.css 487 B
build/block-library/blocks/image/theme-rtl.css 124 B
build/block-library/blocks/image/theme.css 124 B
build/block-library/blocks/latest-comments/style-rtl.css 284 B
build/block-library/blocks/latest-comments/style.css 284 B
build/block-library/blocks/latest-posts/editor-rtl.css 137 B
build/block-library/blocks/latest-posts/editor.css 137 B
build/block-library/blocks/latest-posts/style-rtl.css 528 B
build/block-library/blocks/latest-posts/style.css 527 B
build/block-library/blocks/media-text/editor-rtl.css 266 B
build/block-library/blocks/media-text/editor.css 263 B
build/block-library/blocks/media-text/style-rtl.css 488 B
build/block-library/blocks/media-text/style.css 485 B
build/block-library/blocks/more/editor-rtl.css 431 B
build/block-library/blocks/more/editor.css 431 B
build/block-library/blocks/navigation-link/editor-rtl.css 489 B
build/block-library/blocks/navigation-link/editor.css 488 B
build/block-library/blocks/navigation-link/style-rtl.css 94 B
build/block-library/blocks/navigation-link/style.css 94 B
build/block-library/blocks/navigation/view.min.js 2.52 kB
build/block-library/blocks/nextpage/editor-rtl.css 395 B
build/block-library/blocks/nextpage/editor.css 395 B
build/block-library/blocks/page-list/editor-rtl.css 310 B
build/block-library/blocks/page-list/editor.css 310 B
build/block-library/blocks/page-list/style-rtl.css 241 B
build/block-library/blocks/page-list/style.css 241 B
build/block-library/blocks/paragraph/editor-rtl.css 157 B
build/block-library/blocks/paragraph/editor.css 157 B
build/block-library/blocks/post-author/editor-rtl.css 210 B
build/block-library/blocks/post-author/editor.css 210 B
build/block-library/blocks/post-author/style-rtl.css 182 B
build/block-library/blocks/post-author/style.css 181 B
build/block-library/blocks/post-comments-form/style-rtl.css 140 B
build/block-library/blocks/post-comments-form/style.css 140 B
build/block-library/blocks/post-comments/style-rtl.css 360 B
build/block-library/blocks/post-comments/style.css 359 B
build/block-library/blocks/post-content/editor-rtl.css 138 B
build/block-library/blocks/post-content/editor.css 138 B
build/block-library/blocks/post-excerpt/editor-rtl.css 73 B
build/block-library/blocks/post-excerpt/editor.css 73 B
build/block-library/blocks/post-excerpt/style-rtl.css 69 B
build/block-library/blocks/post-excerpt/style.css 69 B
build/block-library/blocks/post-featured-image/editor-rtl.css 398 B
build/block-library/blocks/post-featured-image/editor.css 398 B
build/block-library/blocks/post-featured-image/style-rtl.css 143 B
build/block-library/blocks/post-featured-image/style.css 143 B
build/block-library/blocks/post-template/editor-rtl.css 99 B
build/block-library/blocks/post-template/editor.css 98 B
build/block-library/blocks/post-template/style-rtl.css 378 B
build/block-library/blocks/post-template/style.css 379 B
build/block-library/blocks/post-terms/style-rtl.css 73 B
build/block-library/blocks/post-terms/style.css 73 B
build/block-library/blocks/post-title/style-rtl.css 60 B
build/block-library/blocks/post-title/style.css 60 B
build/block-library/blocks/preformatted/style-rtl.css 103 B
build/block-library/blocks/preformatted/style.css 103 B
build/block-library/blocks/pullquote/editor-rtl.css 198 B
build/block-library/blocks/pullquote/editor.css 198 B
build/block-library/blocks/pullquote/theme-rtl.css 167 B
build/block-library/blocks/pullquote/theme.css 167 B
build/block-library/blocks/query-pagination-numbers/editor-rtl.css 122 B
build/block-library/blocks/query-pagination-numbers/editor.css 121 B
build/block-library/blocks/query-pagination/editor-rtl.css 270 B
build/block-library/blocks/query-pagination/editor.css 262 B
build/block-library/blocks/query-title/editor-rtl.css 85 B
build/block-library/blocks/query-title/editor.css 85 B
build/block-library/blocks/query/editor-rtl.css 131 B
build/block-library/blocks/query/editor.css 132 B
build/block-library/blocks/quote/theme-rtl.css 220 B
build/block-library/blocks/quote/theme.css 222 B
build/block-library/blocks/rss/editor-rtl.css 202 B
build/block-library/blocks/rss/editor.css 204 B
build/block-library/blocks/rss/style-rtl.css 289 B
build/block-library/blocks/rss/style.css 288 B
build/block-library/blocks/search/editor-rtl.css 165 B
build/block-library/blocks/search/editor.css 165 B
build/block-library/blocks/search/style-rtl.css 374 B
build/block-library/blocks/search/style.css 375 B
build/block-library/blocks/search/theme-rtl.css 64 B
build/block-library/blocks/search/theme.css 64 B
build/block-library/blocks/separator/editor-rtl.css 99 B
build/block-library/blocks/separator/editor.css 99 B
build/block-library/blocks/separator/style-rtl.css 250 B
build/block-library/blocks/separator/style.css 250 B
build/block-library/blocks/separator/theme-rtl.css 172 B
build/block-library/blocks/separator/theme.css 172 B
build/block-library/blocks/shortcode/editor-rtl.css 474 B
build/block-library/blocks/shortcode/editor.css 474 B
build/block-library/blocks/site-logo/editor-rtl.css 462 B
build/block-library/blocks/site-logo/editor.css 464 B
build/block-library/blocks/site-logo/style-rtl.css 153 B
build/block-library/blocks/site-logo/style.css 153 B
build/block-library/blocks/site-tagline/editor-rtl.css 86 B
build/block-library/blocks/site-tagline/editor.css 86 B
build/block-library/blocks/site-title/editor-rtl.css 84 B
build/block-library/blocks/site-title/editor.css 84 B
build/block-library/blocks/social-link/editor-rtl.css 165 B
build/block-library/blocks/social-link/editor.css 165 B
build/block-library/blocks/social-links/editor-rtl.css 812 B
build/block-library/blocks/social-links/editor.css 811 B
build/block-library/blocks/spacer/editor-rtl.css 307 B
build/block-library/blocks/spacer/editor.css 307 B
build/block-library/blocks/spacer/style-rtl.css 48 B
build/block-library/blocks/spacer/style.css 48 B
build/block-library/blocks/table/editor-rtl.css 471 B
build/block-library/blocks/table/editor.css 472 B
build/block-library/blocks/table/style-rtl.css 481 B
build/block-library/blocks/table/style.css 481 B
build/block-library/blocks/table/theme-rtl.css 188 B
build/block-library/blocks/table/theme.css 188 B
build/block-library/blocks/tag-cloud/style-rtl.css 146 B
build/block-library/blocks/tag-cloud/style.css 146 B
build/block-library/blocks/template-part/editor-rtl.css 636 B
build/block-library/blocks/template-part/editor.css 635 B
build/block-library/blocks/template-part/theme-rtl.css 101 B
build/block-library/blocks/template-part/theme.css 101 B
build/block-library/blocks/term-description/editor-rtl.css 90 B
build/block-library/blocks/term-description/editor.css 90 B
build/block-library/blocks/text-columns/editor-rtl.css 95 B
build/block-library/blocks/text-columns/editor.css 95 B
build/block-library/blocks/text-columns/style-rtl.css 166 B
build/block-library/blocks/text-columns/style.css 166 B
build/block-library/blocks/verse/style-rtl.css 87 B
build/block-library/blocks/verse/style.css 87 B
build/block-library/blocks/video/editor-rtl.css 571 B
build/block-library/blocks/video/editor.css 572 B
build/block-library/blocks/video/style-rtl.css 173 B
build/block-library/blocks/video/style.css 173 B
build/block-library/blocks/video/theme-rtl.css 124 B
build/block-library/blocks/video/theme.css 124 B
build/block-library/reset-rtl.css 527 B
build/block-library/reset.css 527 B
build/block-library/theme-rtl.css 658 B
build/block-library/theme.css 663 B
build/block-serialization-default-parser/index.min.js 1.09 kB
build/block-serialization-spec-parser/index.min.js 2.79 kB
build/compose/index.min.js 10.2 kB
build/customize-widgets/style-rtl.css 1.5 kB
build/customize-widgets/style.css 1.49 kB
build/data-controls/index.min.js 614 B
build/data/index.min.js 7.1 kB
build/date/index.min.js 31.5 kB
build/deprecated/index.min.js 428 B
build/dom-ready/index.min.js 304 B
build/edit-post/classic-rtl.css 492 B
build/edit-post/classic.css 494 B
build/edit-site/style-rtl.css 5.07 kB
build/edit-site/style.css 5.07 kB
build/edit-widgets/style-rtl.css 4.06 kB
build/edit-widgets/style.css 4.06 kB
build/editor/style-rtl.css 3.74 kB
build/editor/style.css 3.73 kB
build/element/index.min.js 3.17 kB
build/escape-html/index.min.js 517 B
build/format-library/index.min.js 5.36 kB
build/format-library/style-rtl.css 668 B
build/format-library/style.css 669 B
build/hooks/index.min.js 1.55 kB
build/html-entities/index.min.js 424 B
build/i18n/index.min.js 3.6 kB
build/is-shallow-equal/index.min.js 501 B
build/keyboard-shortcuts/index.min.js 1.49 kB
build/keycodes/index.min.js 1.25 kB
build/list-reusable-blocks/index.min.js 1.85 kB
build/list-reusable-blocks/style-rtl.css 838 B
build/list-reusable-blocks/style.css 838 B
build/media-utils/index.min.js 2.88 kB
build/notices/index.min.js 845 B
build/nux/index.min.js 2.03 kB
build/nux/style-rtl.css 747 B
build/nux/style.css 743 B
build/plugins/index.min.js 1.83 kB
build/primitives/index.min.js 921 B
build/priority-queue/index.min.js 582 B
build/react-i18n/index.min.js 671 B
build/redux-routine/index.min.js 2.63 kB
build/reusable-blocks/index.min.js 2.28 kB
build/reusable-blocks/style-rtl.css 256 B
build/reusable-blocks/style.css 256 B
build/server-side-render/index.min.js 1.32 kB
build/shortcode/index.min.js 1.48 kB
build/token-list/index.min.js 562 B
build/url/index.min.js 1.74 kB
build/viewport/index.min.js 1.02 kB
build/warning/index.min.js 248 B
build/wordcount/index.min.js 1.04 kB

compressed-size-action

// it is not possible to move the block to any other position.
if ( templateLock === 'all' ) {
// If one of the blocks is locked or the parent is locked, we cannot move any block.
if ( ! canMoveBlocks ) {
return;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is already contained inside canMoveBlocks

Comment on lines 1411 to 1320
// undefined blocks can still be deleted.
if ( ! blockType ) {
return true;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My assumption here was that an undefined block type means the block is not registered, but it's still a block, so it can be removed.
However, this ignores if the parent is locked or not. I can remove this and make all undefined blocks removable but it feels more like restrictions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing this, all of this block (1404 to 1414) can be removed, it would always differ to the parent lock status.

@jorgefilipecosta
Copy link
Member

Hi @senadir,
I'm not sure if locking (excluding a user locking functionality) is something that should be specified at the block level.

Using this API if a block defines:

supports: {
	lock: {
		move: true,
		remove: true,
	},
},

The block can be inserted normally using the inserter but then the user can not remove it anymore. Is this a case we want?

The locking for the move is also something that does not depend only on the block, e.g: I may move a block by moving its neighbors.

To me, it seems if a block can be moved or removed is not a property of the block but of the context where it is e.g: parent block or post type template.

Another use case that we're building for is having a Checkout Block with different blocks that act as fundamental steps, we don't want people to delete or move those steps since they're fundamental and their order is also important, but we want to allow people to select them, access settings, and insert blocks in between them.

For blocks, with complex logic like a checkout, I imagine these blocks may need to have some type of validation. E.g: the container block verifies if all the step blocks are inside and if not shows a red alert saying block x is missing and maybe even provides a button that allows the user to press it and fix the situation (e.g: inserts the missing block). This allows the user do everything while building its checkout e.g: delete things etc and then when finished the user adds what's pending.
It also allows more complex things e.g: for a given step we may have multiple blocks that implement the step in different ways, the checkout is valid if one block implementing that step is valid. The validation logic could even depend on the attributes of nested blocks.

The validation logic still allows things like removing a block that should not be removed (although the parent becomes invalid). But what if we expand inner blocks to besides template block also allow the parent block to pass some kind of function that given a block id of a child block defines if the block can be removed or not? This would allow us to have advanced inner block lockings and implement very complex rules.

Basically, the main question is should be locking be the property of the context of a block (e.g: its parent) as it is now, or should it also be a property of a block? If it is just a property of the context of a block I guess we can expand InnerBlocks to allow more complex lockings than the templateLock we have now.

@mikejolley
Copy link
Contributor

I think I can chime in a little about the benefits of this being block-level @jorgefilipecosta

Basically, the main question is should be locking be the property of the context of a block (e.g: its parent) as it is now, or should it also be a property of a block?

Both :)

The locking for the move is also something that does not depend only on the block, e.g: I may move a block by moving its neighbors.

This is only partly true. You can indeed move unlocked neighbours, but you cannot move the original locked blocks. So for instance, take a scenario where we have the following blocks:

<Step1>
<Step2>
<Step3>
<InsertedBlock>

If the steps are locked, you can move the InsertedBlock anywhere between them, but you cannot change the original step order. This is what we're after.

For blocks, with complex logic like a checkout, I imagine these blocks may need to have some type of validation. E.g: the container block verifies if all the step blocks are inside and if not shows a red alert saying block x is missing and maybe even provides a button that allows the user to press it and fix the situation (e.g: inserts the missing block). This allows the user do everything while building its checkout e.g: delete things etc and then when finished the user adds what's pending.

We're hoping to prevent this level of control so the experience is curated and cannot be "broken" by a merchant, whilst enabling themselves and extension developers the ability to add components where needed. Validation is still something we may have to do in the interim, but this is not ideal in our use case. It would be a very poor UI to show move and remove controls on Blocks if using those controls immediately invalidate the parent.

The block can be inserted normally using the inserter but then the user can not remove it anymore. Is this a case we want?

I'm not sure if this is a problem if the API is clear this is a side-effect. We're able to prevent the insertion of these locked blocks in other ways, and just define them in the default template.

@senadir
Copy link
Contributor Author

senadir commented Jun 9, 2021

Hey there Jorge!

I think Mike provided a good answer to all of your questions, but let me expand a bit on a couple of them.

The locking for the move is also something that does not depend only on the block, e.g: I may move a block by moving its neighbors.

This is something I did consider, and so did @jameskoster and @mtias, here's Matias respond around it:

TemplateLocking already handles the disabling of moving for all children. I don't think it's much of an issue that you can move something by moving the things around it, that will always be the case. This is more about the peace of mind and reduced overhead of controls that you don't need.

Alongside the use case that Mike provided.

The block can be inserted normally using the inserter but then the user can not remove it anymore. Is this a case we want?

This is also something I debated with myself and didn't get to a clear solution yet.

According to the original issue, and our use case, those type of blocks are going to be pre-inserted into templates or innerBlocks structures, patterns and so on.

This might not always be the case, and we to consider other possible scenarios.

  • We can limit the usage of lock.remove and lock.move to blocks that have a defined parent block. This would limit usage to other type of templates and FSE. tbh, I have no idea what are the possible use cases for this in FSE so I might be missing a lot of context.

To me, it seems if a block can be moved or removed is not a property of the block but of the context where it is e.g: parent block or post type template.

It is indeed both as Mike mentioned, parent locks is a global attribute to all innerBlocks and per block is an exception or an enhancement to that lock.

If it is just a property of the context of a block I guess we can expand InnerBlocks to allow more complex lockings than the templateLock we have now.

That was my original plan actually but I did run into limitations with it, after a discussion with Matias we decided that having it on the block level is the best approach.

@jorgefilipecosta
Copy link
Member

Thank you @mikejolley and @senadir for detailing the use case and for all the insights provided.
Now I understand better the use-case and I agree it is something we need.

If it is just a property of the context of a block I guess we can expand InnerBlocks to allow more complex lockings than the templateLock we have now.

That was my original plan actually but I did run into limitations with it, after a discussion with Matias we decided that having it on the block level is the best approach.

Could you provide more details into the limitations that this approach had?

Using block supports also has some limitations e.g: the locking affects all block instances no matter where the block is nested on, and the locking could not be dynamically changed.
I think soon we will have requests for cases like I want block A to be locked if inside block Y but unlocked when inside block X, or if the user selects option Z on block A the block should become locked and impossible to move.

I wonder if we could implement the equivalent functionality using attributes e.g: a lock object or lockMove, lockDelete attributes when true the block is locked. This would allow the locking to be totally dynamic e.g: the parent block can control the locking of the children. Is more powerful as we can have the same block locked in some cases and unlocked in others. It seems to fit your use cases very well the parent block can configure the locking of the children using the template that pre-inserts the blocks.

Basically, the main question is should be locking be the property of the context of a block (e.g: its parent) as it is now, or should it also be a property of a block?

Both :)

The attribute approach also goes in the direction of locking being dependent on the block and on the context. Using an attribute locking can be changed dynamically from the block, the parent, children, and or siblings.

Any thoughts on this direction?

In either direction, I guess initially we should go with an "__experimental" to allow us to try it and make sure it is the right choice.

@gziolo
Copy link
Member

gziolo commented Jun 18, 2021

The attribute approach also goes in the direction of locking being dependent on the block and on the context. Using an attribute locking can be changed dynamically from the block, the parent, children, and or siblings.

Just wanted to share that we already use in several places block attributes to control the block inside the editor rather than the content or visual appearance as folks would assume. Some examples:

"templateLock": {
"enum": [ "all", "insert", false ]
}

"templateLock": {
"enum": [ "all", "insert", false ]
}

There is an open PR #31326 that proposed adding templateLock and allowedBlocks attributes to the Cover block.

It even looks like allowedBlocks attribute was added for the Column block with #25183 but I don't see it present in the codebase. @aristath, any idea what happened with the commit? I figured out that something went wrong and it is now tracked in #25778.

@aristath
Copy link
Member

It even looks like allowedBlocks attribute was added for the Column block with #25183 but I don't see it present in the codebase. @aristath, any idea what happened with the commit?

Oh I remember that one... #25183 was merged by mistake while trying to resolve a merge conflict 😬. It was immediately reverted (actually I think there was a hard-push on trunk within seconds in coordination with Riad) and the PR was re-submitted in #25778, so you can ignore it here 👍

@jorgefilipecosta
Copy link
Member

Just wanted to share that we already use in several places block attributes to control the block inside the editor rather than the content or visual appearance as folks would assume. Some examples:

What we have now are attributes to control the locking of descendants, and allowed blocks via the parent attributes.

What I'm suggesting expanding that attribute mechanism and have attributes to control the ability to remove and move a specific block.

@senadir
Copy link
Contributor Author

senadir commented Jun 23, 2021

Could you provide more details into the limitations that this approach had?

One limitation is that locking would be applied on all children so validation would run on two dimensions:
1- A check on each block.
2- A check to see if that block belongs to the template.

It also doesn't offer the same granular control provided by block-per-block locking.

Using block supports also has some limitations e.g: the locking affects all block instances no matter where the block is nested on, and the locking could not be dynamically changed.
I think soon we will have requests for cases like I want block A to be locked if inside block Y but unlocked when inside block X, or if the user selects option Z on block A the block should become locked and impossible to move.

This PR only covers the "system level" locking mentioned in the original PR, it probably makes more sense for blocks who have multiple: false (the case for all of our blocks). Those blocks might be pre-inserted into a page or a template.

A follow up PR would introduce the "user level" locking which would work on attributes themselves and would fulfill the needs you mention.

For locking attributes to be dynamically inserted into the block, I assume we still need some sort of support level?

So the general idea I have is for lock to have three values true, false, "user".

  • false would be no locking (regular behaviour).
  • true would be the current behaviour, locking all instances.
  • "user" would be the attributes approach that allows locking and unlocking.

The only limitation I see here I that you can't definitely lock a block instance, having it on attributes would also make it switchable.

If we can make an instance definitely locked (with an attribute you can't change) then we might be able to skip the system locking level.

@jorgefilipecosta
Copy link
Member

I think having locking attributes in a block and allowing the user to lock and unlock are two different things. E.g: I may want to use the attribute system so locking can be controlled by templates (like we have for allowed blocks and templateLock in columns and groups) but I may not want to allow the user to change the locking.

If we can make an instance definitely locked (with an attribute you can't change) then we might be able to skip the system locking level.

I guess we can have a block definitively locked by setting the locking attributes to be locked and not enabling the UI system. The user would have no way to change the attribute.

We may not need a supports system at all the attributes can be explicitly added by the block as we do for templateLock:

		"templateLock": {
			"enum": [ "all", "insert", false ]
		}

In this case, it is even simpler as everything is a boolean. Adding an attribute is almost the same effort as adding a supports flag. It would also allow each block to define the default value of the attribute, e.g: if it is locked by default.

Regarding the ability for the user to configure locking it could also be another attribute. If the locking is user-configurable for a block and the fact it is user-configurable is static it would never be possible to reliable lock that block inside a CPT template (the user could just unlock it). If the fact the user can change the locking attributes is another attribute, on a CPT template we could just set that attribute to false.

In summary, what I'm proposing are three attributes whose names we can decide:

  • lockDelete - if true the block could not be deleted. Maybe or may not be changed by the user.
  • lockMove - if true the block could not be move. Maybe or may not be changed by the user.
  • lockIsUserConfigurable - if true the user has a UI that allows to lock or unlock the block. This attribute itself can never be changed by the user.

Blocks can set default values for these attributes (using default:), templates CPT's can also freely change these attributes.
It may be even better if we can just have a single attribute e.g: lock that is an object with three properties { delete, move, isUserConfigurable }.

@senadir what are your thoughts on this approach? Do you think it would work for your use case?

@senadir
Copy link
Contributor Author

senadir commented Jul 6, 2021

I'm not following the development of Gutenberg closely but how common are standard predefined attributes? In my experience, all extra features (like typography and colors) are unlocked via the supports property, right?

In both cases, I do agree this solution is the preferable one, but it does increase the number of attributes to N+1. N being what's being locked (moving, removing, editing) and +1 is to unlock.

This also limits the user-configurable property to be all including, e.g allowing locking and unlocking the moving would also unlock removing, same for editing.

This is not a problem for me because I don't want for my blocks to be user-configurable but other developers might come back later and want to have more gradual control which would make this 2N.

@senadir
Copy link
Contributor Author

senadir commented Sep 6, 2021

@jorgefilipecosta fixed test. Should we go and merge?

Comment on lines +13 to +46
const props = {
isFirst: false,
isLast: true,
canMove: true,
numberOfBlocks: 2,
firstIndex: 1,

onMoveDown: jest.fn(),
onMoveUp: jest.fn(),
onLongPress: jest.fn(),
onMoveDown: jest.fn(),
onMoveUp: jest.fn(),
onLongPress: jest.fn(),

rootClientId: '',
isStackedHorizontally: true,
},
} );
rootClientId: '',
isStackedHorizontally: true,
};
const wrapper = shallow( <BlockMover { ...props } /> );
expect( wrapper ).toBeTruthy();
} );

it( 'should match snapshot', () => {
const wrapper = shallow( <BlockMover />, {
context: {
isFirst: false,
isLast: true,
isLocked: false,
numberOfBlocks: 2,
firstIndex: 1,
const props = {
isFirst: false,
isLast: true,
canMove: true,
numberOfBlocks: 2,
firstIndex: 1,

onMoveDown: jest.fn(),
onMoveUp: jest.fn(),
onLongPress: jest.fn(),
onMoveDown: jest.fn(),
onMoveUp: jest.fn(),
onLongPress: jest.fn(),

rootClientId: '',
isStackedHorizontally: true,
},
} );
rootClientId: '',
isStackedHorizontally: true,
};
const wrapper = shallow( <BlockMover { ...props } /> );
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those tests were never running correctly, they're fixed now.

@mtias
Copy link
Member

mtias commented Sep 13, 2021

Thanks for this initial rollout! Do we need to capture pending UI tasks, like showing the lock icon in some circumstances in the toolbar and list view?

@senadir
Copy link
Contributor Author

senadir commented Sep 13, 2021

Do we need to capture pending UI tasks, like showing the lock icon in some circumstances in the toolbar and list view?

I was going to use #29864 as a reference for the remaining tasks, I will probably add a comment there with what we need.

I started working on lock icon showing in list view last week but the interactions are not obvious yet, some questions:

  • Should we show a lock icon in list view when a block is remove locked, move locked, both, or any one of them.
  • Should it be interactive? would clicking it show a modal or unlock or some interaction?
  • Same questions for showing in toolbar.

I'm assuming, at this state, we can probably answer the first question (for both list view and toolbar). We still don't have an API yet to tell the editor that locking is toggleable.

There’s also a bug I spotted last week that I should fix as well, you can drag a block from the list view, your dragging won't persist and the block won't land in its spot, but the interaction is still there and should be disabled.

Should I create the todo list anyway and let the interaction discovery be part of it?

@mtias
Copy link
Member

mtias commented Sep 13, 2021

Yes, we should show lock for any of the locked states. The plan was to show the lock icon only for cases where the user can interact and toggle it off. If a user cannot change the state it won't show up.

A todo would be great! We can incorporate it into #29864.

Copy link
Member

@jorgefilipecosta jorgefilipecosta left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for bringing this task to the finish line @senadir and for all the interactions. This system seems really flexible and powerful let's see what feedback the community gives.

return settings;
}

addFilter( 'blocks.registerBlockType', 'core/lock/addAttribute', addAttribute );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc: @senadir


**Block-level locking is an experimental feature that may be removed or change anytime.**
```js
attributes: {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool work.

It looks like the documentation needs to be fixed. It differs from the PR's description and feels broken here:

Screen Shot 2021-10-06 at 21 21 33

@gziolo
Copy link
Member

gziolo commented Oct 7, 2021

I'm a bit late for the party. What's the plan with templateLock attribute on existing blocks?:

"templateLock": {
"enum": [ "all", "insert", false ]
}

"templateLock": {
"enum": [ "all", "insert", false ]
}

Should we leave it as is or refactor to the new API?

@senadir
Copy link
Contributor Author

senadir commented Oct 7, 2021

templateLock on those blocks apply to children of that block, not the actual block, so I see them working together, not replacing one or the other.

@Lovor01
Copy link
Contributor

Lovor01 commented Oct 15, 2021

I believe placing lock feature in attributes was a poor choice. It forces developer remember a solution that is not logical and he cannot use lock as a name of attribute for his own needs. Lock feature is a good feature, but I would place it among other block props like clientId or isSelected with a pair: (isLocked, setLocking) and not inside attributes.

Or even better proposal: place it in a block editor store and not block itself, and it should be reachable by utilising select / dispatch . After all, block editor should control block locking and it makes more sense to keep it there.

I would also add "allowLocking" property in supports object - which would in block definition indicate if it could be locked at all. This could be true by default.

@jorgefilipecosta
Copy link
Member

Lock feature is a good feature, but I would place it among other block props like clientId or isSelected with a pair: (isLocked, setLocking) and not inside attributes.

Using locking as a property and not as an attribute means locking would not be persisted when the editor loads. We would also lose flexibility in setting locking from templates as we set all the other properties.

@senadir
Copy link
Contributor Author

senadir commented Nov 15, 2021

@Lovor01 I don't know how would locking be of any value to live in block Props, but I do agree that locking is an instance-based setting, not a block type settings, so it either lives there or in attributes, but not in support because support is a block type-based option (it applies to all instance of said block).
It's true that this feature extends the block editor interface and should live there, not in attributes, but you would lose statically implementing this and persisting it. At WooCommerce, we use block forcing on the frontend as well and it allows us (among other tools) to force render/force insert certain blocks at frontend and editor, without having the merchant inserting them.

@amarinediary
Copy link
Contributor

Just wanted to share my thoughts on dynamics between the global template_lock="all" option in correlation to the core/group block.

Logically I feel like the core/group inner blocks should NOT be affected by the global lock, which would leave the user a more broad customization.

I don't know if this is the intended behavior planned for 5.9. Please don't hesitate to share your point of view.

@senadir
Copy link
Contributor Author

senadir commented Jan 6, 2022

@amarinediary do you mean in correlation with block level locking? block level locking (which handles the actual block, not its children) supersedes the templateLock value.

@amarinediary
Copy link
Contributor

amarinediary commented Jan 6, 2022

@senadir Yes exactly. As I understood block level locking will only affect the block itself not the children.

But, Does level block locking will finally overwrite template locking in 5.9 ? Taking core/group and the templateLock option as an example. Currently, setting a template lock all will overwrite the core/group lock insert/false option which, doesn't seems logical.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Block API API that allows to express the block paradigm. [Feature] Nested / Inner Blocks Anything related to the experience of nested/inner blocks inside a larger container, like Group or P Needs Dev Note Requires a developer note for a major WordPress release cycle [Package] Block editor /packages/block-editor [Type] Enhancement A suggestion for improvement.
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

None yet

10 participants