/**
* PatternsWP — Block Editor Patterns Library
*
* Ready-to-use script using WordPress / Gutenberg packages (no build step).
* Text domain: patternswp
*/
( function ( wp, lodash ) {
'use strict';
if ( ! wp || ! wp.element || ! wp.components ) {
return;
}
var el = wp.element.createElement;
var Fragment = wp.element.Fragment;
var useState = wp.element.useState;
var useEffect = wp.element.useEffect;
var useRef = wp.element.useRef;
var useCallback = wp.element.useCallback;
var useMemo = wp.element.useMemo;
var createRoot = wp.element.createRoot;
var render = wp.element.render;
var __ = wp.i18n.__;
var sprintf = wp.i18n.sprintf;
var _n = wp.i18n._n;
var useDispatch = wp.data.useDispatch;
var useSelect = wp.data.useSelect;
var subscribe = wp.data.subscribe;
var useDebounce = wp.compose.useDebounce;
var parse = wp.blocks.parse;
var cloneBlock = wp.blocks.cloneBlock;
var isUnmodifiedDefaultBlock = wp.blocks.isUnmodifiedDefaultBlock;
var registerBlockType = wp.blocks.registerBlockType;
var BlockPreview = wp.blockEditor.BlockPreview;
var storeBlockEditor = wp.blockEditor.store;
var speak = wp.a11y.speak;
var castArray = lodash.castArray;
var isEmpty = lodash.isEmpty;
var registerPlugin = wp.plugins && wp.plugins.registerPlugin;
var PluginMoreMenuItem =
( wp.editor && wp.editor.PluginMoreMenuItem ) ||
( wp.editPost && wp.editPost.PluginMoreMenuItem );
var c = wp.components;
var Modal = c.Modal;
var Button = c.Button;
var SearchControl = c.SearchControl;
var Spinner = c.Spinner;
var DropdownMenu = c.DropdownMenu;
var MenuGroup = c.MenuGroup;
var MenuItem = c.MenuItem;
var Tooltip = c.Tooltip;
var Notice = c.Notice;
var Icon = c.Icon;
var CheckboxControl = c.CheckboxControl;
var Dropdown = c.Dropdown;
var SelectControl = c.SelectControl;
var ButtonGroup = c.ButtonGroup;
var Fill = c.Fill;
var SVG = wp.primitives && wp.primitives.SVG;
var Path = wp.primitives && wp.primitives.Path;
var Circle = wp.primitives && wp.primitives.Circle;
/* IconScout Unicons (thinline + line) — https://iconscout.com/unicons */
var icons = ( function () {
function svgIcon( children ) {
if ( ! SVG ) {
return null;
}
return el(
SVG,
{
xmlns: 'http://www.w3.org/2000/svg',
viewBox: '0 0 24 24',
width: 18,
height: 18,
fill: 'currentColor',
'aria-hidden': true,
focusable: false,
},
children
);
}
function uicon( pathD ) {
return svgIcon(
el( Path, {
d: pathD,
fill: 'currentColor',
} )
);
}
function uiconPaths( paths ) {
return svgIcon(
el(
Fragment,
null,
paths.map( function ( pathD, index ) {
return el( Path, {
key: index,
d: pathD,
fill: 'currentColor',
} );
} )
)
);
}
return {
heart: uicon(
'M17.5.917a6.4,6.4,0,0,0-5.5,3.3A6.4,6.4,0,0,0,6.5.917,6.8,6.8,0,0,0,0,7.967c0,6.775,10.956,14.6,11.422,14.932l.578.409.578-.409C13.044,22.569,24,14.742,24,7.967A6.8,6.8,0,0,0,17.5.917ZM12,20.846c-3.253-2.43-10-8.4-10-12.879a4.8,4.8,0,0,1,4.5-5.05A4.8,4.8,0,0,1,11,7.967h2a4.8,4.8,0,0,1,4.5-5.05A4.8,4.8,0,0,1,22,7.967C22,12.448,15.253,18.416,12,20.846Z'
),
heartFilled: uicon(
'M17.5.917a6.4,6.4,0,0,0-5.5,3.3A6.4,6.4,0,0,0,6.5.917,6.8,6.8,0,0,0,0,7.967c0,6.775,10.956,14.6,11.422,14.932l.578.409.578-.409C13.044,22.569,24,14.742,24,7.967A6.8,6.8,0,0,0,17.5.917Z'
),
moreVertical: svgIcon(
el(
Fragment,
null,
Circle
? el( Circle, { cx: 12, cy: 2, r: 2, fill: 'currentColor' } )
: el( 'circle', { cx: 12, cy: 2, r: 2, fill: 'currentColor' } ),
Circle
? el( Circle, { cx: 12, cy: 12, r: 2, fill: 'currentColor' } )
: el( 'circle', { cx: 12, cy: 12, r: 2, fill: 'currentColor' } ),
Circle
? el( Circle, { cx: 12, cy: 22, r: 2, fill: 'currentColor' } )
: el( 'circle', { cx: 12, cy: 22, r: 2, fill: 'currentColor' } )
)
),
edit: uiconPaths( [
'M5,19H9.414L23.057,5.357a3.125,3.125,0,0,0,0-4.414,3.194,3.194,0,0,0-4.414,0L5,14.586Zm2-3.586L20.057,2.357a1.148,1.148,0,0,1,1.586,0,1.123,1.123,0,0,1,0,1.586L8.586,17H7Z',
'M23.621,7.622,22,9.243V16H16v6H2V3A1,1,0,0,1,3,2H14.758L16.379.379A5.013,5.013,0,0,1,16.84,0H3A3,3,0,0,0,0,3V24H18.414L24,18.414V7.161A5.15,5.15,0,0,1,23.621,7.622ZM18,21.586V18h3.586Z',
] ),
sort: uicon(
'M16.29,14.29,12,18.59l-4.29-4.3a1,1,0,0,0-1.42,1.42l5,5a1,1,0,0,0,1.42,0l5-5a1,1,0,0,0-1.42-1.42ZM7.71,9.71,12,5.41l4.29,4.3a1,1,0,0,0,1.42,0,1,1,0,0,0,0-1.42l-5-5a1,1,0,0,0-1.42,0l-5,5A1,1,0,0,0,7.71,9.71Z'
),
filter: uicon(
'm15 24-6-4.5v-5.12l-8-9v-2.38a3 3 0 0 1 3-3h16a3 3 0 0 1 3 3v2.38l-8 9zm-4-5.5 2 1.5v-6.38l8-9v-1.62a1 1 0 0 0 -1-1h-16a1 1 0 0 0 -1 1v1.62l8 9z'
),
grid: uiconPaths( [
'M11,11H0V3A3,3,0,0,1,3,0h8ZM2,9H9V2H3A1,1,0,0,0,2,3Z',
'M24,11H13V0h8a3,3,0,0,1,3,3ZM15,9h7V3a1,1,0,0,0-1-1H15Z',
'M11,24H3a3,3,0,0,1-3-3V13H11ZM2,15v6a1,1,0,0,0,1,1H9V15Z',
'M21,24H13V13H24v8A3,3,0,0,1,21,24Zm-6-2h6a1,1,0,0,0,1-1V15H15Z',
] ),
list: svgIcon(
el(
Fragment,
null,
el( 'rect', { x: 7, y: 4, width: 17, height: 2, fill: 'currentColor' } ),
el( 'rect', { x: 7, y: 11, width: 17, height: 2, fill: 'currentColor' } ),
el( 'rect', { x: 7, y: 18, width: 17, height: 2, fill: 'currentColor' } ),
Circle
? el( Circle, { cx: 2, cy: 5, r: 2, fill: 'currentColor' } )
: el( 'circle', { cx: 2, cy: 5, r: 2, fill: 'currentColor' } ),
Circle
? el( Circle, { cx: 2, cy: 12, r: 2, fill: 'currentColor' } )
: el( 'circle', { cx: 2, cy: 12, r: 2, fill: 'currentColor' } ),
Circle
? el( Circle, { cx: 2, cy: 19, r: 2, fill: 'currentColor' } )
: el( 'circle', { cx: 2, cy: 19, r: 2, fill: 'currentColor' } )
)
),
plus: svgIcon(
el( 'polygon', {
points:
'24 11 13 11 13 0 11 0 11 11 0 11 0 13 11 13 11 24 13 24 13 13 24 13 24 11',
fill: 'currentColor',
} )
),
lock: svgIcon(
el(
Fragment,
null,
el( Path, {
d: 'M19,8V7A7,7,0,0,0,5,7V8H2V21a3,3,0,0,0,3,3H19a3,3,0,0,0,3-3V8ZM7,7A5,5,0,0,1,17,7V8H7ZM20,21a1,1,0,0,1-1,1H5a1,1,0,0,1-1-1V10H20Z',
fill: 'currentColor',
} ),
el( 'rect', {
x: 11,
y: 14,
width: 2,
height: 4,
fill: 'currentColor',
} )
)
),
check: uicon(
'M18.3534546,7.5735474c-0.1932983-0.1972046-0.5098267-0.2003784-0.7070312-0.0070801l-7.8066406,7.8066406l-3.4863281-3.4863281c-0.194397-0.1905518-0.5054321-0.1905518-0.6998291,0c-0.1972046,0.1932373-0.2003784,0.5097656-0.0071411,0.7069702l3.8398438,3.8398438c0.0936279,0.0939331,0.2208862,0.1466675,0.3535156,0.1464844c0.1326294,0.0001221,0.2598267-0.0526123,0.3534546-0.1464844l8.1601562-8.1601562C18.5440063,8.0790405,18.5440063,7.7679443,18.3534546,7.5735474z'
),
refresh: uicon(
'M12,2A10,10,0,0,0,4.93,4.93,1,1,0,0,0,6.34,6.34,8,8,0,1,1,4,12H7L3,8,0,12H2A10,10,0,1,0,12,2Z'
),
plusBox: uicon(
'M9,13h2v2a1,1,0,0,0,2,0V13h2a1,1,0,0,0,0-2H13V9a1,1,0,0,0-2,0v2H9a1,1,0,0,0,0,2ZM21,2H3A1,1,0,0,0,2,3V21a1,1,0,0,0,1,1H21a1,1,0,0,0,1-1V3A1,1,0,0,0,21,2ZM20,20H4V4H20Z'
),
close: uicon(
'M19.8534546,19.1465454L12.7069092,12l7.1465454-7.1465454c0.1871948-0.1937256,0.1871948-0.5009155,0-0.6947021c-0.1918335-0.1986084-0.5083618-0.2041016-0.7069702-0.0122681l-7.1465454,7.1465454L4.8534546,4.1465454c-0.1937256-0.1871338-0.5009155-0.1871338-0.6947021,0C3.960144,4.3383789,3.9546509,4.6549072,4.1464844,4.8535156L11.2929688,12l-7.1464844,7.1464844c-0.09375,0.09375-0.1464233,0.2208862-0.1464233,0.3534546C4,19.776062,4.223877,19.999939,4.5,20c0.1326294,0.0001221,0.2598267-0.0526123,0.3534546-0.1465454l7.1464844-7.1464844l7.1465454,7.1465454C19.2401123,19.9474487,19.3673706,20.0001831,19.5,20c0.1325073-0.000061,0.2595825-0.0526733,0.3533325-0.1463623C20.048645,19.6583862,20.0487061,19.3417969,19.8534546,19.1465454z'
),
brand: svgIcon(
el(
Fragment,
null,
el( Path, { d: 'M9.34 24L4.45 21.16L4.03 20.92V15.3L9.34 12V24Z', fill: 'currentColor' } ),
el( Path, {
fillRule: 'evenodd',
clipRule: 'evenodd',
d: 'M14.66 9.2L20.97 6L14.66 2.8V9.2Z',
fill: 'currentColor',
} ),
el( Path, { d: 'M14.66 2.8L9.34 6L4.03 9.2V2.8L9.34 0L14.66 2.8Z', fill: 'currentColor' } ),
el( Path, {
fillRule: 'evenodd',
clipRule: 'evenodd',
d: 'M10.79 11.44V17.84L14.66 15.3L20.97 12V6L14.66 9.2L10.79 11.44Z',
fill: 'currentColor',
} )
)
),
settings: uicon(
'M15,24H9V20.487a9,9,0,0,1-2.849-1.646L3.107,20.6l-3-5.2L3.15,13.645a9.1,9.1,0,0,1,0-3.29L.107,8.6l3-5.2L6.151,5.159A9,9,0,0,1,9,3.513V0h6V3.513a9,9,0,0,1,2.849,1.646L20.893,3.4l3,5.2L20.85,10.355a9.1,9.1,0,0,1,0,3.29L23.893,15.4l-3,5.2-3.044-1.758A9,9,0,0,1,15,20.487Zm-4-2h2V18.973l.751-.194A6.984,6.984,0,0,0,16.994,16.9l.543-.553,2.623,1.515,1-1.732-2.62-1.513.206-.746a7.048,7.048,0,0,0,0-3.75l-.206-.746,2.62-1.513-1-1.732L17.537,7.649,16.994,7.1a6.984,6.984,0,0,0-3.243-1.875L13,5.027V2H11V5.027l-.751.194A6.984,6.984,0,0,0,7.006,7.1l-.543.553L3.84,6.134l-1,1.732L5.46,9.379l-.206.746a7.048,7.048,0,0,0,0,3.75l.206.746L2.84,16.134l1,1.732,2.623-1.515.543.553a6.984,6.984,0,0,0,3.243,1.875l.751.194Zm1-6a4,4,0,1,1,4-4A4,4,0,0,1,12,16Zm0-6a2,2,0,1,0,2,2A2,2,0,0,0,12,10Z'
),
arrowUp: uicon(
'M17.71,11.29l-5-5a1,1,0,0,0-.33-.21,1,1,0,0,0-.76,0,1,1,0,0,0-.33.21l-5,5a1,1,0,0,0,1.42,1.42L11,9.41V17a1,1,0,0,0,2,0V9.41l3.29,3.3a1,1,0,0,0,1.42,0A1,1,0,0,0,17.71,11.29Z'
),
arrowDown: uicon(
'M17.71,11.29a1,1,0,0,0-1.42,0L13,14.59V7a1,1,0,0,0-2,0v7.59l-3.29-3.3a1,1,0,0,0-1.42,1.42l5,5a1,1,0,0,0,.33.21.94.94,0,0,0,.76,0,1,1,0,0,0,.33-.21l5-5A1,1,0,0,0,17.71,11.29Z'
),
chevronDown: uicon(
'M17,9.17a1,1,0,0,0-1.41,0L12,12.71,8.46,9.17a1,1,0,0,0-1.41,0,1,1,0,0,0,0,1.42l4.24,4.24a1,1,0,0,0,1.42,0L17,10.59A1,1,0,0,0,17,9.17Z'
),
chevronUp: uicon(
'M16.8535156,13.6465454l-4.5-4.5c-0.000061-0.000061-0.000061-0.0001221-0.0001221-0.0001831c-0.1952515-0.1951294-0.5117188-0.1950684-0.7068481,0.0001831l-4.5,4.5c-0.1871948,0.1937256-0.1871948,0.5009155,0,0.6947021c0.1918335,0.1986084,0.5083618,0.2041016,0.7069702,0.0122681L12,10.2069702l4.1464844,4.1465454C16.2401123,14.4474487,16.3673706,14.5001831,16.5,14.5c0.1325073,0,0.2596436-0.0526733,0.3533936-0.1463623C17.048645,14.1583862,17.0487671,13.8417969,16.8535156,13.6465454z'
),
view: uiconPaths( [
'M23.821,11.181v0C22.943,9.261,19.5,3,12,3S1.057,9.261.179,11.181a1.969,1.969,0,0,0,0,1.64C1.057,14.739,4.5,21,12,21s10.943-6.261,11.821-8.181A1.968,1.968,0,0,0,23.821,11.181ZM12,19c-6.307,0-9.25-5.366-10-6.989C2.75,10.366,5.693,5,12,5c6.292,0,9.236,5.343,10,7C21.236,13.657,18.292,19,12,19Z',
'M12,7a5,5,0,1,0,5,5A5.006,5.006,0,0,0,12,7Zm0,8a3,3,0,1,1,3-3A3,3,0,0,1,12,15Z',
] ),
arrowLeft: uicon(
'M24,13l0-2-21.445.031L6.877,6.707,5.463,5.293.877,9.879a3,3,0,0,0,0,4.242l4.586,4.586,1.414-1.414L2.615,13.031Z'
),
};
} )();
function iconOrDash( modernIcon, dashicon ) {
return modernIcon || dashicon;
}
var FAVORITES_KEY = 'patternswp_favorites_v1';
var RECENT_KEY = 'patternswp_recent_v1';
var PREFS_KEY = 'patternswp_prefs_v2';
var MAX_FAVORITES = 40;
var MAX_RECENT = 24;
var PAGE_SIZE = 20;
var DEFAULT_PREFS = {
layout: 'grid',
viewport: 'desktop',
sortField: 'default',
sortOrder: 'asc',
typeFilter: 'all',
previewSize: 2,
showTitle: true,
showPreview: true,
};
var PREVIEW_SIZE_HEIGHTS = {
1: 160,
2: 200,
3: 240,
4: 300,
};
var SPECIAL = {
ALL: '',
FAVORITES: '__favorites__',
RECENT: '__recent__',
};
var VIEWPORTS = {
desktop: 1300,
tablet: 778,
mobile: 358,
};
var pageCache = new Map();
var openModalExternal = null;
var insertAnchorClientId = null;
function setInsertAnchor( clientId ) {
insertAnchorClientId = clientId || null;
}
function openPatternsLibrary( options ) {
options = options || {};
setInsertAnchor( options.anchorClientId || null );
if ( openModalExternal ) {
openModalExternal();
return true;
}
return false;
}
window.patternswpOpenLibrary = openPatternsLibrary;
/* ------------------------------------------------------------------ */
/* Helpers */
/* ------------------------------------------------------------------ */
function getBoot() {
return window.patternsWpData || {
isLicenseActive: false,
patternCategories: [],
patternsNonce: '',
libraryComplete: false,
ajaxUrl: window.ajaxurl || '',
};
}
function ajaxUrl() {
var boot = getBoot();
return boot.ajaxUrl || window.ajaxurl || '';
}
function isLicenseUnlocked() {
return !! getBoot().isLicenseActive;
}
function isPatternLocked( pattern ) {
if ( ! pattern || isLicenseUnlocked() ) {
return false;
}
return pattern.locked === true || pattern.type === 'pro';
}
function parseAjaxJson( response ) {
return response.text().then( function ( text ) {
var json = null;
try {
json = text ? JSON.parse( text ) : null;
} catch ( e ) {
json = null;
}
if ( ! json ) {
throw new Error( 'invalid' );
}
return json;
} );
}
function normalizeLibraryPayload( json ) {
if ( ! json || ! json.success ) {
throw new Error( 'invalid' );
}
var data = json.data;
if ( Array.isArray( data ) ) {
return {
patterns: data,
total: data.length,
complete: true,
categories: null,
};
}
if ( data && Array.isArray( data.patterns ) ) {
if ( typeof data.licenseActive === 'boolean' ) {
window.patternsWpData = window.patternsWpData || {};
window.patternsWpData.isLicenseActive = data.licenseActive;
}
return {
patterns: data.patterns,
total: typeof data.total === 'number' ? data.total : data.patterns.length,
complete: !! data.complete,
categories: Array.isArray( data.categories ) ? data.categories : null,
licenseActive: data.licenseActive === true,
};
}
throw new Error( 'invalid' );
}
function patternKey( pattern ) {
return ( pattern.title || '' ) + '::' + ( ( pattern.categories || [] ).join( ',' ) );
}
function formatCategoryLabel( slug ) {
if ( ! slug ) {
return __( 'All patterns', 'patternswp' );
}
var map = {
cta: 'CTA',
faq: 'FAQ',
};
return String( slug )
.replace( /^patternswp-/, '' )
.split( '-' )
.map( function ( part ) {
if ( map[ part ] ) {
return map[ part ];
}
return part.charAt( 0 ).toUpperCase() + part.slice( 1 );
} )
.join( ' ' );
}
function readStore( key, fallback ) {
try {
var raw = window.localStorage.getItem( key );
if ( ! raw ) {
return fallback;
}
var parsed = JSON.parse( raw );
return parsed != null ? parsed : fallback;
} catch ( e ) {
return fallback;
}
}
function writeStore( key, value ) {
try {
window.localStorage.setItem( key, JSON.stringify( value ) );
} catch ( e ) {
/* quota / private mode */
}
}
function decodePatternContent( content ) {
if ( ! content ) {
return '';
}
try {
/* API returns base64-encoded block markup */
return window.atob( content );
} catch ( e ) {
return content;
}
}
function cacheKey( category, search, page, perPage ) {
return category + '|' + search + '|' + page + '|' + perPage;
}
function sortPatterns( list, sortField, sortOrder ) {
var items = list.slice();
var dir = sortOrder === 'desc' ? -1 : 1;
if ( sortField === 'title' ) {
items.sort( function ( a, b ) {
return dir * ( a.title || '' ).localeCompare( b.title || '' );
} );
return items;
}
if ( sortField === 'type' ) {
items.sort( function ( a, b ) {
var av = a.type === 'pro' ? 1 : 0;
var bv = b.type === 'pro' ? 1 : 0;
return dir * ( av - bv ) || ( a.title || '' ).localeCompare( b.title || '' );
} );
return items;
}
/* default: keep API order; desc reverses loaded list */
if ( sortOrder === 'desc' ) {
items.reverse();
}
return items;
}
function filterByType( list, typeFilter ) {
if ( typeFilter === 'free' ) {
return list.filter( function ( p ) {
return p.type !== 'pro';
} );
}
if ( typeFilter === 'pro' ) {
return list.filter( function ( p ) {
return p.type === 'pro';
} );
}
return list;
}
function upsertByKey( list, items ) {
var seen = new Set( list.map( patternKey ) );
var next = list.slice();
items.forEach( function ( item ) {
var key = patternKey( item );
if ( ! seen.has( key ) ) {
seen.add( key );
next.push( item );
}
} );
return next;
}
/* ------------------------------------------------------------------ */
/* Data fetching */
/* ------------------------------------------------------------------ */
function fetchPatternsPage( page, search, category, perPage, signal, retries ) {
var pageSize = perPage || PAGE_SIZE;
var key = cacheKey( category, search, page, pageSize );
if ( pageCache.has( key ) ) {
return Promise.resolve( pageCache.get( key ) );
}
if ( typeof retries !== 'number' ) {
retries = 1;
}
var boot = getBoot();
var body = new window.URLSearchParams( {
action: 'fetch_patterns',
page: String( page ),
patternsPerPage: String( pageSize ),
search: search || '',
category: category || '',
nonce: boot.patternsNonce || '',
} );
return window
.fetch( ajaxUrl(), {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
},
body: body.toString(),
signal: signal,
} )
.then( parseAjaxJson )
.then( function ( json ) {
var payload = normalizeLibraryPayload( json );
if ( payload.patterns.length || payload.complete ) {
pageCache.set( key, payload );
}
return payload;
} )
.catch( function ( err ) {
if ( err && err.name === 'AbortError' ) {
throw err;
}
if ( retries > 0 ) {
return new Promise( function ( resolve ) {
window.setTimeout( resolve, 1200 );
} ).then( function () {
return fetchPatternsPage( page, search, category, perPage, signal, retries - 1 );
} );
}
throw err;
} );
}
function warmLibrary( signal, mode, force ) {
var boot = getBoot();
var body = new window.URLSearchParams( {
action: 'patternswp_warm_library',
nonce: boot.patternsNonce || '',
mode: mode || 'sync',
force: force ? '1' : '0',
} );
return window
.fetch( ajaxUrl(), {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
},
body: body.toString(),
signal: signal,
} )
.then( parseAjaxJson )
.then( function ( json ) {
if ( ! json || ! json.success || ! json.data || typeof json.data !== 'object' ) {
throw new Error( 'invalid' );
}
return {
total: typeof json.data.total === 'number' ? json.data.total : 0,
complete: !! json.data.complete,
categories: Array.isArray( json.data.categories ) ? json.data.categories : null,
hasUpdates: !! json.data.has_updates,
};
} );
}
/* ------------------------------------------------------------------ */
/* Insert hook (mirrors core inserter destination logic) */
/* ------------------------------------------------------------------ */
function useInsertPattern( onInserted ) {
var getSelectedBlock = useSelect( function ( select ) {
return select( storeBlockEditor ).getSelectedBlock;
}, [] );
var destination = useSelect(
function ( select ) {
var s = select( storeBlockEditor );
var selectedId = s.getSelectedBlockClientId();
var rootClientId = '';
var index = s.getBlockOrder( rootClientId ).length;
if ( selectedId ) {
rootClientId = s.getBlockRootClientId( selectedId );
index = s.getBlockIndex( selectedId ) + 1;
}
return {
rootClientId: rootClientId,
index: index,
};
},
[]
);
var dispatch = useDispatch( storeBlockEditor );
var notices = useDispatch( 'core/notices' );
return useCallback(
function ( patternOrList, options ) {
options = options || {};
var list = Array.isArray( patternOrList ) ? patternOrList : [ patternOrList ];
var blocks = [];
list.forEach( function ( pattern ) {
if ( isPatternLocked( pattern ) ) {
return;
}
try {
var markup = decodePatternContent( pattern.content );
if ( ! markup ) {
return;
}
parse( markup ).forEach( function ( block ) {
blocks.push( cloneBlock( block ) );
} );
} catch ( e ) {
/* skip broken pattern */
}
} );
if ( ! blocks.length ) {
var blocked = list.some( isPatternLocked );
notices.createErrorNotice(
blocked
? __( 'Activate your PatternsWP license to insert Pro patterns.', 'patternswp' )
: __( 'Could not insert this pattern.', 'patternswp' ),
{ type: 'snackbar' }
);
if ( blocked ) {
window.open( 'https://thepatternswp.com/pricing/', '_blank', 'noopener,noreferrer' );
}
return;
}
var anchorId = options.anchorClientId || insertAnchorClientId;
var selected = getSelectedBlock();
var editorSelect = wp.data.select( storeBlockEditor );
if ( anchorId && editorSelect.getBlock( anchorId ) ) {
var rootClientId = editorSelect.getBlockRootClientId( anchorId );
var index = editorSelect.getBlockIndex( anchorId ) + 1;
dispatch.insertBlocks( blocks, index, rootClientId, true );
} else if ( selected && isUnmodifiedDefaultBlock( selected ) ) {
dispatch.replaceBlocks( selected.clientId, blocks );
} else {
dispatch.insertBlocks(
blocks,
destination.index,
destination.rootClientId,
true
);
}
var count = castArray( blocks ).length;
speak(
sprintf(
_n( '%d block added.', '%d blocks added.', count, 'patternswp' ),
count
)
);
if ( list.length === 1 ) {
notices.createSuccessNotice(
sprintf(
/* translators: %s: pattern title */
__( '“%s” inserted.', 'patternswp' ),
list[ 0 ].title || __( 'Pattern', 'patternswp' )
),
{ type: 'snackbar' }
);
} else {
notices.createSuccessNotice(
sprintf(
/* translators: %d: number of patterns */
_n( '%d pattern inserted.', '%d patterns inserted.', list.length, 'patternswp' ),
list.length
),
{ type: 'snackbar' }
);
}
if ( onInserted ) {
onInserted( list );
}
},
[ destination.index, destination.rootClientId, dispatch, getSelectedBlock, notices, onInserted ]
);
}
/* ------------------------------------------------------------------ */
/* Lazy BlockPreview — mount only while in view */
/* ------------------------------------------------------------------ */
function LazyPreviewInner( props ) {
var ref = useRef( null );
var visibleState = useState( false );
var visible = visibleState[ 0 ];
var setVisible = visibleState[ 1 ];
useEffect(
function () {
var node = ref.current;
if ( ! node || ! window.IntersectionObserver ) {
setVisible( true );
return;
}
var observer = new window.IntersectionObserver(
function ( entries ) {
entries.forEach( function ( entry ) {
setVisible( entry.isIntersecting );
} );
},
{ root: props.root || null, rootMargin: '300px 0px', threshold: 0.01 }
);
observer.observe( node );
return function () {
observer.disconnect();
};
},
[ props.root ]
);
var minHeight = props.minHeight || 200;
return el(
'div',
{ ref: ref, style: { width: '100%', height: '100%', minHeight: minHeight } },
visible
? el( BlockPreview, {
blocks: props.blocks,
viewportWidth: props.viewportWidth,
minHeight: minHeight,
} )
: el( 'div', { style: { display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%' } }, el( Spinner ) )
);
}
/**
* Detail preview that shows the pattern at its real height.
* Gutenberg's ScaledBlockPreview hard-caps iframe/content at 2000px, which
* clips page templates. CSS removes the cap; this re-applies it after React
* writes inline max-height, and sizes the wrapper so the modal can scroll.
*/
function FullPatternPreview( props ) {
var viewportWidth = props.viewportWidth || VIEWPORTS.desktop;
var wrapRef = useRef( null );
var heightState = useState( 0 );
var height = heightState[ 0 ];
var setHeight = heightState[ 1 ];
useEffect(
function () {
var wrap = wrapRef.current;
if ( ! wrap ) {
return;
}
var timer = 0;
var tries = 0;
var boundIframe = null;
function unlockEl( el, propsMap ) {
if ( ! el ) {
return;
}
Object.keys( propsMap ).forEach( function ( key ) {
el.style.setProperty( key, propsMap[ key ], 'important' );
} );
}
function contentHeight( iframe ) {
var doc = iframe.contentDocument;
if ( ! doc ) {
return 0;
}
var html = doc.documentElement;
var body = doc.body;
if ( html ) {
html.style.setProperty( 'max-height', 'none', 'important' );
html.style.setProperty( 'overflow', 'visible', 'important' );
}
if ( body ) {
body.style.setProperty( 'max-height', 'none', 'important' );
body.style.setProperty( 'overflow', 'visible', 'important' );
}
return Math.max(
body ? body.scrollHeight : 0,
html ? html.scrollHeight : 0,
body ? body.offsetHeight : 0,
html ? html.offsetHeight : 0
);
}
function measure() {
var iframe = wrap.querySelector( 'iframe' );
var containerEl = wrap.querySelector( '.block-editor-block-preview__container' );
var contentEl = wrap.querySelector( '.block-editor-block-preview__content' );
unlockEl( containerEl, {
overflow: 'visible',
'max-height': 'none',
} );
unlockEl( contentEl, {
overflow: 'visible',
'max-height': 'none',
} );
unlockEl( wrap.querySelector( '.block-editor-iframe__container' ), {
overflow: 'visible',
'max-height': 'none',
} );
unlockEl( wrap.querySelector( '.block-editor-iframe__scale-container' ), {
overflow: 'visible',
'max-height': 'none',
} );
if ( ! iframe ) {
return;
}
if ( boundIframe !== iframe ) {
boundIframe = iframe;
iframe.addEventListener( 'load', measure );
}
var full = contentHeight( iframe );
unlockEl( iframe, {
'max-height': 'none',
overflow: 'visible',
} );
if ( full > 40 ) {
unlockEl( iframe, { height: full + 'px' } );
}
var next = 0;
if ( contentEl ) {
next = Math.ceil( contentEl.getBoundingClientRect().height );
}
if ( ( ! next || next < 40 ) && full > 40 ) {
var containerWidth = wrap.getBoundingClientRect().width || viewportWidth;
var scale = viewportWidth ? containerWidth / viewportWidth : 1;
next = Math.ceil( full * scale );
}
if ( next > 40 ) {
unlockEl( wrap, { 'min-height': next + 'px' } );
setHeight( function ( prev ) {
return Math.abs( prev - next ) > 8 ? next : prev;
} );
}
}
function tick() {
measure();
tries += 1;
if ( tries < 40 ) {
timer = window.setTimeout( tick, tries < 12 ? 100 : 300 );
}
}
var observer = window.MutationObserver
? new window.MutationObserver( function () {
measure();
} )
: null;
if ( observer ) {
observer.observe( wrap, { childList: true, subtree: true } );
}
tick();
return function () {
if ( observer ) {
observer.disconnect();
}
window.clearTimeout( timer );
if ( boundIframe ) {
boundIframe.removeEventListener( 'load', measure );
}
};
},
[ props.blocks, viewportWidth ]
);
return el(
'div',
{
ref: wrapRef,
className: 'patternswp-detail__preview',
style: height ? { minHeight: height } : undefined,
},
el( BlockPreview, {
blocks: props.blocks,
viewportWidth: viewportWidth,
minHeight: 120,
} )
);
}
/* ------------------------------------------------------------------ */
/* Sidebar nav item */
/* ------------------------------------------------------------------ */
function SidebarNavItem( props ) {
return el(
'li',
null,
el(
'button',
{
type: 'button',
className: 'patternswp-sidebar__nav-item' + ( props.isActive ? ' is-active' : '' ),
onClick: props.onClick,
'aria-current': props.isActive ? 'page' : undefined,
},
props.label
)
);
}
function SidebarCollapsibleSection( props ) {
var isExpanded = !! props.isExpanded;
var panelId = props.panelId;
return el(
'div',
{
className:
'patternswp-sidebar__section' +
( isExpanded ? ' is-expanded' : ' is-collapsed' ),
},
el(
'button',
{
type: 'button',
className: 'patternswp-sidebar__section-toggle',
onClick: props.onToggle,
'aria-expanded': isExpanded,
'aria-controls': panelId,
'aria-label': isExpanded
? sprintf(
/* translators: %s: sidebar section title */
__( 'Minimize %s', 'patternswp' ),
props.title
)
: sprintf(
/* translators: %s: sidebar section title */
__( 'Maximize %s', 'patternswp' ),
props.title
),
},
el( 'span', { className: 'patternswp-sidebar__section-title' }, props.title ),
el( Icon, {
className: 'patternswp-sidebar__section-toggle-icon',
icon: iconOrDash(
isExpanded ? icons.chevronUp : icons.chevronDown,
isExpanded ? 'arrow-up-alt2' : 'arrow-down-alt2'
),
size: 16,
} )
),
isExpanded
? el(
'div',
{ id: panelId, className: 'patternswp-sidebar__section-panel' },
props.children
)
: null
);
}
/* ------------------------------------------------------------------ */
/* Appearance settings panel */
/* ------------------------------------------------------------------ */
function PreviewSizeControl( props ) {
var value = props.value || 2;
var onChange = props.onChange;
var steps = [ 1, 2, 3, 4 ];
/* Fill covers completed segments (value 1 = 25%, … value 4 = 100%). */
var fillPercent = ( value / 4 ) * 100;
function setSize( next, event ) {
if ( event ) {
event.preventDefault();
event.stopPropagation();
}
if ( next >= 1 && next <= 4 && next !== value ) {
onChange( next );
}
}
return el(
'div',
{
className: 'patternswp-preview-size',
role: 'slider',
'aria-label': __( 'Preview size', 'patternswp' ),
'aria-valuemin': 1,
'aria-valuemax': 4,
'aria-valuenow': value,
tabIndex: 0,
onKeyDown: function ( event ) {
if ( event.key === 'ArrowRight' || event.key === 'ArrowUp' ) {
setSize( Math.min( 4, value + 1 ), event );
}
if ( event.key === 'ArrowLeft' || event.key === 'ArrowDown' ) {
setSize( Math.max( 1, value - 1 ), event );
}
},
onMouseDown: function ( event ) {
/* Keep Appearance dropdown open while interacting. */
event.stopPropagation();
},
},
el(
'div',
{ className: 'patternswp-preview-size__track' },
el( 'div', {
className: 'patternswp-preview-size__fill',
style: { width: fillPercent + '%' },
} ),
el(
'div',
{ className: 'patternswp-preview-size__breaks', 'aria-hidden': true },
el( 'span' ),
el( 'span' ),
el( 'span' )
),
el( 'span', {
className: 'patternswp-preview-size__thumb',
style: { left: fillPercent + '%' },
} ),
el(
'div',
{ className: 'patternswp-preview-size__hits' },
steps.map( function ( step ) {
return el( 'button', {
key: step,
type: 'button',
className:
'patternswp-preview-size__hit' +
( step === value ? ' is-active' : '' ),
'aria-label': sprintf(
/* translators: %d: preview size level */
__( 'Size %d', 'patternswp' ),
step
),
onMouseDown: function ( event ) {
setSize( step, event );
},
onClick: function ( event ) {
setSize( step, event );
},
} );
} )
)
)
);
}
/* ------------------------------------------------------------------ */
/* Appearance settings panel */
/* ------------------------------------------------------------------ */
function AppearancePanel( props ) {
var sortField = props.sortField;
var sortOrder = props.sortOrder;
var previewSize = props.previewSize;
var showTitle = props.showTitle;
var showPreview = props.showPreview;
function propertyRow( key, label, checked, onToggle ) {
return el(
'button',
{
type: 'button',
key: key,
className: 'patternswp-appearance__property' + ( checked ? ' is-active' : '' ),
onClick: onToggle,
},
el( 'span', { className: 'patternswp-appearance__property-check' }, checked ? el( Icon, { icon: iconOrDash( icons.check, 'yes' ) } ) : null ),
el( 'span', null, label )
);
}
return el(
'div',
{ className: 'patternswp-appearance' },
el(
'div',
{ className: 'patternswp-appearance__header' },
el( 'h2', { className: 'patternswp-appearance__title' }, __( 'Appearance', 'patternswp' ) ),
el(
Button,
{
variant: 'tertiary',
className: 'patternswp-appearance__reset',
onClick: props.onReset,
},
__( 'Reset view', 'patternswp' )
)
),
el(
'div',
{ className: 'patternswp-appearance__row patternswp-appearance__row--sort' },
el(
'div',
{ className: 'patternswp-appearance__field' },
el( 'span', { className: 'patternswp-appearance__label' }, __( 'Sort by', 'patternswp' ) ),
SelectControl
? el( SelectControl, {
hideLabelFromVision: true,
label: __( 'Sort by', 'patternswp' ),
value: sortField,
options: [
{ label: __( 'Default', 'patternswp' ), value: 'default' },
{ label: __( 'Title', 'patternswp' ), value: 'title' },
{ label: __( 'Type', 'patternswp' ), value: 'type' },
],
onChange: props.onSortFieldChange,
__nextHasNoMarginBottom: true,
__next40pxDefaultSize: true,
} )
: null
),
el(
'div',
{ className: 'patternswp-appearance__field patternswp-appearance__field--order' },
el( 'span', { className: 'patternswp-appearance__label' }, __( 'Order', 'patternswp' ) ),
el(
ButtonGroup,
{ className: 'patternswp-appearance__order' },
el( Button, {
icon: iconOrDash( icons.arrowUp, 'arrow-up-alt' ),
label: __( 'Ascending', 'patternswp' ),
isPressed: sortOrder === 'asc',
onClick: function () {
props.onSortOrderChange( 'asc' );
},
} ),
el( Button, {
icon: iconOrDash( icons.arrowDown, 'arrow-down-alt' ),
label: __( 'Descending', 'patternswp' ),
isPressed: sortOrder === 'desc',
onClick: function () {
props.onSortOrderChange( 'desc' );
},
} )
)
)
),
el(
'div',
{ className: 'patternswp-appearance__section' },
el( 'span', { className: 'patternswp-appearance__label' }, __( 'Preview size', 'patternswp' ) ),
el( PreviewSizeControl, {
value: previewSize,
onChange: props.onPreviewSizeChange,
} )
),
el(
'div',
{ className: 'patternswp-appearance__section' },
el( 'span', { className: 'patternswp-appearance__label' }, __( 'Properties', 'patternswp' ) ),
el(
'div',
{ className: 'patternswp-appearance__properties' },
propertyRow( 'title', __( 'Title', 'patternswp' ), showTitle, function () {
props.onShowTitleChange( ! showTitle );
} ),
propertyRow( 'preview', __( 'Preview', 'patternswp' ), showPreview, function () {
props.onShowPreviewChange( ! showPreview );
} )
)
)
);
}
/* ------------------------------------------------------------------ */
/* Pattern card */
/* ------------------------------------------------------------------ */
function PatternCard( props ) {
var pattern = props.pattern;
var isLicenseActive = props.isLicenseActive;
var isPro = pattern.type === 'pro' || pattern.locked === true;
var locked = isPatternLocked( pattern );
var isFavorite = props.isFavorite;
var isSelected = props.isSelected;
var bulkMode = props.bulkMode;
var viewportWidth = props.viewportWidth;
var scrollRoot = props.scrollRoot;
var showTitle = props.showTitle !== false;
var showPreview = props.showPreview !== false;
var previewHeight = props.previewHeight || 200;
var blocks = useMemo(
function () {
try {
return parse( decodePatternContent( pattern.content ) );
} catch ( e ) {
return [];
}
},
[ pattern.content ]
);
function handlePreviewClick() {
if ( bulkMode ) {
props.onToggleSelect( pattern );
return;
}
if ( locked ) {
props.onView( pattern );
return;
}
props.onInsert( pattern );
}
return el(
'article',
{
className:
'patternswp-card' +
( locked ? ' is-locked' : '' ) +
( isSelected ? ' is-selected' : '' ) +
( ! showPreview ? ' no-preview' : '' ),
},
showPreview
? el(
'div',
{
className: 'patternswp-card__preview',
style: { height: previewHeight, minHeight: previewHeight, maxHeight: previewHeight },
onClick: handlePreviewClick,
onKeyDown: function ( event ) {
if ( event.key === 'Enter' || event.key === ' ' ) {
event.preventDefault();
handlePreviewClick();
}
},
role: 'button',
tabIndex: 0,
'aria-label': sprintf(
/* translators: %s: pattern title */
__( 'Insert pattern: %s', 'patternswp' ),
pattern.title || ''
),
},
isPro
? el( 'span', { className: 'patternswp-card__badge' }, __( 'Pro', 'patternswp' ) )
: null,
el( LazyPreviewInner, {
blocks: blocks,
viewportWidth: viewportWidth,
root: scrollRoot,
minHeight: previewHeight,
} )
)
: null,
el(
'div',
{ className: 'patternswp-card__footer' },
el(
'div',
{ className: 'patternswp-card__select' },
CheckboxControl
? el( CheckboxControl, {
checked: !! isSelected,
onChange: function () {
props.onToggleSelect( pattern );
},
__nextHasNoMarginBottom: true,
} )
: el( 'input', {
type: 'checkbox',
checked: !! isSelected,
onChange: function () {
props.onToggleSelect( pattern );
},
'aria-label': __( 'Select pattern', 'patternswp' ),
} )
),
showTitle
? el( 'div', { className: 'patternswp-card__title', title: pattern.title }, pattern.title )
: el( 'div', { className: 'patternswp-card__title is-empty' } ),
el(
'div',
{ className: 'patternswp-card__icons' },
el(
Tooltip,
{ text: __( 'View pattern', 'patternswp' ) },
el( Button, {
icon: iconOrDash( icons.view, 'visibility' ),
label: __( 'View', 'patternswp' ),
size: 'compact',
onClick: function ( event ) {
event.stopPropagation();
props.onView( pattern );
},
} )
),
el(
Tooltip,
{
text: isFavorite
? __( 'Remove from favorites', 'patternswp' )
: __( 'Add to favorites', 'patternswp' ),
},
el( Button, {
icon: iconOrDash(
isFavorite ? icons.heartFilled : icons.heart,
'heart'
),
label: __( 'Favorite', 'patternswp' ),
size: 'compact',
isPressed: !! isFavorite,
onClick: function ( event ) {
event.stopPropagation();
props.onToggleFavorite( pattern );
},
} )
),
el(
DropdownMenu,
{
icon: iconOrDash( icons.moreVertical, 'ellipsis' ),
label: __( 'Pattern actions', 'patternswp' ),
toggleProps: { size: 'compact' },
},
function ( { onClose } ) {
return el(
MenuGroup,
null,
locked
? el( MenuItem, {
icon: iconOrDash( icons.lock, 'lock' ),
onClick: function () {
window.open(
'https://thepatternswp.com/pricing/',
'_blank',
'noopener,noreferrer'
);
onClose();
},
}, __( 'Upgrade to insert', 'patternswp' ) )
: el( MenuItem, {
icon: iconOrDash( icons.plus, 'plus' ),
onClick: function () {
props.onInsert( pattern );
onClose();
},
}, __( 'Add pattern', 'patternswp' ) ),
el( MenuItem, {
icon: iconOrDash(
isFavorite ? icons.heartFilled : icons.heart,
'heart'
),
onClick: function () {
props.onToggleFavorite( pattern );
onClose();
},
}, isFavorite
? __( 'Remove favorite', 'patternswp' )
: __( 'Add to favorites', 'patternswp' ) )
);
}
)
)
)
);
}
function PatternDetailView( props ) {
var pattern = props.pattern;
var isLicenseActive = props.isLicenseActive;
var isPro = pattern.type === 'pro' || pattern.locked === true;
var locked = isPatternLocked( pattern );
var isFavorite = props.isFavorite;
var viewportWidth = props.viewportWidth;
var blocks = useMemo(
function () {
try {
return parse( decodePatternContent( pattern.content ) );
} catch ( e ) {
return [];
}
},
[ pattern.content ]
);
return el(
'div',
{ className: 'patternswp-detail' },
el(
'div',
{ className: 'patternswp-detail__header' },
el( Button, {
className: 'patternswp-detail__back',
icon: iconOrDash( icons.arrowLeft, 'arrow-left-alt2' ),
label: __( 'Back to patterns', 'patternswp' ),
showTooltip: true,
onClick: props.onClose,
} ),
el(
'h2',
{ className: 'patternswp-detail__title' },
pattern.title || __( 'Untitled pattern', 'patternswp' )
),
isPro
? el( 'span', { className: 'patternswp-detail__badge' }, __( 'Pro', 'patternswp' ) )
: null,
el(
'div',
{ className: 'patternswp-detail__actions' },
el( Button, {
icon: iconOrDash(
isFavorite ? icons.heartFilled : icons.heart,
'heart'
),
label: isFavorite
? __( 'Remove from favorites', 'patternswp' )
: __( 'Add to favorites', 'patternswp' ),
isPressed: !! isFavorite,
onClick: function () {
props.onToggleFavorite( pattern );
},
} ),
locked
? el( Button, {
variant: 'primary',
icon: iconOrDash( icons.lock, 'lock' ),
onClick: function () {
window.open(
'https://thepatternswp.com/pricing/',
'_blank',
'noopener,noreferrer'
);
},
}, __( 'Upgrade to insert', 'patternswp' ) )
: el( Button, {
className: 'patternswp-detail__insert',
variant: 'primary',
icon: iconOrDash( icons.plus, 'plus' ),
onClick: function () {
props.onInsert( pattern );
},
}, __( 'Add pattern', 'patternswp' ) )
)
),
el(
'div',
{ className: 'patternswp-detail__body' },
el(
'div',
{
className: 'patternswp-detail__canvas',
style: { maxWidth: viewportWidth + 'px' },
},
el( FullPatternPreview, {
blocks: blocks,
viewportWidth: viewportWidth,
} )
)
)
);
}
/* ------------------------------------------------------------------ */
/* Library modal */
/* ------------------------------------------------------------------ */
function PatternsLibrary( props ) {
var onRequestClose = props.onRequestClose;
var boot = getBoot();
var isLicenseActive = !! boot.isLicenseActive;
var remoteCategoriesState = useState( function () {
var list = Array.isArray( boot.patternCategories ) ? boot.patternCategories : [];
return list
.filter( function ( cat ) {
return cat && cat.name;
} )
.slice()
.sort( function ( a, b ) {
return formatCategoryLabel( a.name ).localeCompare( formatCategoryLabel( b.name ) );
} );
} );
var categories = remoteCategoriesState[ 0 ];
var setRemoteCategories = remoteCategoriesState[ 1 ];
var libraryCompleteState = useState( !! boot.libraryComplete );
var libraryComplete = libraryCompleteState[ 0 ];
var setLibraryComplete = libraryCompleteState[ 1 ];
var libraryTotalState = useState( 0 );
var libraryTotal = libraryTotalState[ 0 ];
var setLibraryTotal = libraryTotalState[ 1 ];
var syncingState = useState( false );
var syncing = syncingState[ 0 ];
var setSyncing = syncingState[ 1 ];
var gridReadyState = useState( false );
var gridReady = gridReadyState[ 0 ];
var setGridReady = gridReadyState[ 1 ];
var syncRequestRef = useRef( 0 );
var prefs = Object.assign( {}, DEFAULT_PREFS, readStore( PREFS_KEY, {} ) );
var categoryState = useState( SPECIAL.ALL );
var category = categoryState[ 0 ];
var setCategory = categoryState[ 1 ];
var searchState = useState( '' );
var search = searchState[ 0 ];
var setSearch = searchState[ 1 ];
var debouncedSearchState = useState( '' );
var debouncedSearch = debouncedSearchState[ 0 ];
var setDebouncedSearch = debouncedSearchState[ 1 ];
var debounceSearch = useDebounce( setDebouncedSearch, 250 );
var sortFieldState = useState( prefs.sortField || 'default' );
var sortField = sortFieldState[ 0 ];
var setSortField = sortFieldState[ 1 ];
var sortOrderState = useState( prefs.sortOrder || 'asc' );
var sortOrder = sortOrderState[ 0 ];
var setSortOrder = sortOrderState[ 1 ];
var typeState = useState( prefs.typeFilter || 'all' );
var typeFilter = typeState[ 0 ];
var setTypeFilter = typeState[ 1 ];
var layoutState = useState( prefs.layout || 'grid' );
var layout = layoutState[ 0 ];
var setLayout = layoutState[ 1 ];
var viewportState = useState( prefs.viewport || 'desktop' );
var viewport = viewportState[ 0 ];
var setViewport = viewportState[ 1 ];
var previewSizeState = useState( prefs.previewSize || 2 );
var previewSize = previewSizeState[ 0 ];
var setPreviewSize = previewSizeState[ 1 ];
var showTitleState = useState( prefs.showTitle !== false );
var showTitle = showTitleState[ 0 ];
var setShowTitle = showTitleState[ 1 ];
var showPreviewState = useState( prefs.showPreview !== false );
var showPreview = showPreviewState[ 0 ];
var setShowPreview = showPreviewState[ 1 ];
var patternsState = useState( [] );
var patterns = patternsState[ 0 ];
var setPatterns = patternsState[ 1 ];
var pageState = useState( 1 );
var page = pageState[ 0 ];
var setPage = pageState[ 1 ];
var hasMoreState = useState( true );
var hasMore = hasMoreState[ 0 ];
var setHasMore = hasMoreState[ 1 ];
var loadingState = useState( true );
var loading = loadingState[ 0 ];
var setLoading = loadingState[ 1 ];
var loadingMoreState = useState( false );
var loadingMore = loadingMoreState[ 0 ];
var setLoadingMore = loadingMoreState[ 1 ];
var errorState = useState( '' );
var error = errorState[ 0 ];
var setError = errorState[ 1 ];
var favoritesState = useState( function () {
return readStore( FAVORITES_KEY, [] );
} );
var favorites = favoritesState[ 0 ];
var setFavorites = favoritesState[ 1 ];
var recentState = useState( function () {
return readStore( RECENT_KEY, [] );
} );
var recent = recentState[ 0 ];
var setRecent = recentState[ 1 ];
var bulkModeState = useState( false );
var bulkMode = bulkModeState[ 0 ];
var setBulkMode = bulkModeState[ 1 ];
var collectionsExpandedState = useState( false );
var collectionsExpanded = collectionsExpandedState[ 0 ];
var setCollectionsExpanded = collectionsExpandedState[ 1 ];
var previewPatternState = useState( null );
var previewPattern = previewPatternState[ 0 ];
var setPreviewPattern = previewPatternState[ 1 ];
var selectedState = useState( {} );
var selectedMap = selectedState[ 0 ];
var setSelectedMap = selectedState[ 1 ];
var scrollRef = useRef( null );
var sentinelRef = useRef( null );
var abortRef = useRef( null );
var requestIdRef = useRef( 0 );
var firstPageReadyRef = useRef( false );
useEffect(
function () {
debounceSearch( search );
},
[ search, debounceSearch ]
);
useEffect(
function () {
writeStore( PREFS_KEY, {
layout: layout,
viewport: viewport,
sortField: sortField,
sortOrder: sortOrder,
typeFilter: typeFilter,
previewSize: previewSize,
showTitle: showTitle,
showPreview: showPreview,
} );
},
[
layout,
viewport,
sortField,
sortOrder,
typeFilter,
previewSize,
showTitle,
showPreview,
]
);
var isLocalCollection =
category === SPECIAL.FAVORITES || category === SPECIAL.RECENT;
useEffect(
function () {
setPreviewPattern( null );
},
[ category, debouncedSearch ]
);
useEffect(
function () {
if ( ! previewPattern ) {
return;
}
function onKeyDown( event ) {
if ( event.key === 'Escape' ) {
event.preventDefault();
event.stopPropagation();
setPreviewPattern( null );
}
}
document.addEventListener( 'keydown', onKeyDown, true );
return function () {
document.removeEventListener( 'keydown', onKeyDown, true );
};
},
[ previewPattern ]
);
var loadPage = useCallback(
function ( nextPage, replace ) {
if ( isLocalCollection ) {
return;
}
if ( abortRef.current ) {
abortRef.current.abort();
}
var controller = window.AbortController ? new window.AbortController() : null;
abortRef.current = controller;
var requestId = ++requestIdRef.current;
if ( replace ) {
setLoading( true );
setError( '' );
} else {
setLoadingMore( true );
}
fetchPatternsPage(
nextPage,
debouncedSearch,
category === SPECIAL.ALL ? '' : category,
PAGE_SIZE,
controller && controller.signal
)
.then( function ( payload ) {
if ( requestId !== requestIdRef.current ) {
return;
}
var data = payload && Array.isArray( payload.patterns ) ? payload.patterns : [];
var complete = !!( payload && payload.complete );
var total = payload && typeof payload.total === 'number' ? payload.total : 0;
if ( payload && payload.categories && payload.categories.length ) {
setRemoteCategories(
payload.categories
.filter( function ( cat ) {
return cat && cat.name;
} )
.slice()
.sort( function ( a, b ) {
return formatCategoryLabel( a.name ).localeCompare(
formatCategoryLabel( b.name )
);
} )
);
}
setLibraryComplete( complete );
if ( total ) {
setLibraryTotal( total );
}
setPage( nextPage );
setPatterns( function ( prev ) {
var next = replace ? data : upsertByKey( prev, data );
setHasMore( next.length < total || ( ! complete && data.length >= PAGE_SIZE ) );
return next;
} );
} )
.catch( function ( err ) {
if ( err && err.name === 'AbortError' ) {
return;
}
if ( requestId !== requestIdRef.current ) {
return;
}
setError(
__( 'Failed to load patterns. Please try again.', 'patternswp' )
);
if ( replace ) {
setPatterns( [] );
setHasMore( false );
}
} )
.finally( function () {
if ( requestId !== requestIdRef.current ) {
return;
}
setLoading( false );
setLoadingMore( false );
if ( ! firstPageReadyRef.current ) {
firstPageReadyRef.current = true;
setGridReady( true );
}
} );
},
[ category, debouncedSearch, isLocalCollection ]
);
/* Local collections (favorites / recent) — no AJAX */
useEffect(
function () {
if ( ! isLocalCollection ) {
return;
}
var source = category === SPECIAL.FAVORITES ? favorites : recent;
var filtered = source;
if ( debouncedSearch ) {
var q = debouncedSearch.toLowerCase();
filtered = source.filter( function ( p ) {
return ( p.title || '' ).toLowerCase().indexOf( q ) !== -1;
} );
}
setPatterns( filtered );
setHasMore( false );
setLoading( false );
setError( '' );
setPage( 1 );
},
[ category, debouncedSearch, isLocalCollection, favorites, recent ]
);
/* Remote patterns — reset + fetch when category / search changes */
useEffect(
function () {
if ( isLocalCollection ) {
return;
}
setPatterns( [] );
setPage( 1 );
setHasMore( true );
loadPage( 1, true );
return function () {
if ( abortRef.current ) {
abortRef.current.abort();
}
};
},
[ category, debouncedSearch, isLocalCollection, loadPage ]
);
/* Fill the rest of the catalog after the first page is on screen. */
useEffect(
function () {
if ( ! gridReady ) {
return;
}
var stopped = false;
var timer = 0;
var tries = 0;
var requestId = ++syncRequestRef.current;
function applyStatus( status ) {
if ( status.categories && status.categories.length ) {
setRemoteCategories(
status.categories
.filter( function ( cat ) {
return cat && cat.name;
} )
.slice()
.sort( function ( a, b ) {
return formatCategoryLabel( a.name ).localeCompare(
formatCategoryLabel( b.name )
);
} )
);
}
if ( typeof status.total === 'number' ) {
setLibraryTotal( status.total );
}
setLibraryComplete( !! status.complete );
if ( ! status.complete ) {
setHasMore( true );
}
}
function tick( mode ) {
if ( stopped || requestId !== syncRequestRef.current || tries > 40 ) {
setSyncing( false );
return;
}
tries += 1;
setSyncing( true );
warmLibrary( null, mode )
.then( function ( status ) {
if ( stopped || requestId !== syncRequestRef.current ) {
return;
}
applyStatus( status );
if ( status.complete && ( mode === 'check' ? ! status.hasUpdates : true ) ) {
setSyncing( false );
return;
}
timer = window.setTimeout( function () {
tick( 'sync' );
}, 600 );
} )
.catch( function ( err ) {
if ( err && err.name === 'AbortError' ) {
return;
}
if ( ! stopped && requestId === syncRequestRef.current ) {
timer = window.setTimeout( function () {
tick( 'sync' );
}, 2000 );
}
} );
}
tick( boot.libraryComplete ? 'check' : 'sync' );
return function () {
stopped = true;
window.clearTimeout( timer );
};
},
[ gridReady ]
);
function onSyncClick() {
syncRequestRef.current += 1;
var requestId = syncRequestRef.current;
setSyncing( true );
function applyStatus( status ) {
if ( status.categories && status.categories.length ) {
setRemoteCategories(
status.categories
.filter( function ( cat ) {
return cat && cat.name;
} )
.slice()
.sort( function ( a, b ) {
return formatCategoryLabel( a.name ).localeCompare(
formatCategoryLabel( b.name )
);
} )
);
}
if ( typeof status.total === 'number' ) {
setLibraryTotal( status.total );
}
setLibraryComplete( !! status.complete );
if ( ! status.complete || status.hasUpdates ) {
setHasMore( true );
}
}
function tick( mode ) {
if ( requestId !== syncRequestRef.current ) {
return;
}
warmLibrary( null, mode, true )
.then( function ( status ) {
if ( requestId !== syncRequestRef.current ) {
return;
}
applyStatus( status );
if ( status.complete && ! status.hasUpdates ) {
setSyncing( false );
return;
}
window.setTimeout( function () {
tick( 'sync' );
}, 500 );
} )
.catch( function () {
if ( requestId === syncRequestRef.current ) {
setSyncing( false );
}
} );
}
tick( 'check' );
}
useEffect(
function () {
var sentinel = sentinelRef.current;
var root = scrollRef.current;
if ( ! sentinel || ! root || ! window.IntersectionObserver ) {
return;
}
if ( isLocalCollection || ! hasMore || loading || loadingMore ) {
return;
}
var observer = new window.IntersectionObserver(
function ( entries ) {
if ( entries[ 0 ] && entries[ 0 ].isIntersecting ) {
loadPage( page + 1, false );
}
},
{ root: root, rootMargin: '600px 0px', threshold: 0 }
);
observer.observe( sentinel );
return function () {
observer.disconnect();
};
},
[ hasMore, loading, loadingMore, page, loadPage, isLocalCollection, patterns.length ]
);
var favoriteKeys = useMemo(
function () {
return new Set( favorites.map( patternKey ) );
},
[ favorites ]
);
var visiblePatterns = useMemo(
function () {
return sortPatterns( filterByType( patterns, typeFilter ), sortField, sortOrder );
},
[ patterns, typeFilter, sortField, sortOrder ]
);
var previewHeight = PREVIEW_SIZE_HEIGHTS[ previewSize ] || PREVIEW_SIZE_HEIGHTS[ 2 ];
if ( layout === 'list' ) {
previewHeight = Math.max( previewHeight, 300 );
}
function resetAppearance() {
setSortField( DEFAULT_PREFS.sortField );
setSortOrder( DEFAULT_PREFS.sortOrder );
setPreviewSize( DEFAULT_PREFS.previewSize );
setShowTitle( DEFAULT_PREFS.showTitle );
setShowPreview( DEFAULT_PREFS.showPreview );
setLayout( DEFAULT_PREFS.layout );
setViewport( DEFAULT_PREFS.viewport );
}
var insertPattern = useInsertPattern( function ( patterns ) {
var list = Array.isArray( patterns ) ? patterns : [ patterns ];
setRecent( function ( prev ) {
var next = prev.slice();
list.forEach( function ( pattern ) {
var key = patternKey( pattern );
next = [ pattern ].concat(
next.filter( function ( p ) {
return patternKey( p ) !== key;
} )
);
} );
next = next.slice( 0, MAX_RECENT );
writeStore( RECENT_KEY, next );
return next;
} );
onRequestClose();
} );
var toggleFavorite = useCallback( function ( pattern ) {
setFavorites( function ( prev ) {
var key = patternKey( pattern );
var exists = prev.some( function ( p ) {
return patternKey( p ) === key;
} );
var next;
if ( exists ) {
next = prev.filter( function ( p ) {
return patternKey( p ) !== key;
} );
} else {
next = [ pattern ].concat( prev ).slice( 0, MAX_FAVORITES );
}
writeStore( FAVORITES_KEY, next );
return next;
} );
}, [] );
var toggleSelect = useCallback( function ( pattern ) {
var key = patternKey( pattern );
setSelectedMap( function ( prev ) {
var next = Object.assign( {}, prev );
if ( next[ key ] ) {
delete next[ key ];
} else {
next[ key ] = pattern;
}
return next;
} );
}, [] );
var selectedList = useMemo(
function () {
return Object.keys( selectedMap ).map( function ( key ) {
return selectedMap[ key ];
} );
},
[ selectedMap ]
);
var insertSelected = useCallback(
function () {
var allowed = selectedList.filter( function ( pattern ) {
return ! isPatternLocked( pattern );
} );
if ( ! allowed.length ) {
return;
}
insertPattern( allowed );
},
[ selectedList, insertPattern, isLicenseActive ]
);
var collectionItems = useMemo(
function () {
return [
{ value: SPECIAL.ALL, label: __( 'All patterns', 'patternswp' ) },
{
value: SPECIAL.FAVORITES,
label: sprintf(
/* translators: %d: favorites count */
__( 'Favorites (%d)', 'patternswp' ),
favorites.length
),
},
{
value: SPECIAL.RECENT,
label: sprintf(
/* translators: %d: recent count */
__( 'Recently used (%d)', 'patternswp' ),
recent.length
),
},
];
},
[ favorites.length, recent.length ]
);
var categoryItems = useMemo(
function () {
return categories.map( function ( cat ) {
return {
value: cat.name,
label: formatCategoryLabel( cat.name ),
};
} );
},
[ categories ]
);
var viewportWidth = VIEWPORTS[ viewport ] || VIEWPORTS.desktop;
var gridClass =
'patternswp-grid' +
( layout === 'list' ? ' is-list' : '' ) +
( viewport === 'tablet' ? ' is-tablet' : '' ) +
( viewport === 'mobile' ? ' is-mobile' : '' ) +
' preview-size-' +
previewSize;
return el(
Modal,
{
title: __( 'PatternsWP Patterns', 'patternswp' ),
className: 'patternswp-modal',
onRequestClose: onRequestClose,
isFullScreen: true,
shouldCloseOnClickOutside: false,
headerActions: el(
Button,
{
className: 'patternswp-library-sync' + ( syncing ? ' is-busy' : '' ),
icon: syncing
? el( Spinner )
: iconOrDash( icons.refresh, 'update' ),
label: syncing
? __( 'Updating the pattern library', 'patternswp' )
: libraryComplete
? __( 'Check for new patterns', 'patternswp' )
: __( 'Load remaining patterns', 'patternswp' ),
showTooltip: true,
onClick: onSyncClick,
disabled: syncing,
},
syncing
? ( libraryTotal
? sprintf(
/* translators: %d: number of cached patterns */
__( '%d', 'patternswp' ),
libraryTotal
)
: __( 'Syncing', 'patternswp' ) )
: __( 'Sync', 'patternswp' )
),
},
el(
'div',
{ className: 'patternswp-layout' },
el(
'aside',
{ className: 'patternswp-sidebar', 'aria-label': __( 'Pattern categories', 'patternswp' ) },
el( SidebarCollapsibleSection, {
title: __( 'Collections', 'patternswp' ),
panelId: 'patternswp-collections-panel',
isExpanded: collectionsExpanded,
onToggle: function () {
setCollectionsExpanded( ! collectionsExpanded );
},
},
el(
'ul',
{ className: 'patternswp-sidebar__nav' },
collectionItems.map( function ( item ) {
return el( SidebarNavItem, {
key: item.value || 'all',
label: item.label,
isActive: category === item.value,
onClick: function () {
setCategory( item.value );
},
} );
} )
)
),
el(
'div',
{ className: 'patternswp-sidebar__section' },
el( 'h2', { className: 'patternswp-sidebar__section-title' }, __( 'Categories', 'patternswp' ) ),
el(
'ul',
{ className: 'patternswp-sidebar__nav' },
categoryItems.map( function ( item ) {
return el( SidebarNavItem, {
key: item.value,
label: item.label,
isActive: category === item.value,
onClick: function () {
setCategory( item.value );
},
} );
} )
)
),
el(
'div',
{ className: 'patternswp-sidebar__section patternswp-sidebar__footer' },
el( 'h2', { className: 'patternswp-sidebar__section-title' }, __( 'Resources', 'patternswp' ) ),
el(
'a',
{
className: 'patternswp-sidebar__link',
href: 'https://thepatternswp.com/docs/',
target: '_blank',
rel: 'noopener noreferrer',
},
__( 'Pattern guide', 'patternswp' )
),
el(
'a',
{
className: 'patternswp-sidebar__link',
href: isLicenseActive
? 'https://thepatternswp.com/account/'
: 'https://thepatternswp.com/pricing/',
target: '_blank',
rel: 'noopener noreferrer',
},
isLicenseActive
? __( 'PatternsWP account', 'patternswp' )
: __( 'Upgrade to Pro', 'patternswp' )
),
el(
'a',
{
className: 'patternswp-sidebar__link',
href: 'https://thepatternswp.com/contact/',
target: '_blank',
rel: 'noopener noreferrer',
},
__( 'Support', 'patternswp' )
)
)
),
el(
'section',
{
className:
'patternswp-main' + ( previewPattern ? ' patternswp-main--detail' : '' ),
},
previewPattern
? el( PatternDetailView, {
pattern: previewPattern,
isLicenseActive: isLicenseActive,
isFavorite: favoriteKeys.has( patternKey( previewPattern ) ),
viewportWidth: viewportWidth,
onClose: function () {
setPreviewPattern( null );
},
onInsert: insertPattern,
onToggleFavorite: toggleFavorite,
} )
: el(
Fragment,
null,
el(
'div',
{ className: 'patternswp-toolbar' },
el(
'div',
{ className: 'patternswp-toolbar__search' },
el( SearchControl, {
label: __( 'Search patterns', 'patternswp' ),
placeholder: __( 'Search', 'patternswp' ),
value: search,
onChange: setSearch,
__nextHasNoMarginBottom: true,
} )
),
el(
'div',
{ className: 'patternswp-toolbar__actions' },
el(
'span',
{ className: 'patternswp-toolbar__meta' },
loading
? el( Spinner )
: sprintf(
/* translators: %d: number of patterns */
_n( '%d pattern', '%d patterns', visiblePatterns.length, 'patternswp' ),
visiblePatterns.length
) + ( hasMore && ! isLocalCollection ? '+' : '' )
),
el( Button, {
className: 'patternswp-toolbar__bulk-edit',
icon: iconOrDash( icons.edit, 'edit' ),
onClick: function () {
setBulkMode( ! bulkMode );
if ( bulkMode ) {
setSelectedMap( {} );
}
},
isPressed: bulkMode,
}, __( 'Bulk edit', 'patternswp' ) ),
el(
DropdownMenu,
{
icon: iconOrDash( icons.filter, 'filter' ),
label: __( 'Filter', 'patternswp' ),
toggleProps: {
children: __( 'Filter', 'patternswp' ),
},
},
function ( { onClose } ) {
return el(
Fragment,
null,
el(
MenuGroup,
{ label: __( 'Type', 'patternswp' ) },
[
{ value: 'all', label: __( 'All', 'patternswp' ) },
{ value: 'free', label: __( 'Free', 'patternswp' ) },
{ value: 'pro', label: __( 'Pro', 'patternswp' ) },
].map( function ( opt ) {
return el( MenuItem, {
key: opt.value,
icon: typeFilter === opt.value ? iconOrDash( icons.check, 'yes' ) : null,
onClick: function () {
setTypeFilter( opt.value );
onClose();
},
}, opt.label );
} )
),
el(
MenuGroup,
{ label: __( 'Preview width', 'patternswp' ) },
[
{ value: 'desktop', label: __( 'Desktop', 'patternswp' ) },
{ value: 'tablet', label: __( 'Tablet', 'patternswp' ) },
{ value: 'mobile', label: __( 'Mobile', 'patternswp' ) },
].map( function ( opt ) {
return el( MenuItem, {
key: opt.value,
icon: viewport === opt.value ? iconOrDash( icons.check, 'yes' ) : null,
onClick: function () {
setViewport( opt.value );
onClose();
},
}, opt.label );
} )
)
);
}
),
el( Button, {
icon: iconOrDash(
layout === 'grid' ? icons.grid : icons.list,
layout === 'grid' ? 'grid-view' : 'list-view'
),
label:
layout === 'grid'
? __( 'Grid view', 'patternswp' )
: __( 'List view', 'patternswp' ),
onClick: function () {
setLayout( layout === 'grid' ? 'list' : 'grid' );
},
} ),
Dropdown
? el( Dropdown, {
className: 'patternswp-appearance-dropdown',
popoverProps: {
placement: 'bottom-end',
offset: 8,
focusOnMount: false,
},
renderToggle: function ( toggleProps ) {
return el( Button, {
className: 'patternswp-appearance-toggle',
icon: iconOrDash( icons.settings, 'admin-generic' ),
label: __( 'Appearance', 'patternswp' ),
onClick: function ( event ) {
event.preventDefault();
toggleProps.onToggle();
},
'aria-expanded': toggleProps.isOpen,
isPressed: !! toggleProps.isOpen,
} );
},
renderContent: function () {
return el( AppearancePanel, {
sortField: sortField,
sortOrder: sortOrder,
previewSize: previewSize,
showTitle: showTitle,
showPreview: showPreview,
onSortFieldChange: setSortField,
onSortOrderChange: setSortOrder,
onPreviewSizeChange: setPreviewSize,
onShowTitleChange: setShowTitle,
onShowPreviewChange: setShowPreview,
onReset: resetAppearance,
} );
},
} )
: null
)
),
bulkMode
? el(
'div',
{ className: 'patternswp-bulk-bar' },
el(
'span',
null,
sprintf(
/* translators: %d: selected count */
_n( '%d selected', '%d selected', selectedList.length, 'patternswp' ),
selectedList.length
)
),
el(
'div',
{ className: 'patternswp-bulk-bar__actions' },
el( Button, {
variant: 'tertiary',
onClick: function () {
setSelectedMap( {} );
},
disabled: ! selectedList.length,
}, __( 'Clear', 'patternswp' ) ),
el( Button, {
variant: 'primary',
onClick: insertSelected,
disabled: ! selectedList.length,
}, __( 'Insert selected', 'patternswp' ) )
)
)
: null,
error
? el(
Notice,
{
status: 'error',
isDismissible: true,
onRemove: function () {
setError( '' );
},
},
error
)
: null,
el(
'div',
{
className: 'patternswp-grid-scroll',
ref: scrollRef,
},
loading && isEmpty( visiblePatterns )
? el( 'div', { className: 'patternswp-loading' }, el( Spinner ), __( 'Loading patterns…', 'patternswp' ) )
: null,
! loading && isEmpty( visiblePatterns )
? el(
'div',
{ className: 'patternswp-empty' },
el( Icon, { icon: iconOrDash( icons.search, 'search' ), size: 36 } ),
el(
'p',
null,
debouncedSearch
? __( 'No search results found.', 'patternswp' )
: libraryComplete
? __( 'No patterns were found.', 'patternswp' )
: __( 'Loading the pattern library…', 'patternswp' )
)
)
: null,
! isEmpty( visiblePatterns )
? el(
'div',
{ className: gridClass, role: 'list' },
visiblePatterns.map( function ( pattern ) {
var key = patternKey( pattern );
return el( PatternCard, {
key: key,
pattern: pattern,
isLicenseActive: isLicenseActive,
isFavorite: favoriteKeys.has( key ),
isSelected: !! selectedMap[ key ],
bulkMode: bulkMode,
viewportWidth: viewportWidth,
scrollRoot: scrollRef.current,
showTitle: showTitle,
showPreview: showPreview,
previewHeight: previewHeight,
onInsert: insertPattern,
onToggleFavorite: toggleFavorite,
onToggleSelect: toggleSelect,
onView: setPreviewPattern,
} );
} ),
hasMore && ! isLocalCollection
? el(
'div',
{ className: 'patternswp-sentinel', ref: sentinelRef },
loadingMore ? el( Spinner ) : null
)
: null
)
: null
)
)
)
)
);
}
function PatternsModal( props ) {
if ( ! props.isOpen ) {
return null;
}
return el( PatternsLibrary, { onRequestClose: props.onRequestClose } );
}
/* ------------------------------------------------------------------ */
/* Pattern Library block (inserter / /patternswp) */
/* ------------------------------------------------------------------ */
function InstantSearch( props ) {
var clientId = props.clientId;
var isLicenseActive = !! getBoot().isLicenseActive;
var queryState = useState( '' );
var query = queryState[ 0 ];
var setQuery = queryState[ 1 ];
var resultsState = useState( [] );
var results = resultsState[ 0 ];
var setResults = resultsState[ 1 ];
var loadingState = useState( false );
var loading = loadingState[ 0 ];
var setLoading = loadingState[ 1 ];
var openState = useState( false );
var isOpen = openState[ 0 ];
var setOpen = openState[ 1 ];
var wrapRef = useRef( null );
var abortRef = useRef( null );
var insertPattern = useInsertPattern( function ( patterns ) {
var list = Array.isArray( patterns ) ? patterns : [ patterns ];
var prev = readStore( RECENT_KEY, [] );
var next = prev.slice();
list.forEach( function ( pattern ) {
var key = patternKey( pattern );
next = [ pattern ].concat(
next.filter( function ( p ) {
return patternKey( p ) !== key;
} )
);
} );
next = next.slice( 0, MAX_RECENT );
writeStore( RECENT_KEY, next );
setQuery( '' );
setResults( [] );
setOpen( false );
} );
var runSearch = useCallback(
function ( value ) {
if ( abortRef.current ) {
abortRef.current.abort();
}
var trimmed = ( value || '' ).trim();
if ( ! trimmed ) {
setResults( [] );
setLoading( false );
setOpen( false );
return;
}
var controller =
typeof window.AbortController !== 'undefined'
? new window.AbortController()
: null;
abortRef.current = controller;
setLoading( true );
setOpen( true );
fetchPatternsPage(
1,
trimmed,
'',
undefined,
controller ? controller.signal : undefined
)
.then( function ( payload ) {
var data = payload && Array.isArray( payload.patterns ) ? payload.patterns : [];
setResults( data.slice( 0, 8 ) );
setLoading( false );
} )
.catch( function ( err ) {
if ( err && err.name === 'AbortError' ) {
return;
}
setResults( [] );
setLoading( false );
} );
},
[]
);
var debouncedSearch = useDebounce( runSearch, 300 );
useEffect(
function () {
return function () {
if ( abortRef.current ) {
abortRef.current.abort();
}
};
},
[]
);
useEffect(
function () {
function onDocClick( event ) {
if ( wrapRef.current && ! wrapRef.current.contains( event.target ) ) {
setOpen( false );
}
}
document.addEventListener( 'mousedown', onDocClick );
return function () {
document.removeEventListener( 'mousedown', onDocClick );
};
},
[]
);
function onChange( event ) {
var value = event.target.value;
setQuery( value );
debouncedSearch( value );
}
function onPick( pattern ) {
if ( isPatternLocked( pattern ) ) {
window.open( 'https://thepatternswp.com/pricing/', '_blank', 'noopener,noreferrer' );
return;
}
insertPattern( pattern, { anchorClientId: clientId } );
}
return el(
'div',
{ className: 'patternswp-library-block__instant', ref: wrapRef },
el(
'div',
{ className: 'patternswp-library-block__instant-head' },
el(
'label',
{
className: 'patternswp-library-block__instant-label',
htmlFor: 'patternswp-instant-search-' + clientId,
},
__( 'Instant Search', 'patternswp' )
),
el(
'p',
{ className: 'patternswp-library-block__instant-help' },
__( 'Type to find a pattern and insert it here.', 'patternswp' )
)
),
el(
'div',
{ className: 'patternswp-library-block__search-wrap' },
el( 'input', {
id: 'patternswp-instant-search-' + clientId,
type: 'search',
className: 'patternswp-library-block__search-input',
placeholder: __( 'Search patterns…', 'patternswp' ),
value: query,
onChange: onChange,
onFocus: function () {
if ( results.length || loading ) {
setOpen( true );
}
},
autoComplete: 'off',
} ),
el(
'span',
{ className: 'patternswp-library-block__search-icon', 'aria-hidden': true },
loading
? el( Spinner )
: el( Icon, { icon: iconOrDash( icons.search, 'search' ) } )
),
isOpen && ( loading || results.length || query.trim() )
? el(
'div',
{
className: 'patternswp-library-block__results',
role: 'listbox',
'aria-label': __( 'Pattern search results', 'patternswp' ),
},
! loading && ! results.length
? el(
'div',
{ className: 'patternswp-library-block__results-empty' },
__( 'No patterns found.', 'patternswp' )
)
: null,
results.map( function ( pattern ) {
var locked = isPatternLocked( pattern );
return el(
'button',
{
type: 'button',
key: patternKey( pattern ),
role: 'option',
className:
'patternswp-library-block__result' +
( locked ? ' is-locked' : '' ),
onClick: function () {
onPick( pattern );
},
},
el(
'span',
{ className: 'patternswp-library-block__result-title' },
pattern.title || __( 'Untitled pattern', 'patternswp' )
),
pattern.type === 'pro' || pattern.locked
? el(
'span',
{ className: 'patternswp-library-block__result-badge' },
__( 'Pro', 'patternswp' )
)
: null
);
} )
)
: null
)
);
}
function PatternsLibraryBlockEdit( props ) {
var clientId = props.clientId;
var localOpenState = useState( false );
var localOpen = localOpenState[ 0 ];
var setLocalOpen = localOpenState[ 1 ];
var removeBlock = useDispatch( storeBlockEditor ).removeBlock;
function openLibrary() {
var opened = openPatternsLibrary( { anchorClientId: clientId } );
if ( ! opened ) {
setInsertAnchor( clientId );
setLocalOpen( true );
}
}
function closeLocal() {
setLocalOpen( false );
setInsertAnchor( null );
}
function removeLibraryBlock() {
setInsertAnchor( null );
if ( removeBlock ) {
removeBlock( clientId );
}
}
return el(
'div',
{ className: 'patternswp-library-block' },
el(
Button,
{
className: 'patternswp-library-block__close',
icon: iconOrDash( icons.close, 'no-alt' ),
label: __( 'Remove PatternsWP Pattern Library', 'patternswp' ),
showTooltip: true,
onClick: removeLibraryBlock,
}
),
el(
'div',
{ className: 'patternswp-library-block__inner' },
el(
'div',
{ className: 'patternswp-library-block__header' },
el(
'span',
{ className: 'patternswp-library-block__logo', 'aria-hidden': true },
el( Icon, { icon: iconOrDash( icons.brand, 'layout' ) } )
),
el(
'h2',
{ className: 'patternswp-library-block__title' },
__( 'PatternsWP Pattern Library', 'patternswp' )
),
el(
'p',
{ className: 'patternswp-library-block__desc' },
__( 'Search and insert patterns into this page.', 'patternswp' )
)
),
el( InstantSearch, { clientId: clientId } ),
el(
'div',
{ className: 'patternswp-library-block__browse' },
el(
'button',
{
type: 'button',
className: 'patternswp-library-block__browse-link',
onClick: openLibrary,
},
__( 'Or browse the full library', 'patternswp' )
)
)
),
el( PatternsModal, {
isOpen: localOpen,
onRequestClose: closeLocal,
} )
);
}
if ( registerBlockType ) {
registerBlockType( 'patternswp/library', {
apiVersion: 3,
title: __( 'PatternsWP Pattern Library', 'patternswp' ),
description: __(
'Browse the PatternsWP pattern library and insert patterns into your page.',
'patternswp'
),
category: 'widgets',
icon: iconOrDash( icons.brand, 'layout' ),
keywords: [ 'patternswp', 'patterns', 'library', 'templates', 'blocks' ],
supports: {
html: false,
multiple: true,
reusable: false,
},
edit: PatternsLibraryBlockEdit,
save: function () {
return null;
},
} );
}
/* ------------------------------------------------------------------ */
/* Header toolbar + canvas block inserter (+) icon */
/* ------------------------------------------------------------------ */
var HEADER_SLOT_CLASS = 'patternswp-header-toolbar-slot';
var HEADER_BUTTON_CLASS = 'patternswp-toolbar-button';
var CANVAS_BUTTON_CLASS = 'patternswp-appender-button';
var CANVAS_INSERTER_SELECTOR =
'.components-dropdown.block-editor-inserter, .block-editor-inserter, .block-editor-inserter__toggle, .block-editor-button-block-appender, .block-list-appender__toggle, button[aria-label="Add block"]';
var toolbarOpenRef = { current: null };
var CANVAS_BRAND_SVG =
'';
function openPatternsLibraryFromChrome() {
if ( toolbarOpenRef.current ) {
toolbarOpenRef.current();
} else if ( openModalExternal ) {
openModalExternal();
}
}
function getHeaderToolbarLeft() {
return (
document.querySelector( '.editor-document-tools__left' ) ||
document.querySelector( '.edit-post-header-toolbar__left' )
);
}
var editorChrome = ( function () {
var started = false;
var syncTimer = null;
var storeUnsub = null;
var iframeWatch = null;
var lastSelection = null;
var headerLeftObservers = new WeakMap();
var canvasInjectTimer = null;
var canvasPollTimer = null;
function getCanvasDocuments() {
var docs = [];
document
.querySelectorAll( 'iframe' )
.forEach( function ( iframe ) {
try {
if ( iframe.contentDocument && iframe.contentDocument.body ) {
var doc = iframe.contentDocument;
if (
doc.querySelector( '.block-editor-block-list__layout' ) ||
doc.querySelector( '.editor-styles-wrapper' ) ||
doc.body.classList.contains( 'block-editor-page' ) ||
iframe.name === 'editor-canvas'
) {
docs.push( doc );
}
}
} catch ( e ) {}
} );
// Always include the main document, because Gutenberg renders side inserters
// in the main document DOM even if the block list is inside an iframe.
if ( docs.indexOf( document ) === -1 ) {
docs.push( document );
}
return docs;
}
function canvasNeedsInjection() {
var docs = getCanvasDocuments();
for ( var d = 0; d < docs.length; d++ ) {
var inserters = docs[ d ].querySelectorAll(
CANVAS_INSERTER_SELECTOR
);
for ( var i = 0; i < inserters.length; i++ ) {
var target = inserters[ i ];
var parent = ( target.classList.contains('block-editor-inserter') || target.classList.contains('components-dropdown') ) ? target : target.parentNode;
if (
parent &&
! parent.querySelector( '.' + CANVAS_BUTTON_CLASS )
) {
return true;
}
}
}
return false;
}
function injectCanvasIntoDropdown( target ) {
if ( ! target || ! target.isConnected || target.classList.contains(CANVAS_BUTTON_CLASS) ) {
return;
}
var parent;
var toggleBtn;
if ( target.classList.contains('block-editor-inserter') || target.classList.contains('components-dropdown') ) {
parent = target;
toggleBtn = target.querySelector('.block-editor-inserter__toggle') || target.querySelector('button');
} else {
parent = target.parentNode;
toggleBtn = target;
}
// Do not inject if it's the top header toolbar (handled by syncHeaderToolbar)
if ( parent && (parent.closest('.edit-post-header-toolbar') || parent.closest('.editor-document-tools')) ) {
return;
}
// Do not inject next to the inline default block appender (e.g. empty paragraph block)
if ( parent && parent.closest('.block-editor-default-block-appender') ) {
return;
}
if ( ! parent || parent.querySelector( '.' + CANVAS_BUTTON_CLASS ) ) {
return;
}
/* Copy the computed style from the + inserter toggle so we match it */
var bgColor = '#1e1e1e';
var btnSize = '24px';
var borderRadius = '2px';
try {
if ( toggleBtn ) {
var cs = toggleBtn.ownerDocument.defaultView.getComputedStyle( toggleBtn );
if ( cs.backgroundColor && cs.backgroundColor !== 'rgba(0, 0, 0, 0)' && cs.backgroundColor !== 'transparent' ) {
bgColor = cs.backgroundColor;
}
if ( cs.height && cs.height !== 'auto' && cs.height !== '0px' ) {
btnSize = cs.height;
}
if ( cs.borderRadius ) {
borderRadius = cs.borderRadius;
}
}
} catch ( e ) {}
var btn = parent.ownerDocument.createElement( 'button' );
btn.type = 'button';
btn.setAttribute( 'aria-haspopup', 'true' );
btn.className =
'components-button block-editor-inserter__toggle has-icon ' +
CANVAS_BUTTON_CLASS;
btn.setAttribute(
'aria-label',
__( 'PatternsWP Library', 'patternswp' )
);
btn.innerHTML = CANVAS_BRAND_SVG;
btn.style.cssText =
'display:inline-flex;align-items:center;justify-content:center;' +
'padding:0;width:' + btnSize + ';height:' + btnSize + ';border:none;' +
'background:' + bgColor + ';color:#fff;cursor:pointer;' +
'flex-shrink:0;border-radius:' + borderRadius + ';';
btn.addEventListener( 'click', function ( event ) {
event.preventDefault();
event.stopPropagation();
openPatternsLibraryFromChrome();
} );
// Do not break Gutenberg layout by overriding parent display.
// Just ensure our button is inline and has a margin to separate it.
parent.style.display = 'flex';
parent.style.gap = '5px';
// Insert after the toggle button if it exists
if ( toggleBtn && toggleBtn.nextSibling ) {
parent.insertBefore( btn, toggleBtn.nextSibling );
} else {
parent.appendChild( btn );
}
}
/**
* Inject a minimal inline