-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
23 changed files
with
1,097 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
{ | ||
"$schema": "https://schemas.wp.org/trunk/block.json", | ||
"apiVersion": 3, | ||
"name": "core/checklist-item", | ||
"title": "List item", | ||
"category": "text", | ||
"parent": [ "core/checklist" ], | ||
"allowedBlocks": [ "core/checklist" ], | ||
"description": "Create a list item.", | ||
"textdomain": "default", | ||
"attributes": { | ||
"placeholder": { | ||
"type": "string" | ||
}, | ||
"content": { | ||
"type": "rich-text", | ||
"source": "rich-text", | ||
"selector": "li", | ||
"__experimentalRole": "content" | ||
}, | ||
"completed": { | ||
"type": "string" | ||
} | ||
}, | ||
"supports": { | ||
"className": false, | ||
"__experimentalSelector": ".wp-block-list > li", | ||
"splitting": true, | ||
"spacing": { | ||
"margin": true, | ||
"padding": true, | ||
"__experimentalDefaultControls": { | ||
"margin": false, | ||
"padding": false | ||
} | ||
}, | ||
"typography": { | ||
"fontSize": true, | ||
"lineHeight": true, | ||
"__experimentalFontFamily": true, | ||
"__experimentalFontWeight": true, | ||
"__experimentalFontStyle": true, | ||
"__experimentalTextTransform": true, | ||
"__experimentalTextDecoration": true, | ||
"__experimentalLetterSpacing": true, | ||
"__experimentalDefaultControls": { | ||
"fontSize": true | ||
} | ||
}, | ||
"interactivity": { | ||
"clientNavigation": true | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import React from 'react'; | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { | ||
RichText, | ||
useBlockProps, | ||
useInnerBlocksProps, | ||
BlockControls, | ||
store as blockEditorStore, | ||
} from '@wordpress/block-editor'; | ||
import { isRTL, __ } from '@wordpress/i18n'; | ||
import { ToolbarButton, CheckboxControl } from '@wordpress/components'; | ||
import { | ||
formatOutdent, | ||
formatOutdentRTL, | ||
formatIndentRTL, | ||
formatIndent, | ||
} from '@wordpress/icons'; | ||
import { useMergeRefs } from '@wordpress/compose'; | ||
import { useSelect } from '@wordpress/data'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { | ||
useEnter, | ||
useSpace, | ||
useIndentListItem, | ||
useOutdentListItem, | ||
useMerge, | ||
} from './hooks'; | ||
|
||
export function IndentUI({ clientId }) { | ||
const indentListItem = useIndentListItem(clientId); | ||
const outdentListItem = useOutdentListItem(); | ||
const { canIndent, canOutdent } = useSelect( | ||
(select) => { | ||
const { getBlockIndex, getBlockRootClientId, getBlockName } = | ||
select(blockEditorStore); | ||
return { | ||
canIndent: getBlockIndex(clientId) > 0, | ||
canOutdent: | ||
getBlockName( | ||
getBlockRootClientId(getBlockRootClientId(clientId)) | ||
) === 'core/checklist-item', | ||
}; | ||
}, | ||
[clientId] | ||
); | ||
|
||
return ( | ||
<> | ||
<ToolbarButton | ||
icon={isRTL() ? formatOutdentRTL : formatOutdent} | ||
title={__('Outdent')} | ||
describedBy={__('Outdent list item')} | ||
disabled={!canOutdent} | ||
onClick={() => outdentListItem()} | ||
/> | ||
<ToolbarButton | ||
icon={isRTL() ? formatIndentRTL : formatIndent} | ||
title={__('Indent')} | ||
describedBy={__('Indent list item')} | ||
isDisabled={!canIndent} | ||
onClick={() => indentListItem()} | ||
/> | ||
</> | ||
); | ||
} | ||
|
||
export default function ListItemEdit({ | ||
attributes, | ||
setAttributes, | ||
clientId, | ||
mergeBlocks, | ||
}) { | ||
const { placeholder, content } = attributes; | ||
const blockProps = useBlockProps(); | ||
const innerBlocksProps = useInnerBlocksProps(blockProps, { | ||
renderAppender: false, | ||
__unstableDisableDropZone: true, | ||
}); | ||
const useEnterRef = useEnter({ content, clientId }); | ||
const useSpaceRef = useSpace(clientId); | ||
const onMerge = useMerge(clientId, mergeBlocks); | ||
return ( | ||
<> | ||
<li {...innerBlocksProps}> | ||
<RichText | ||
ref={useMergeRefs([useEnterRef, useSpaceRef])} | ||
identifier="content" | ||
tagName="div" | ||
onChange={(nextContent) => | ||
setAttributes({ content: nextContent }) | ||
} | ||
value={content} | ||
aria-label={__('List text')} | ||
placeholder={placeholder || __('List')} | ||
onMerge={onMerge} | ||
/> | ||
<CheckboxControl | ||
checked={attributes.completed === 'done'} | ||
indeterminate={attributes.completed === 'progress'} | ||
onChange={() => { | ||
if (!attributes.completed) { | ||
setAttributes({ completed: 'progress' }); | ||
} else if (attributes.completed === 'progress') { | ||
setAttributes({ completed: 'done' }); | ||
} else { | ||
setAttributes({ completed: false }); | ||
} | ||
}} | ||
/> | ||
{innerBlocksProps.children} | ||
</li> | ||
<BlockControls group="block"> | ||
<IndentUI clientId={clientId} /> | ||
</BlockControls> | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export { default as useOutdentListItem } from './use-outdent-list-item'; | ||
export { default as useIndentListItem } from './use-indent-list-item'; | ||
export { default as useEnter } from './use-enter'; | ||
export { default as useSpace } from './use-space'; | ||
export { default as useSplit } from './use-split'; | ||
export { default as useMerge } from './use-merge'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { | ||
createBlock, | ||
getDefaultBlockName, | ||
cloneBlock, | ||
} from '@wordpress/blocks'; | ||
import { useRef } from '@wordpress/element'; | ||
import { useRefEffect } from '@wordpress/compose'; | ||
import { ENTER } from '@wordpress/keycodes'; | ||
import { useSelect, useDispatch } from '@wordpress/data'; | ||
import { store as blockEditorStore } from '@wordpress/block-editor'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import useOutdentListItem from './use-outdent-list-item'; | ||
|
||
export default function useEnter(props) { | ||
const { replaceBlocks, selectionChange } = useDispatch(blockEditorStore); | ||
const { getBlock, getBlockRootClientId, getBlockIndex, getBlockName } = | ||
useSelect(blockEditorStore); | ||
const propsRef = useRef(props); | ||
propsRef.current = props; | ||
const outdentListItem = useOutdentListItem(); | ||
return useRefEffect((element) => { | ||
function onKeyDown(event) { | ||
if (event.defaultPrevented || event.keyCode !== ENTER) { | ||
return; | ||
} | ||
const { content, clientId } = propsRef.current; | ||
if (content.length) { | ||
return; | ||
} | ||
event.preventDefault(); | ||
const canOutdent = | ||
getBlockName( | ||
getBlockRootClientId( | ||
getBlockRootClientId(propsRef.current.clientId) | ||
) | ||
) === 'core/checklist-item'; | ||
if (canOutdent) { | ||
outdentListItem(); | ||
return; | ||
} | ||
// Here we are in top level list so we need to split. | ||
const topParentListBlock = getBlock(getBlockRootClientId(clientId)); | ||
const blockIndex = getBlockIndex(clientId); | ||
const head = cloneBlock({ | ||
...topParentListBlock, | ||
innerBlocks: topParentListBlock.innerBlocks.slice( | ||
0, | ||
blockIndex | ||
), | ||
}); | ||
const middle = createBlock(getDefaultBlockName()); | ||
// Last list item might contain a `list` block innerBlock | ||
// In that case append remaining innerBlocks blocks. | ||
const after = [ | ||
...(topParentListBlock.innerBlocks[blockIndex].innerBlocks[0] | ||
?.innerBlocks || []), | ||
...topParentListBlock.innerBlocks.slice(blockIndex + 1), | ||
]; | ||
const tail = after.length | ||
? [ | ||
cloneBlock({ | ||
...topParentListBlock, | ||
innerBlocks: after, | ||
}), | ||
] | ||
: []; | ||
replaceBlocks( | ||
topParentListBlock.clientId, | ||
[head, middle, ...tail], | ||
1 | ||
); | ||
// We manually change the selection here because we are replacing | ||
// a different block than the selected one. | ||
selectionChange(middle.clientId); | ||
} | ||
|
||
element.addEventListener('keydown', onKeyDown); | ||
return () => { | ||
element.removeEventListener('keydown', onKeyDown); | ||
}; | ||
}, []); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { useCallback } from '@wordpress/element'; | ||
import { useSelect, useDispatch } from '@wordpress/data'; | ||
import { store as blockEditorStore } from '@wordpress/block-editor'; | ||
import { createBlock, cloneBlock } from '@wordpress/blocks'; | ||
|
||
export default function useIndentListItem(clientId) { | ||
const { replaceBlocks, selectionChange, multiSelect } = | ||
useDispatch(blockEditorStore); | ||
const { | ||
getBlock, | ||
getPreviousBlockClientId, | ||
getSelectionStart, | ||
getSelectionEnd, | ||
hasMultiSelection, | ||
getMultiSelectedBlockClientIds, | ||
} = useSelect(blockEditorStore); | ||
return useCallback(() => { | ||
const _hasMultiSelection = hasMultiSelection(); | ||
const clientIds = _hasMultiSelection | ||
? getMultiSelectedBlockClientIds() | ||
: [clientId]; | ||
const clonedBlocks = clientIds.map((_clientId) => | ||
cloneBlock(getBlock(_clientId)) | ||
); | ||
const previousSiblingId = getPreviousBlockClientId(clientId); | ||
const newListItem = cloneBlock(getBlock(previousSiblingId)); | ||
// If the sibling has no innerBlocks, create a new `list` block. | ||
if (!newListItem.innerBlocks?.length) { | ||
newListItem.innerBlocks = [createBlock('core/checklist')]; | ||
} | ||
// A list item usually has one `list`, but it's possible to have | ||
// more. So we need to preserve the previous `list` blocks and | ||
// merge the new blocks to the last `list`. | ||
newListItem.innerBlocks[ | ||
newListItem.innerBlocks.length - 1 | ||
].innerBlocks.push(...clonedBlocks); | ||
|
||
// We get the selection start/end here, because when | ||
// we replace blocks, the selection is updated too. | ||
const selectionStart = getSelectionStart(); | ||
const selectionEnd = getSelectionEnd(); | ||
// Replace the previous sibling of the block being indented and the indented blocks, | ||
// with a new block whose attributes are equal to the ones of the previous sibling and | ||
// whose descendants are the children of the previous sibling, followed by the indented blocks. | ||
replaceBlocks([previousSiblingId, ...clientIds], [newListItem]); | ||
if (!_hasMultiSelection) { | ||
selectionChange( | ||
clonedBlocks[0].clientId, | ||
selectionEnd.attributeKey, | ||
selectionEnd.clientId === selectionStart.clientId | ||
? selectionStart.offset | ||
: selectionEnd.offset, | ||
selectionEnd.offset | ||
); | ||
} else { | ||
multiSelect( | ||
clonedBlocks[0].clientId, | ||
clonedBlocks[clonedBlocks.length - 1].clientId | ||
); | ||
} | ||
|
||
return true; | ||
}, [clientId]); | ||
Check warning on line 66 in src/blocks/list-item/hooks/use-indent-list-item.js
|
||
} |
Oops, something went wrong.