| 1 |
/** |
| 2 |
* Tooltip on greyed-out once-per-form "New Fields" inserter entries. |
| 3 |
* Document-level listener (sidebar + mini-inserter popovers). Only uniqueBlocks. |
| 4 |
*/ |
| 5 |
|
| 6 |
import { useEffect, useState } from '@wordpress/element'; |
| 7 |
import { useSelect } from '@wordpress/data'; |
| 8 |
import { registerPlugin } from '@wordpress/plugins'; |
| 9 |
import { Popover } from '@wordpress/components'; |
| 10 |
import { __, sprintf } from '@wordpress/i18n'; |
| 11 |
|
| 12 |
const PB_CPTS = [ 'wppb-rf-cpt', 'wppb-epf-cpt' ]; |
| 13 |
const ITEM_SELECTOR = '.block-editor-block-types-list__item'; |
| 14 |
const TITLE_SELECTOR = '.block-editor-block-types-list__item-title'; |
| 15 |
|
| 16 |
/** |
| 17 |
* Inserter item classes for once-per-form blocks (`editor-block-list-item-{name}`). |
| 18 |
* Built from names; class→name reverse is ambiguous (names have dashes). |
| 19 |
* |
| 20 |
* @return {Set<string>} |
| 21 |
*/ |
| 22 |
const uniqueItemClasses = () => { |
| 23 |
const names = window.wppbFb?.uniqueness?.uniqueBlocks; |
| 24 |
return new Set( |
| 25 |
Array.isArray( names ) |
| 26 |
? names.map( ( name ) => `editor-block-list-item-${ name.replace( '/', '-' ) }` ) |
| 27 |
: [] |
| 28 |
); |
| 29 |
}; |
| 30 |
|
| 31 |
// Core marks the entry `aria-disabled` (Composite's accessibleWhenDisabled), not |
| 32 |
// `disabled` — which is exactly why it still receives hover and focus. The |
| 33 |
// `disabled` half is a guard in case a future version switches back. |
| 34 |
const isDimmed = ( item ) => item.disabled || item.getAttribute( 'aria-disabled' ) === 'true'; |
| 35 |
|
| 36 |
const UniqueFieldTooltip = () => { |
| 37 |
const isOurPostType = useSelect( |
| 38 |
( select ) => PB_CPTS.includes( select( 'core/editor' ).getCurrentPostType() ), |
| 39 |
[] |
| 40 |
); |
| 41 |
const [ anchor, setAnchor ] = useState( null ); |
| 42 |
|
| 43 |
useEffect( () => { |
| 44 |
if ( ! isOurPostType ) return undefined; |
| 45 |
|
| 46 |
const classes = uniqueItemClasses(); |
| 47 |
if ( ! classes.size ) return undefined; |
| 48 |
|
| 49 |
const resolve = ( target ) => { |
| 50 |
const item = target?.closest?.( ITEM_SELECTOR ); |
| 51 |
if ( ! item || ! isDimmed( item ) ) return null; |
| 52 |
return Array.from( item.classList ).some( ( cls ) => classes.has( cls ) ) ? item : null; |
| 53 |
}; |
| 54 |
|
| 55 |
// `mouseover` (not `mouseenter`) so one document-level listener sees |
| 56 |
// every entry; it re-fires on each element the pointer enters, so |
| 57 |
// leaving an entry for anything else resolves to null and closes. |
| 58 |
const onPoint = ( event ) => setAnchor( resolve( event.target ) ); |
| 59 |
const onLeaveWindow = () => setAnchor( null ); |
| 60 |
|
| 61 |
document.addEventListener( 'mouseover', onPoint ); |
| 62 |
document.addEventListener( 'focusin', onPoint ); |
| 63 |
document.documentElement.addEventListener( 'mouseleave', onLeaveWindow ); |
| 64 |
|
| 65 |
return () => { |
| 66 |
document.removeEventListener( 'mouseover', onPoint ); |
| 67 |
document.removeEventListener( 'focusin', onPoint ); |
| 68 |
document.documentElement.removeEventListener( 'mouseleave', onLeaveWindow ); |
| 69 |
}; |
| 70 |
}, [ isOurPostType ] ); |
| 71 |
|
| 72 |
// `isConnected`: the entry can be re-rendered out from under a hover (the |
| 73 |
// list rebuilds whenever the canvas changes), and positioning against a |
| 74 |
// detached node would strand the tooltip at the top-left of the screen. |
| 75 |
if ( ! anchor || ! anchor.isConnected ) return null; |
| 76 |
|
| 77 |
const title = anchor.querySelector( TITLE_SELECTOR )?.textContent?.trim(); |
| 78 |
|
| 79 |
return ( |
| 80 |
<Popover |
| 81 |
className="wppb-fb-unique-tip" |
| 82 |
anchor={ anchor } |
| 83 |
placement="bottom" |
| 84 |
offset={ 8 } |
| 85 |
focusOnMount={ false } |
| 86 |
animate={ false } |
| 87 |
> |
| 88 |
{ title |
| 89 |
? sprintf( |
| 90 |
/* translators: %s: field name, e.g. "Username". */ |
| 91 |
__( '“%s” is already in this form. This field can only be added once per form.', 'profile-builder' ), |
| 92 |
title |
| 93 |
) |
| 94 |
: __( 'This field is already in this form. It can only be added once per form.', 'profile-builder' ) } |
| 95 |
</Popover> |
| 96 |
); |
| 97 |
}; |
| 98 |
|
| 99 |
registerPlugin( 'wppb-fb-unique-field-tooltip', { render: UniqueFieldTooltip } ); |
| 100 |
|