@cocreate/plugins 1.2.1 → 1.4.0
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/.github/workflows/automated.yml +23 -11
- package/CHANGELOG.md +33 -0
- package/demo/index.html +67 -218
- package/package.json +10 -12
- package/release.config.js +11 -3
- package/src/index.js +193 -531
- package/src/operators.js +293 -0
package/src/index.js
CHANGED
|
@@ -1,592 +1,254 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (C) 2026 CoCreate and Contributors.
|
|
3
|
+
*
|
|
4
|
+
* This program is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU Affero General Public License as published
|
|
6
|
+
* by the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* This program is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU Affero General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
*
|
|
17
|
+
********************************************************************************/
|
|
3
18
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* @property {Array<string|Object>} [js] - List of JS files to load. Can be strings (URLs) or objects with src, integrity, etc
|
|
7
|
-
* @property {Array<string>} [css] - List of CSS files to load.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
// --- CONFIGURATION ---
|
|
19
|
+
import Observer from "@cocreate/observer";
|
|
20
|
+
import { processOperators } from "./operators.js";
|
|
11
21
|
|
|
12
|
-
/**
|
|
13
|
-
* @type {Object.<string, PluginDefinition>}
|
|
14
|
-
* Configuration object containing plugin definitions.
|
|
15
|
-
* Populated dynamically from CoCreate.config.js or defaults.
|
|
16
|
-
*/
|
|
17
22
|
const plugins = {};
|
|
18
|
-
|
|
19
|
-
// --- CORE ENGINE ---
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Global Cache for script promises to prevent race conditions and duplicate loads.
|
|
23
|
-
* Stores the pending Promise for a script source URL.
|
|
24
|
-
* @type {Map<string, Promise<void>>}
|
|
25
|
-
*/
|
|
26
23
|
const scriptCache = new Map();
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Cache the CSS marker once on load to determine injection point.
|
|
30
|
-
* Used to ensure plugin CSS is injected in the correct order relative to user styles.
|
|
31
|
-
* @type {Element|null}
|
|
32
|
-
*/
|
|
33
24
|
const cssMarker = typeof document !== 'undefined' ? document.querySelector('link[plugins]') : null;
|
|
34
25
|
|
|
35
26
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* * @param {HTMLElement|NodeList|Array<HTMLElement>} elements - Single Element, NodeList, or Array of Elements to initialize.
|
|
39
|
-
* @returns {void}
|
|
27
|
+
* Initializes plugins on a collection of HTML elements.
|
|
28
|
+
* @param {HTMLElement|HTMLElement[]|NodeList} elements
|
|
40
29
|
*/
|
|
41
|
-
function init(elements) {
|
|
30
|
+
export function init(elements) {
|
|
42
31
|
if (!elements) return;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
} else if (elements.length !== undefined && typeof elements !== 'function') {
|
|
48
|
-
collection = Array.from(elements);
|
|
49
|
-
} else {
|
|
50
|
-
collection = [elements];
|
|
51
|
-
}
|
|
52
|
-
|
|
32
|
+
const collection = (elements instanceof HTMLElement || elements instanceof Element)
|
|
33
|
+
? [elements]
|
|
34
|
+
: Array.from(elements);
|
|
35
|
+
|
|
53
36
|
collection.forEach(el => {
|
|
54
|
-
if (el
|
|
37
|
+
if (el) {
|
|
55
38
|
processPlugin(el);
|
|
56
39
|
}
|
|
57
40
|
});
|
|
58
41
|
}
|
|
59
42
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
for (const pluginName of pluginNames) {
|
|
74
|
-
const pluginDef = plugins[pluginName];
|
|
75
|
-
|
|
76
|
-
// Only attempt to load resources if a configuration exists
|
|
77
|
-
if (pluginDef) {
|
|
78
|
-
// Load CSS
|
|
79
|
-
if (pluginDef.css) pluginDef.css.forEach(href => {
|
|
80
|
-
if (!document.querySelector(`link[href="${href}"]`)) {
|
|
81
|
-
const link = document.createElement("link");
|
|
82
|
-
link.rel = "stylesheet";
|
|
83
|
-
link.href = href;
|
|
84
|
-
|
|
85
|
-
// CSS INJECTION STRATEGY:
|
|
86
|
-
// 1. Priority: Check for a specific marker element <link plugin>
|
|
87
|
-
// (Cached globally in cssMarker)
|
|
88
|
-
|
|
89
|
-
if (cssMarker) {
|
|
90
|
-
// Insert before the marker
|
|
91
|
-
cssMarker.parentNode.insertBefore(link, cssMarker);
|
|
92
|
-
} else {
|
|
93
|
-
// 2. Fallback: Prepend before existing CSS
|
|
94
|
-
// To allow custom CSS to easily override plugin defaults, we must ensure
|
|
95
|
-
// plugin CSS loads BEFORE user CSS.
|
|
96
|
-
const firstStyle = document.head.querySelector('link[rel="stylesheet"], style');
|
|
97
|
-
|
|
98
|
-
if (firstStyle) {
|
|
99
|
-
document.head.insertBefore(link, firstStyle);
|
|
100
|
-
} else {
|
|
101
|
-
// If no CSS exists yet, appending is safe
|
|
102
|
-
document.head.appendChild(link);
|
|
103
|
-
}
|
|
43
|
+
async function preloadResources(pluginNames) {
|
|
44
|
+
for (const name of pluginNames) {
|
|
45
|
+
const pluginDef = plugins[name];
|
|
46
|
+
if (pluginDef && !window[name]) {
|
|
47
|
+
if (pluginDef.css) {
|
|
48
|
+
pluginDef.css.forEach(href => {
|
|
49
|
+
if (!document.querySelector(`link[href="${href}"]`)) {
|
|
50
|
+
const link = document.createElement("link");
|
|
51
|
+
link.rel = "stylesheet";
|
|
52
|
+
link.href = href;
|
|
53
|
+
if (cssMarker) cssMarker.parentNode.insertBefore(link, cssMarker);
|
|
54
|
+
else document.head.appendChild(link);
|
|
104
55
|
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
107
58
|
|
|
108
|
-
// Load JS with Promise Cache
|
|
109
59
|
if (pluginDef.js) {
|
|
110
|
-
const preWindowKeys = (typeof window !== 'undefined') ? new Set(Object.keys(window)) : new Set();
|
|
111
|
-
|
|
112
60
|
for (const item of pluginDef.js) {
|
|
113
61
|
const src = typeof item === 'string' ? item : item.src;
|
|
114
|
-
|
|
115
|
-
const crossOrigin = typeof item === 'object' ? item.crossOrigin : null;
|
|
116
|
-
|
|
62
|
+
if (!src) continue;
|
|
117
63
|
if (!scriptCache.has(src)) {
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (prevOnload) prevOnload();
|
|
128
|
-
existing.dataset.loaded = "true";
|
|
129
|
-
resolve();
|
|
130
|
-
};
|
|
131
|
-
existing.onerror = reject;
|
|
132
|
-
}
|
|
133
|
-
} else {
|
|
134
|
-
const s = document.createElement("script");
|
|
135
|
-
s.src = src;
|
|
136
|
-
if (integrity) {
|
|
137
|
-
s.integrity = integrity;
|
|
138
|
-
s.crossOrigin = crossOrigin || "anonymous";
|
|
139
|
-
}
|
|
140
|
-
s.onload = () => {
|
|
141
|
-
s.dataset.loaded = "true";
|
|
142
|
-
resolve();
|
|
143
|
-
};
|
|
144
|
-
s.onerror = reject;
|
|
64
|
+
const existingScript = document.querySelector(`script[src*="${src}"]`);
|
|
65
|
+
if (existingScript && (existingScript.dataset.loaded === "true" || window[name])) {
|
|
66
|
+
scriptCache.set(src, Promise.resolve());
|
|
67
|
+
} else {
|
|
68
|
+
const s = document.createElement("script");
|
|
69
|
+
s.src = src;
|
|
70
|
+
scriptCache.set(src, new Promise((resolve) => {
|
|
71
|
+
s.onload = () => { s.dataset.loaded = "true"; resolve(); };
|
|
72
|
+
s.onerror = () => resolve();
|
|
145
73
|
document.head.appendChild(s);
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
scriptCache.set(src, scriptPromise);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
try {
|
|
152
|
-
await scriptCache.get(src);
|
|
153
|
-
} catch (e) {
|
|
154
|
-
console.error(`Failed to load script: ${src}`, e);
|
|
155
|
-
}
|
|
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}`);
|
|
74
|
+
}));
|
|
182
75
|
}
|
|
183
76
|
}
|
|
184
|
-
|
|
185
|
-
// Non-fatal
|
|
77
|
+
await scriptCache.get(src);
|
|
186
78
|
}
|
|
187
79
|
}
|
|
188
80
|
}
|
|
189
|
-
|
|
190
|
-
// Attempt to execute plugin even if no config was found (it might be on window already)
|
|
191
|
-
executeGenericPlugin(el, pluginName);
|
|
192
81
|
}
|
|
193
82
|
}
|
|
194
83
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
return false;
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Executes the logic for a generic plugin on a specific element.
|
|
215
|
-
* Handles:
|
|
216
|
-
* 1. Resolving the target class/function from window.
|
|
217
|
-
* 2. Initializing the base instance.
|
|
218
|
-
* 3. Processing attribute paths and nested JSON objects to execute methods or set properties.
|
|
219
|
-
* * @param {HTMLElement} el - The target element.
|
|
220
|
-
* @param {string} name - The name of the plugin (case-insensitive identifier).
|
|
221
|
-
* @returns {void}
|
|
222
|
-
*/
|
|
223
|
-
function executeGenericPlugin(el, name) {
|
|
224
|
-
const prefix = name.toLowerCase();
|
|
225
|
-
const mainAttr = el.getAttribute(prefix);
|
|
226
|
-
let rawData = {};
|
|
227
|
-
|
|
228
|
-
for (let attr of el.attributes) {
|
|
229
|
-
let key = attr.name;
|
|
230
|
-
if (key === prefix) {
|
|
231
|
-
key = name;
|
|
232
|
-
} else if (key.startsWith(prefix + '-')) {
|
|
233
|
-
key = key.replaceAll("-", ".");
|
|
234
|
-
} else if (!key.startsWith(prefix + '.')) {
|
|
235
|
-
continue
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
try {
|
|
239
|
-
rawData[key] = JSON.parse(attr.value);
|
|
240
|
-
} catch(e) {
|
|
241
|
-
rawData[key] = attr.value;
|
|
242
|
-
}
|
|
243
|
-
|
|
84
|
+
async function processPlugin(el) {
|
|
85
|
+
if (!el.attributes) return;
|
|
86
|
+
|
|
87
|
+
// Direct attributes collection map lookup to avoid binding/context prototype errors
|
|
88
|
+
const pluginAttrNode = el.attributes['plugin'] || el.attributes.getNamedItem?.('plugin');
|
|
89
|
+
const rawPluginAttr = pluginAttrNode?.value?.trim();
|
|
90
|
+
if (!rawPluginAttr) return;
|
|
91
|
+
|
|
92
|
+
const context = {
|
|
93
|
+
element: el,
|
|
94
|
+
this: el,
|
|
95
|
+
plugin: null, // Tracks active sibling plugin instance
|
|
96
|
+
data: { ...el.dataset },
|
|
97
|
+
event: {},
|
|
98
|
+
crud: window.CoCreate?.crud || null
|
|
244
99
|
};
|
|
245
100
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
// el and name used to store the result on the element.
|
|
269
|
-
update(Target, val, window, name, el, name, el);
|
|
101
|
+
try {
|
|
102
|
+
let configs = [];
|
|
103
|
+
|
|
104
|
+
// Unified Direct Parser: Pure JSON parsing with simple zero-argument fallback
|
|
105
|
+
if (rawPluginAttr.startsWith('{') || rawPluginAttr.startsWith('[')) {
|
|
106
|
+
const parsedConfig = JSON.parse(rawPluginAttr);
|
|
107
|
+
configs = Array.isArray(parsedConfig) ? parsedConfig : [parsedConfig];
|
|
108
|
+
} else {
|
|
109
|
+
// Symmetrical Fallback: Plain strings mapped straight to constructor blocks
|
|
110
|
+
configs = [{ [rawPluginAttr]: [] }];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const targetPlugins = new Set();
|
|
114
|
+
configs.forEach(conf => {
|
|
115
|
+
Object.keys(conf).forEach(k => {
|
|
116
|
+
if (!k.startsWith('$')) {
|
|
117
|
+
targetPlugins.add(k.split('.')[0]);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
await preloadResources(Array.from(targetPlugins));
|
|
270
123
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}
|
|
124
|
+
// Execute each configuration block sequentially
|
|
125
|
+
for (const configBlock of configs) {
|
|
126
|
+
await executeConfigBlock(context, configBlock);
|
|
275
127
|
}
|
|
128
|
+
} catch (err) {
|
|
129
|
+
console.error("[Plugin System] Failed to parse and execute plugin configuration:", err, rawPluginAttr);
|
|
276
130
|
}
|
|
277
131
|
}
|
|
278
132
|
|
|
279
|
-
function
|
|
280
|
-
if (
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const part = parts[i];
|
|
288
|
-
if (current == null) return { parent: null, value: undefined };
|
|
289
|
-
parent = current;
|
|
290
|
-
current = current[part];
|
|
133
|
+
function instantiatePlugin(ConstructorFunc, argsArray) {
|
|
134
|
+
if (typeof ConstructorFunc !== 'function') return ConstructorFunc;
|
|
135
|
+
|
|
136
|
+
// Check standard ES6 class structure
|
|
137
|
+
const isEs6Class = /^\s*class\s+/.test(ConstructorFunc.toString());
|
|
138
|
+
|
|
139
|
+
if (isEs6Class) {
|
|
140
|
+
return new ConstructorFunc(...argsArray);
|
|
291
141
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
// Fallback Step A: Attempt standard function invocation (e.g. particlesJS)
|
|
145
|
+
return ConstructorFunc(...argsArray);
|
|
146
|
+
} catch (err) {
|
|
147
|
+
// Fallback Step B: If invocation throws constructor error (e.g. Babel transpiled ES5 classes)
|
|
148
|
+
if (err instanceof TypeError) {
|
|
149
|
+
return new ConstructorFunc(...argsArray);
|
|
150
|
+
}
|
|
151
|
+
throw err;
|
|
302
152
|
}
|
|
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
153
|
}
|
|
319
154
|
|
|
320
|
-
function
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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 };
|
|
155
|
+
async function executeConfigBlock(context, configBlock) {
|
|
156
|
+
for (const [key, value] of Object.entries(configBlock)) {
|
|
157
|
+
// 1. If key is an operator, process it natively via AST engine
|
|
158
|
+
if (key.startsWith("$")) {
|
|
159
|
+
await processOperators(context, { [key]: value });
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// 2. Intercept standard lifecycle methods to bind them directly as functions on the Element
|
|
164
|
+
if (["getValue", "setValue", "read", "write"].includes(key)) {
|
|
165
|
+
context.element[key] = async function(...args) {
|
|
166
|
+
const localContext = {
|
|
167
|
+
...context,
|
|
168
|
+
event: { ...context.event, value: args[0], arguments: args }
|
|
169
|
+
};
|
|
170
|
+
return await processOperators(localContext, value);
|
|
171
|
+
};
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const parts = key.split('.');
|
|
176
|
+
const rootPluginName = parts[0];
|
|
177
|
+
|
|
178
|
+
// 3. Resolve target from window object using dot notation
|
|
179
|
+
let ConstructorFunc = window;
|
|
180
|
+
for (const part of parts) {
|
|
181
|
+
if (ConstructorFunc) {
|
|
182
|
+
ConstructorFunc = ConstructorFunc[part];
|
|
360
183
|
}
|
|
361
184
|
}
|
|
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
185
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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
|
-
}
|
|
186
|
+
// 4. If it's a function, treat it as a constructor to initialize the plugin
|
|
187
|
+
if (typeof ConstructorFunc === 'function' && !context.element[rootPluginName]) {
|
|
188
|
+
let constructorArgs = [];
|
|
189
|
+
if (value !== undefined) {
|
|
190
|
+
const evaluatedArgs = await processOperators(context, value);
|
|
191
|
+
constructorArgs = Array.isArray(evaluatedArgs) ? evaluatedArgs : [evaluatedArgs];
|
|
389
192
|
}
|
|
390
193
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
property = key;
|
|
407
|
-
if (elProperty) elProperty = key; // Update element structure key to match real property
|
|
408
|
-
break;
|
|
194
|
+
// Create the plugin instance and bind it directly to the element under its root name
|
|
195
|
+
const instance = instantiatePlugin(ConstructorFunc, constructorArgs);
|
|
196
|
+
context.element[rootPluginName] = instance;
|
|
197
|
+
context.plugin = instance; // Update context.plugin reference for sibling method chaining
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// 5. Explicit Instance Method Chaining (e.g. "FullCalendar.render")
|
|
202
|
+
let methodTarget = context.element[rootPluginName];
|
|
203
|
+
if (methodTarget && parts.length > 1) {
|
|
204
|
+
let method = methodTarget;
|
|
205
|
+
let parent = methodTarget;
|
|
206
|
+
for (let i = 1; i < parts.length; i++) {
|
|
207
|
+
parent = method;
|
|
208
|
+
if (method) method = method[parts[i]];
|
|
409
209
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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
|
-
|
|
422
|
-
if (!isConstructor(Target, property)) {
|
|
423
|
-
// Call as a function (method or standalone)
|
|
424
|
-
// Use 'parent' as context (this) if available to maintain class references
|
|
425
|
-
if (parent) {
|
|
426
|
-
if (Array.isArray(val)) {
|
|
427
|
-
instance = Target.apply(parent, val);
|
|
428
|
-
} else {
|
|
429
|
-
instance = Target.call(parent, val);
|
|
430
|
-
}
|
|
431
|
-
} else {
|
|
432
|
-
if (Array.isArray(val)) {
|
|
433
|
-
instance = Target(...val);
|
|
434
|
-
} else {
|
|
435
|
-
instance = Target(val);
|
|
210
|
+
if (typeof method === 'function') {
|
|
211
|
+
let methodArgs = [];
|
|
212
|
+
if (value !== undefined) {
|
|
213
|
+
const evaluatedArgs = await processOperators(context, value);
|
|
214
|
+
methodArgs = Array.isArray(evaluatedArgs) ? evaluatedArgs : [evaluatedArgs];
|
|
436
215
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
// Call as a Constructor
|
|
440
|
-
if (Array.isArray(val)) {
|
|
441
|
-
instance = new Target(...val);
|
|
442
|
-
} else {
|
|
443
|
-
instance = new Target(val);
|
|
216
|
+
await method.apply(parent, methodArgs);
|
|
217
|
+
continue;
|
|
444
218
|
}
|
|
445
219
|
}
|
|
446
|
-
|
|
447
|
-
// Assign the result to the element structure
|
|
448
|
-
if (elParent && elProperty) {
|
|
449
|
-
elParent[elProperty] = instance;
|
|
450
|
-
}
|
|
451
220
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
} else if (typeof Target === 'object' && Target !== null && typeof val === 'object' && val !== null && !Array.isArray(val)) {
|
|
459
|
-
// Prepare the next level of the element structure
|
|
460
|
-
if (elParent && elProperty) {
|
|
461
|
-
if (!elParent[elProperty]) {
|
|
462
|
-
elParent[elProperty] = {};
|
|
463
|
-
}
|
|
464
|
-
const nextElParent = elParent[elProperty];
|
|
465
|
-
|
|
466
|
-
for (let key in val) {
|
|
467
|
-
update(Target[key], val[key], Target, key, nextElParent, key, hostElement);
|
|
221
|
+
// 6. Sibling Method Chaining (e.g. "render" directly on context.plugin)
|
|
222
|
+
if (context.plugin && typeof context.plugin[key] === 'function') {
|
|
223
|
+
let methodArgs = [];
|
|
224
|
+
if (value !== undefined) {
|
|
225
|
+
const evaluatedArgs = await processOperators(context, value);
|
|
226
|
+
methodArgs = Array.isArray(evaluatedArgs) ? evaluatedArgs : [evaluatedArgs];
|
|
468
227
|
}
|
|
228
|
+
await context.plugin[key].apply(context.plugin, methodArgs);
|
|
229
|
+
continue;
|
|
469
230
|
}
|
|
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
|
-
|
|
478
|
-
// If it's not a function, we are setting a value on the plugin object
|
|
479
|
-
parent[property] = val;
|
|
480
|
-
|
|
481
|
-
// Map the value to the element structure
|
|
482
|
-
if (elParent && elProperty) {
|
|
483
|
-
elParent[elProperty] = val;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
console.log(`Set plugin property ${property} to`, val);
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
/**
|
|
491
|
-
* Generic Parameter Processor
|
|
492
|
-
* Handles:
|
|
493
|
-
* - $this / $this.children
|
|
494
|
-
* - $window.path.to.function(arg)
|
|
495
|
-
* - $anime.stagger(100)
|
|
496
|
-
* - Global access: $document, $window, etc.
|
|
497
|
-
*/
|
|
498
|
-
function processParams(el, params) {
|
|
499
|
-
if (typeof params === 'string' && params.startsWith('\u0024')) {
|
|
500
|
-
try {
|
|
501
|
-
// 1. Check for Method Call: $root.path.to.func(arg)
|
|
502
|
-
const callMatch = params.match(/^\u0024([^.]+)\.(.+)\((.*)\)$/);
|
|
503
|
-
if (callMatch) {
|
|
504
|
-
const [_, root, path, arg] = callMatch;
|
|
505
|
-
const obj = (root === 'this') ? el : window[root];
|
|
506
|
-
|
|
507
|
-
// If root object exists, drill down
|
|
508
|
-
if (obj) {
|
|
509
|
-
const func = path.split('.').reduce((o, k) => (o || {})[k], obj);
|
|
510
|
-
if (typeof func === 'function') {
|
|
511
|
-
// Parse argument if JSON-like, else string
|
|
512
|
-
const parsedArg = arg ? (function() { try { return JSON.parse(arg); } catch(e) { return arg; } })() : undefined;
|
|
513
|
-
return func(parsedArg);
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
// 2. Check for Property Access: $root.path.to.prop or just $root
|
|
519
|
-
const propMatch = params.match(/^\u0024([^.]+)(?:\.(.+))?$/);
|
|
520
|
-
if (propMatch) {
|
|
521
|
-
const [_, root, path] = propMatch;
|
|
522
|
-
const obj = (root === 'this') ? el : window[root];
|
|
523
|
-
|
|
524
|
-
if (obj) {
|
|
525
|
-
if (!path) return (obj instanceof HTMLCollection) ? Array.from(obj) : obj;
|
|
526
|
-
|
|
527
|
-
const val = path.split('.').reduce((o, k) => (o || {})[k], obj);
|
|
528
|
-
// Convert HTMLCollections to Arrays
|
|
529
|
-
return (val instanceof HTMLCollection) ? Array.from(val) : val;
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
231
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
if (window[globalKey]) {
|
|
536
|
-
return window[globalKey];
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
} catch (e) {
|
|
540
|
-
console.warn("Failed to resolve dynamic token:", params);
|
|
541
|
-
}
|
|
232
|
+
// 7. Fallback: Process operators or generic actions on properties
|
|
233
|
+
await processOperators(context, { [key]: value });
|
|
542
234
|
}
|
|
543
|
-
|
|
544
|
-
if (Array.isArray(params)) return params.map(p => processParams(el, p));
|
|
545
|
-
if (typeof params === 'object' && params !== null) {
|
|
546
|
-
const res = {};
|
|
547
|
-
for (let k in params) res[k] = processParams(el, params[k]);
|
|
548
|
-
return res;
|
|
549
|
-
}
|
|
550
|
-
return params;
|
|
551
235
|
}
|
|
552
236
|
|
|
553
|
-
// --- STARTUP LOGIC ---
|
|
554
|
-
|
|
555
237
|
if (typeof document !== 'undefined') {
|
|
556
|
-
|
|
557
|
-
|
|
238
|
+
const selector = "[plugin]";
|
|
239
|
+
Observer.init({
|
|
240
|
+
name: "plugin",
|
|
241
|
+
types: ["addedNodes", "attributes"],
|
|
242
|
+
selector: selector,
|
|
243
|
+
attributeFilter: ["plugin"],
|
|
244
|
+
callback: (mutation) => init(mutation.target)
|
|
245
|
+
});
|
|
246
|
+
|
|
558
247
|
import("./CoCreate.config.js")
|
|
559
248
|
.then((Config) => {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
Object.assign(plugins, Config.plugins);
|
|
563
|
-
}
|
|
564
|
-
else if (Config.default) {
|
|
565
|
-
if (Config.default.plugins) {
|
|
566
|
-
Object.assign(plugins, Config.default.plugins);
|
|
567
|
-
} else {
|
|
568
|
-
Object.assign(plugins, Config.default);
|
|
569
|
-
}
|
|
570
|
-
}
|
|
249
|
+
const data = Config.plugins || Config.default?.plugins || Config.default || {};
|
|
250
|
+
Object.assign(plugins, data);
|
|
571
251
|
})
|
|
572
|
-
.catch((
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
.finally(() => {
|
|
576
|
-
// Start Observer
|
|
577
|
-
Observer.init({
|
|
578
|
-
name: "plugin",
|
|
579
|
-
types: ["addedNodes", "attributes"],
|
|
580
|
-
selector: "[plugin]",
|
|
581
|
-
attributeFilter: ["plugin"],
|
|
582
|
-
callback: (mutation) => {
|
|
583
|
-
init(mutation.target);
|
|
584
|
-
}
|
|
585
|
-
});
|
|
586
|
-
|
|
587
|
-
// Initial Init
|
|
588
|
-
init(document.querySelectorAll("[plugin]"));
|
|
589
|
-
});
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
export default { init, plugins }
|
|
252
|
+
.catch(() => {})
|
|
253
|
+
.finally(() => init(document.querySelectorAll(selector)));
|
|
254
|
+
}
|