@@ -3,7 +3,7 @@ import {dotNotationToObject} from "@cocreate/utils";
33
44/**
55 * @typedef {Object } PluginDefinition
6- * @property {Array<string|Object> } [js] - List of JS files to load. Can be strings (URLs) or objects with src, integrity, etc.
6+ * @property {Array<string|Object> } [js] - List of JS files to load. Can be strings (URLs) or objects with src, integrity, etc
77 * @property {Array<string> } [css] - List of CSS files to load.
88 */
99
@@ -107,6 +107,8 @@ async function processPlugin(el) {
107107
108108 // Load JS with Promise Cache
109109 if ( pluginDef . js ) {
110+ const preWindowKeys = ( typeof window !== 'undefined' ) ? new Set ( Object . keys ( window ) ) : new Set ( ) ;
111+
110112 for ( const item of pluginDef . js ) {
111113 const src = typeof item === 'string' ? item : item . src ;
112114 const integrity = typeof item === 'object' ? item . integrity : null ;
@@ -152,6 +154,36 @@ async function processPlugin(el) {
152154 console . error ( `Failed to load script: ${ src } ` , e ) ;
153155 }
154156 }
157+
158+ // After loading JS files, map newly-added globals to the expected plugin name.
159+ // Exact (case-insensitive) matching only.
160+ try {
161+ if ( typeof window !== 'undefined' ) {
162+ const expectedName = pluginName ;
163+ const lower = expectedName . toLowerCase ( ) ;
164+
165+ const allKeys = Object . keys ( window ) ;
166+ const newKeys = allKeys . filter ( k => ! preWindowKeys . has ( k ) ) ;
167+ let mappedKey = null ;
168+
169+ for ( const k of newKeys ) {
170+ if ( k . toLowerCase ( ) === lower ) { mappedKey = k ; break ; }
171+ }
172+
173+ if ( ! mappedKey ) {
174+ for ( const k of allKeys ) {
175+ if ( k . toLowerCase ( ) === lower ) { mappedKey = k ; break ; }
176+ }
177+ }
178+
179+ if ( mappedKey && ! window [ expectedName ] ) {
180+ window [ expectedName ] = window [ mappedKey ] ;
181+ console . debug ( `Mapped plugin global: window.${ expectedName } <- window.${ mappedKey } ` ) ;
182+ }
183+ }
184+ } catch ( err ) {
185+ // Non-fatal
186+ }
155187 }
156188 }
157189
@@ -234,7 +266,7 @@ function executeGenericPlugin(el, name) {
234266
235267 // Pass context: Window as parent, Plugin Name as property (for potential context binding)
236268 // el and name used to store the result on the element.
237- update ( Target , val , window , name , el , name ) ;
269+ update ( Target , val , window , name , el , name , el ) ;
238270
239271 console . log ( `Processed ${ name } ` ) ;
240272 } catch ( e ) {
@@ -244,7 +276,126 @@ function executeGenericPlugin(el, name) {
244276 }
245277}
246278
247- function update ( Target , val , parent , property , elParent , elProperty ) {
279+ function resolvePathWithParent ( root , path ) {
280+ if ( ! root || ! path || typeof path !== "string" ) return { parent : null , value : undefined } ;
281+ const parts = path . split ( "." ) . filter ( Boolean ) ;
282+ if ( ! parts . length ) return { parent : null , value : undefined } ;
283+
284+ let parent = null ;
285+ let current = root ;
286+ for ( let i = 0 ; i < parts . length ; i ++ ) {
287+ const part = parts [ i ] ;
288+ if ( current == null ) return { parent : null , value : undefined } ;
289+ parent = current ;
290+ current = current [ part ] ;
291+ }
292+ return { parent, value : current } ;
293+ }
294+
295+ function normalizeCrudPayload ( value ) {
296+ if ( ! value || typeof value !== "object" || Array . isArray ( value ) ) return value ;
297+
298+ if ( value . type && Array . isArray ( value [ value . type ] ) ) return value [ value . type ] ;
299+ if ( value . method && typeof value . method === "string" ) {
300+ const type = value . method . split ( "." ) [ 0 ] ;
301+ if ( type && Array . isArray ( value [ type ] ) ) return value [ type ] ;
302+ }
303+ return value ;
304+ }
305+
306+ function getPluginInstancesFromElement ( el ) {
307+ if ( ! el || ! el . __cocreatePluginInstances ) return [ ] ;
308+ return Object . values ( el . __cocreatePluginInstances ) . filter ( Boolean ) ;
309+ }
310+
311+ function isReferenceAssignment ( val ) {
312+ return typeof val === "string" && val . trim ( ) . startsWith ( "=" ) ;
313+ }
314+
315+ function normalizeReferencePath ( refPath ) {
316+ if ( typeof refPath !== "string" ) return "" ;
317+ return refPath . trim ( ) . replace ( / ^ = \s * / , "" ) ;
318+ }
319+
320+ function resolveCallableReference ( refPath , parent , hostElement ) {
321+ const normalized = normalizeReferencePath ( refPath ) ;
322+ if ( ! normalized ) return { fn : undefined , context : undefined , methodName : undefined } ;
323+
324+ const methodName = normalized . split ( "." ) . pop ( ) ;
325+ const startsWithThis = normalized === "$this" || normalized . startsWith ( "$this." ) ;
326+ const startsWithWindow = normalized === "$window" || normalized . startsWith ( "$window." ) ;
327+ const startsWithToken = normalized . startsWith ( "$" ) ;
328+
329+ const candidates = [ ] ;
330+ if ( startsWithThis ) {
331+ const path = normalized . replace ( / ^ \$ t h i s \. ? / , "" ) ;
332+ candidates . push ( { root : hostElement || parent , path } ) ;
333+ } else if ( startsWithWindow ) {
334+ const path = normalized . replace ( / ^ \$ w i n d o w \. ? / , "" ) ;
335+ candidates . push ( { root : window , path } ) ;
336+ } else if ( startsWithToken ) {
337+ const path = normalized . replace ( / ^ \$ / , "" ) ;
338+ candidates . push ( { root : hostElement , path } ) ;
339+ candidates . push ( { root : parent , path } ) ;
340+ candidates . push ( { root : window , path } ) ;
341+ } else {
342+ candidates . push ( { root : hostElement , path : normalized } ) ;
343+ candidates . push ( { root : parent , path : normalized } ) ;
344+ candidates . push ( { root : window , path : normalized } ) ;
345+ }
346+
347+ for ( const candidate of candidates ) {
348+ if ( ! candidate . root ) continue ;
349+ const { parent : resolvedParent , value } = resolvePathWithParent ( candidate . root , candidate . path ) ;
350+ if ( typeof value === "function" ) {
351+ return { fn : value , context : resolvedParent , methodName } ;
352+ }
353+ }
354+
355+ if ( methodName ) {
356+ const instances = getPluginInstancesFromElement ( hostElement || parent ) ;
357+ for ( const instance of instances ) {
358+ if ( instance && typeof instance [ methodName ] === "function" ) {
359+ return { fn : instance [ methodName ] , context : instance , methodName } ;
360+ }
361+ }
362+ }
363+
364+ return { fn : undefined , context : undefined , methodName } ;
365+ }
366+
367+ function createFunctionAdapter ( refPath , parent , property , hostElement ) {
368+ const normalizedRefPath = normalizeReferencePath ( refPath ) ;
369+ const methodName = normalizedRefPath . split ( "." ) . pop ( ) ;
370+
371+ return function ( ...args ) {
372+ const resolved = resolveCallableReference ( normalizedRefPath , parent , hostElement ) ;
373+ const fn = resolved . fn ;
374+ const context = resolved . context ;
375+
376+ if ( typeof fn !== "function" ) {
377+ console . error ( `Plugin adapter failed: "${ normalizedRefPath } " did not resolve to a function for ${ property } .` ) ;
378+ return ;
379+ }
380+
381+ if ( property === "setValue" ) {
382+ const payload = normalizeCrudPayload ( args [ 0 ] ) ;
383+
384+ if ( methodName === "addEventSource" && context && typeof context . getEventSources === "function" ) {
385+ const sources = context . getEventSources ( ) ;
386+ if ( Array . isArray ( sources ) ) {
387+ sources . forEach ( source => source && typeof source . remove === "function" && source . remove ( ) ) ;
388+ }
389+ }
390+
391+ return fn . call ( context || this , payload ) ;
392+ }
393+
394+ return fn . apply ( context || this , args ) ;
395+ } ;
396+ }
397+
398+ function update ( Target , val , parent , property , elParent , elProperty , hostElement ) {
248399 // RESOLUTION: Handle case-insensitivity before processing targets.
249400 // If Target is missing, check parent for a property matching 'property' (case-insensitive).
250401 if ( ! Target && parent && property ) {
@@ -261,6 +412,13 @@ function update(Target, val, parent, property, elParent, elProperty) {
261412
262413 let instance ;
263414 if ( typeof Target === 'function' ) {
415+ if ( isReferenceAssignment ( val ) && parent && property ) {
416+ instance = createFunctionAdapter ( val , parent , property , hostElement ) ;
417+ parent [ property ] = instance ;
418+ if ( elParent && elProperty ) elParent [ elProperty ] = instance ;
419+ return ;
420+ }
421+
264422 if ( ! isConstructor ( Target , property ) ) {
265423 // Call as a function (method or standalone)
266424 // Use 'parent' as context (this) if available to maintain class references
@@ -291,6 +449,12 @@ function update(Target, val, parent, property, elParent, elProperty) {
291449 elParent [ elProperty ] = instance ;
292450 }
293451
452+ if ( instance && instance . el && typeof instance . el === "object" ) {
453+ if ( ! instance . el . __cocreatePluginInstances ) instance . el . __cocreatePluginInstances = { } ;
454+ const key = property || ( Target && Target . name ) || "instance" ;
455+ instance . el . __cocreatePluginInstances [ key ] = instance ;
456+ }
457+
294458 } else if ( typeof Target === 'object' && Target !== null && typeof val === 'object' && val !== null && ! Array . isArray ( val ) ) {
295459 // Prepare the next level of the element structure
296460 if ( elParent && elProperty ) {
@@ -300,10 +464,17 @@ function update(Target, val, parent, property, elParent, elProperty) {
300464 const nextElParent = elParent [ elProperty ] ;
301465
302466 for ( let key in val ) {
303- update ( Target [ key ] , val [ key ] , Target , key , nextElParent , key ) ;
467+ update ( Target [ key ] , val [ key ] , Target , key , nextElParent , key , hostElement ) ;
304468 }
305469 }
306470 } else if ( parent && property ) {
471+ if ( isReferenceAssignment ( val ) ) {
472+ const adapter = createFunctionAdapter ( val , parent , property , hostElement ) ;
473+ parent [ property ] = adapter ;
474+ if ( elParent && elProperty ) elParent [ elProperty ] = adapter ;
475+ return ;
476+ }
477+
307478 // If it's not a function, we are setting a value on the plugin object
308479 parent [ property ] = val ;
309480
@@ -418,4 +589,4 @@ if (typeof document !== 'undefined') {
418589 } ) ;
419590}
420591
421- export default { init, plugins }
592+ export default { init, plugins }
0 commit comments