Skip to content
This repository was archived by the owner on Feb 23, 2024. It is now read-only.

Commit 1d2c7dc

Browse files
authored
Update the Interactivity API files to include latest changes (#9924)
* Update Interactivity API JS files * Disable TS checks in the Interactivity API for now * Add new SSR files * Replace wp_ prefixes with wc_ ones * Replace wp- prefix with wc- * Replace guternberg_ prefix with woocommerce_ * Remove file comments from Gutenberg * Rename files with `wp` prefix * Fix code to load Interactivity API php files * Remove TODO comments * Replace @WordPress with @woocommerce * Update Webpack configuration * Fix directive prefix * Remove interactivity folder from tsconfig exclude * Add client-side navigation meta tag code * Remove unneeded blocks.php file * Fix store tag id * Register Interactivity API runtime script * Fix Interactivity API runtime registering * Remove all files related to directive processing in PHP * Move json_encode to Store's render method
1 parent 6872626 commit 1d2c7dc

27 files changed

+560
-661
lines changed

assets/js/interactivity/components.js

Lines changed: 0 additions & 26 deletions
This file was deleted.
Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
1-
export const csnMetaTagItemprop = 'woo-client-side-navigation';
2-
export const componentPrefix = 'woo-';
3-
export const directivePrefix = 'data-woo-';
1+
export const csnMetaTagItemprop = 'wc-client-side-navigation';
2+
export const directivePrefix = 'wc';

assets/js/interactivity/directives.js

Lines changed: 90 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
import { useContext, useMemo, useEffect } from 'preact/hooks';
2-
import { useSignalEffect } from '@preact/signals';
32
import { deepSignal, peek } from 'deepsignal';
3+
import { useSignalEffect } from './utils';
44
import { directive } from './hooks';
55
import { prefetch, navigate, canDoClientSideNavigation } from './router';
66

7-
// Until useSignalEffects is fixed:
8-
// https://github.com/preactjs/signals/issues/228
9-
const raf = window.requestAnimationFrame;
10-
const tick = () => new Promise( ( r ) => raf( () => raf( r ) ) );
11-
127
// Check if current page can do client-side navigation.
138
const clientSideNavigation = canDoClientSideNavigation( document.head );
149

@@ -32,7 +27,7 @@ const mergeDeepSignals = ( target, source ) => {
3227
};
3328

3429
export default () => {
35-
// wp-context
30+
// data-wc-context
3631
directive(
3732
'context',
3833
( {
@@ -51,20 +46,21 @@ export default () => {
5146
}, [ context, inheritedValue ] );
5247

5348
return <Provider value={ value }>{ children }</Provider>;
54-
}
49+
},
50+
{ priority: 5 }
5551
);
5652

57-
// wp-effect:[name]
53+
// data-wc-effect--[name]
5854
directive( 'effect', ( { directives: { effect }, context, evaluate } ) => {
5955
const contextValue = useContext( context );
6056
Object.values( effect ).forEach( ( path ) => {
6157
useSignalEffect( () => {
62-
evaluate( path, { context: contextValue } );
58+
return evaluate( path, { context: contextValue } );
6359
} );
6460
} );
6561
} );
6662

67-
// wp-on:[event]
63+
// data-wc-on--[event]
6864
directive( 'on', ( { directives: { on }, element, evaluate, context } ) => {
6965
const contextValue = useContext( context );
7066
Object.entries( on ).forEach( ( [ name, path ] ) => {
@@ -74,7 +70,7 @@ export default () => {
7470
} );
7571
} );
7672

77-
// wp-class:[classname]
73+
// data-wc-class--[classname]
7874
directive(
7975
'class',
8076
( {
@@ -119,22 +115,43 @@ export default () => {
119115
}
120116
);
121117

122-
// wp-bind:[attribute]
118+
// data-wc-bind--[attribute]
123119
directive(
124120
'bind',
125121
( { directives: { bind }, element, context, evaluate } ) => {
126122
const contextValue = useContext( context );
127123
Object.entries( bind )
128124
.filter( ( n ) => n !== 'default' )
129125
.forEach( ( [ attribute, path ] ) => {
130-
element.props[ attribute ] = evaluate( path, {
126+
const result = evaluate( path, {
131127
context: contextValue,
132128
} );
129+
element.props[ attribute ] = result;
130+
131+
// This seems necessary because Preact doesn't change the attributes
132+
// on the hydration, so we have to do it manually. It doesn't need
133+
// deps because it only needs to do it the first time.
134+
useEffect( () => {
135+
// aria- and data- attributes have no boolean representation.
136+
// A `false` value is different from the attribute not being
137+
// present, so we can't remove it.
138+
// We follow Preact's logic: https://github.com/preactjs/preact/blob/ea49f7a0f9d1ff2c98c0bdd66aa0cbc583055246/src/diff/props.js#L131C24-L136
139+
if ( result === false && attribute[ 4 ] !== '-' ) {
140+
element.ref.current.removeAttribute( attribute );
141+
} else {
142+
element.ref.current.setAttribute(
143+
attribute,
144+
result === true && attribute[ 4 ] !== '-'
145+
? ''
146+
: result
147+
);
148+
}
149+
}, [] );
133150
} );
134151
}
135152
);
136153

137-
// wp-link
154+
// data-wc-link
138155
directive(
139156
'link',
140157
( {
@@ -173,4 +190,62 @@ export default () => {
173190
}
174191
}
175192
);
193+
194+
// data-wc-show
195+
directive(
196+
'show',
197+
( {
198+
directives: {
199+
show: { default: show },
200+
},
201+
element,
202+
evaluate,
203+
context,
204+
} ) => {
205+
const contextValue = useContext( context );
206+
207+
if ( ! evaluate( show, { context: contextValue } ) )
208+
element.props.children = (
209+
<template>{ element.props.children }</template>
210+
);
211+
}
212+
);
213+
214+
// data-wc-ignore
215+
directive(
216+
'ignore',
217+
( {
218+
element: {
219+
type: Type,
220+
props: { innerHTML, ...rest },
221+
},
222+
} ) => {
223+
// Preserve the initial inner HTML.
224+
const cached = useMemo( () => innerHTML, [] );
225+
return (
226+
<Type
227+
dangerouslySetInnerHTML={ { __html: cached } }
228+
{ ...rest }
229+
/>
230+
);
231+
}
232+
);
233+
234+
// data-wc-text
235+
directive(
236+
'text',
237+
( {
238+
directives: {
239+
text: { default: text },
240+
},
241+
element,
242+
evaluate,
243+
context,
244+
} ) => {
245+
const contextValue = useContext( context );
246+
element.props.children = evaluate( text, {
247+
context: contextValue,
248+
} );
249+
}
250+
);
176251
};

assets/js/interactivity/hooks.js

Lines changed: 96 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,21 @@
1-
import { h, options, createContext } from 'preact';
2-
import { useRef } from 'preact/hooks';
1+
import { h, options, createContext, cloneElement } from 'preact';
2+
import { useRef, useMemo } from 'preact/hooks';
33
import { rawStore as store } from './store';
4-
import { componentPrefix } from './constants';
54

65
// Main context.
76
const context = createContext( {} );
87

98
// WordPress Directives.
109
const directiveMap = {};
11-
export const directive = ( name, cb ) => {
10+
const directivePriorities = {};
11+
export const directive = ( name, cb, { priority = 10 } = {} ) => {
1212
directiveMap[ name ] = cb;
13-
};
14-
15-
// WordPress Components.
16-
const componentMap = {};
17-
export const component = ( name, Comp ) => {
18-
componentMap[ name ] = Comp;
13+
directivePriorities[ name ] = priority;
1914
};
2015

2116
// Resolve the path to some property of the store object.
22-
const resolve = ( path, context ) => {
23-
let current = { ...store, context };
17+
const resolve = ( path, ctx ) => {
18+
let current = { ...store, context: ctx };
2419
path.split( '.' ).forEach( ( p ) => ( current = current[ p ] ) );
2520
return current;
2621
};
@@ -29,22 +24,92 @@ const resolve = ( path, context ) => {
2924
const getEvaluate =
3025
( { ref } = {} ) =>
3126
( path, extraArgs = {} ) => {
27+
// If path starts with !, remove it and save a flag.
28+
const hasNegationOperator =
29+
path[ 0 ] === '!' && !! ( path = path.slice( 1 ) );
3230
const value = resolve( path, extraArgs.context );
33-
return typeof value === 'function'
34-
? value( {
35-
state: store.state,
36-
...( ref !== undefined ? { ref } : {} ),
37-
...extraArgs,
38-
} )
39-
: value;
31+
const returnValue =
32+
typeof value === 'function'
33+
? value( {
34+
ref: ref.current,
35+
...store,
36+
...extraArgs,
37+
} )
38+
: value;
39+
return hasNegationOperator ? ! returnValue : returnValue;
4040
};
4141

42+
// Separate directives by priority. The resulting array contains objects
43+
// of directives grouped by same priority, and sorted in ascending order.
44+
const usePriorityLevels = ( directives ) =>
45+
useMemo( () => {
46+
const byPriority = Object.entries( directives ).reduce(
47+
( acc, [ name, values ] ) => {
48+
const priority = directivePriorities[ name ];
49+
if ( ! acc[ priority ] ) acc[ priority ] = {};
50+
acc[ priority ][ name ] = values;
51+
52+
return acc;
53+
},
54+
{}
55+
);
56+
57+
return Object.entries( byPriority )
58+
.sort( ( [ p1 ], [ p2 ] ) => p1 - p2 )
59+
.map( ( [ , obj ] ) => obj );
60+
}, [ directives ] );
61+
4262
// Directive wrapper.
4363
const Directive = ( { type, directives, props: originalProps } ) => {
4464
const ref = useRef( null );
45-
const element = h( type, { ...originalProps, ref, _wrapped: true } );
46-
const props = { ...originalProps, children: element };
47-
const evaluate = getEvaluate( { ref: ref.current } );
65+
const element = h( type, { ...originalProps, ref } );
66+
const evaluate = useMemo( () => getEvaluate( { ref } ), [] );
67+
68+
// Add wrappers recursively for each priority level.
69+
const byPriorityLevel = usePriorityLevels( directives );
70+
return (
71+
<RecursivePriorityLevel
72+
directives={ byPriorityLevel }
73+
element={ element }
74+
evaluate={ evaluate }
75+
originalProps={ originalProps }
76+
/>
77+
);
78+
};
79+
80+
// Priority level wrapper.
81+
const RecursivePriorityLevel = ( {
82+
directives: [ directives, ...rest ],
83+
element,
84+
evaluate,
85+
originalProps,
86+
} ) => {
87+
// This element needs to be a fresh copy so we are not modifying an already
88+
// rendered element with Preact's internal properties initialized. This
89+
// prevents an error with changes in `element.props.children` not being
90+
// reflected in `element.__k`.
91+
element = cloneElement( element );
92+
93+
// Recursively render the wrapper for the next priority level.
94+
//
95+
// Note that, even though we're instantiating a vnode with a
96+
// `RecursivePriorityLevel` here, its render function will not be executed
97+
// just yet. Actually, it will be delayed until the current render function
98+
// has finished. That ensures directives in the current priorty level have
99+
// run (and thus modified the passed `element`) before the next level.
100+
const children =
101+
rest.length > 0 ? (
102+
<RecursivePriorityLevel
103+
directives={ rest }
104+
element={ element }
105+
evaluate={ evaluate }
106+
originalProps={ originalProps }
107+
/>
108+
) : (
109+
element
110+
);
111+
112+
const props = { ...originalProps, children };
48113
const directiveArgs = { directives, props, element, context, evaluate };
49114

50115
for ( const d in directives ) {
@@ -58,27 +123,16 @@ const Directive = ( { type, directives, props: originalProps } ) => {
58123
// Preact Options Hook called each time a vnode is created.
59124
const old = options.vnode;
60125
options.vnode = ( vnode ) => {
61-
const type = vnode.type;
62-
const { directives } = vnode.props;
63-
64-
if (
65-
typeof type === 'string' &&
66-
type.slice( 0, componentPrefix.length ) === componentPrefix
67-
) {
68-
vnode.props.children = h(
69-
componentMap[ type.slice( componentPrefix.length ) ],
70-
{ ...vnode.props, context, evaluate: getEvaluate() },
71-
vnode.props.children
72-
);
73-
} else if ( directives ) {
126+
if ( vnode.props.__directives ) {
74127
const props = vnode.props;
75-
delete props.directives;
76-
if ( ! props._wrapped ) {
77-
vnode.props = { type: vnode.type, directives, props };
78-
vnode.type = Directive;
79-
} else {
80-
delete props._wrapped;
81-
}
128+
const directives = props.__directives;
129+
delete props.__directives;
130+
vnode.props = {
131+
type: vnode.type,
132+
directives,
133+
props,
134+
};
135+
vnode.type = Directive;
82136
}
83137

84138
if ( old ) old( vnode );

assets/js/interactivity/index.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import registerDirectives from './directives';
2-
import registerComponents from './components';
32
import { init } from './router';
43
export { store } from './store';
4+
export { navigate } from './router';
55

66
/**
7-
* Initialize the initial vDOM.
7+
* Initialize the Interactivity API.
88
*/
99
document.addEventListener( 'DOMContentLoaded', async () => {
1010
registerDirectives();
11-
registerComponents();
1211
await init();
13-
console.log( 'hydrated!' );
12+
// eslint-disable-next-line no-console
13+
console.log( 'Interactivity API started' );
1414
} );

0 commit comments

Comments
 (0)