89 lines
2.2 KiB
JavaScript
89 lines
2.2 KiB
JavaScript
|
/**
|
||
|
* External dependencies
|
||
|
*/
|
||
|
import { h, options, createContext } from 'preact';
|
||
|
import { useRef } from 'preact/hooks';
|
||
|
|
||
|
/**
|
||
|
* Internal dependencies
|
||
|
*/
|
||
|
import { store } from './wpx';
|
||
|
|
||
|
// Main context.
|
||
|
const context = createContext( {} );
|
||
|
|
||
|
// WordPress Directives.
|
||
|
const directives = {};
|
||
|
export const directive = ( name, cb ) => {
|
||
|
directives[ name ] = cb;
|
||
|
};
|
||
|
|
||
|
// WordPress Components.
|
||
|
const components = {};
|
||
|
export const component = ( name, Comp ) => {
|
||
|
components[ name ] = Comp;
|
||
|
};
|
||
|
|
||
|
// Resolve the path to some property of the wpx 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 WpDirective = ( { type, wp, 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: wp, props, element, context, evaluate };
|
||
|
|
||
|
for ( const d in wp ) {
|
||
|
const wrapper = directives[ 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 wp = vnode.props.wp;
|
||
|
|
||
|
if ( typeof type === 'string' && type.startsWith( 'wp-' ) ) {
|
||
|
vnode.props.children = h(
|
||
|
components[ type ],
|
||
|
{ ...vnode.props, context, evaluate: getEvaluate() },
|
||
|
vnode.props.children
|
||
|
);
|
||
|
} else if ( wp ) {
|
||
|
const props = vnode.props;
|
||
|
delete props.wp;
|
||
|
if ( ! props._wrapped ) {
|
||
|
vnode.props = { type: vnode.type, wp, props };
|
||
|
vnode.type = WpDirective;
|
||
|
} else {
|
||
|
delete props._wrapped;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( old ) old( vnode );
|
||
|
};
|