@cocreate/plugins 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## [1.2.1](https://github.com/CoCreate-app/CoCreate-plugins/compare/v1.2.0...v1.2.1) (2026-03-02)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Handle case-insensitive plugin global mapping and add support for function reference assignments in plugin properties. ([904a947](https://github.com/CoCreate-app/CoCreate-plugins/commit/904a947abc868937b6e650b62e080d00341272a1))
7
+
1
8
  # [1.2.0](https://github.com/CoCreate-app/CoCreate-plugins/compare/v1.1.1...v1.2.0) (2026-02-15)
2
9
 
3
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocreate/plugins",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "CoCreate plugins",
5
5
  "author": "CoCreate LLC",
6
6
  "license": "AGPL-3.0",
package/src/index.js CHANGED
@@ -3,7 +3,7 @@ import {dotNotationToObject} from "@cocreate/utils";
3
3
 
4
4
  /**
5
5
  * @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
7
7
  * @property {Array<string>} [css] - List of CSS files to load.
8
8
  */
9
9
 
@@ -107,6 +107,8 @@ async function processPlugin(el) {
107
107
 
108
108
  // Load JS with Promise Cache
109
109
  if (pluginDef.js) {
110
+ const preWindowKeys = (typeof window !== 'undefined') ? new Set(Object.keys(window)) : new Set();
111
+
110
112
  for (const item of pluginDef.js) {
111
113
  const src = typeof item === 'string' ? item : item.src;
112
114
  const integrity = typeof item === 'object' ? item.integrity : null;
@@ -152,6 +154,36 @@ async function processPlugin(el) {
152
154
  console.error(`Failed to load script: ${src}`, e);
153
155
  }
154
156
  }
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
+ }
155
187
  }
156
188
  }
157
189
 
@@ -234,7 +266,7 @@ function executeGenericPlugin(el, name) {
234
266
 
235
267
  // Pass context: Window as parent, Plugin Name as property (for potential context binding)
236
268
  // 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);
238
270
 
239
271
  console.log(`Processed ${name}`);
240
272
  } catch (e) {
@@ -244,7 +276,126 @@ function executeGenericPlugin(el, name) {
244
276
  }
245
277
  }
246
278
 
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(/^\$this\.?/, "");
332
+ candidates.push({ root: hostElement || parent, path });
333
+ } else if (startsWithWindow) {
334
+ const path = normalized.replace(/^\$window\.?/, "");
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) {
248
399
  // RESOLUTION: Handle case-insensitivity before processing targets.
249
400
  // If Target is missing, check parent for a property matching 'property' (case-insensitive).
250
401
  if (!Target && parent && property) {
@@ -261,6 +412,13 @@ function update(Target, val, parent, property, elParent, elProperty) {
261
412
 
262
413
  let instance;
263
414
  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
+
264
422
  if (!isConstructor(Target, property)) {
265
423
  // Call as a function (method or standalone)
266
424
  // Use 'parent' as context (this) if available to maintain class references
@@ -291,6 +449,12 @@ function update(Target, val, parent, property, elParent, elProperty) {
291
449
  elParent[elProperty] = instance;
292
450
  }
293
451
 
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
+
294
458
  } else if (typeof Target === 'object' && Target !== null && typeof val === 'object' && val !== null && !Array.isArray(val)) {
295
459
  // Prepare the next level of the element structure
296
460
  if (elParent && elProperty) {
@@ -300,10 +464,17 @@ function update(Target, val, parent, property, elParent, elProperty) {
300
464
  const nextElParent = elParent[elProperty];
301
465
 
302
466
  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);
304
468
  }
305
469
  }
306
470
  } 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
+
307
478
  // If it's not a function, we are setting a value on the plugin object
308
479
  parent[property] = val;
309
480
 
@@ -418,4 +589,4 @@ if (typeof document !== 'undefined') {
418
589
  });
419
590
  }
420
591
 
421
- export default { init, plugins }
592
+ export default { init, plugins }