Skip to content
3 changes: 3 additions & 0 deletions src/block-components/block-link/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Edit } from './edit'
* External dependencies
*/
import { Link } from '~stackable/components'
import { CustomAttributes } from '../custom-attributes'

export const BlockLink = () => {
return null
Expand All @@ -21,9 +22,11 @@ BlockLink.Content = props => {
if ( ! attributes.blockLinkUrl ) {
return null
}
const customAttributes = CustomAttributes.getCustomAttributes( attributes, 'blockLinkCustomAttributes' )

return (
<Link.Content
{ ...customAttributes }
className="stk-block-link stk--transparent-overlay"
href={ href || attributes.blockLinkUrl }
target={ attributes.blockLinkNewTab ? '_blank' : '' }
Expand Down
30 changes: 30 additions & 0 deletions src/block-components/custom-attributes/__test__/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { CustomAttributes } from '..'

describe( 'CustomAttributes.getCustomAttributes', () => {
it( 'resolves a named custom attribute list', () => {
const attributes = {
text: 'Product details',
linkCustomAttributes: [
[ 'aria-label', 'Open %text%' ],
[ 'data-link-id', 'details' ],
],
}

expect( CustomAttributes.getCustomAttributes( attributes, 'linkCustomAttributes' ) ).toEqual( {
'aria-label': 'Open Product details',
'data-link-id': 'details',
} )
} )

it( 'does not resolve the custom attribute list into itself', () => {
const attributes = {
linkCustomAttributes: [
[ 'data-value', '%linkCustomAttributes%' ],
],
}

expect( CustomAttributes.getCustomAttributes( attributes, 'linkCustomAttributes' ) ).toEqual( {
'data-value': '%linkCustomAttributes%',
} )
} )
} )
10 changes: 8 additions & 2 deletions src/block-components/custom-attributes/attributes.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
export const addAttributes = attrObject => {
export const addAttributes = ( attrObject, options = {} ) => {
const {
attrNameTemplate = '%s',
versionAdded = '3.0.0',
} = options

attrObject.add( {
attributes: {
customAttributes: {
type: 'array',
default: [],
},
},
versionAdded: '3.0.0',
attrNameTemplate,
versionAdded,
versionDeprecated: '',
} )
}
50 changes: 47 additions & 3 deletions src/block-components/custom-attributes/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/
import { i18n } from 'stackable'
import {
ControlSeparator,
CustomAttributesControl,
InspectorAdvancedControls,
PanelAdvancedSettings,
Expand All @@ -17,10 +18,36 @@ import { useBlockAttributesContext, useBlockSetAttributesContext } from '~stacka
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n'
import { ExternalLink } from '@wordpress/components'
import { INVALID_HTML_ATTRIBUTES } from '.'

export const Edit = () => {
const customAttributes = useBlockAttributesContext( attributes => attributes.customAttributes )
const CustomAttributesHelp = ( { hasLink } ) => {
return <>
<p>{ __( 'Add custom HTML attributes to enhance the functionality of your block or link. For example:', i18n ) }</p>
<code>data-id=&quot;my-title&quot;</code>
{ hasLink && <>
<br />
<code>aria-label=&quot;Learn more&quot;</code>
</> }
<br />
<br />
<ExternalLink
href="https://docs.wpstackable.com/article/461-how-to-use-custom-attributes?utm_source=inspector&utm_campaign=learnmore&utm_medium=gutenberg"
target="_docs"
>
{ __( 'Learn more about Custom Attributes', i18n ) }
</ExternalLink>
</>
}

export const Edit = props => {
const {
customAttributes,
linkCustomAttributes,
} = useBlockAttributesContext( attributes => ( {
customAttributes: attributes.customAttributes,
linkCustomAttributes: props.linkAttributeName ? attributes[ props.linkAttributeName ] : undefined,
} ) )
const setAttributes = useBlockSetAttributesContext()

return (
Expand All @@ -31,12 +58,29 @@ export const Edit = () => {
responsive={ false }
>
<CustomAttributesControl
label={ __( 'Custom Attributes', i18n ) }
label={ props.linkAttributeName ? __( 'Block Custom Attributes', i18n ) : __( 'Custom Attributes', i18n ) }
value={ customAttributes }
placeholder={ __( 'data-id="my-title"', i18n ) }
invalidHtmlAttributes={ INVALID_HTML_ATTRIBUTES }
onChange={ customAttributes => setAttributes( { customAttributes } ) }
help={ __( 'Attributes placed here will be added to the block element. Separate multiple attributes with a space.', i18n ) }
/>
{ props.linkAttributeName && <CustomAttributesControl
label={ __( 'Link Custom Attributes', i18n ) }
value={ linkCustomAttributes }
placeholder={ __( 'aria-label="Learn more"', i18n ) }
invalidHtmlAttributes={ INVALID_HTML_ATTRIBUTES }
onChange={ linkCustomAttributes => setAttributes( { [ props.linkAttributeName ]: linkCustomAttributes } ) }
help={ __( 'Attributes placed here will be added to the anchor tag if this block has a link. Separate multiple attributes with a space.', i18n ) }
/>
}
<ControlSeparator />
<CustomAttributesHelp hasLink />
</PanelAdvancedSettings>
</InspectorAdvancedControls>
)
}

Edit.defaultProps = {
linkAttributeName: '',
}
9 changes: 5 additions & 4 deletions src/block-components/custom-attributes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@ CustomAttributes.addAttributes = addAttributes

// CustomAttributes.Style = Style

CustomAttributes.getCustomAttributes = attributes => {
if ( ! Array.isArray( attributes.customAttributes ) || attributes.customAttributes.length === 0 ) {
CustomAttributes.getCustomAttributes = ( attributes, attributeName = 'customAttributes' ) => {
if ( ! Array.isArray( attributes[ attributeName ] ) || attributes[ attributeName ].length === 0 ) {
return {}
}

const customAttributes = Object.fromEntries( attributes.customAttributes )
const customAttributes = Object.fromEntries( attributes[ attributeName ] )
const invalidBlockAttributes = [ ...INVALID_BLOCK_ATTRIBUTES, attributeName ]
Object.keys( customAttributes ).forEach( key => {
// Unescape the value, since we're storing them as escaped strings.
let value = unescape( customAttributes[ key ] )
Expand All @@ -44,7 +45,7 @@ CustomAttributes.getCustomAttributes = attributes => {
dynamicAttributeMatch.forEach( _match => {
const match = _match.substr( 1, _match.length - 2 )
if (
! INVALID_BLOCK_ATTRIBUTES.includes( match ) &&
! invalidBlockAttributes.includes( match ) &&
attributes.hasOwnProperty( match ) &&
! isUndefined( attributes[ match ] )
) {
Expand Down
13 changes: 11 additions & 2 deletions src/block-components/custom-attributes/readme.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
# Custom Attributes Block Component

Gives the ability to add your own custom attributes which will be placed on the block.
Gives the ability to add custom attributes to a block or its generated link.

## Usage

The custom attribute implementation is already handled by the Block Div Block Component
The block custom attribute implementation is already handled by the Block Div Block Component.
For a link, render the attributes with `CustomAttributes.getCustomAttributes( attributes, 'linkCustomAttributes' )` or its block-link equivalent.

### Adding inspector controls in `edit.js`

```
<CustomAttributes.InspectorControls />
```

For a block with a configurable link, pass the link attribute name to add a second field below the block attributes.

```
<CustomAttributes.InspectorControls linkAttributeName="linkCustomAttributes" />
```

### Adding attributes in `schema.js`

```
CustomAttributes.addAttributes( attrObject )
```

Link attributes are registered through `Link.addAttributes` or `BlockLink.addAttributes`.
7 changes: 7 additions & 0 deletions src/block-components/helpers/link/attributes.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { addAttributes as addCustomAttributes } from '../../custom-attributes/attributes'

export const linkAttributes = ( selector = 'a' ) => {
return {
hasLink: {
Expand Down Expand Up @@ -50,4 +52,9 @@ export const addLinkAttributes = ( attrObject, attrNameTemplate = '%s', selector
versionAdded: '3.0.0',
versionDeprecated: '',
} )

addCustomAttributes( attrObject, {
attrNameTemplate,
versionAdded: '3.20.4',
} )
}
4 changes: 3 additions & 1 deletion src/block-components/link/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/
import { addAttributes } from './attributes'
import { Edit } from './edit'
import { CustomAttributes } from '../custom-attributes'

/*+
* External dependencies
Expand Down Expand Up @@ -42,13 +43,15 @@ Link.Content = props => {
linkProps = {},
attributes,
} = props
const customAttributes = CustomAttributes.getCustomAttributes( attributes, 'linkCustomAttributes' )

if ( ! attributes.linkHasLink ) {
return props.children
}

return (
<LinkComponent.Content
{ ...customAttributes }
{ ...linkProps }
className={ props.className }
href={ attributes.linkUrl || undefined }
Expand All @@ -64,4 +67,3 @@ Link.Content = props => {
Link.InspectorControls = Edit

Link.addAttributes = addAttributes

2 changes: 1 addition & 1 deletion src/block/call-to-action/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ const InspectorControls = memo( () => {
<Transform.InspectorControls />
<Separator.InspectorControls />
<EffectsAnimations.InspectorControls />
<CustomAttributes.InspectorControls />
<CustomAttributes.InspectorControls linkAttributeName="blockLinkCustomAttributes" />
<CustomCSS.InspectorControls mainBlockClass="stk-block-call-to-action" />
<Responsive.InspectorControls />
<ConditionalDisplay.InspectorControls />
Expand Down
2 changes: 1 addition & 1 deletion src/block/card/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ const InspectorControls = memo( props => {
<Advanced.InspectorControls />
<Transform.InspectorControls />
<EffectsAnimations.InspectorControls />
<CustomAttributes.InspectorControls />
<CustomAttributes.InspectorControls linkAttributeName="blockLinkCustomAttributes" />
<CustomCSS.InspectorControls mainBlockClass="stk-block-card" />
<Responsive.InspectorControls />
<ConditionalDisplay.InspectorControls />
Expand Down
2 changes: 1 addition & 1 deletion src/block/column/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ const InspectorControls = memo( props => {
<Advanced.InspectorControls />
<Transform.InspectorControls />
<EffectsAnimations.InspectorControls />
<CustomAttributes.InspectorControls />
<CustomAttributes.InspectorControls linkAttributeName="blockLinkCustomAttributes" />
<CustomCSS.InspectorControls mainBlockClass="stk-block-column" />
<Responsive.InspectorControls />
<ConditionalDisplay.InspectorControls />
Expand Down
2 changes: 1 addition & 1 deletion src/block/icon-box/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ const InspectorControls = memo( () => {
<Advanced.InspectorControls />
<Transform.InspectorControls />
<EffectsAnimations.InspectorControls />
<CustomAttributes.InspectorControls />
<CustomAttributes.InspectorControls linkAttributeName="blockLinkCustomAttributes" />
<CustomCSS.InspectorControls mainBlockClass="stk-block-icon-box" />
<Responsive.InspectorControls />
<ConditionalDisplay.InspectorControls />
Expand Down
2 changes: 1 addition & 1 deletion src/block/icon/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ const InspectorControls = memo( props => {
<Transform.InspectorControls />

<EffectsAnimations.InspectorControls />
<CustomAttributes.InspectorControls />
<CustomAttributes.InspectorControls linkAttributeName="linkCustomAttributes" />
<CustomCSS.InspectorControls mainBlockClass="stk-block-icon" />
<Responsive.InspectorControls />
<ConditionalDisplay.InspectorControls />
Expand Down
2 changes: 1 addition & 1 deletion src/block/image-box/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ const InspectorControls = memo( () => {
<Advanced.InspectorControls />
<Transform.InspectorControls />
<EffectsAnimations.InspectorControls />
<CustomAttributes.InspectorControls />
<CustomAttributes.InspectorControls linkAttributeName="blockLinkCustomAttributes" />
<CustomCSS.InspectorControls mainBlockClass="stk-block-image-box" />
<Responsive.InspectorControls />
<ConditionalDisplay.InspectorControls />
Expand Down
2 changes: 1 addition & 1 deletion src/block/image/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ const InspectorControls = memo( props => {
<Advanced.InspectorControls />
<Transform.InspectorControls />
<EffectsAnimations.InspectorControls />
<CustomAttributes.InspectorControls />
<CustomAttributes.InspectorControls linkAttributeName={ props.enableLink ? 'linkCustomAttributes' : '' } />
<CustomCSS.InspectorControls mainBlockClass="stk-block-image" />
<Responsive.InspectorControls />
<ConditionalDisplay.InspectorControls />
Expand Down
2 changes: 1 addition & 1 deletion src/block/notification/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ const InspectorControls = memo( props => {
<Advanced.InspectorControls />
<Transform.InspectorControls />
<EffectsAnimations.InspectorControls />
<CustomAttributes.InspectorControls />
<CustomAttributes.InspectorControls linkAttributeName="blockLinkCustomAttributes" />
<CustomCSS.InspectorControls mainBlockClass="stk-block-notification" />
<Responsive.InspectorControls />
<ConditionalDisplay.InspectorControls />
Expand Down
2 changes: 1 addition & 1 deletion src/block/pricing-box/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ const InspectorControls = memo( () => {
<Advanced.InspectorControls />
<Transform.InspectorControls />
<EffectsAnimations.InspectorControls />
<CustomAttributes.InspectorControls />
<CustomAttributes.InspectorControls linkAttributeName="blockLinkCustomAttributes" />
<CustomCSS.InspectorControls mainBlockClass="stk-block-pricing-box" />
<Responsive.InspectorControls />
<ConditionalDisplay.InspectorControls />
Expand Down
2 changes: 1 addition & 1 deletion src/block/team-member/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ const InspectorControls = memo( () => {
<Advanced.InspectorControls />
<Transform.InspectorControls />
<EffectsAnimations.InspectorControls />
<CustomAttributes.InspectorControls />
<CustomAttributes.InspectorControls linkAttributeName="blockLinkCustomAttributes" />
<CustomCSS.InspectorControls mainBlockClass="stk-block-team-member" />
<Responsive.InspectorControls />
<ConditionalDisplay.InspectorControls />
Expand Down
2 changes: 1 addition & 1 deletion src/block/testimonial/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ const InspectorControls = memo( () => {
<Advanced.InspectorControls />
<Transform.InspectorControls />
<EffectsAnimations.InspectorControls />
<CustomAttributes.InspectorControls />
<CustomAttributes.InspectorControls linkAttributeName="blockLinkCustomAttributes" />
<CustomCSS.InspectorControls mainBlockClass="stk-block-testimonial" />
<Responsive.InspectorControls />
<ConditionalDisplay.InspectorControls />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@ describe( 'CustomAttributesControl', () => {
}

const {
getByTestId, getByText,
getByTestId, getByText, rerender,
} = render( <CustomAttributesControl { ...dummyProps } /> )
expect( getByText( 'Custom Attributes' ) ).toBeTruthy()

rerender( <CustomAttributesControl { ...dummyProps } label="Link Custom Attributes" /> )
expect( getByText( 'Link Custom Attributes' ) ).toBeTruthy()

const invalidInputs = [
`test`,
`key=value`,
Expand Down
Loading
Loading