@cocreate/plugins 1.0.2 → 1.0.3

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/package.json +1 -1
  3. package/src/index.js +147 -301
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## [1.0.3](https://github.com/CoCreate-app/CoCreate-plugins/compare/v1.0.2...v1.0.3) (2025-12-26)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * update worklow ([8976d08](https://github.com/CoCreate-app/CoCreate-plugins/commit/8976d080f8972e840ff5530d5dfb15f839e9ac5b))
7
+
1
8
  ## [1.0.2](https://github.com/CoCreate-app/CoCreate-plugins/compare/v1.0.1...v1.0.2) (2025-12-25)
2
9
 
3
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocreate/plugins",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "CoCreate plugins",
5
5
  "author": "CoCreate LLC",
6
6
  "license": "AGPL-3.0",
package/src/index.js CHANGED
@@ -6,17 +6,14 @@ const plugins = {
6
6
  Toastify: {
7
7
  js: [{ src: "https://cdn.jsdelivr.net/npm/toastify-js", crossOrigin: "anonymous" }],
8
8
  css: ["https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css"]
9
- // attribute prefix: toastify-
10
9
  },
11
10
  Choices: {
12
11
  js: [{ src: "https://cdn.jsdelivr.net/npm/choices.js/public/assets/scripts/choices.min.js", crossOrigin: "anonymous" }],
13
12
  css: ["https://cdn.jsdelivr.net/npm/choices.js/public/assets/styles/choices.min.css"]
14
- // attribute prefix: choices-
15
13
  },
16
14
  flatpickr: {
17
15
  js: [{ src: "https://cdn.jsdelivr.net/npm/flatpickr", crossOrigin: "anonymous" }],
18
16
  css: ["https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css"]
19
- // attribute prefix: flatpickr-
20
17
  },
21
18
  Quill: {
22
19
  js: [{ src: "https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.min.js", crossOrigin: "anonymous" }],
@@ -25,47 +22,37 @@ const plugins = {
25
22
  "https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.snow.css",
26
23
  "https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.bubble.css"
27
24
  ]
28
- // attribute prefix: quill-
29
25
  },
30
26
  ClassicEditor: {
31
27
  js: [{ src: "https://cdn.ckeditor.com/ckeditor5/41.2.0/classic/ckeditor.js", crossOrigin: "anonymous" }]
32
- // attribute prefix: classiceditor-
33
28
  },
34
29
  Dropzone: {
35
30
  js: [{ src: "https://unpkg.com/dropzone@5/dist/min/dropzone.min.js", crossOrigin: "anonymous" }],
36
31
  css: ["https://unpkg.com/dropzone@5/dist/min/dropzone.min.css"]
37
- // attribute prefix: dropzone-
38
32
  },
39
33
  SimpleBar: {
40
34
  js: [{ src: "https://cdn.jsdelivr.net/npm/simplebar@latest/dist/simplebar.min.js", crossOrigin: "anonymous" }],
41
35
  css: ["https://cdn.jsdelivr.net/npm/simplebar@latest/dist/simplebar.css"]
42
- // attribute prefix: simplebar-
43
36
  },
44
37
  GLightbox: {
45
38
  js: [{ src: "https://cdn.jsdelivr.net/npm/glightbox/dist/js/glightbox.min.js", crossOrigin: "anonymous" }],
46
39
  css: ["https://cdn.jsdelivr.net/npm/glightbox/dist/css/glightbox.min.css"]
47
- // attribute prefix: glightbox-
48
40
  },
49
41
  FgEmojiPicker: {
50
42
  js: [{ src: "https://cdn.jsdelivr.net/npm/fg-emoji-picker/fgEmojiPicker.js", crossOrigin: "anonymous" }]
51
- // attribute prefix: fgemojipicker-
52
43
  },
53
44
  bootstrap: {
54
45
  js: [{ src: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js", crossOrigin: "anonymous" }]
55
- // attribute prefix: bootstrap-
56
46
  },
57
47
  Waves: {
58
48
  js: [{ src: "https://cdn.jsdelivr.net/npm/node-waves/dist/waves.min.js", crossOrigin: "anonymous" }],
59
49
  css: ["https://cdn.jsdelivr.net/npm/node-waves/dist/waves.min.css"]
60
- // attribute prefix: waves-
61
50
  },
62
51
  feather: {
63
52
  js: [{ src: "https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js", crossOrigin: "anonymous" }]
64
- // attribute prefix: feather-
65
53
  },
66
54
  ApexCharts: {
67
55
  js: [{ src: "https://cdn.jsdelivr.net/npm/apexcharts", crossOrigin: "anonymous" }]
68
- // attribute prefix: apexcharts-
69
56
  },
70
57
  jsVectorMap: {
71
58
  js: [
@@ -73,41 +60,33 @@ const plugins = {
73
60
  { src: "https://cdn.jsdelivr.net/npm/jsvectormap/dist/maps/world.js", crossOrigin: "anonymous" }
74
61
  ],
75
62
  css: ["https://cdn.jsdelivr.net/npm/jsvectormap/dist/css/jsvectormap.min.css"]
76
- // attribute prefix: jsvectormap-
77
63
  },
78
64
  Swiper: {
79
65
  js: [{ src: "https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js", crossOrigin: "anonymous" }],
80
66
  css: ["https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css"]
81
- // attribute prefix: swiper-
82
67
  },
83
68
  List: {
84
69
  js: [
85
70
  { src: "https://cdnjs.cloudflare.com/ajax/libs/list.js/2.3.1/list.min.js", crossOrigin: "anonymous" },
86
71
  { src: "https://cdnjs.cloudflare.com/ajax/libs/list.pagination.js/0.1.1/list.pagination.min.js", crossOrigin: "anonymous" }
87
72
  ]
88
- // attribute prefix: list-
89
73
  },
90
74
  Swal: {
91
75
  js: [{ src: "https://cdn.jsdelivr.net/npm/sweetalert2@11", crossOrigin: "anonymous" }],
92
76
  css: ["https://cdn.jsdelivr.net/npm/sweetalert2@11/dist/sweetalert2.min.css"]
93
- // attribute prefix: swal-
94
77
  },
95
78
  FullCalendar: {
96
79
  js: [{ src: "https://cdn.jsdelivr.net/npm/fullcalendar@6.1.15/index.global.min.js", crossOrigin: "anonymous" }]
97
- // attribute prefix: fullcalendar-
98
80
  },
99
81
  Cleave: {
100
82
  js: [{ src: "https://cdn.jsdelivr.net/npm/cleave.js/dist/cleave.min.js", crossOrigin: "anonymous" }]
101
- // attribute prefix: cleave-
102
83
  },
103
84
  noUiSlider: {
104
85
  js: [{ src: "https://cdn.jsdelivr.net/npm/nouislider/dist/nouislider.min.js", crossOrigin: "anonymous" }],
105
86
  css: ["https://cdn.jsdelivr.net/npm/nouislider/dist/nouislider.min.css"]
106
- // attribute prefix: nouislider-
107
87
  },
108
88
  wNumb: {
109
89
  js: [{ src: "https://cdn.jsdelivr.net/npm/wnumb/wNumb.min.js", crossOrigin: "anonymous" }]
110
- // attribute prefix: wnumb-
111
90
  },
112
91
  Grid: {
113
92
  js: [
@@ -115,7 +94,6 @@ const plugins = {
115
94
  { src: "https://unpkg.com/gridjs/plugins/selection/dist/selection.umd.js", crossOrigin: "anonymous" }
116
95
  ],
117
96
  css: ["https://unpkg.com/gridjs/dist/theme/mermaid.min.css"]
118
- // attribute prefix: grid-
119
97
  },
120
98
  FilePond: {
121
99
  js: [
@@ -129,83 +107,72 @@ const plugins = {
129
107
  "https://unpkg.com/filepond/dist/filepond.min.css",
130
108
  "https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.min.css"
131
109
  ]
132
- // attribute prefix: filepond-
133
110
  },
134
111
  Prism: {
135
112
  js: [{ src: "https://cdn.jsdelivr.net/npm/prismjs/prism.min.js", crossOrigin: "anonymous" }],
136
113
  css: ["https://cdn.jsdelivr.net/npm/prismjs/themes/prism.min.css"]
137
- // attribute prefix: prism-
138
114
  },
139
115
  Isotope: {
140
116
  js: [{ src: "https://unpkg.com/isotope-layout@3/dist/isotope.pkgd.min.js", crossOrigin: "anonymous" }]
141
- // attribute prefix: isotope-
142
117
  },
143
118
  particlesJS: {
144
119
  js: [{ src: "https://cdn.jsdelivr.net/npm/particles.js@2.0.0/particles.min.js", crossOrigin: "anonymous" }]
145
- // attribute prefix: particlesjs-
146
120
  },
147
121
  dragula: {
148
122
  js: [{ src: "https://cdn.jsdelivr.net/npm/dragula/dist/dragula.min.js", crossOrigin: "anonymous" }],
149
123
  css: ["https://cdn.jsdelivr.net/npm/dragula/dist/dragula.min.css"]
150
- // attribute prefix: dragula-
151
124
  },
152
125
  DomAutoscroller: {
153
126
  js: [{ src: "https://cdn.jsdelivr.net/npm/dom-autoscroller", crossOrigin: "anonymous" }]
154
- // attribute prefix: domautoscroller-
155
127
  },
156
128
  Card: {
157
129
  js: [{ src: "https://cdn.jsdelivr.net/npm/card/dist/card.js", crossOrigin: "anonymous" }]
158
- // attribute prefix: card-
159
130
  },
160
131
  Chart: {
161
132
  js: [{ src: "https://cdn.jsdelivr.net/npm/chart.js", crossOrigin: "anonymous" }]
162
- // attribute prefix: chart-
163
133
  },
164
134
  echarts: {
165
135
  js: [{ src: "https://cdn.jsdelivr.net/npm/echarts/dist/echarts.min.js", crossOrigin: "anonymous" }]
166
- // attribute prefix: echarts-
167
136
  },
168
137
  Multi: {
169
138
  js: [{ src: "https://cdn.jsdelivr.net/npm/multi.js/dist/multi.min.js", crossOrigin: "anonymous" }],
170
139
  css: ["https://cdn.jsdelivr.net/npm/multi.js/dist/multi.min.css"]
171
- // attribute prefix: multi-
172
140
  },
173
141
  autoComplete: {
174
142
  js: [{ src: "https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.7/dist/autoComplete.min.js", crossOrigin: "anonymous" }],
175
143
  css: ["https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.7/dist/css/autoComplete.01.min.css"]
176
- // attribute prefix: autocomplete-
177
144
  },
178
145
  Pickr: {
179
146
  js: [{ src: "https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/pickr.min.js", crossOrigin: "anonymous" }],
180
147
  css: ["https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/themes/classic.min.css"]
181
- // attribute prefix: pickr-
182
148
  },
183
149
  Shepherd: {
184
150
  js: [{ src: "https://cdn.jsdelivr.net/npm/shepherd.js/dist/js/shepherd.min.js", crossOrigin: "anonymous" }],
185
151
  css: ["https://cdn.jsdelivr.net/npm/shepherd.js/dist/css/shepherd.css"]
186
- // attribute prefix: shepherd-
187
152
  },
188
153
  GMaps: {
189
154
  js: [{ src: "https://cdn.jsdelivr.net/npm/gmaps/gmaps.min.js", crossOrigin: "anonymous" }]
190
- // attribute prefix: gmaps-
191
155
  },
192
156
  L: {
193
157
  js: [{ src: "https://unpkg.com/leaflet@1.9.4/dist/leaflet.js", crossOrigin: "anonymous" }],
194
158
  css: ["https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"]
195
- // attribute prefix: l-
196
159
  },
197
160
  Masonry: {
198
161
  js: [{ src: "https://unpkg.com/masonry-layout@4/dist/masonry.pkgd.min.js", crossOrigin: "anonymous" }]
199
- // attribute prefix: masonry-
200
162
  },
201
163
  Rater: {
202
164
  js: [{ src: "https://cdn.jsdelivr.net/npm/rater-js/index.js", crossOrigin: "anonymous" }]
203
- // attribute prefix: rater-
165
+ },
166
+ Anime: {
167
+ js: [{ src: "https://cdn.jsdelivr.net/npm/animejs@3.2.2/lib/anime.min.js", crossOrigin: "anonymous" }]
204
168
  }
205
169
  };
206
170
 
207
171
  // --- CORE ENGINE ---
208
172
 
173
+ // Global Cache for script promises to prevent race conditions and duplicate loads
174
+ const scriptCache = new Map();
175
+
209
176
  /**
210
177
  * Global Initialization Function
211
178
  * Processes one or more elements to attach plugins.
@@ -214,323 +181,197 @@ const plugins = {
214
181
  export function init(elements) {
215
182
  if (!elements) return;
216
183
 
217
- // 1. Detect Type & Normalize to Array
218
- // Normalize string handling (removed), Element, NodeList, HTMLCollection, Array
219
184
  let collection = [];
220
-
221
185
  if (elements instanceof HTMLElement || elements instanceof Element) {
222
186
  collection = [elements];
223
187
  } else if (elements.length !== undefined && typeof elements !== 'function') {
224
- // Handle Array-like objects (NodeList, HTMLCollection, Array)
225
188
  collection = Array.from(elements);
226
189
  } else {
227
- // Fallback for single object that isn't element (unlikely given spec, but safe)
228
190
  collection = [elements];
229
191
  }
230
192
 
231
- // 2. Iterate and Process
232
193
  collection.forEach(el => {
233
- // Ensure valid element before processing
234
194
  if (el && typeof el.getAttribute === 'function') {
235
195
  processPlugin(el);
236
196
  }
237
197
  });
238
198
  }
239
199
 
240
- // Auto-Init on load
241
- if (typeof document !== 'undefined') {
242
- document.addEventListener('DOMContentLoaded', () => {
243
- init(document.querySelectorAll("[plugin]"));
244
- });
245
- }
246
-
247
200
  async function processPlugin(el) {
248
201
  const rawAttr = el.getAttribute("plugin");
249
202
  if (!rawAttr) return;
250
203
 
251
- const pluginName = rawAttr.split(',')[0].trim();
204
+ const pluginNames = rawAttr.split(',').map(s => s.trim());
252
205
 
253
- // SECURITY CHECK:
254
- // Look up the definition in our hardcoded allowlist.
255
- const pluginDef = plugins[pluginName];
256
-
257
- if (pluginDef) {
258
- console.log(`[Plugin System] Loading ${pluginName}...`);
206
+ for (const pluginName of pluginNames) {
207
+ const pluginDef = plugins[pluginName];
208
+ if (!pluginDef) continue;
259
209
 
260
- // Load CSS
261
- if(pluginDef.css) pluginDef.css.forEach(href => {
262
- if (!document.querySelector(`link[href="${href}"]`)) {
210
+ // Load CSS
211
+ if (pluginDef.css) pluginDef.css.forEach(href => {
212
+ if (!document.querySelector(`link[href="${href}"]`)) {
263
213
  const link = document.createElement("link");
264
- link.rel="stylesheet"; link.href=href; document.head.appendChild(link);
265
- }
266
- });
267
-
268
- // Load JS with Integrity Checks
269
- if(pluginDef.js) {
270
- for(const item of pluginDef.js) {
271
- const src = typeof item === 'string' ? item : item.src;
272
- const integrity = typeof item === 'object' ? item.integrity : null;
273
- const crossOrigin = typeof item === 'object' ? item.crossOrigin : null;
274
-
275
- if (document.querySelector(`script[src="${src}"]`)) continue;
276
-
277
- await new Promise((resolve, reject) => {
278
- const s = document.createElement("script");
279
- s.src = src;
280
- if (integrity) {
281
- s.integrity = integrity;
282
- s.crossOrigin = crossOrigin || "anonymous";
214
+ link.rel = "stylesheet"; link.href = href; document.head.appendChild(link);
215
+ }
216
+ });
217
+
218
+ // Load JS with Promise Cache
219
+ if (pluginDef.js) {
220
+ for (const item of pluginDef.js) {
221
+ const src = typeof item === 'string' ? item : item.src;
222
+ const integrity = typeof item === 'object' ? item.integrity : null;
223
+ const crossOrigin = typeof item === 'object' ? item.crossOrigin : null;
224
+
225
+ if (!scriptCache.has(src)) {
226
+ const scriptPromise = new Promise((resolve, reject) => {
227
+ // Check if already in DOM (manual load)
228
+ const existing = document.querySelector(`script[src="${src}"]`);
229
+ if (existing) {
230
+ if (existing.dataset.loaded === "true") {
231
+ resolve();
232
+ } else {
233
+ const prevOnload = existing.onload;
234
+ existing.onload = () => {
235
+ if (prevOnload) prevOnload();
236
+ existing.dataset.loaded = "true";
237
+ resolve();
238
+ };
239
+ existing.onerror = reject;
240
+ }
241
+ } else {
242
+ const s = document.createElement("script");
243
+ s.src = src;
244
+ if (integrity) {
245
+ s.integrity = integrity;
246
+ s.crossOrigin = crossOrigin || "anonymous";
247
+ }
248
+ s.onload = () => {
249
+ s.dataset.loaded = "true";
250
+ resolve();
251
+ };
252
+ s.onerror = reject;
253
+ document.head.appendChild(s);
283
254
  }
284
- s.onload = resolve;
285
- s.onerror = reject;
286
- document.head.appendChild(s);
287
255
  });
256
+ scriptCache.set(src, scriptPromise);
288
257
  }
289
- }
290
-
291
- // Initialize after load
292
- executeGenericPlugin(el, pluginName);
293
258
 
294
- } else {
295
- console.warn(`[Plugin System] Blocked: Plugin "${pluginName}" is not in the allowlist.`);
259
+ try {
260
+ await scriptCache.get(src);
261
+ } catch (e) {
262
+ console.error(`Failed to load script: ${src}`, e);
263
+ }
264
+ }
265
+ }
266
+
267
+ executeGenericPlugin(el, pluginName);
296
268
  }
297
269
  }
298
270
 
299
- async function executeGenericPlugin(el, pluginName) {
300
- // MODIFIED: Removed 'data-' prefix.
301
- // Now looks for attributes like `quill=""` and `quill-theme=""`.
302
- const prefix = pluginName.toLowerCase();
271
+ function executeGenericPlugin(el, name) {
272
+ const prefix = name.toLowerCase();
303
273
  const mainAttr = el.getAttribute(prefix);
274
+ let rawData;
304
275
 
305
- // 1. CONFIG GATHERING
306
- const config = {};
307
- const attributes = Array.from(el.attributes);
308
-
309
- attributes.forEach(attr => {
310
- if (!attr.name.startsWith(prefix + '-')) return;
311
- const key = attr.name.substring(prefix.length + 1);
312
-
313
- if (key.startsWith('$')) return;
276
+ try {
277
+ rawData = JSON.parse(mainAttr);
278
+ } catch(e) {
279
+ rawData = {};
280
+ }
314
281
 
315
- let val = attr.value;
316
- try { val = JSON.parse(val); } catch(e) {}
282
+ // 1. Gather configuration from attributes
283
+ if (rawData && typeof rawData === 'object' && !Array.isArray(rawData)) {
284
+ Array.from(el.attributes).forEach(attr => {
285
+ if (!attr.name.startsWith(prefix + '-')) return;
286
+ let key = attr.name.substring(prefix.length + 1);
287
+ if (key.startsWith('$')) key = key.substring(1);
288
+ let val = attr.value;
289
+ try { val = JSON.parse(val); } catch (e) { }
290
+ rawData[key] = val;
291
+ });
292
+ }
317
293
 
318
- if (key.includes('-')) {
319
- const parts = key.split('-');
320
- let target = config;
321
- for(let i=0; i<parts.length-1; i++) {
322
- if (!target[parts[i]]) target[parts[i]] = {};
323
- target = target[parts[i]];
324
- }
325
- target[parts[parts.length-1]] = val;
326
- } else {
327
- config[key] = val;
328
- }
329
- });
294
+ // 2. Resolve parameters (Token Resolver)
295
+ const resolved = processParams(el, rawData);
330
296
 
331
- // 2. INSTANTIATION
332
- const Constructor = window[pluginName];
333
-
334
- if (!Constructor) {
335
- console.error(`Constructor for ${pluginName} not found on window.`);
297
+ const Target = window[name] || window[prefix];
298
+ if (!Target) {
299
+ console.error(`Constructor for ${name} not found on window.`);
336
300
  return;
337
301
  }
338
302
 
339
- let instance = null;
303
+ let instance;
340
304
  try {
341
- let args = [el, config];
342
- if (mainAttr) {
343
- try {
344
- const json = JSON.parse(mainAttr);
345
- if (Array.isArray(json)) {
346
- args = processParams(el, json, config, null, pluginName);
347
- }
348
- } catch(e) {}
305
+ // 3. Determine if we call as a function (Anime) or as a Constructor (Swiper)
306
+ if (typeof Target === 'function' && (!Target.prototype || name === 'Anime')) {
307
+ instance = Target(resolved);
308
+ } else {
309
+ if (Array.isArray(resolved)) {
310
+ instance = new Target(...resolved);
311
+ } else {
312
+ instance = new Target(resolved);
313
+ }
349
314
  }
350
- instance = new Constructor(...args);
351
- } catch(e) {
352
- console.error(`Init failed for ${pluginName}`, e);
353
- return;
354
- }
355
-
356
- if (instance) el[pluginName] = instance;
357
- else return;
358
-
359
- // 3. OPERATOR PIPELINE
360
- const opPipeline = [];
361
- attributes.forEach(attr => {
362
- if (!attr.name.startsWith(prefix + '-')) return;
363
- let key = attr.name.substring(prefix.length + 1);
364
- if (!key.startsWith('$')) return;
365
- let val = attr.value;
366
- try { val = JSON.parse(val); } catch(e) {}
367
- let opName = key;
368
- opPipeline.push(opName, [val]);
369
- });
370
-
371
- if (opPipeline.length > 0) {
372
- await executePipeline(el, opPipeline, pluginName, instance, config);
315
+ console.log(`Initialized ${name}`);
316
+ } catch (e) {
317
+ console.error(`Error initializing ${name}:`, e);
373
318
  }
319
+ if (instance) el[name] = instance;
374
320
  }
375
321
 
376
- async function executePipeline(element, pipelineArray, pluginName, currentCtx, config = {}) {
377
- let parentCtx = window;
378
- const instance = element[pluginName] || currentCtx;
379
-
380
- for (let i = 0; i < pipelineArray.length; i++) {
381
- const item = pipelineArray[i];
382
- if (Array.isArray(item)) continue;
383
-
384
- if (typeof item === 'string') {
385
- let opName = item;
386
-
387
- if ((typeof currentCtx === 'undefined' || currentCtx === null) && instance) {
388
- currentCtx = instance;
389
- }
390
-
391
- if (opName.toLowerCase() === '$this') {
392
- const args = (i + 1 < pipelineArray.length && Array.isArray(pipelineArray[i + 1])) ? pipelineArray[i + 1] : [];
393
- const mapDef = args[0];
394
- if (Array.isArray(mapDef)) {
395
- mapDef.forEach(def => {
396
- applyMapping(element, instance, def);
397
- });
398
- }
399
- continue;
400
- }
401
-
402
- if (opName.startsWith('$')) {
403
- if (!currentCtx) {
404
- if (element[pluginName]) currentCtx = element[pluginName];
405
- if (!currentCtx) {
406
- console.warn(`Context undefined for operator ${opName}. Skipping.`);
407
- continue;
408
- }
409
- }
410
-
411
- let cleanName = opName.substring(1);
412
- if (!cleanName.includes('.')) {
413
- cleanName = cleanName.replace(/-/g, '');
414
- }
415
-
416
- let realKey = cleanName;
417
- if (!cleanName.includes('.') && currentCtx && (typeof currentCtx === 'object' || typeof currentCtx === 'function')) {
418
- try {
419
- const keys = Object.keys(currentCtx.constructor?.prototype || currentCtx);
420
- const found = keys.find(k => k.toLowerCase() === cleanName.toLowerCase());
421
- if (found) realKey = found;
422
- else {
423
- const directFound = Object.keys(currentCtx).find(k => k.toLowerCase() === cleanName.toLowerCase());
424
- if(directFound) realKey = directFound;
425
- }
426
- } catch (e) {}
322
+ /**
323
+ * Generic Parameter Processor
324
+ * Handles:
325
+ * - $this / $this.children
326
+ * - $window.path.to.function(arg)
327
+ * - $anime.stagger(100)
328
+ */
329
+ function processParams(el, params) {
330
+ if (typeof params === 'string' && params.startsWith('$')) {
331
+ try {
332
+ // 1. Check for Method Call: $root.path.to.func(arg)
333
+ const callMatch = params.match(/^\$([^.]+)\.(.+)\((.*)\)$/);
334
+ if (callMatch) {
335
+ const [_, root, path, arg] = callMatch;
336
+ const obj = (root === 'this') ? el : window[root];
337
+
338
+ // If root object exists, drill down
339
+ if (obj) {
340
+ const func = path.split('.').reduce((o, k) => (o || {})[k], obj);
341
+ if (typeof func === 'function') {
342
+ // Parse argument if JSON-like, else string
343
+ const parsedArg = arg ? (function() { try { return JSON.parse(arg); } catch(e) { return arg; } })() : undefined;
344
+ return func(parsedArg);
345
+ }
427
346
  }
428
- opName = realKey;
429
347
  }
430
-
431
- const [parent, val] = resolvePath(i === 0 ? window : currentCtx, opName);
432
348
 
433
- if (typeof val === 'undefined') {
434
- console.warn(`Method/Prop "${opName}" not found on context.`);
435
- continue;
349
+ // 2. Check for Property Access: $root.path.to.prop or just $root
350
+ const propMatch = params.match(/^\$([^.]+)(?:\.(.+))?$/);
351
+ if (propMatch) {
352
+ const [_, root, path] = propMatch;
353
+ const obj = (root === 'this') ? el : window[root];
354
+ if (!obj) return params; // Return raw string if root not found
355
+ if (!path) return obj;
356
+
357
+ const val = path.split('.').reduce((o, k) => (o || {})[k], obj);
358
+ // Convert HTMLCollections to Arrays
359
+ return (val instanceof HTMLCollection) ? Array.from(val) : val;
436
360
  }
437
-
438
- parentCtx = parent;
439
- currentCtx = val;
440
-
441
- if (typeof currentCtx === 'function') {
442
- let args = [];
443
- if (i + 1 < pipelineArray.length && Array.isArray(pipelineArray[i + 1])) {
444
- args = processParams(element, pipelineArray[i + 1], config, instance, pluginName);
445
- }
446
-
447
- try {
448
- const finalArgs = (args.length === 1) ? [args[0]] : args;
449
- try {
450
- currentCtx = await currentCtx.apply(parentCtx, finalArgs);
451
- } catch (err) {
452
- currentCtx = new currentCtx(...finalArgs);
453
- parentCtx = currentCtx;
454
- }
455
- } catch(e) {
456
- console.error(`Exec error ${opName}`, e);
457
- }
458
- }
459
- }
460
- }
461
- return currentCtx;
462
- }
463
-
464
- // --- HELPERS ---
465
- function applyMapping(element, instance, def) {
466
- if (def.includes('=')) {
467
- const [lhs, rhs] = def.split('=').map(s => s.trim());
468
- const pathRaw = rhs;
469
- const match = lhs.match(/^[$]?([\w\.]+)(\(\))?$/);
470
- if (!match) return;
471
- const alias = match[1];
472
- const isFunction = match[2] === '()';
473
-
474
- if (isFunction) {
475
- element[alias] = function(val) {
476
- if (arguments.length > 0) return assignDeep(instance, pathRaw, val);
477
- else return resolveDeep(instance, pathRaw);
478
- };
479
- } else {
480
- Object.defineProperty(element, alias, {
481
- get: () => resolveDeep(instance, pathRaw),
482
- set: (v) => assignDeep(instance, pathRaw, v),
483
- configurable: true
484
- });
485
- }
486
- } else {
487
- if (typeof instance[def] === 'function') {
488
- element[def] = instance[def].bind(instance);
361
+ } catch (e) {
362
+ console.warn("Failed to resolve dynamic token:", params);
489
363
  }
490
364
  }
491
- }
492
-
493
- function processParams(element, params, config, instance, pluginName) {
494
- if (params === "$this") return element;
495
- if (params === "$config") return config;
496
- if (params === "$root" || params === "$instance") return instance;
497
- if (typeof params === 'string' && params === `$${pluginName}`) return instance;
498
- if (typeof params === 'string' && params.startsWith('$this.')) return resolveDeep(element, params.substring(6));
499
- if (Array.isArray(params)) return params.map(p => processParams(element, p, config, instance, pluginName));
365
+
366
+ if (Array.isArray(params)) return params.map(p => processParams(el, p));
500
367
  if (typeof params === 'object' && params !== null) {
501
368
  const res = {};
502
- for(let k in params) res[k] = processParams(element, params[k], config, instance, pluginName);
369
+ for (let k in params) res[k] = processParams(el, params[k]);
503
370
  return res;
504
371
  }
505
372
  return params;
506
373
  }
507
374
 
508
- function resolveDeep(obj, path) {
509
- if (!obj || !path) return undefined;
510
- return path.split('.').reduce((o, k) => (o || {})[k], obj);
511
- }
512
- function assignDeep(obj, path, val) {
513
- if (!obj || !path) return;
514
- const parts = path.split('.');
515
- let target = obj;
516
- for(let i=0; i<parts.length-1; i++) target = target[parts[i]];
517
- const last = parts[parts.length-1];
518
- if(typeof target[last] === 'function') target[last](val);
519
- else target[last] = val;
520
- }
521
- function resolvePath(obj, path) {
522
- if (!obj || !path) return [null, undefined];
523
- const parts = path.split('.');
524
- let current = obj;
525
- let parent = null;
526
- for (const part of parts) {
527
- if (!current) return [null, undefined];
528
- parent = current;
529
- current = current[part];
530
- }
531
- return [parent, current];
532
- }
533
-
534
375
  // --- OBSERVER INTEGRATION ---
535
376
  Observer.init({
536
377
  name: "plugin",
@@ -540,4 +381,9 @@ Observer.init({
540
381
  callback: (mutation) => {
541
382
  init(mutation.target);
542
383
  }
543
- });
384
+ });
385
+
386
+ // Auto-init for existing elements
387
+ if (typeof document !== 'undefined') {
388
+ init(document.querySelectorAll("[plugin]"));
389
+ }