woocommerce/plugins/woocommerce-blocks/assets/js/interactivity/hooks.js

86 lines
2.3 KiB
JavaScript

import { h, options, createContext } from 'preact';
import { useRef } from 'preact/hooks';
import { rawStore as store } from './store';
import { componentPrefix } from './constants';
// Main context.
const context = createContext( {} );
// WordPress Directives.
const directiveMap = {};
export const directive = ( name, cb ) => {
directiveMap[ name ] = cb;
};
// WordPress Components.
const componentMap = {};
export const component = ( name, Comp ) => {
componentMap[ name ] = Comp;
};
// Resolve the path to some property of the store object.
const resolve = ( path, context ) => {
let current = { ...store, context };
path.split( '.' ).forEach( ( p ) => ( current = current[ p ] ) );
return current;
};
// Generate the evaluate function.
const getEvaluate =
( { ref } = {} ) =>
( path, extraArgs = {} ) => {
const value = resolve( path, extraArgs.context );
return typeof value === 'function'
? value( {
state: store.state,
...( ref !== undefined ? { ref } : {} ),
...extraArgs,
} )
: value;
};
// Directive wrapper.
const Directive = ( { type, directives, props: originalProps } ) => {
const ref = useRef( null );
const element = h( type, { ...originalProps, ref, _wrapped: true } );
const props = { ...originalProps, children: element };
const evaluate = getEvaluate( { ref: ref.current } );
const directiveArgs = { directives, props, element, context, evaluate };
for ( const d in directives ) {
const wrapper = directiveMap[ d ]?.( directiveArgs );
if ( wrapper !== undefined ) props.children = wrapper;
}
return props.children;
};
// Preact Options Hook called each time a vnode is created.
const old = options.vnode;
options.vnode = ( vnode ) => {
const type = vnode.type;
const { directives } = vnode.props;
if (
typeof type === 'string' &&
type.slice( 0, componentPrefix.length ) === componentPrefix
) {
vnode.props.children = h(
componentMap[ type.slice( componentPrefix.length ) ],
{ ...vnode.props, context, evaluate: getEvaluate() },
vnode.props.children
);
} else if ( directives ) {
const props = vnode.props;
delete props.directives;
if ( ! props._wrapped ) {
vnode.props = { type: vnode.type, directives, props };
vnode.type = Directive;
} else {
delete props._wrapped;
}
}
if ( old ) old( vnode );
};