@kupola/kupola 1.7.9 → 1.9.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/css/scaffold.css +9 -3
- package/dist/core-Dfj-u6qC.cjs +1 -0
- package/dist/core-LA_QgrQO.js +4955 -0
- package/dist/core.cjs.js +1 -0
- package/dist/core.esm.js +1 -0
- package/dist/core.umd.js +1 -0
- package/dist/css/scaffold.css +9 -3
- package/dist/kupola-lite.cjs.js +1 -0
- package/dist/kupola-lite.esm.js +1 -0
- package/dist/kupola.cjs.js +1 -215
- package/dist/kupola.cjs.js.map +1 -1
- package/dist/kupola.esm.js +1 -8586
- package/dist/kupola.esm.js.map +1 -1
- package/dist/kupola.min.js +1 -0
- package/dist/kupola.umd.js +1 -215
- package/dist/kupola.umd.js.map +1 -1
- package/dist/plugins/vite-plugin-kupola.js +110 -99
- package/dist/plugins/webpack-plugin-kupola.js +58 -0
- package/dist/react-theme.js +111 -0
- package/dist/theme-preload.js +28 -0
- package/dist/theme-standalone.js +98 -0
- package/dist/vue-theme.js +78 -0
- package/js/icons.js +153 -81
- package/js/react-theme.js +111 -0
- package/js/theme-preload.js +28 -0
- package/js/theme-standalone.js +15 -5
- package/js/theme.js +17 -1
- package/js/vue-theme.js +78 -0
- package/package.json +32 -1
- package/plugins/vite-plugin-kupola.js +110 -99
- package/plugins/webpack-plugin-kupola.js +58 -0
|
@@ -0,0 +1,4955 @@
|
|
|
1
|
+
class KupolaLifecycle {
|
|
2
|
+
constructor(scope = "app") {
|
|
3
|
+
this.scope = scope;
|
|
4
|
+
this.hooks = /* @__PURE__ */ new Map();
|
|
5
|
+
this.state = "created";
|
|
6
|
+
this.stateHistory = ["created"];
|
|
7
|
+
this.transitions = /* @__PURE__ */ new Map([
|
|
8
|
+
["created", ["bootstrapped", "destroyed"]],
|
|
9
|
+
["bootstrapped", ["mounted", "destroyed"]],
|
|
10
|
+
["mounted", ["updated", "unmounted", "destroyed"]],
|
|
11
|
+
["updated", ["updated", "unmounted", "destroyed"]],
|
|
12
|
+
["unmounted", ["mounted", "destroyed"]],
|
|
13
|
+
["destroyed", []]
|
|
14
|
+
]);
|
|
15
|
+
this.phaseStateMap = {
|
|
16
|
+
"bootstrap": { from: "created", to: "bootstrapped" },
|
|
17
|
+
"mount": { from: ["bootstrapped", "unmounted"], to: "mounted" },
|
|
18
|
+
"update": { from: ["mounted", "updated"], to: "updated" },
|
|
19
|
+
"unmount": { from: ["mounted", "updated"], to: "unmounted" },
|
|
20
|
+
"destroy": { from: ["bootstrapped", "mounted", "updated", "unmounted"], to: "destroyed" }
|
|
21
|
+
};
|
|
22
|
+
this.basePhases = ["bootstrap", "mount", "update", "unmount", "destroy"];
|
|
23
|
+
this.allPhases = ["error", "errorBoundary"];
|
|
24
|
+
this.basePhases.forEach((phase) => {
|
|
25
|
+
this.allPhases.push(`before${phase.charAt(0).toUpperCase() + phase.slice(1)}`);
|
|
26
|
+
this.allPhases.push(phase);
|
|
27
|
+
this.allPhases.push(`after${phase.charAt(0).toUpperCase() + phase.slice(1)}`);
|
|
28
|
+
});
|
|
29
|
+
this.allPhases.forEach((phase) => {
|
|
30
|
+
this.hooks.set(phase, []);
|
|
31
|
+
});
|
|
32
|
+
this.pendingHooks = /* @__PURE__ */ new Set();
|
|
33
|
+
this.trace = [];
|
|
34
|
+
this.errorHandler = null;
|
|
35
|
+
this.errorBoundary = null;
|
|
36
|
+
this.lastError = null;
|
|
37
|
+
this.errorCount = 0;
|
|
38
|
+
this.maxErrors = 10;
|
|
39
|
+
this._onErrorCallback = null;
|
|
40
|
+
}
|
|
41
|
+
_validateTransition(nextState) {
|
|
42
|
+
const allowed = this.transitions.get(this.state);
|
|
43
|
+
if (!allowed || !allowed.includes(nextState)) {
|
|
44
|
+
throw new Error(`Invalid state transition: ${this.state} -> ${nextState}`);
|
|
45
|
+
}
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
_updateState(nextState) {
|
|
49
|
+
this._validateTransition(nextState);
|
|
50
|
+
this.state = nextState;
|
|
51
|
+
this.stateHistory.push(nextState);
|
|
52
|
+
}
|
|
53
|
+
_resetResolved(phase) {
|
|
54
|
+
const handlers = this.hooks.get(phase);
|
|
55
|
+
if (handlers) {
|
|
56
|
+
handlers.forEach((hook) => {
|
|
57
|
+
hook.resolved = false;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
on(phase, handler, options = {}) {
|
|
62
|
+
if (!this.allPhases.includes(phase)) {
|
|
63
|
+
throw new Error(`Unknown lifecycle phase: ${phase}`);
|
|
64
|
+
}
|
|
65
|
+
const hooks = this.hooks.get(phase);
|
|
66
|
+
hooks.push({
|
|
67
|
+
handler,
|
|
68
|
+
priority: options.priority || 0,
|
|
69
|
+
depends: options.depends || [],
|
|
70
|
+
name: options.name || handler.name || `anonymous_${hooks.length}`
|
|
71
|
+
});
|
|
72
|
+
hooks.sort((a, b) => b.priority - a.priority);
|
|
73
|
+
return () => {
|
|
74
|
+
const index = hooks.findIndex((h) => h.handler === handler);
|
|
75
|
+
if (index > -1) {
|
|
76
|
+
hooks.splice(index, 1);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
async _resolveDepends(depends, phase) {
|
|
81
|
+
if (!depends || depends.length === 0) return;
|
|
82
|
+
for (const depName of depends) {
|
|
83
|
+
const phaseHooks = this.hooks.get(phase);
|
|
84
|
+
const depHook = phaseHooks.find((h) => h.name === depName);
|
|
85
|
+
if (depHook && !depHook.resolved) {
|
|
86
|
+
await depHook.handler();
|
|
87
|
+
depHook.resolved = true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async emit(phase, ...args) {
|
|
92
|
+
if (this.state === "destroyed" && phase !== "error") return;
|
|
93
|
+
const handlers = this.hooks.get(phase);
|
|
94
|
+
if (!handlers || handlers.length === 0) return;
|
|
95
|
+
const emitId = `${phase}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
96
|
+
this.pendingHooks.add(emitId);
|
|
97
|
+
const startTime = performance.now();
|
|
98
|
+
try {
|
|
99
|
+
for (const hook of handlers) {
|
|
100
|
+
await this._resolveDepends(hook.depends, phase);
|
|
101
|
+
const hookStartTime = performance.now();
|
|
102
|
+
let hookResult, hookError;
|
|
103
|
+
try {
|
|
104
|
+
hookResult = hook.handler(...args);
|
|
105
|
+
if (hookResult instanceof Promise) {
|
|
106
|
+
await hookResult;
|
|
107
|
+
}
|
|
108
|
+
hook.resolved = true;
|
|
109
|
+
} catch (error) {
|
|
110
|
+
hookError = error;
|
|
111
|
+
console.error(`[KupolaLifecycle] Error in ${phase} hook "${hook.name}":`, error);
|
|
112
|
+
if (phase !== "error") {
|
|
113
|
+
await this._handleError({ phase, hook: hook.name, error, args });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const hookDuration = performance.now() - hookStartTime;
|
|
117
|
+
this.trace.push({
|
|
118
|
+
emitId,
|
|
119
|
+
phase,
|
|
120
|
+
hookName: hook.name,
|
|
121
|
+
duration: hookDuration,
|
|
122
|
+
status: hookError ? "error" : "success",
|
|
123
|
+
error: hookError ? hookError.message : null,
|
|
124
|
+
timestamp: Date.now()
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
const totalDuration = performance.now() - startTime;
|
|
128
|
+
console.debug(`[KupolaLifecycle] ${phase} completed in ${totalDuration.toFixed(2)}ms (${this.scope})`);
|
|
129
|
+
} finally {
|
|
130
|
+
this.pendingHooks.delete(emitId);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async runPhase(phase, ...args) {
|
|
134
|
+
if (!this.basePhases.includes(phase)) {
|
|
135
|
+
throw new Error(`Unknown base phase: ${phase}`);
|
|
136
|
+
}
|
|
137
|
+
const stateMap = this.phaseStateMap[phase];
|
|
138
|
+
if (stateMap) {
|
|
139
|
+
if (Array.isArray(stateMap.from)) {
|
|
140
|
+
if (!stateMap.from.includes(this.state)) {
|
|
141
|
+
throw new Error(`Cannot ${phase} from state ${this.state}, expected one of: ${stateMap.from.join(", ")}`);
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
if (this.state !== stateMap.from) {
|
|
145
|
+
throw new Error(`Cannot ${phase} from state ${this.state}, expected ${stateMap.from}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const beforePhase = `before${phase.charAt(0).toUpperCase() + phase.slice(1)}`;
|
|
150
|
+
const afterPhase = `after${phase.charAt(0).toUpperCase() + phase.slice(1)}`;
|
|
151
|
+
this._resetResolved(beforePhase);
|
|
152
|
+
this._resetResolved(phase);
|
|
153
|
+
this._resetResolved(afterPhase);
|
|
154
|
+
if (this.allPhases.includes(beforePhase)) {
|
|
155
|
+
await this.emit(beforePhase, ...args);
|
|
156
|
+
}
|
|
157
|
+
await this.emit(phase, ...args);
|
|
158
|
+
if (stateMap) {
|
|
159
|
+
this._updateState(stateMap.to);
|
|
160
|
+
}
|
|
161
|
+
if (this.allPhases.includes(afterPhase)) {
|
|
162
|
+
await this.emit(afterPhase, ...args);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
async bootstrap(...args) {
|
|
166
|
+
await this.runPhase("bootstrap", ...args);
|
|
167
|
+
}
|
|
168
|
+
async _waitForDOMReady() {
|
|
169
|
+
return new Promise((resolve) => {
|
|
170
|
+
if (document.readyState === "complete" || document.readyState === "interactive") {
|
|
171
|
+
resolve();
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const onReady = () => {
|
|
175
|
+
document.removeEventListener("DOMContentLoaded", onReady);
|
|
176
|
+
window.removeEventListener("load", onReady);
|
|
177
|
+
resolve();
|
|
178
|
+
};
|
|
179
|
+
document.addEventListener("DOMContentLoaded", onReady);
|
|
180
|
+
window.addEventListener("load", onReady);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
async mount(...args) {
|
|
184
|
+
await this.runPhase("mount", ...args);
|
|
185
|
+
}
|
|
186
|
+
async mountWithDOMReady(...args) {
|
|
187
|
+
await this._waitForDOMReady();
|
|
188
|
+
await this.runPhase("mount", ...args);
|
|
189
|
+
}
|
|
190
|
+
async update(...args) {
|
|
191
|
+
await this.runPhase("update", ...args);
|
|
192
|
+
}
|
|
193
|
+
async unmount(...args) {
|
|
194
|
+
await this.runPhase("unmount", ...args);
|
|
195
|
+
}
|
|
196
|
+
async destroy(...args) {
|
|
197
|
+
await this.runPhase("destroy", ...args);
|
|
198
|
+
this.hooks.forEach((handlers) => {
|
|
199
|
+
handlers.length = 0;
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
getPhaseHandlers(phase) {
|
|
203
|
+
return this.hooks.get(phase) || [];
|
|
204
|
+
}
|
|
205
|
+
hasHandlers(phase) {
|
|
206
|
+
const handlers = this.hooks.get(phase);
|
|
207
|
+
return handlers && handlers.length > 0;
|
|
208
|
+
}
|
|
209
|
+
getTrace() {
|
|
210
|
+
return [...this.trace];
|
|
211
|
+
}
|
|
212
|
+
clearTrace() {
|
|
213
|
+
this.trace = [];
|
|
214
|
+
}
|
|
215
|
+
getState() {
|
|
216
|
+
return this.state;
|
|
217
|
+
}
|
|
218
|
+
getStateHistory() {
|
|
219
|
+
return [...this.stateHistory];
|
|
220
|
+
}
|
|
221
|
+
isInState(targetState) {
|
|
222
|
+
return this.state === targetState;
|
|
223
|
+
}
|
|
224
|
+
onError(handler) {
|
|
225
|
+
this._onErrorCallback = handler;
|
|
226
|
+
return this.on("error", handler);
|
|
227
|
+
}
|
|
228
|
+
setErrorBoundary(boundary) {
|
|
229
|
+
this.errorBoundary = boundary;
|
|
230
|
+
return this.on("errorBoundary", (errorInfo) => {
|
|
231
|
+
if (typeof boundary === "function") {
|
|
232
|
+
return boundary(errorInfo);
|
|
233
|
+
}
|
|
234
|
+
return null;
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
setMaxErrors(max2) {
|
|
238
|
+
this.maxErrors = max2;
|
|
239
|
+
}
|
|
240
|
+
getErrorCount() {
|
|
241
|
+
return this.errorCount;
|
|
242
|
+
}
|
|
243
|
+
getLastError() {
|
|
244
|
+
return this.lastError;
|
|
245
|
+
}
|
|
246
|
+
resetErrorCount() {
|
|
247
|
+
this.errorCount = 0;
|
|
248
|
+
this.lastError = null;
|
|
249
|
+
}
|
|
250
|
+
async _handleError(errorInfo) {
|
|
251
|
+
this.errorCount++;
|
|
252
|
+
this.lastError = errorInfo.error;
|
|
253
|
+
if (this.errorCount >= this.maxErrors) {
|
|
254
|
+
console.error(`[KupolaLifecycle] Error limit reached (${this.maxErrors}), stopping error handling`);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
await this.emit("error", errorInfo);
|
|
258
|
+
if (typeof this._onErrorCallback === "function") {
|
|
259
|
+
try {
|
|
260
|
+
await this._onErrorCallback(errorInfo);
|
|
261
|
+
} catch (e) {
|
|
262
|
+
console.error("[KupolaLifecycle] Error in onError callback:", e);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
const boundaryHandlers = this.hooks.get("errorBoundary");
|
|
266
|
+
if (boundaryHandlers && boundaryHandlers.length > 0) {
|
|
267
|
+
for (const hook of boundaryHandlers) {
|
|
268
|
+
try {
|
|
269
|
+
const result = hook.handler(errorInfo);
|
|
270
|
+
if (result instanceof Promise) {
|
|
271
|
+
await result;
|
|
272
|
+
}
|
|
273
|
+
if (result === "handled") {
|
|
274
|
+
console.debug(`[KupolaLifecycle] Error handled by errorBoundary hook "${hook.name}"`);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
} catch (e) {
|
|
278
|
+
console.error(`[KupolaLifecycle] Error in errorBoundary hook "${hook.name}":`, e);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
console.error(`[KupolaLifecycle] Unhandled error in ${errorInfo.phase}:`, errorInfo.error);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const kupolaLifecycle = new KupolaLifecycle("app");
|
|
286
|
+
function createLifecycle(scope = "app") {
|
|
287
|
+
return new KupolaLifecycle(scope);
|
|
288
|
+
}
|
|
289
|
+
const _UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
290
|
+
function _isUnsafeKey(key) {
|
|
291
|
+
return _UNSAFE_KEYS.has(key);
|
|
292
|
+
}
|
|
293
|
+
function trim(str) {
|
|
294
|
+
return str ? str.trim() : "";
|
|
295
|
+
}
|
|
296
|
+
function trimLeft(str) {
|
|
297
|
+
return str ? str.replace(/^\s+/, "") : "";
|
|
298
|
+
}
|
|
299
|
+
function trimRight(str) {
|
|
300
|
+
return str ? str.replace(/\s+$/, "") : "";
|
|
301
|
+
}
|
|
302
|
+
function toUpperCase(str) {
|
|
303
|
+
return str ? str.toUpperCase() : "";
|
|
304
|
+
}
|
|
305
|
+
function toLowerCase(str) {
|
|
306
|
+
return str ? str.toLowerCase() : "";
|
|
307
|
+
}
|
|
308
|
+
function capitalize(str) {
|
|
309
|
+
if (!str) return "";
|
|
310
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
311
|
+
}
|
|
312
|
+
function camelize(str) {
|
|
313
|
+
if (!str) return "";
|
|
314
|
+
return str.replace(/-(\w)/g, (_, c) => c ? c.toUpperCase() : "");
|
|
315
|
+
}
|
|
316
|
+
function hyphenate(str) {
|
|
317
|
+
if (!str) return "";
|
|
318
|
+
return str.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "");
|
|
319
|
+
}
|
|
320
|
+
function padStart(str, length, padChar = " ") {
|
|
321
|
+
return (String(str) || "").padStart(length, padChar);
|
|
322
|
+
}
|
|
323
|
+
function padEnd(str, length, padChar = " ") {
|
|
324
|
+
return (String(str) || "").padEnd(length, padChar);
|
|
325
|
+
}
|
|
326
|
+
function truncate(str, maxLength2, suffix = "...") {
|
|
327
|
+
if (!str || str.length <= maxLength2) return str || "";
|
|
328
|
+
return str.slice(0, maxLength2) + suffix;
|
|
329
|
+
}
|
|
330
|
+
function replaceAll(str, search, replacement) {
|
|
331
|
+
if (!str) return "";
|
|
332
|
+
return str.split(search).join(replacement);
|
|
333
|
+
}
|
|
334
|
+
function format(template, data) {
|
|
335
|
+
if (!template) return "";
|
|
336
|
+
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => data[key] !== void 0 ? data[key] : `{{${key}}}`);
|
|
337
|
+
}
|
|
338
|
+
function startsWith(str, prefix) {
|
|
339
|
+
return (str || "").startsWith(prefix);
|
|
340
|
+
}
|
|
341
|
+
function endsWith(str, suffix) {
|
|
342
|
+
return (str || "").endsWith(suffix);
|
|
343
|
+
}
|
|
344
|
+
function includesStr(str, search) {
|
|
345
|
+
return (str || "").includes(search);
|
|
346
|
+
}
|
|
347
|
+
function repeat(str, times) {
|
|
348
|
+
return (str || "").repeat(times);
|
|
349
|
+
}
|
|
350
|
+
function reverse(str) {
|
|
351
|
+
return (str || "").split("").reverse().join("");
|
|
352
|
+
}
|
|
353
|
+
function countOccurrences(str, search) {
|
|
354
|
+
if (!str || !search) return 0;
|
|
355
|
+
return str.split(search).length - 1;
|
|
356
|
+
}
|
|
357
|
+
function escapeHtml$1(str) {
|
|
358
|
+
if (!str) return "";
|
|
359
|
+
const div = document.createElement("div");
|
|
360
|
+
div.textContent = str;
|
|
361
|
+
return div.innerHTML;
|
|
362
|
+
}
|
|
363
|
+
function unescapeHtml(str) {
|
|
364
|
+
if (!str) return "";
|
|
365
|
+
const div = document.createElement("div");
|
|
366
|
+
div.innerHTML = str;
|
|
367
|
+
return div.textContent;
|
|
368
|
+
}
|
|
369
|
+
function generateRandom(length = 8) {
|
|
370
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
371
|
+
let result = "";
|
|
372
|
+
for (let i = 0; i < length; i++) {
|
|
373
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
374
|
+
}
|
|
375
|
+
return result;
|
|
376
|
+
}
|
|
377
|
+
function generateUUID() {
|
|
378
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
379
|
+
const r = Math.random() * 16 | 0;
|
|
380
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
381
|
+
return v.toString(16);
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
const stringUtils = { trim, trimLeft, trimRight, toUpperCase, toLowerCase, capitalize, camelize, hyphenate, padStart, padEnd, truncate, replaceAll, format, startsWith, endsWith, includes: includesStr, repeat, reverse, countOccurrences, escapeHtml: escapeHtml$1, unescapeHtml, generateRandom, generateUUID };
|
|
385
|
+
function isArray(arr) {
|
|
386
|
+
return Array.isArray(arr);
|
|
387
|
+
}
|
|
388
|
+
function isEmpty(arr) {
|
|
389
|
+
return !arr || arr.length === 0;
|
|
390
|
+
}
|
|
391
|
+
function size(arr) {
|
|
392
|
+
return arr ? arr.length : 0;
|
|
393
|
+
}
|
|
394
|
+
function first(arr, defaultValue) {
|
|
395
|
+
return arr && arr.length > 0 ? arr[0] : defaultValue;
|
|
396
|
+
}
|
|
397
|
+
function last(arr, defaultValue) {
|
|
398
|
+
return arr && arr.length > 0 ? arr[arr.length - 1] : defaultValue;
|
|
399
|
+
}
|
|
400
|
+
function get(arr, index, defaultValue) {
|
|
401
|
+
return arr && arr[index] !== void 0 ? arr[index] : defaultValue;
|
|
402
|
+
}
|
|
403
|
+
function slice(arr, start, end) {
|
|
404
|
+
return arr ? arr.slice(start, end) : [];
|
|
405
|
+
}
|
|
406
|
+
function concat(...args) {
|
|
407
|
+
return args.reduce((result, arg) => result.concat(arg || []), []);
|
|
408
|
+
}
|
|
409
|
+
function join(arr, separator = ",") {
|
|
410
|
+
return arr ? arr.join(separator) : "";
|
|
411
|
+
}
|
|
412
|
+
function indexOf(arr, item, fromIndex = 0) {
|
|
413
|
+
if (!arr) return -1;
|
|
414
|
+
if (Number.isNaN(item)) {
|
|
415
|
+
for (let i = fromIndex; i < arr.length; i++) {
|
|
416
|
+
if (Number.isNaN(arr[i])) return i;
|
|
417
|
+
}
|
|
418
|
+
return -1;
|
|
419
|
+
}
|
|
420
|
+
return arr.indexOf(item, fromIndex);
|
|
421
|
+
}
|
|
422
|
+
function lastIndexOf(arr, item, fromIndex) {
|
|
423
|
+
if (!arr) return -1;
|
|
424
|
+
if (Number.isNaN(item)) {
|
|
425
|
+
const start = fromIndex !== void 0 ? fromIndex : arr.length - 1;
|
|
426
|
+
for (let i = start; i >= 0; i--) {
|
|
427
|
+
if (Number.isNaN(arr[i])) return i;
|
|
428
|
+
}
|
|
429
|
+
return -1;
|
|
430
|
+
}
|
|
431
|
+
return arr.lastIndexOf(item, fromIndex);
|
|
432
|
+
}
|
|
433
|
+
function includes(arr, item) {
|
|
434
|
+
return arr ? arr.includes(item) : false;
|
|
435
|
+
}
|
|
436
|
+
function push(arr, ...items) {
|
|
437
|
+
if (arr) arr.push(...items);
|
|
438
|
+
return arr;
|
|
439
|
+
}
|
|
440
|
+
function pop(arr) {
|
|
441
|
+
return arr ? arr.pop() : void 0;
|
|
442
|
+
}
|
|
443
|
+
function shift(arr) {
|
|
444
|
+
return arr ? arr.shift() : void 0;
|
|
445
|
+
}
|
|
446
|
+
function unshift(arr, ...items) {
|
|
447
|
+
if (arr) arr.unshift(...items);
|
|
448
|
+
return arr;
|
|
449
|
+
}
|
|
450
|
+
function remove(arr, item) {
|
|
451
|
+
if (!arr) return arr;
|
|
452
|
+
const index = Number.isNaN(item) ? arr.findIndex((v) => Number.isNaN(v)) : arr.indexOf(item);
|
|
453
|
+
if (index > -1) arr.splice(index, 1);
|
|
454
|
+
return arr;
|
|
455
|
+
}
|
|
456
|
+
function removeAt(arr, index) {
|
|
457
|
+
if (!arr || index < 0 || index >= arr.length) return arr;
|
|
458
|
+
arr.splice(index, 1);
|
|
459
|
+
return arr;
|
|
460
|
+
}
|
|
461
|
+
function insert(arr, index, item) {
|
|
462
|
+
if (!arr) return arr;
|
|
463
|
+
arr.splice(index, 0, item);
|
|
464
|
+
return arr;
|
|
465
|
+
}
|
|
466
|
+
function reverseArr(arr) {
|
|
467
|
+
return arr ? arr.slice().reverse() : [];
|
|
468
|
+
}
|
|
469
|
+
function sort(arr, compareFn) {
|
|
470
|
+
return arr ? arr.slice().sort(compareFn) : [];
|
|
471
|
+
}
|
|
472
|
+
function sortBy(arr, key, order = "asc") {
|
|
473
|
+
if (!arr) return [];
|
|
474
|
+
return arr.slice().sort((a, b) => {
|
|
475
|
+
const valA = typeof a === "object" ? a[key] : a;
|
|
476
|
+
const valB = typeof b === "object" ? b[key] : b;
|
|
477
|
+
if (valA < valB) return order === "asc" ? -1 : 1;
|
|
478
|
+
if (valA > valB) return order === "asc" ? 1 : -1;
|
|
479
|
+
return 0;
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
function filter(arr, predicate) {
|
|
483
|
+
return arr ? arr.filter(predicate) : [];
|
|
484
|
+
}
|
|
485
|
+
function map(arr, fn) {
|
|
486
|
+
return arr ? arr.map(fn) : [];
|
|
487
|
+
}
|
|
488
|
+
function reduce(arr, fn, initialValue) {
|
|
489
|
+
return arr ? arr.reduce(fn, initialValue) : initialValue;
|
|
490
|
+
}
|
|
491
|
+
function forEach(arr, fn) {
|
|
492
|
+
if (arr) arr.forEach(fn);
|
|
493
|
+
}
|
|
494
|
+
function every(arr, predicate) {
|
|
495
|
+
return arr ? arr.every(predicate) : true;
|
|
496
|
+
}
|
|
497
|
+
function some(arr, predicate) {
|
|
498
|
+
return arr ? arr.some(predicate) : false;
|
|
499
|
+
}
|
|
500
|
+
function find(arr, predicate) {
|
|
501
|
+
return arr ? arr.find(predicate) : void 0;
|
|
502
|
+
}
|
|
503
|
+
function findIndex(arr, predicate) {
|
|
504
|
+
return arr ? arr.findIndex(predicate) : -1;
|
|
505
|
+
}
|
|
506
|
+
function flat(arr, depth = 1) {
|
|
507
|
+
return arr ? arr.flat(depth) : [];
|
|
508
|
+
}
|
|
509
|
+
function flattenDeep(arr) {
|
|
510
|
+
return arr ? arr.reduce((acc, item) => Array.isArray(item) ? acc.concat(flattenDeep(item)) : acc.concat(item), []) : [];
|
|
511
|
+
}
|
|
512
|
+
function unique(arr) {
|
|
513
|
+
return arr ? [...new Set(arr)] : [];
|
|
514
|
+
}
|
|
515
|
+
function uniqueBy(arr, key) {
|
|
516
|
+
if (!arr) return [];
|
|
517
|
+
const seen = /* @__PURE__ */ new Set();
|
|
518
|
+
return arr.filter((item) => {
|
|
519
|
+
const value = typeof item === "object" ? item[key] : item;
|
|
520
|
+
if (seen.has(value)) return false;
|
|
521
|
+
seen.add(value);
|
|
522
|
+
return true;
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
function chunk(arr, chunkSize) {
|
|
526
|
+
if (!arr || chunkSize <= 0) return [];
|
|
527
|
+
const chunks = [];
|
|
528
|
+
for (let i = 0; i < arr.length; i += chunkSize) {
|
|
529
|
+
chunks.push(arr.slice(i, i + chunkSize));
|
|
530
|
+
}
|
|
531
|
+
return chunks;
|
|
532
|
+
}
|
|
533
|
+
function shuffle(arr) {
|
|
534
|
+
if (!arr) return [];
|
|
535
|
+
const result = arr.slice();
|
|
536
|
+
for (let i = result.length - 1; i > 0; i--) {
|
|
537
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
538
|
+
[result[i], result[j]] = [result[j], result[i]];
|
|
539
|
+
}
|
|
540
|
+
return result;
|
|
541
|
+
}
|
|
542
|
+
function sum(arr) {
|
|
543
|
+
return arr ? arr.reduce((acc, val) => acc + (Number(val) || 0), 0) : 0;
|
|
544
|
+
}
|
|
545
|
+
function average(arr) {
|
|
546
|
+
if (!arr || arr.length === 0) return 0;
|
|
547
|
+
return sum(arr) / arr.length;
|
|
548
|
+
}
|
|
549
|
+
function max(arr) {
|
|
550
|
+
return arr && arr.length > 0 ? Math.max(...arr) : -Infinity;
|
|
551
|
+
}
|
|
552
|
+
function min(arr) {
|
|
553
|
+
return arr && arr.length > 0 ? Math.min(...arr) : Infinity;
|
|
554
|
+
}
|
|
555
|
+
function intersection(...args) {
|
|
556
|
+
if (args.length === 0) return [];
|
|
557
|
+
return args.reduce((result, a) => result.filter((item) => a && a.includes(item)));
|
|
558
|
+
}
|
|
559
|
+
function union(...args) {
|
|
560
|
+
return [...new Set(args.flat().filter(Boolean))];
|
|
561
|
+
}
|
|
562
|
+
function difference(arr1, arr2) {
|
|
563
|
+
return arr1 ? arr1.filter((item) => !arr2 || !arr2.includes(item)) : [];
|
|
564
|
+
}
|
|
565
|
+
function zip(...args) {
|
|
566
|
+
if (args.length === 0) return [];
|
|
567
|
+
const maxLength2 = Math.max(...args.map((a) => a ? a.length : 0));
|
|
568
|
+
return Array.from({ length: maxLength2 }, (_, i) => args.map((a) => a && a[i]));
|
|
569
|
+
}
|
|
570
|
+
const arrayUtils = { isArray, isEmpty, size, first, last, get, slice, concat, join, indexOf, lastIndexOf, includes, push, pop, shift, unshift, remove, removeAt, insert, reverse: reverseArr, sort, sortBy, filter, map, reduce, forEach, every, some, find, findIndex, flat, flattenDeep, unique, uniqueBy, chunk, shuffle, sum, average, max, min, intersection, union, difference, zip };
|
|
571
|
+
function isObject(obj) {
|
|
572
|
+
return obj !== null && typeof obj === "object" && !Array.isArray(obj);
|
|
573
|
+
}
|
|
574
|
+
function isEmptyObj(obj) {
|
|
575
|
+
if (!obj || typeof obj !== "object") return true;
|
|
576
|
+
return Object.keys(obj).length === 0;
|
|
577
|
+
}
|
|
578
|
+
function keys(obj) {
|
|
579
|
+
return obj ? Object.keys(obj) : [];
|
|
580
|
+
}
|
|
581
|
+
function values(obj) {
|
|
582
|
+
return obj ? Object.values(obj) : [];
|
|
583
|
+
}
|
|
584
|
+
function entries(obj) {
|
|
585
|
+
return obj ? Object.entries(obj) : [];
|
|
586
|
+
}
|
|
587
|
+
function has(obj, key) {
|
|
588
|
+
return obj ? Object.prototype.hasOwnProperty.call(obj, key) : false;
|
|
589
|
+
}
|
|
590
|
+
function getObj(obj, path, defaultValue) {
|
|
591
|
+
if (!obj) return defaultValue;
|
|
592
|
+
const k = path.split(".");
|
|
593
|
+
if (k.some(_isUnsafeKey)) return defaultValue;
|
|
594
|
+
return k.reduce((current, key) => current && current[key], obj) ?? defaultValue;
|
|
595
|
+
}
|
|
596
|
+
function setObj(obj, path, value) {
|
|
597
|
+
if (!obj || typeof obj !== "object") return obj;
|
|
598
|
+
const k = path.split(".");
|
|
599
|
+
if (k.some(_isUnsafeKey)) return obj;
|
|
600
|
+
const lastKey = k.pop();
|
|
601
|
+
let current = obj;
|
|
602
|
+
k.forEach((key) => {
|
|
603
|
+
if (!current[key] || typeof current[key] !== "object") current[key] = {};
|
|
604
|
+
current = current[key];
|
|
605
|
+
});
|
|
606
|
+
current[lastKey] = value;
|
|
607
|
+
return obj;
|
|
608
|
+
}
|
|
609
|
+
function pick(obj, keys2) {
|
|
610
|
+
if (!obj) return {};
|
|
611
|
+
return keys2.reduce((result, key) => {
|
|
612
|
+
if (obj[key] !== void 0) result[key] = obj[key];
|
|
613
|
+
return result;
|
|
614
|
+
}, {});
|
|
615
|
+
}
|
|
616
|
+
function omit(obj, keys2) {
|
|
617
|
+
if (!obj) return {};
|
|
618
|
+
return Object.keys(obj).reduce((result, key) => {
|
|
619
|
+
if (!keys2.includes(key)) result[key] = obj[key];
|
|
620
|
+
return result;
|
|
621
|
+
}, {});
|
|
622
|
+
}
|
|
623
|
+
function merge(...args) {
|
|
624
|
+
return args.reduce((result, obj) => {
|
|
625
|
+
if (obj && typeof obj === "object") {
|
|
626
|
+
Object.keys(obj).forEach((key) => {
|
|
627
|
+
if (_isUnsafeKey(key)) return;
|
|
628
|
+
if (isObject(obj[key]) && isObject(result[key])) {
|
|
629
|
+
result[key] = merge(result[key], obj[key]);
|
|
630
|
+
} else {
|
|
631
|
+
result[key] = obj[key];
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
return result;
|
|
636
|
+
}, {});
|
|
637
|
+
}
|
|
638
|
+
function clone(obj) {
|
|
639
|
+
return obj ? JSON.parse(JSON.stringify(obj)) : obj;
|
|
640
|
+
}
|
|
641
|
+
function deepClone(obj, hash = /* @__PURE__ */ new WeakMap()) {
|
|
642
|
+
if (!obj || typeof obj !== "object") return obj;
|
|
643
|
+
if (hash.has(obj)) return hash.get(obj);
|
|
644
|
+
if (obj instanceof Date) return new Date(obj);
|
|
645
|
+
if (obj instanceof RegExp) return new RegExp(obj);
|
|
646
|
+
if (obj instanceof Map) {
|
|
647
|
+
const m = /* @__PURE__ */ new Map();
|
|
648
|
+
hash.set(obj, m);
|
|
649
|
+
obj.forEach((val, key) => m.set(key, deepClone(val, hash)));
|
|
650
|
+
return m;
|
|
651
|
+
}
|
|
652
|
+
if (obj instanceof Set) {
|
|
653
|
+
const s = /* @__PURE__ */ new Set();
|
|
654
|
+
hash.set(obj, s);
|
|
655
|
+
obj.forEach((val) => s.add(deepClone(val, hash)));
|
|
656
|
+
return s;
|
|
657
|
+
}
|
|
658
|
+
if (Array.isArray(obj)) {
|
|
659
|
+
const a = [];
|
|
660
|
+
hash.set(obj, a);
|
|
661
|
+
obj.forEach((item) => a.push(deepClone(item, hash)));
|
|
662
|
+
return a;
|
|
663
|
+
}
|
|
664
|
+
const c = {};
|
|
665
|
+
hash.set(obj, c);
|
|
666
|
+
Object.keys(obj).forEach((key) => {
|
|
667
|
+
if (!_isUnsafeKey(key)) {
|
|
668
|
+
c[key] = deepClone(obj[key], hash);
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
return c;
|
|
672
|
+
}
|
|
673
|
+
function forEachObj(obj, fn) {
|
|
674
|
+
if (!obj) return;
|
|
675
|
+
Object.keys(obj).forEach((key) => fn(obj[key], key, obj));
|
|
676
|
+
}
|
|
677
|
+
function mapObj(obj, fn) {
|
|
678
|
+
if (!obj) return {};
|
|
679
|
+
const result = {};
|
|
680
|
+
Object.keys(obj).forEach((key) => {
|
|
681
|
+
result[key] = fn(obj[key], key, obj);
|
|
682
|
+
});
|
|
683
|
+
return result;
|
|
684
|
+
}
|
|
685
|
+
function filterObj(obj, fn) {
|
|
686
|
+
if (!obj) return {};
|
|
687
|
+
const result = {};
|
|
688
|
+
Object.keys(obj).forEach((key) => {
|
|
689
|
+
if (fn(obj[key], key, obj)) result[key] = obj[key];
|
|
690
|
+
});
|
|
691
|
+
return result;
|
|
692
|
+
}
|
|
693
|
+
function reduceObj(obj, fn, initialValue) {
|
|
694
|
+
if (!obj) return initialValue;
|
|
695
|
+
return Object.keys(obj).reduce((acc, key) => fn(acc, obj[key], key, obj), initialValue);
|
|
696
|
+
}
|
|
697
|
+
function toArray(obj) {
|
|
698
|
+
if (!obj) return [];
|
|
699
|
+
return Object.keys(obj).map((key) => ({ key, value: obj[key] }));
|
|
700
|
+
}
|
|
701
|
+
function fromArray(arr, keyField, valueField) {
|
|
702
|
+
if (!arr) return {};
|
|
703
|
+
return arr.reduce((result, item) => {
|
|
704
|
+
const key = typeof item === "object" ? item[keyField] : item;
|
|
705
|
+
const value = valueField ? item[valueField] : item;
|
|
706
|
+
if (key !== void 0) result[key] = value;
|
|
707
|
+
return result;
|
|
708
|
+
}, {});
|
|
709
|
+
}
|
|
710
|
+
function sizeObj(obj) {
|
|
711
|
+
return obj ? Object.keys(obj).length : 0;
|
|
712
|
+
}
|
|
713
|
+
function invert(obj) {
|
|
714
|
+
if (!obj) return {};
|
|
715
|
+
const result = {};
|
|
716
|
+
Object.keys(obj).forEach((key) => {
|
|
717
|
+
result[obj[key]] = key;
|
|
718
|
+
});
|
|
719
|
+
return result;
|
|
720
|
+
}
|
|
721
|
+
function isEqual(obj1, obj2) {
|
|
722
|
+
if (obj1 === obj2) return true;
|
|
723
|
+
if (!obj1 || !obj2 || typeof obj1 !== "object" || typeof obj2 !== "object") return false;
|
|
724
|
+
const keys1 = Object.keys(obj1);
|
|
725
|
+
const keys2 = Object.keys(obj2);
|
|
726
|
+
if (keys1.length !== keys2.length) return false;
|
|
727
|
+
return keys1.every((key) => isEqual(obj1[key], obj2[key]));
|
|
728
|
+
}
|
|
729
|
+
function freeze(obj) {
|
|
730
|
+
if (!obj) return obj;
|
|
731
|
+
Object.freeze(obj);
|
|
732
|
+
Object.keys(obj).forEach((key) => {
|
|
733
|
+
if (typeof obj[key] === "object") freeze(obj[key]);
|
|
734
|
+
});
|
|
735
|
+
return obj;
|
|
736
|
+
}
|
|
737
|
+
function seal(obj) {
|
|
738
|
+
return obj ? Object.seal(obj) : obj;
|
|
739
|
+
}
|
|
740
|
+
const objectUtils = { isObject, isEmpty: isEmptyObj, keys, values, entries, has, get: getObj, set: setObj, pick, omit, merge, clone, deepClone, forEach: forEachObj, map: mapObj, filter: filterObj, reduce: reduceObj, toArray, fromArray, size: sizeObj, invert, isEqual, freeze, seal };
|
|
741
|
+
function isNumber(val) {
|
|
742
|
+
return typeof val === "number" && !isNaN(val);
|
|
743
|
+
}
|
|
744
|
+
function isInteger(val) {
|
|
745
|
+
return Number.isInteger(val);
|
|
746
|
+
}
|
|
747
|
+
function isFloat(val) {
|
|
748
|
+
return isNumber(val) && !Number.isInteger(val);
|
|
749
|
+
}
|
|
750
|
+
function isPositive(val) {
|
|
751
|
+
return isNumber(val) && val > 0;
|
|
752
|
+
}
|
|
753
|
+
function isNegative(val) {
|
|
754
|
+
return isNumber(val) && val < 0;
|
|
755
|
+
}
|
|
756
|
+
function isZero(val) {
|
|
757
|
+
return isNumber(val) && val === 0;
|
|
758
|
+
}
|
|
759
|
+
function clamp(val, minVal, maxVal) {
|
|
760
|
+
if (!isNumber(val)) return val;
|
|
761
|
+
return Math.min(Math.max(val, minVal), maxVal);
|
|
762
|
+
}
|
|
763
|
+
function round(val, precision = 0) {
|
|
764
|
+
if (!isNumber(val)) return val;
|
|
765
|
+
const factor = Math.pow(10, precision);
|
|
766
|
+
return Math.round(val * factor) / factor;
|
|
767
|
+
}
|
|
768
|
+
function floor(val) {
|
|
769
|
+
return isNumber(val) ? Math.floor(val) : val;
|
|
770
|
+
}
|
|
771
|
+
function ceil(val) {
|
|
772
|
+
return isNumber(val) ? Math.ceil(val) : val;
|
|
773
|
+
}
|
|
774
|
+
function abs(val) {
|
|
775
|
+
return isNumber(val) ? Math.abs(val) : val;
|
|
776
|
+
}
|
|
777
|
+
function minNum(...args) {
|
|
778
|
+
const nums = args.filter(isNumber);
|
|
779
|
+
return nums.length > 0 ? Math.min(...nums) : void 0;
|
|
780
|
+
}
|
|
781
|
+
function maxNum(...args) {
|
|
782
|
+
const nums = args.filter(isNumber);
|
|
783
|
+
return nums.length > 0 ? Math.max(...nums) : void 0;
|
|
784
|
+
}
|
|
785
|
+
function sumNum(...args) {
|
|
786
|
+
const nums = args.flat().filter(isNumber);
|
|
787
|
+
return nums.reduce((acc, val) => acc + val, 0);
|
|
788
|
+
}
|
|
789
|
+
function averageNum(...args) {
|
|
790
|
+
const nums = args.flat().filter(isNumber);
|
|
791
|
+
return nums.length > 0 ? sumNum(nums) / nums.length : 0;
|
|
792
|
+
}
|
|
793
|
+
function random(minVal = 0, maxVal = 1) {
|
|
794
|
+
return Math.random() * (maxVal - minVal) + minVal;
|
|
795
|
+
}
|
|
796
|
+
function randomInt(minVal, maxVal) {
|
|
797
|
+
return Math.floor(random(minVal, maxVal + 1));
|
|
798
|
+
}
|
|
799
|
+
function formatNum(val, decimals = 2) {
|
|
800
|
+
if (!isNumber(val)) return String(val);
|
|
801
|
+
return val.toFixed(decimals);
|
|
802
|
+
}
|
|
803
|
+
function formatCurrency$1(val, currency = "CNY", decimals = 2) {
|
|
804
|
+
if (!isNumber(val)) return String(val);
|
|
805
|
+
const formatter = new Intl.NumberFormat("zh-CN", { style: "currency", currency, minimumFractionDigits: decimals, maximumFractionDigits: decimals });
|
|
806
|
+
return formatter.format(val);
|
|
807
|
+
}
|
|
808
|
+
function formatPercent(val, decimals = 0) {
|
|
809
|
+
if (!isNumber(val)) return String(val);
|
|
810
|
+
return `${(val * 100).toFixed(decimals)}%`;
|
|
811
|
+
}
|
|
812
|
+
function toFixed(val, decimals = 0) {
|
|
813
|
+
if (!isNumber(val)) return String(val);
|
|
814
|
+
return val.toFixed(decimals);
|
|
815
|
+
}
|
|
816
|
+
function toPrecision(val, precision = 6) {
|
|
817
|
+
if (!isNumber(val)) return String(val);
|
|
818
|
+
return val.toPrecision(precision);
|
|
819
|
+
}
|
|
820
|
+
function isNaNVal(val) {
|
|
821
|
+
return Number.isNaN(val);
|
|
822
|
+
}
|
|
823
|
+
function isFiniteVal(val) {
|
|
824
|
+
return Number.isFinite(val);
|
|
825
|
+
}
|
|
826
|
+
function parseIntSafe(val, radix = 10) {
|
|
827
|
+
return Number.parseInt(val, radix);
|
|
828
|
+
}
|
|
829
|
+
function parseFloatSafe(val) {
|
|
830
|
+
return Number.parseFloat(val);
|
|
831
|
+
}
|
|
832
|
+
function toNumber(val, defaultValue = 0) {
|
|
833
|
+
const num = Number(val);
|
|
834
|
+
return isNaN(num) ? defaultValue : num;
|
|
835
|
+
}
|
|
836
|
+
function safeDivide(a, b, defaultValue = 0) {
|
|
837
|
+
if (!isNumber(a) || !isNumber(b) || b === 0) return defaultValue;
|
|
838
|
+
return a / b;
|
|
839
|
+
}
|
|
840
|
+
function safeMultiply(...args) {
|
|
841
|
+
return args.reduce((result, val) => {
|
|
842
|
+
if (!isNumber(result) || !isNumber(val)) return 0;
|
|
843
|
+
return result * val;
|
|
844
|
+
}, 1);
|
|
845
|
+
}
|
|
846
|
+
const numberUtils = { isNumber, isInteger, isFloat, isPositive, isNegative, isZero, clamp, round, floor, ceil, abs, min: minNum, max: maxNum, sum: sumNum, average: averageNum, random, randomInt, format: formatNum, formatCurrency: formatCurrency$1, formatPercent, toFixed, toPrecision, isNaN: isNaNVal, isFinite: isFiniteVal, parseInt: parseIntSafe, parseFloat: parseFloatSafe, toNumber, safeDivide, safeMultiply };
|
|
847
|
+
function now() {
|
|
848
|
+
return Date.now();
|
|
849
|
+
}
|
|
850
|
+
function today() {
|
|
851
|
+
const d = /* @__PURE__ */ new Date();
|
|
852
|
+
d.setHours(0, 0, 0, 0);
|
|
853
|
+
return d;
|
|
854
|
+
}
|
|
855
|
+
function tomorrow() {
|
|
856
|
+
const d = today();
|
|
857
|
+
d.setDate(d.getDate() + 1);
|
|
858
|
+
return d;
|
|
859
|
+
}
|
|
860
|
+
function yesterday() {
|
|
861
|
+
const d = today();
|
|
862
|
+
d.setDate(d.getDate() - 1);
|
|
863
|
+
return d;
|
|
864
|
+
}
|
|
865
|
+
function isDate(val) {
|
|
866
|
+
return val instanceof Date && !isNaN(val.getTime());
|
|
867
|
+
}
|
|
868
|
+
function isValidDate(date) {
|
|
869
|
+
return isDate(date);
|
|
870
|
+
}
|
|
871
|
+
function parseDate(str) {
|
|
872
|
+
const d = new Date(str);
|
|
873
|
+
return isValidDate(d) ? d : null;
|
|
874
|
+
}
|
|
875
|
+
function formatDate$1(date, formatStr = "YYYY-MM-DD HH:mm:ss") {
|
|
876
|
+
if (!isDate(date)) return "";
|
|
877
|
+
const year = date.getFullYear();
|
|
878
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
879
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
880
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
881
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
882
|
+
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
883
|
+
const milliseconds = String(date.getMilliseconds()).padStart(3, "0");
|
|
884
|
+
const weekDays = ["日", "一", "二", "三", "四", "五", "六"];
|
|
885
|
+
const weekDay = weekDays[date.getDay()];
|
|
886
|
+
return formatStr.replace("YYYY", year).replace("MM", month).replace("DD", day).replace("HH", hours).replace("mm", minutes).replace("ss", seconds).replace("SSS", milliseconds).replace("D", date.getDate()).replace("M", date.getMonth() + 1).replace("H", date.getHours()).replace("m", date.getMinutes()).replace("s", date.getSeconds()).replace("W", weekDay);
|
|
887
|
+
}
|
|
888
|
+
function toISO(date) {
|
|
889
|
+
return isDate(date) ? date.toISOString() : "";
|
|
890
|
+
}
|
|
891
|
+
function toUTC(date) {
|
|
892
|
+
return isDate(date) ? new Date(date.toUTCString()) : null;
|
|
893
|
+
}
|
|
894
|
+
function addDays(date, days) {
|
|
895
|
+
if (!isDate(date)) return date;
|
|
896
|
+
const d = new Date(date);
|
|
897
|
+
d.setDate(d.getDate() + days);
|
|
898
|
+
return d;
|
|
899
|
+
}
|
|
900
|
+
function addHours(date, hours) {
|
|
901
|
+
if (!isDate(date)) return date;
|
|
902
|
+
const d = new Date(date);
|
|
903
|
+
d.setHours(d.getHours() + hours);
|
|
904
|
+
return d;
|
|
905
|
+
}
|
|
906
|
+
function addMinutes(date, minutes) {
|
|
907
|
+
if (!isDate(date)) return date;
|
|
908
|
+
const d = new Date(date);
|
|
909
|
+
d.setMinutes(d.getMinutes() + minutes);
|
|
910
|
+
return d;
|
|
911
|
+
}
|
|
912
|
+
function addSeconds(date, seconds) {
|
|
913
|
+
if (!isDate(date)) return date;
|
|
914
|
+
const d = new Date(date);
|
|
915
|
+
d.setSeconds(d.getSeconds() + seconds);
|
|
916
|
+
return d;
|
|
917
|
+
}
|
|
918
|
+
function diffDays(date1, date2) {
|
|
919
|
+
if (!isDate(date1) || !isDate(date2)) return 0;
|
|
920
|
+
const d1 = new Date(date1);
|
|
921
|
+
d1.setHours(0, 0, 0, 0);
|
|
922
|
+
const d2 = new Date(date2);
|
|
923
|
+
d2.setHours(0, 0, 0, 0);
|
|
924
|
+
return Math.floor((d1.getTime() - d2.getTime()) / (1e3 * 60 * 60 * 24));
|
|
925
|
+
}
|
|
926
|
+
function diffHours(date1, date2) {
|
|
927
|
+
if (!isDate(date1) || !isDate(date2)) return 0;
|
|
928
|
+
return Math.floor((date1.getTime() - date2.getTime()) / (1e3 * 60 * 60));
|
|
929
|
+
}
|
|
930
|
+
function diffMinutes(date1, date2) {
|
|
931
|
+
if (!isDate(date1) || !isDate(date2)) return 0;
|
|
932
|
+
return Math.floor((date1.getTime() - date2.getTime()) / (1e3 * 60));
|
|
933
|
+
}
|
|
934
|
+
function diffSeconds(date1, date2) {
|
|
935
|
+
if (!isDate(date1) || !isDate(date2)) return 0;
|
|
936
|
+
return Math.floor((date1.getTime() - date2.getTime()) / 1e3);
|
|
937
|
+
}
|
|
938
|
+
function isToday(date) {
|
|
939
|
+
if (!isDate(date)) return false;
|
|
940
|
+
return diffDays(date, today()) === 0;
|
|
941
|
+
}
|
|
942
|
+
function isYesterday(date) {
|
|
943
|
+
if (!isDate(date)) return false;
|
|
944
|
+
return diffDays(date, today()) === -1;
|
|
945
|
+
}
|
|
946
|
+
function isTomorrow(date) {
|
|
947
|
+
if (!isDate(date)) return false;
|
|
948
|
+
return diffDays(date, today()) === 1;
|
|
949
|
+
}
|
|
950
|
+
function isFuture(date) {
|
|
951
|
+
if (!isDate(date)) return false;
|
|
952
|
+
return date.getTime() > now();
|
|
953
|
+
}
|
|
954
|
+
function isPast(date) {
|
|
955
|
+
if (!isDate(date)) return false;
|
|
956
|
+
return date.getTime() < now();
|
|
957
|
+
}
|
|
958
|
+
function isLeapYear(date) {
|
|
959
|
+
if (!isDate(date)) return false;
|
|
960
|
+
const year = date.getFullYear();
|
|
961
|
+
return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
|
962
|
+
}
|
|
963
|
+
function getDaysInMonth(date) {
|
|
964
|
+
if (!isDate(date)) return 0;
|
|
965
|
+
return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
|
|
966
|
+
}
|
|
967
|
+
function getWeekOfYear(date) {
|
|
968
|
+
if (!isDate(date)) return 0;
|
|
969
|
+
const startOfYear = new Date(date.getFullYear(), 0, 1);
|
|
970
|
+
const diff = date.getTime() - startOfYear.getTime();
|
|
971
|
+
return Math.ceil(diff / (1e3 * 60 * 60 * 24 * 7));
|
|
972
|
+
}
|
|
973
|
+
function getQuarter(date) {
|
|
974
|
+
if (!isDate(date)) return 0;
|
|
975
|
+
return Math.ceil((date.getMonth() + 1) / 3);
|
|
976
|
+
}
|
|
977
|
+
function startOfDay(date) {
|
|
978
|
+
if (!isDate(date)) return date;
|
|
979
|
+
const d = new Date(date);
|
|
980
|
+
d.setHours(0, 0, 0, 0);
|
|
981
|
+
return d;
|
|
982
|
+
}
|
|
983
|
+
function endOfDay(date) {
|
|
984
|
+
if (!isDate(date)) return date;
|
|
985
|
+
const d = new Date(date);
|
|
986
|
+
d.setHours(23, 59, 59, 999);
|
|
987
|
+
return d;
|
|
988
|
+
}
|
|
989
|
+
function startOfMonth(date) {
|
|
990
|
+
if (!isDate(date)) return date;
|
|
991
|
+
return new Date(date.getFullYear(), date.getMonth(), 1);
|
|
992
|
+
}
|
|
993
|
+
function endOfMonth(date) {
|
|
994
|
+
if (!isDate(date)) return date;
|
|
995
|
+
return new Date(date.getFullYear(), date.getMonth() + 1, 0, 23, 59, 59, 999);
|
|
996
|
+
}
|
|
997
|
+
function startOfWeek(date, startDay = 1) {
|
|
998
|
+
if (!isDate(date)) return date;
|
|
999
|
+
const d = new Date(date);
|
|
1000
|
+
const day = d.getDay();
|
|
1001
|
+
const diff = day >= startDay ? day - startDay : day + (7 - startDay);
|
|
1002
|
+
d.setDate(d.getDate() - diff);
|
|
1003
|
+
d.setHours(0, 0, 0, 0);
|
|
1004
|
+
return d;
|
|
1005
|
+
}
|
|
1006
|
+
function endOfWeek(date, startDay = 1) {
|
|
1007
|
+
if (!isDate(date)) return date;
|
|
1008
|
+
const start = startOfWeek(date, startDay);
|
|
1009
|
+
const end = new Date(start);
|
|
1010
|
+
end.setDate(end.getDate() + 6);
|
|
1011
|
+
end.setHours(23, 59, 59, 999);
|
|
1012
|
+
return end;
|
|
1013
|
+
}
|
|
1014
|
+
function getAge(birthDate) {
|
|
1015
|
+
if (!isDate(birthDate)) return 0;
|
|
1016
|
+
const n2 = /* @__PURE__ */ new Date();
|
|
1017
|
+
let age = n2.getFullYear() - birthDate.getFullYear();
|
|
1018
|
+
if (n2.getMonth() < birthDate.getMonth() || n2.getMonth() === birthDate.getMonth() && n2.getDate() < birthDate.getDate()) {
|
|
1019
|
+
age--;
|
|
1020
|
+
}
|
|
1021
|
+
return Math.max(0, age);
|
|
1022
|
+
}
|
|
1023
|
+
function fromNow(date) {
|
|
1024
|
+
if (!isDate(date)) return "";
|
|
1025
|
+
const n2 = now();
|
|
1026
|
+
const diff = n2 - date.getTime();
|
|
1027
|
+
const minute = 60 * 1e3;
|
|
1028
|
+
const hour = 60 * minute;
|
|
1029
|
+
const day = 24 * hour;
|
|
1030
|
+
const week = 7 * day;
|
|
1031
|
+
const month = 30 * day;
|
|
1032
|
+
const year = 365 * day;
|
|
1033
|
+
if (diff < minute) return "刚刚";
|
|
1034
|
+
if (diff < hour) return `${Math.floor(diff / minute)}分钟前`;
|
|
1035
|
+
if (diff < day) return `${Math.floor(diff / hour)}小时前`;
|
|
1036
|
+
if (diff < week) return `${Math.floor(diff / day)}天前`;
|
|
1037
|
+
if (diff < month) return `${Math.floor(diff / week)}周前`;
|
|
1038
|
+
if (diff < year) return `${Math.floor(diff / month)}个月前`;
|
|
1039
|
+
return `${Math.floor(diff / year)}年前`;
|
|
1040
|
+
}
|
|
1041
|
+
const dateUtils = { now, today, tomorrow, yesterday, isDate, isValid: isValidDate, parse: parseDate, format: formatDate$1, toISO, toUTC, addDays, addHours, addMinutes, addSeconds, diffDays, diffHours, diffMinutes, diffSeconds, isToday, isYesterday, isTomorrow, isFuture, isPast, isLeapYear, getDaysInMonth, getWeekOfYear, getQuarter, startOfDay, endOfDay, startOfMonth, endOfMonth, startOfWeek, endOfWeek, getAge, fromNow };
|
|
1042
|
+
function debounce(func, wait, options = {}) {
|
|
1043
|
+
let timeout = null, lastArgs = null, lastThis = null, lastCallTime = 0;
|
|
1044
|
+
const leading = options.leading || false;
|
|
1045
|
+
const trailing = options.trailing !== false;
|
|
1046
|
+
function invokeFunc() {
|
|
1047
|
+
func.apply(lastThis, lastArgs);
|
|
1048
|
+
}
|
|
1049
|
+
function leadingEdge() {
|
|
1050
|
+
lastCallTime = Date.now();
|
|
1051
|
+
if (leading) {
|
|
1052
|
+
timeout = setTimeout(trailingEdge, wait);
|
|
1053
|
+
invokeFunc();
|
|
1054
|
+
} else {
|
|
1055
|
+
timeout = setTimeout(trailingEdge, wait);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
function trailingEdge() {
|
|
1059
|
+
timeout = null;
|
|
1060
|
+
if (trailing && lastArgs) {
|
|
1061
|
+
invokeFunc();
|
|
1062
|
+
}
|
|
1063
|
+
lastArgs = null;
|
|
1064
|
+
lastThis = null;
|
|
1065
|
+
}
|
|
1066
|
+
function remainingWait() {
|
|
1067
|
+
return Math.max(0, wait - (Date.now() - lastCallTime));
|
|
1068
|
+
}
|
|
1069
|
+
return function(...args) {
|
|
1070
|
+
lastArgs = args;
|
|
1071
|
+
lastThis = this;
|
|
1072
|
+
lastCallTime = Date.now();
|
|
1073
|
+
if (!timeout) {
|
|
1074
|
+
leadingEdge();
|
|
1075
|
+
} else {
|
|
1076
|
+
clearTimeout(timeout);
|
|
1077
|
+
timeout = setTimeout(trailingEdge, remainingWait());
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
function throttle(func, limit, options = {}) {
|
|
1082
|
+
let inThrottle = false;
|
|
1083
|
+
const trailing = options.trailing || false;
|
|
1084
|
+
let lastArgs = null, lastThis = null;
|
|
1085
|
+
function invokeFunc() {
|
|
1086
|
+
func.apply(lastThis, lastArgs);
|
|
1087
|
+
lastArgs = null;
|
|
1088
|
+
lastThis = null;
|
|
1089
|
+
}
|
|
1090
|
+
return function(...args) {
|
|
1091
|
+
if (!inThrottle) {
|
|
1092
|
+
inThrottle = true;
|
|
1093
|
+
lastArgs = args;
|
|
1094
|
+
lastThis = this;
|
|
1095
|
+
invokeFunc();
|
|
1096
|
+
setTimeout(() => {
|
|
1097
|
+
inThrottle = false;
|
|
1098
|
+
if (trailing && lastArgs) {
|
|
1099
|
+
invokeFunc();
|
|
1100
|
+
}
|
|
1101
|
+
}, limit);
|
|
1102
|
+
} else if (trailing) {
|
|
1103
|
+
lastArgs = args;
|
|
1104
|
+
lastThis = this;
|
|
1105
|
+
}
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
function isEmail(str) {
|
|
1109
|
+
return /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(str || "");
|
|
1110
|
+
}
|
|
1111
|
+
function isPhone(str) {
|
|
1112
|
+
return /^1[3-9]\d{9}$/.test(str || "");
|
|
1113
|
+
}
|
|
1114
|
+
function isURL(str) {
|
|
1115
|
+
return /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([/\w.-]*)*\/?$/.test(str || "");
|
|
1116
|
+
}
|
|
1117
|
+
function isIPv4(str) {
|
|
1118
|
+
return /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(str || "");
|
|
1119
|
+
}
|
|
1120
|
+
function isIPv6(str) {
|
|
1121
|
+
return /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/.test(str || "");
|
|
1122
|
+
}
|
|
1123
|
+
function isIP(str) {
|
|
1124
|
+
return isIPv4(str) || isIPv6(str);
|
|
1125
|
+
}
|
|
1126
|
+
function isIDCard(str) {
|
|
1127
|
+
return /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/.test(str || "");
|
|
1128
|
+
}
|
|
1129
|
+
function isPassport(str) {
|
|
1130
|
+
return /^[A-Z][0-9]{8}$|^[A-Z]{2}[0-9]{7}$/.test(str || "");
|
|
1131
|
+
}
|
|
1132
|
+
function isCreditCard(str) {
|
|
1133
|
+
const regex = /^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9]{2})[0-9]{12}|3[47][0-9]{13})$/;
|
|
1134
|
+
const cleanStr = str.replace(/\s/g, "");
|
|
1135
|
+
if (!regex.test(cleanStr)) return false;
|
|
1136
|
+
let s = 0, alt = false;
|
|
1137
|
+
for (let i = cleanStr.length - 1; i >= 0; i--) {
|
|
1138
|
+
let digit = parseInt(cleanStr[i], 10);
|
|
1139
|
+
if (alt) {
|
|
1140
|
+
digit *= 2;
|
|
1141
|
+
if (digit > 9) digit -= 9;
|
|
1142
|
+
}
|
|
1143
|
+
s += digit;
|
|
1144
|
+
alt = !alt;
|
|
1145
|
+
}
|
|
1146
|
+
return s % 10 === 0;
|
|
1147
|
+
}
|
|
1148
|
+
function isHexColor(str) {
|
|
1149
|
+
return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(str || "");
|
|
1150
|
+
}
|
|
1151
|
+
function isRGB(str) {
|
|
1152
|
+
const m = /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/.exec(str || "");
|
|
1153
|
+
if (!m) return false;
|
|
1154
|
+
return m.slice(1).every((v) => parseInt(v) >= 0 && parseInt(v) <= 255);
|
|
1155
|
+
}
|
|
1156
|
+
function isRGBA(str) {
|
|
1157
|
+
const m = /^rgba\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3}),\s*([01]|0\.\d+)\)$/.exec(str || "");
|
|
1158
|
+
if (!m) return false;
|
|
1159
|
+
const [, r, g, b, a] = m;
|
|
1160
|
+
return parseInt(r) >= 0 && parseInt(r) <= 255 && parseInt(g) >= 0 && parseInt(g) <= 255 && parseInt(b) >= 0 && parseInt(b) <= 255 && parseFloat(a) >= 0 && parseFloat(a) <= 1;
|
|
1161
|
+
}
|
|
1162
|
+
function isColor(str) {
|
|
1163
|
+
return isHexColor(str) || isRGB(str) || isRGBA(str);
|
|
1164
|
+
}
|
|
1165
|
+
function isDateStr(str) {
|
|
1166
|
+
return !isNaN(new Date(str).getTime());
|
|
1167
|
+
}
|
|
1168
|
+
function isJSON(str) {
|
|
1169
|
+
try {
|
|
1170
|
+
JSON.parse(str);
|
|
1171
|
+
return true;
|
|
1172
|
+
} catch {
|
|
1173
|
+
return false;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
function isEmptyStr(str) {
|
|
1177
|
+
return !str || str.trim() === "";
|
|
1178
|
+
}
|
|
1179
|
+
function isWhitespace(str) {
|
|
1180
|
+
return /^\s+$/.test(str || "");
|
|
1181
|
+
}
|
|
1182
|
+
function isNumberStr(str) {
|
|
1183
|
+
return !isNaN(parseFloat(str)) && isFinite(str);
|
|
1184
|
+
}
|
|
1185
|
+
function isIntegerStr(str) {
|
|
1186
|
+
return /^-?\d+$/.test(str || "");
|
|
1187
|
+
}
|
|
1188
|
+
function isFloatStr(str) {
|
|
1189
|
+
return /^-?\d+\.\d+$/.test(str || "");
|
|
1190
|
+
}
|
|
1191
|
+
function isPositiveStr(str) {
|
|
1192
|
+
const num = parseFloat(str);
|
|
1193
|
+
return !isNaN(num) && num > 0;
|
|
1194
|
+
}
|
|
1195
|
+
function isNegativeStr(str) {
|
|
1196
|
+
const num = parseFloat(str);
|
|
1197
|
+
return !isNaN(num) && num < 0;
|
|
1198
|
+
}
|
|
1199
|
+
function isAlpha(str) {
|
|
1200
|
+
return /^[a-zA-Z]+$/.test(str || "");
|
|
1201
|
+
}
|
|
1202
|
+
function isAlphaNumeric(str) {
|
|
1203
|
+
return /^[a-zA-Z0-9]+$/.test(str || "");
|
|
1204
|
+
}
|
|
1205
|
+
function isChinese(str) {
|
|
1206
|
+
return /^[\u4e00-\u9fa5]+$/.test(str || "");
|
|
1207
|
+
}
|
|
1208
|
+
function isLength(str, minVal, maxVal) {
|
|
1209
|
+
const len = (str || "").length;
|
|
1210
|
+
return len >= minVal && (maxVal === void 0 || len <= maxVal);
|
|
1211
|
+
}
|
|
1212
|
+
function minLength(str, minVal) {
|
|
1213
|
+
return (str || "").length >= minVal;
|
|
1214
|
+
}
|
|
1215
|
+
function maxLength(str, maxVal) {
|
|
1216
|
+
return (str || "").length <= maxVal;
|
|
1217
|
+
}
|
|
1218
|
+
function matches(str, regex) {
|
|
1219
|
+
return regex instanceof RegExp ? regex.test(str || "") : false;
|
|
1220
|
+
}
|
|
1221
|
+
function equals(str1, str2) {
|
|
1222
|
+
return String(str1) === String(str2);
|
|
1223
|
+
}
|
|
1224
|
+
function contains(str, substring) {
|
|
1225
|
+
return (str || "").includes(substring);
|
|
1226
|
+
}
|
|
1227
|
+
function notContains(str, substring) {
|
|
1228
|
+
return !contains(str, substring);
|
|
1229
|
+
}
|
|
1230
|
+
function isArrayVal(arr) {
|
|
1231
|
+
return Array.isArray(arr);
|
|
1232
|
+
}
|
|
1233
|
+
function arrayLength(arr, minVal, maxVal) {
|
|
1234
|
+
const len = arr ? arr.length : 0;
|
|
1235
|
+
return len >= minVal && (maxVal === void 0 || len <= maxVal);
|
|
1236
|
+
}
|
|
1237
|
+
function arrayMinLength(arr, minVal) {
|
|
1238
|
+
return arr ? arr.length >= minVal : false;
|
|
1239
|
+
}
|
|
1240
|
+
function arrayMaxLength(arr, maxVal) {
|
|
1241
|
+
return arr ? arr.length <= maxVal : false;
|
|
1242
|
+
}
|
|
1243
|
+
function isObjectVal(obj) {
|
|
1244
|
+
return obj !== null && typeof obj === "object" && !Array.isArray(obj);
|
|
1245
|
+
}
|
|
1246
|
+
function hasKeys(obj, keys2) {
|
|
1247
|
+
if (!obj || !keys2 || !Array.isArray(keys2)) return false;
|
|
1248
|
+
return keys2.every((key) => Object.prototype.hasOwnProperty.call(obj, key));
|
|
1249
|
+
}
|
|
1250
|
+
function validate(obj, rules) {
|
|
1251
|
+
const errors = {};
|
|
1252
|
+
Object.keys(rules).forEach((key) => {
|
|
1253
|
+
const value = obj[key];
|
|
1254
|
+
const fieldRules = rules[key];
|
|
1255
|
+
const fieldErrors = [];
|
|
1256
|
+
fieldRules.forEach((rule) => {
|
|
1257
|
+
if (typeof rule === "string") {
|
|
1258
|
+
const [name, ...params] = rule.split(":");
|
|
1259
|
+
if (!validatorUtils[name](value, ...params)) {
|
|
1260
|
+
fieldErrors.push(name);
|
|
1261
|
+
}
|
|
1262
|
+
} else if (typeof rule === "function") {
|
|
1263
|
+
const result = rule(value, obj);
|
|
1264
|
+
if (result !== true) {
|
|
1265
|
+
fieldErrors.push(result || "validation_failed");
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
});
|
|
1269
|
+
if (fieldErrors.length > 0) {
|
|
1270
|
+
errors[key] = fieldErrors;
|
|
1271
|
+
}
|
|
1272
|
+
});
|
|
1273
|
+
return { valid: Object.keys(errors).length === 0, errors };
|
|
1274
|
+
}
|
|
1275
|
+
const validatorUtils = { isEmail, isPhone, isURL, isIPv4, isIPv6, isIP, isIDCard, isPassport, isCreditCard, isHexColor, isRGB, isRGBA, isColor, isDate: isDateStr, isJSON, isEmpty: isEmptyStr, isWhitespace, isNumber: isNumberStr, isInteger: isIntegerStr, isFloat: isFloatStr, isPositive: isPositiveStr, isNegative: isNegativeStr, isAlpha, isAlphaNumeric, isChinese, isLength, minLength, maxLength, matches, equals, contains, notContains, isArray: isArrayVal, arrayLength, arrayMinLength, arrayMaxLength, isObject: isObjectVal, hasKeys, validate };
|
|
1276
|
+
function md5(str) {
|
|
1277
|
+
const message = str ? String(str) : "";
|
|
1278
|
+
const md5Array = [1732584193, 4023233417, 2562383102, 271733878];
|
|
1279
|
+
const K = [3614090360, 3905402710, 606105819, 3250441966, 4118548399, 1200080426, 2821735955, 4249261313, 1770035416, 2336552879, 4294925233, 2304563134, 1804603682, 4254626195, 2792965006, 1236535329, 4129170786, 3225465664, 643717713, 3921069994, 3593408605, 38016083, 3634488961, 3889429448, 568446438, 3275163606, 4107603335, 1163531501, 2850285829, 4243563512, 1735328473, 2368359562, 4294588738, 2272392833, 1839030562, 4259657740, 2763975236, 1272893353, 4139469664, 3200236656, 681279174, 3936430074, 3572445317, 76029189, 3654602809, 3873151461, 530742520, 3299628645, 4096336452, 1126891415, 2878612391, 4237533241, 1700485571, 2399980690, 4293915773, 2240044497, 1873313359, 4264355552, 2734768916, 1309151649, 4149444226, 3174756917, 718787259, 3951481745];
|
|
1280
|
+
const S = [[7, 12, 17, 22], [5, 9, 14, 20], [4, 11, 16, 23], [6, 10, 15, 21]];
|
|
1281
|
+
function leftRotate(value, shift2) {
|
|
1282
|
+
return value << shift2 | value >>> 32 - shift2;
|
|
1283
|
+
}
|
|
1284
|
+
function addPadding(msg) {
|
|
1285
|
+
const bitLength = msg.length * 8;
|
|
1286
|
+
msg += "";
|
|
1287
|
+
while (msg.length % 64 !== 56) {
|
|
1288
|
+
msg += "\0";
|
|
1289
|
+
}
|
|
1290
|
+
const lowBytes = bitLength & 4294967295;
|
|
1291
|
+
const highBytes = bitLength >>> 32 & 4294967295;
|
|
1292
|
+
for (let i = 0; i < 4; i++) {
|
|
1293
|
+
msg += String.fromCharCode(lowBytes >>> 8 * i & 255);
|
|
1294
|
+
}
|
|
1295
|
+
for (let i = 0; i < 4; i++) {
|
|
1296
|
+
msg += String.fromCharCode(highBytes >>> 8 * i & 255);
|
|
1297
|
+
}
|
|
1298
|
+
return msg;
|
|
1299
|
+
}
|
|
1300
|
+
function processBlock(block, state2) {
|
|
1301
|
+
const [a, b, c, d] = state2;
|
|
1302
|
+
const words = [];
|
|
1303
|
+
for (let i = 0; i < 16; i++) {
|
|
1304
|
+
words[i] = block.charCodeAt(i * 4) & 255 | (block.charCodeAt(i * 4 + 1) & 255) << 8 | (block.charCodeAt(i * 4 + 2) & 255) << 16 | (block.charCodeAt(i * 4 + 3) & 255) << 24;
|
|
1305
|
+
}
|
|
1306
|
+
let AA = a, BB = b, CC = c, DD = d;
|
|
1307
|
+
for (let i = 0; i < 64; i++) {
|
|
1308
|
+
let f, g;
|
|
1309
|
+
const r = Math.floor(i / 16);
|
|
1310
|
+
const wi = i % 16;
|
|
1311
|
+
if (r === 0) {
|
|
1312
|
+
f = BB & CC | ~BB & DD;
|
|
1313
|
+
g = wi;
|
|
1314
|
+
} else if (r === 1) {
|
|
1315
|
+
f = DD & BB | ~DD & CC;
|
|
1316
|
+
g = (5 * wi + 1) % 16;
|
|
1317
|
+
} else if (r === 2) {
|
|
1318
|
+
f = BB ^ CC ^ DD;
|
|
1319
|
+
g = (3 * wi + 5) % 16;
|
|
1320
|
+
} else {
|
|
1321
|
+
f = CC ^ (BB | ~DD);
|
|
1322
|
+
g = 7 * wi % 16;
|
|
1323
|
+
}
|
|
1324
|
+
const temp = DD;
|
|
1325
|
+
DD = CC;
|
|
1326
|
+
CC = BB;
|
|
1327
|
+
BB = BB + leftRotate(AA + f + K[i] + words[g] & 4294967295, S[r][i % 4]);
|
|
1328
|
+
AA = temp;
|
|
1329
|
+
}
|
|
1330
|
+
return [a + AA & 4294967295, b + BB & 4294967295, c + CC & 4294967295, d + DD & 4294967295];
|
|
1331
|
+
}
|
|
1332
|
+
const paddedMessage = addPadding(message);
|
|
1333
|
+
let state = [...md5Array];
|
|
1334
|
+
for (let i = 0; i < paddedMessage.length; i += 64) {
|
|
1335
|
+
state = processBlock(paddedMessage.substring(i, i + 64), state);
|
|
1336
|
+
}
|
|
1337
|
+
let result = "";
|
|
1338
|
+
state.forEach((word) => {
|
|
1339
|
+
for (let i = 0; i < 4; i++) {
|
|
1340
|
+
result += (word >>> 8 * i & 255).toString(16).padStart(2, "0");
|
|
1341
|
+
}
|
|
1342
|
+
});
|
|
1343
|
+
return result;
|
|
1344
|
+
}
|
|
1345
|
+
function sha256(str) {
|
|
1346
|
+
const message = str ? String(str) : "";
|
|
1347
|
+
const K = [1116352408, 1899447441, 3049323471, 3921009573, 961987163, 1508970993, 2453635748, 2870763221, 3624381080, 310598401, 607225278, 1426881987, 1925078388, 2162078206, 2614888103, 3248222580, 3835390401, 4022224774, 264347078, 604807628, 770255983, 1249150122, 1555081692, 1996064986, 2554220882, 2821834349, 2952996808, 3210313671, 3336571891, 3584528711, 113926993, 338241895, 666307205, 773529912, 1294757372, 1396182291, 1695183700, 1986661051, 2177026350, 2456956037, 2730485921, 2820302411, 3259730800, 3345764771, 3516065817, 3600352804, 4094571909, 275423344, 430227734, 506948616, 659060556, 883997877, 958139571, 1322822218, 1537002063, 1747873779, 1955562222, 2024104815, 2227730452, 2361852424, 2428436474, 2756734187, 3204031479, 3329325298];
|
|
1348
|
+
function rightRotate(value, shift2) {
|
|
1349
|
+
return value >>> shift2 | value << 32 - shift2;
|
|
1350
|
+
}
|
|
1351
|
+
function addPadding(msg) {
|
|
1352
|
+
const bitLength = msg.length * 8;
|
|
1353
|
+
msg += "";
|
|
1354
|
+
while (msg.length % 64 !== 56) {
|
|
1355
|
+
msg += "\0";
|
|
1356
|
+
}
|
|
1357
|
+
const lowBytes = bitLength & 4294967295;
|
|
1358
|
+
const highBytes = bitLength >>> 32 & 4294967295;
|
|
1359
|
+
for (let i = 0; i < 4; i++) {
|
|
1360
|
+
msg += String.fromCharCode(highBytes >>> 8 * i & 255);
|
|
1361
|
+
}
|
|
1362
|
+
for (let i = 0; i < 4; i++) {
|
|
1363
|
+
msg += String.fromCharCode(lowBytes >>> 8 * i & 255);
|
|
1364
|
+
}
|
|
1365
|
+
return msg;
|
|
1366
|
+
}
|
|
1367
|
+
function processBlock(block, state2) {
|
|
1368
|
+
const words = [];
|
|
1369
|
+
for (let i = 0; i < 16; i++) {
|
|
1370
|
+
words[i] = block.charCodeAt(i * 4) & 255 | (block.charCodeAt(i * 4 + 1) & 255) << 8 | (block.charCodeAt(i * 4 + 2) & 255) << 16 | (block.charCodeAt(i * 4 + 3) & 255) << 24;
|
|
1371
|
+
}
|
|
1372
|
+
for (let i = 16; i < 64; i++) {
|
|
1373
|
+
const s0 = rightRotate(words[i - 15], 7) ^ rightRotate(words[i - 15], 18) ^ words[i - 15] >>> 3;
|
|
1374
|
+
const s1 = rightRotate(words[i - 2], 17) ^ rightRotate(words[i - 2], 19) ^ words[i - 2] >>> 10;
|
|
1375
|
+
words[i] = words[i - 16] + s0 + words[i - 7] + s1 & 4294967295;
|
|
1376
|
+
}
|
|
1377
|
+
let [a, b, c, d, e, f, g, h] = state2;
|
|
1378
|
+
for (let i = 0; i < 64; i++) {
|
|
1379
|
+
const S1 = rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25);
|
|
1380
|
+
const ch = e & f ^ ~e & g;
|
|
1381
|
+
const temp1 = h + S1 + ch + K[i] + words[i] & 4294967295;
|
|
1382
|
+
const S0 = rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22);
|
|
1383
|
+
const maj = a & b ^ a & c ^ b & c;
|
|
1384
|
+
const temp2 = S0 + maj & 4294967295;
|
|
1385
|
+
h = g;
|
|
1386
|
+
g = f;
|
|
1387
|
+
f = e;
|
|
1388
|
+
e = d + temp1 & 4294967295;
|
|
1389
|
+
d = c;
|
|
1390
|
+
c = b;
|
|
1391
|
+
b = a;
|
|
1392
|
+
a = temp1 + temp2 & 4294967295;
|
|
1393
|
+
}
|
|
1394
|
+
return [state2[0] + a & 4294967295, state2[1] + b & 4294967295, state2[2] + c & 4294967295, state2[3] + d & 4294967295, state2[4] + e & 4294967295, state2[5] + f & 4294967295, state2[6] + g & 4294967295, state2[7] + h & 4294967295];
|
|
1395
|
+
}
|
|
1396
|
+
const paddedMessage = addPadding(message);
|
|
1397
|
+
let state = [1779033703, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225];
|
|
1398
|
+
for (let i = 0; i < paddedMessage.length; i += 64) {
|
|
1399
|
+
state = processBlock(paddedMessage.substring(i, i + 64), state);
|
|
1400
|
+
}
|
|
1401
|
+
let result = "";
|
|
1402
|
+
state.forEach((word) => {
|
|
1403
|
+
for (let i = 3; i >= 0; i--) {
|
|
1404
|
+
result += (word >>> 8 * i & 255).toString(16).padStart(2, "0");
|
|
1405
|
+
}
|
|
1406
|
+
});
|
|
1407
|
+
return result;
|
|
1408
|
+
}
|
|
1409
|
+
function base64Encode(str) {
|
|
1410
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
1411
|
+
let result = "";
|
|
1412
|
+
let i = 0;
|
|
1413
|
+
const bytes = str ? str.split("").map((c) => c.charCodeAt(0)) : [];
|
|
1414
|
+
while (i < bytes.length) {
|
|
1415
|
+
const byte1 = bytes[i++];
|
|
1416
|
+
const byte2 = bytes[i++] || 0;
|
|
1417
|
+
const byte3 = bytes[i++] || 0;
|
|
1418
|
+
const enc1 = byte1 >> 2;
|
|
1419
|
+
const enc2 = (byte1 & 3) << 4 | byte2 >> 4;
|
|
1420
|
+
const enc3 = (byte2 & 15) << 2 | byte3 >> 6;
|
|
1421
|
+
const enc4 = byte3 & 63;
|
|
1422
|
+
result += chars[enc1] + chars[enc2] + (i > bytes.length + 1 ? "=" : chars[enc3]) + (i > bytes.length ? "=" : chars[enc4]);
|
|
1423
|
+
}
|
|
1424
|
+
return result;
|
|
1425
|
+
}
|
|
1426
|
+
function base64Decode(str) {
|
|
1427
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
1428
|
+
let result = "";
|
|
1429
|
+
let i = 0;
|
|
1430
|
+
str = str.replace(/[^A-Za-z0-9+/=]/g, "");
|
|
1431
|
+
while (i < str.length) {
|
|
1432
|
+
const enc1 = chars.indexOf(str.charAt(i++));
|
|
1433
|
+
const enc2 = chars.indexOf(str.charAt(i++));
|
|
1434
|
+
const enc3 = chars.indexOf(str.charAt(i++));
|
|
1435
|
+
const enc4 = chars.indexOf(str.charAt(i++));
|
|
1436
|
+
const byte1 = enc1 << 2 | enc2 >> 4;
|
|
1437
|
+
const byte2 = (enc2 & 15) << 4 | enc3 >> 2;
|
|
1438
|
+
const byte3 = (enc3 & 3) << 6 | enc4;
|
|
1439
|
+
result += String.fromCharCode(byte1);
|
|
1440
|
+
if (enc3 !== 64) result += String.fromCharCode(byte2);
|
|
1441
|
+
if (enc4 !== 64) result += String.fromCharCode(byte3);
|
|
1442
|
+
}
|
|
1443
|
+
return result;
|
|
1444
|
+
}
|
|
1445
|
+
function uuid() {
|
|
1446
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
1447
|
+
const r = Math.random() * 16 | 0;
|
|
1448
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
1449
|
+
return v.toString(16);
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
const cryptoUtils = { md5, sha256, base64Encode, base64Decode, uuid };
|
|
1453
|
+
const _preloadCache = /* @__PURE__ */ new Map();
|
|
1454
|
+
async function loadImage(url, options = {}) {
|
|
1455
|
+
const { crossOrigin = "anonymous" } = options;
|
|
1456
|
+
if (_preloadCache.has(url)) return _preloadCache.get(url);
|
|
1457
|
+
return new Promise((resolve, reject) => {
|
|
1458
|
+
const img = new Image();
|
|
1459
|
+
img.crossOrigin = crossOrigin;
|
|
1460
|
+
img.onload = () => {
|
|
1461
|
+
_preloadCache.set(url, img);
|
|
1462
|
+
resolve(img);
|
|
1463
|
+
};
|
|
1464
|
+
img.onerror = () => {
|
|
1465
|
+
reject(new Error(`Failed to load image: ${url}`));
|
|
1466
|
+
};
|
|
1467
|
+
img.src = url;
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
async function loadImages(urls, options = {}) {
|
|
1471
|
+
const { parallel = true } = options;
|
|
1472
|
+
if (parallel) return Promise.all(urls.map((url) => loadImage(url, options)));
|
|
1473
|
+
const results = [];
|
|
1474
|
+
for (const url of urls) {
|
|
1475
|
+
results.push(await loadImage(url, options));
|
|
1476
|
+
}
|
|
1477
|
+
return results;
|
|
1478
|
+
}
|
|
1479
|
+
async function loadScript(url, options = {}) {
|
|
1480
|
+
const { type = "text/javascript", async: isAsync = true, defer = false } = options;
|
|
1481
|
+
if (_preloadCache.has(url)) return _preloadCache.get(url);
|
|
1482
|
+
return new Promise((resolve, reject) => {
|
|
1483
|
+
const script = document.createElement("script");
|
|
1484
|
+
script.type = type;
|
|
1485
|
+
script.async = isAsync;
|
|
1486
|
+
script.defer = defer;
|
|
1487
|
+
script.onload = () => {
|
|
1488
|
+
_preloadCache.set(url, script);
|
|
1489
|
+
resolve(script);
|
|
1490
|
+
};
|
|
1491
|
+
script.onerror = () => {
|
|
1492
|
+
script.remove();
|
|
1493
|
+
reject(new Error(`Failed to load script: ${url}`));
|
|
1494
|
+
};
|
|
1495
|
+
script.src = url;
|
|
1496
|
+
document.head.appendChild(script);
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1499
|
+
async function loadStylesheet(url, options = {}) {
|
|
1500
|
+
const { media = "all" } = options;
|
|
1501
|
+
if (_preloadCache.has(url)) return _preloadCache.get(url);
|
|
1502
|
+
return new Promise((resolve, reject) => {
|
|
1503
|
+
const link = document.createElement("link");
|
|
1504
|
+
link.rel = "stylesheet";
|
|
1505
|
+
link.href = url;
|
|
1506
|
+
link.media = media;
|
|
1507
|
+
link.onload = () => {
|
|
1508
|
+
_preloadCache.set(url, link);
|
|
1509
|
+
resolve(link);
|
|
1510
|
+
};
|
|
1511
|
+
link.onerror = () => {
|
|
1512
|
+
link.remove();
|
|
1513
|
+
reject(new Error(`Failed to load stylesheet: ${url}`));
|
|
1514
|
+
};
|
|
1515
|
+
document.head.appendChild(link);
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
async function loadFont(fontFamily, src, options = {}) {
|
|
1519
|
+
const { weight = "normal", style = "normal" } = options;
|
|
1520
|
+
const fontFace = new FontFace(fontFamily, `url(${src})`, { weight, style });
|
|
1521
|
+
try {
|
|
1522
|
+
await fontFace.load();
|
|
1523
|
+
document.fonts.add(fontFace);
|
|
1524
|
+
return fontFace;
|
|
1525
|
+
} catch (e) {
|
|
1526
|
+
throw new Error(`Failed to load font: ${fontFamily}`);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
async function preload(url, type = "image") {
|
|
1530
|
+
switch (type) {
|
|
1531
|
+
case "image":
|
|
1532
|
+
return loadImage(url);
|
|
1533
|
+
case "script":
|
|
1534
|
+
return loadScript(url);
|
|
1535
|
+
case "stylesheet":
|
|
1536
|
+
case "style":
|
|
1537
|
+
return loadStylesheet(url);
|
|
1538
|
+
default:
|
|
1539
|
+
throw new Error(`Unsupported preload type: ${type}`);
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
function isLoaded(url) {
|
|
1543
|
+
return _preloadCache.has(url);
|
|
1544
|
+
}
|
|
1545
|
+
function clearPreloadCache() {
|
|
1546
|
+
_preloadCache.clear();
|
|
1547
|
+
}
|
|
1548
|
+
function clearCacheByUrl(url) {
|
|
1549
|
+
_preloadCache.delete(url);
|
|
1550
|
+
}
|
|
1551
|
+
const preloadUtils = { loadImage, loadImages, loadScript, loadStylesheet, loadFont, preload, isLoaded, clearCache: clearPreloadCache, clearCacheByUrl };
|
|
1552
|
+
const KupolaUtils = {
|
|
1553
|
+
string: stringUtils,
|
|
1554
|
+
array: arrayUtils,
|
|
1555
|
+
object: objectUtils,
|
|
1556
|
+
number: numberUtils,
|
|
1557
|
+
date: dateUtils,
|
|
1558
|
+
debounce,
|
|
1559
|
+
throttle,
|
|
1560
|
+
validator: validatorUtils,
|
|
1561
|
+
crypto: cryptoUtils,
|
|
1562
|
+
preload: preloadUtils
|
|
1563
|
+
};
|
|
1564
|
+
function sanitizeHtml$1(html) {
|
|
1565
|
+
if (!html) return "";
|
|
1566
|
+
let str = String(html);
|
|
1567
|
+
const dangerousTags = /<\s*(script|iframe|object|embed|applet|form|base|link|meta|style)\b[^>]*>[\s\S]*?<\s*\/\s*\1\s*>|<\s*(script|iframe|object|embed|applet|form|base|link|meta|style)\b[^>]*\/?>/gi;
|
|
1568
|
+
let prev;
|
|
1569
|
+
do {
|
|
1570
|
+
prev = str;
|
|
1571
|
+
str = str.replace(dangerousTags, "");
|
|
1572
|
+
} while (str !== prev);
|
|
1573
|
+
str = str.replace(/\bon\w+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, "");
|
|
1574
|
+
str = str.replace(/(href|src|action|background)\s*=\s*(?:"[^"]*(?:javascript|vbscript|data)\s*:[^"]*"|'[^']*(?:javascript|vbscript|data)\s*:[^']*'|[^\s>]*(?:javascript|vbscript|data)\s*:[^\s>]*)/gi, '$1=""');
|
|
1575
|
+
str = str.replace(/expression\s*\([^)]*\)/gi, "");
|
|
1576
|
+
return str;
|
|
1577
|
+
}
|
|
1578
|
+
class TrieNode {
|
|
1579
|
+
constructor() {
|
|
1580
|
+
this.children = {};
|
|
1581
|
+
this.keys = [];
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
class PathTrie {
|
|
1585
|
+
constructor() {
|
|
1586
|
+
this.root = new TrieNode();
|
|
1587
|
+
}
|
|
1588
|
+
insert(key) {
|
|
1589
|
+
let node = this.root;
|
|
1590
|
+
const parts = key.split(".");
|
|
1591
|
+
parts.forEach((part, index) => {
|
|
1592
|
+
if (!node.children[part]) {
|
|
1593
|
+
node.children[part] = new TrieNode();
|
|
1594
|
+
}
|
|
1595
|
+
node = node.children[part];
|
|
1596
|
+
if (index === parts.length - 1) {
|
|
1597
|
+
node.keys.push(key);
|
|
1598
|
+
}
|
|
1599
|
+
});
|
|
1600
|
+
}
|
|
1601
|
+
getSubKeys(key) {
|
|
1602
|
+
let node = this.root;
|
|
1603
|
+
const parts = key.split(".");
|
|
1604
|
+
const result = [];
|
|
1605
|
+
for (let i = 0; i < parts.length; i++) {
|
|
1606
|
+
const part = parts[i];
|
|
1607
|
+
if (!node.children[part]) {
|
|
1608
|
+
break;
|
|
1609
|
+
}
|
|
1610
|
+
node = node.children[part];
|
|
1611
|
+
const collectKeys = (n2) => {
|
|
1612
|
+
if (n2.keys.length > 0) {
|
|
1613
|
+
result.push(...n2.keys);
|
|
1614
|
+
}
|
|
1615
|
+
Object.values(n2.children).forEach((child) => collectKeys(child));
|
|
1616
|
+
};
|
|
1617
|
+
collectKeys(node);
|
|
1618
|
+
}
|
|
1619
|
+
return [...new Set(result)];
|
|
1620
|
+
}
|
|
1621
|
+
getParentKeys(key) {
|
|
1622
|
+
const parts = key.split(".");
|
|
1623
|
+
const result = [];
|
|
1624
|
+
for (let i = 1; i <= parts.length; i++) {
|
|
1625
|
+
const parentPath = parts.slice(0, i).join(".");
|
|
1626
|
+
result.push(parentPath);
|
|
1627
|
+
}
|
|
1628
|
+
return result;
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
const REACTIVE_SYMBOLS = {
|
|
1632
|
+
parent: Symbol("reactive_parent"),
|
|
1633
|
+
path: Symbol("reactive_path"),
|
|
1634
|
+
isReactive: Symbol("reactive_is_reactive")
|
|
1635
|
+
};
|
|
1636
|
+
class KupolaDataBind {
|
|
1637
|
+
constructor() {
|
|
1638
|
+
this.rawData = {};
|
|
1639
|
+
this.data = null;
|
|
1640
|
+
this.observers = {};
|
|
1641
|
+
this.elements = {};
|
|
1642
|
+
this.computedProperties = {};
|
|
1643
|
+
this.pathTrie = new PathTrie();
|
|
1644
|
+
this.updateQueue = /* @__PURE__ */ new Map();
|
|
1645
|
+
this.isProcessing = false;
|
|
1646
|
+
this.pendingComputed = /* @__PURE__ */ new Set();
|
|
1647
|
+
this.persistedKeys = /* @__PURE__ */ new Map();
|
|
1648
|
+
this.snapshots = [];
|
|
1649
|
+
this.snapshotLimit = 10;
|
|
1650
|
+
this._proxyCache = /* @__PURE__ */ new WeakMap();
|
|
1651
|
+
this.createReactiveData();
|
|
1652
|
+
}
|
|
1653
|
+
createReactiveData() {
|
|
1654
|
+
const handler = {
|
|
1655
|
+
get: (target, key, receiver2) => {
|
|
1656
|
+
if (key === "__raw__") return target;
|
|
1657
|
+
const result = Reflect.get(target, key, receiver2);
|
|
1658
|
+
if (result && typeof result === "object" && !Array.isArray(result)) {
|
|
1659
|
+
return this.wrapReactive(result, key);
|
|
1660
|
+
}
|
|
1661
|
+
return result;
|
|
1662
|
+
},
|
|
1663
|
+
set: (target, key, value, receiver2) => {
|
|
1664
|
+
const oldValue = Reflect.get(target, key, receiver2);
|
|
1665
|
+
const result = Reflect.set(target, key, value, receiver2);
|
|
1666
|
+
const fullPath = this.resolvePath(target, key);
|
|
1667
|
+
this.notify(fullPath, value, oldValue);
|
|
1668
|
+
this.queueUpdate(fullPath, value);
|
|
1669
|
+
return result;
|
|
1670
|
+
},
|
|
1671
|
+
deleteProperty: (target, key) => {
|
|
1672
|
+
const oldValue = Reflect.get(target, key, receiver);
|
|
1673
|
+
const result = Reflect.deleteProperty(target, key);
|
|
1674
|
+
const fullPath = this.resolvePath(target, key);
|
|
1675
|
+
this.notify(fullPath, void 0, oldValue);
|
|
1676
|
+
this.queueUpdate(fullPath, void 0);
|
|
1677
|
+
return result;
|
|
1678
|
+
}
|
|
1679
|
+
};
|
|
1680
|
+
this.data = new Proxy(this.rawData, handler);
|
|
1681
|
+
this.data.__parent__ = null;
|
|
1682
|
+
this.data.__path__ = "";
|
|
1683
|
+
}
|
|
1684
|
+
wrapReactive(obj, parentKey) {
|
|
1685
|
+
if (obj[REACTIVE_SYMBOLS.isReactive]) return obj;
|
|
1686
|
+
if (this._proxyCache.has(obj)) {
|
|
1687
|
+
return this._proxyCache.get(obj);
|
|
1688
|
+
}
|
|
1689
|
+
const handler = {
|
|
1690
|
+
get: (target, key, receiver2) => {
|
|
1691
|
+
if (key === "__raw__") return target;
|
|
1692
|
+
if (key === REACTIVE_SYMBOLS.parent || key === "__parent__") return target[REACTIVE_SYMBOLS.parent];
|
|
1693
|
+
if (key === REACTIVE_SYMBOLS.path || key === "__path__") return target[REACTIVE_SYMBOLS.path];
|
|
1694
|
+
if (key === REACTIVE_SYMBOLS.isReactive || key === "__isReactive__") return true;
|
|
1695
|
+
const result = Reflect.get(target, key, receiver2);
|
|
1696
|
+
if (result && typeof result === "object" && !Array.isArray(result)) {
|
|
1697
|
+
return this.wrapReactive(result, `${target[REACTIVE_SYMBOLS.path]}${target[REACTIVE_SYMBOLS.path] ? "." : ""}${key}`);
|
|
1698
|
+
}
|
|
1699
|
+
return result;
|
|
1700
|
+
},
|
|
1701
|
+
set: (target, key, value, receiver2) => {
|
|
1702
|
+
if (key === REACTIVE_SYMBOLS.parent || key === REACTIVE_SYMBOLS.path || key === REACTIVE_SYMBOLS.isReactive || key === "__parent__" || key === "__path__" || key === "__isReactive__") {
|
|
1703
|
+
return true;
|
|
1704
|
+
}
|
|
1705
|
+
const oldValue = Reflect.get(target, key, receiver2);
|
|
1706
|
+
const result = Reflect.set(target, key, value, receiver2);
|
|
1707
|
+
const fullPath = `${target[REACTIVE_SYMBOLS.path]}${target[REACTIVE_SYMBOLS.path] ? "." : ""}${key}`;
|
|
1708
|
+
this.notify(fullPath, value, oldValue);
|
|
1709
|
+
this.queueUpdate(fullPath, value);
|
|
1710
|
+
return result;
|
|
1711
|
+
},
|
|
1712
|
+
deleteProperty: (target, key) => {
|
|
1713
|
+
if (key === REACTIVE_SYMBOLS.parent || key === REACTIVE_SYMBOLS.path || key === REACTIVE_SYMBOLS.isReactive) {
|
|
1714
|
+
return false;
|
|
1715
|
+
}
|
|
1716
|
+
const oldValue = Reflect.get(target, key);
|
|
1717
|
+
const result = Reflect.deleteProperty(target, key);
|
|
1718
|
+
const fullPath = `${target[REACTIVE_SYMBOLS.path]}${target[REACTIVE_SYMBOLS.path] ? "." : ""}${key}`;
|
|
1719
|
+
this.notify(fullPath, void 0, oldValue);
|
|
1720
|
+
this.queueUpdate(fullPath, void 0);
|
|
1721
|
+
return result;
|
|
1722
|
+
},
|
|
1723
|
+
has: (target, key) => {
|
|
1724
|
+
if (key === "__raw__" || key === REACTIVE_SYMBOLS.parent || key === REACTIVE_SYMBOLS.path || key === REACTIVE_SYMBOLS.isReactive || key === "__parent__" || key === "__path__" || key === "__isReactive__") {
|
|
1725
|
+
return true;
|
|
1726
|
+
}
|
|
1727
|
+
return key in target;
|
|
1728
|
+
},
|
|
1729
|
+
ownKeys: (target) => {
|
|
1730
|
+
return Reflect.ownKeys(target).filter(
|
|
1731
|
+
(key) => key !== REACTIVE_SYMBOLS.parent && key !== REACTIVE_SYMBOLS.path && key !== REACTIVE_SYMBOLS.isReactive
|
|
1732
|
+
);
|
|
1733
|
+
},
|
|
1734
|
+
getOwnPropertyDescriptor: (target, key) => {
|
|
1735
|
+
if (key === REACTIVE_SYMBOLS.parent || key === REACTIVE_SYMBOLS.path || key === REACTIVE_SYMBOLS.isReactive) {
|
|
1736
|
+
return {
|
|
1737
|
+
configurable: false,
|
|
1738
|
+
enumerable: false,
|
|
1739
|
+
writable: false,
|
|
1740
|
+
value: target[key]
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
return Reflect.getOwnPropertyDescriptor(target, key);
|
|
1744
|
+
}
|
|
1745
|
+
};
|
|
1746
|
+
const proxy = new Proxy(obj, handler);
|
|
1747
|
+
obj[REACTIVE_SYMBOLS.parent] = obj;
|
|
1748
|
+
obj[REACTIVE_SYMBOLS.path] = parentKey;
|
|
1749
|
+
obj[REACTIVE_SYMBOLS.isReactive] = true;
|
|
1750
|
+
this._proxyCache.set(obj, proxy);
|
|
1751
|
+
Object.keys(obj).forEach((key) => {
|
|
1752
|
+
if (obj[key] && typeof obj[key] === "object" && !Array.isArray(obj[key])) {
|
|
1753
|
+
obj[key] = this.wrapReactive(obj[key], `${parentKey}${parentKey ? "." : ""}${key}`);
|
|
1754
|
+
}
|
|
1755
|
+
});
|
|
1756
|
+
return proxy;
|
|
1757
|
+
}
|
|
1758
|
+
resolvePath(target, key) {
|
|
1759
|
+
if (target[REACTIVE_SYMBOLS.path]) {
|
|
1760
|
+
return `${target[REACTIVE_SYMBOLS.path]}.${key}`;
|
|
1761
|
+
}
|
|
1762
|
+
return key;
|
|
1763
|
+
}
|
|
1764
|
+
queueUpdate(key, value) {
|
|
1765
|
+
this.updateQueue.set(key, value);
|
|
1766
|
+
if (!this.isProcessing) {
|
|
1767
|
+
this.isProcessing = true;
|
|
1768
|
+
requestAnimationFrame(() => {
|
|
1769
|
+
this.processQueue();
|
|
1770
|
+
});
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
processQueue() {
|
|
1774
|
+
const updatedKeys = /* @__PURE__ */ new Set();
|
|
1775
|
+
this.updateQueue.forEach((value, key) => {
|
|
1776
|
+
updatedKeys.add(key);
|
|
1777
|
+
this.updateElementsDirect(key, value);
|
|
1778
|
+
const subKeys = this.pathTrie.getSubKeys(key);
|
|
1779
|
+
subKeys.forEach((subKey) => {
|
|
1780
|
+
if (!updatedKeys.has(subKey)) {
|
|
1781
|
+
const subValue = this.get(subKey);
|
|
1782
|
+
this.updateElementsDirect(subKey, subValue);
|
|
1783
|
+
updatedKeys.add(subKey);
|
|
1784
|
+
}
|
|
1785
|
+
});
|
|
1786
|
+
});
|
|
1787
|
+
this.updateQueue.clear();
|
|
1788
|
+
this.processComputed();
|
|
1789
|
+
this.isProcessing = false;
|
|
1790
|
+
}
|
|
1791
|
+
updateElementsDirect(key, value) {
|
|
1792
|
+
if (this.elements[key]) {
|
|
1793
|
+
this.elements[key].forEach((element) => {
|
|
1794
|
+
this.updateElement(element, value);
|
|
1795
|
+
});
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
processComputed() {
|
|
1799
|
+
const computedKeys = Object.keys(this.computedProperties);
|
|
1800
|
+
computedKeys.forEach((name) => {
|
|
1801
|
+
const prop = this.computedProperties[name];
|
|
1802
|
+
const depsChanged = prop.deps.some((dep) => {
|
|
1803
|
+
return this.updateQueue.has(dep) || this.pathTrie.getSubKeys(dep).some((k) => this.updateQueue.has(k));
|
|
1804
|
+
});
|
|
1805
|
+
if (depsChanged) {
|
|
1806
|
+
this.updateComputedProperty(name);
|
|
1807
|
+
}
|
|
1808
|
+
});
|
|
1809
|
+
}
|
|
1810
|
+
set(key, value, silent = false) {
|
|
1811
|
+
const oldValue = this.get(key);
|
|
1812
|
+
if (typeof key === "object") {
|
|
1813
|
+
Object.assign(this.rawData, key);
|
|
1814
|
+
Object.keys(key).forEach((k) => {
|
|
1815
|
+
if (!silent) {
|
|
1816
|
+
this.notify(k, key[k], oldValue?.[k]);
|
|
1817
|
+
this.queueUpdate(k, key[k]);
|
|
1818
|
+
}
|
|
1819
|
+
});
|
|
1820
|
+
} else {
|
|
1821
|
+
if (key.includes(".")) {
|
|
1822
|
+
this.setNested(key, value);
|
|
1823
|
+
} else {
|
|
1824
|
+
this.rawData[key] = value;
|
|
1825
|
+
}
|
|
1826
|
+
if (!silent) {
|
|
1827
|
+
this.notify(key, value, oldValue);
|
|
1828
|
+
this.queueUpdate(key, value);
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
if (!silent) {
|
|
1832
|
+
this.processComputed();
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
get(key) {
|
|
1836
|
+
if (!key) return void 0;
|
|
1837
|
+
if (key.includes(".")) {
|
|
1838
|
+
return this.getNested(key);
|
|
1839
|
+
}
|
|
1840
|
+
return this.rawData[key];
|
|
1841
|
+
}
|
|
1842
|
+
getNested(path) {
|
|
1843
|
+
if (!path) return void 0;
|
|
1844
|
+
return path.split(".").reduce((obj, key) => obj?.[key], this.rawData);
|
|
1845
|
+
}
|
|
1846
|
+
setNested(path, value) {
|
|
1847
|
+
const keys2 = path.split(".");
|
|
1848
|
+
const lastKey = keys2.pop();
|
|
1849
|
+
const parent = keys2.reduce((obj, key) => {
|
|
1850
|
+
if (!obj[key]) obj[key] = {};
|
|
1851
|
+
return obj[key];
|
|
1852
|
+
}, this.rawData);
|
|
1853
|
+
const oldValue = parent[lastKey];
|
|
1854
|
+
parent[lastKey] = value;
|
|
1855
|
+
this.notify(path, value, oldValue);
|
|
1856
|
+
this.queueUpdate(path, value);
|
|
1857
|
+
}
|
|
1858
|
+
observe(key, callback) {
|
|
1859
|
+
if (!this.observers[key]) {
|
|
1860
|
+
this.observers[key] = [];
|
|
1861
|
+
}
|
|
1862
|
+
this.observers[key].push(callback);
|
|
1863
|
+
}
|
|
1864
|
+
unobserve(key, callback) {
|
|
1865
|
+
if (this.observers[key]) {
|
|
1866
|
+
this.observers[key] = this.observers[key].filter((cb) => cb !== callback);
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
notify(key, value, oldValue) {
|
|
1870
|
+
if (this.observers[key]) {
|
|
1871
|
+
this.observers[key].forEach((callback) => {
|
|
1872
|
+
try {
|
|
1873
|
+
callback(value, oldValue);
|
|
1874
|
+
} catch (e) {
|
|
1875
|
+
console.error(`Observer error for ${key}:`, e);
|
|
1876
|
+
}
|
|
1877
|
+
});
|
|
1878
|
+
}
|
|
1879
|
+
this.observers["*"]?.forEach((callback) => {
|
|
1880
|
+
try {
|
|
1881
|
+
callback(key, value, oldValue);
|
|
1882
|
+
} catch (e) {
|
|
1883
|
+
console.error("Wildcard observer error:", e);
|
|
1884
|
+
}
|
|
1885
|
+
});
|
|
1886
|
+
}
|
|
1887
|
+
updateElement(element, value) {
|
|
1888
|
+
const binding = element.getAttribute("data-bind");
|
|
1889
|
+
if (!binding) return;
|
|
1890
|
+
const bindings = binding.split("|");
|
|
1891
|
+
bindings.forEach((b) => {
|
|
1892
|
+
const parts = b.split(":");
|
|
1893
|
+
const type = parts[0].trim();
|
|
1894
|
+
const target = parts[1]?.trim();
|
|
1895
|
+
switch (type) {
|
|
1896
|
+
case "text":
|
|
1897
|
+
if (element.textContent !== String(value ?? "")) {
|
|
1898
|
+
element.textContent = value ?? "";
|
|
1899
|
+
}
|
|
1900
|
+
break;
|
|
1901
|
+
case "html":
|
|
1902
|
+
const sanitized = sanitizeHtml$1(value);
|
|
1903
|
+
if (element.innerHTML !== sanitized) {
|
|
1904
|
+
element.innerHTML = sanitized;
|
|
1905
|
+
}
|
|
1906
|
+
break;
|
|
1907
|
+
case "value":
|
|
1908
|
+
if (element.type === "checkbox") {
|
|
1909
|
+
if (element.checked !== !!value) {
|
|
1910
|
+
element.checked = !!value;
|
|
1911
|
+
}
|
|
1912
|
+
} else if (element.value !== String(value ?? "")) {
|
|
1913
|
+
element.value = value ?? "";
|
|
1914
|
+
}
|
|
1915
|
+
break;
|
|
1916
|
+
case "checked":
|
|
1917
|
+
if (element.checked !== !!value) {
|
|
1918
|
+
element.checked = !!value;
|
|
1919
|
+
}
|
|
1920
|
+
break;
|
|
1921
|
+
case "disabled":
|
|
1922
|
+
if (element.disabled !== !!value) {
|
|
1923
|
+
element.disabled = !!value;
|
|
1924
|
+
}
|
|
1925
|
+
break;
|
|
1926
|
+
case "hidden":
|
|
1927
|
+
const display = value ? "none" : "";
|
|
1928
|
+
if (element.style.display !== display) {
|
|
1929
|
+
element.style.display = display;
|
|
1930
|
+
}
|
|
1931
|
+
break;
|
|
1932
|
+
case "class":
|
|
1933
|
+
if (target) {
|
|
1934
|
+
if (value) {
|
|
1935
|
+
element.classList.add(target);
|
|
1936
|
+
} else {
|
|
1937
|
+
element.classList.remove(target);
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
break;
|
|
1941
|
+
case "style":
|
|
1942
|
+
if (target && element.style[target] !== String(value ?? "")) {
|
|
1943
|
+
element.style[target] = value ?? "";
|
|
1944
|
+
}
|
|
1945
|
+
break;
|
|
1946
|
+
case "attr":
|
|
1947
|
+
if (target) {
|
|
1948
|
+
const current = element.getAttribute(target);
|
|
1949
|
+
if (current !== String(value ?? "")) {
|
|
1950
|
+
element.setAttribute(target, value ?? "");
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
break;
|
|
1954
|
+
case "src":
|
|
1955
|
+
if (element.src !== String(value ?? "")) {
|
|
1956
|
+
element.src = value ?? "";
|
|
1957
|
+
}
|
|
1958
|
+
break;
|
|
1959
|
+
case "href":
|
|
1960
|
+
if (element.href !== String(value ?? "")) {
|
|
1961
|
+
element.href = value ?? "";
|
|
1962
|
+
}
|
|
1963
|
+
break;
|
|
1964
|
+
case "placeholder":
|
|
1965
|
+
if (element.placeholder !== String(value ?? "")) {
|
|
1966
|
+
element.placeholder = value ?? "";
|
|
1967
|
+
}
|
|
1968
|
+
break;
|
|
1969
|
+
}
|
|
1970
|
+
});
|
|
1971
|
+
}
|
|
1972
|
+
computed(name, deps, callback) {
|
|
1973
|
+
this.computedProperties[name] = { deps, callback };
|
|
1974
|
+
deps.forEach((dep) => {
|
|
1975
|
+
this.pathTrie.insert(dep);
|
|
1976
|
+
});
|
|
1977
|
+
this.updateComputedProperty(name);
|
|
1978
|
+
}
|
|
1979
|
+
updateComputedProperty(name) {
|
|
1980
|
+
const prop = this.computedProperties[name];
|
|
1981
|
+
if (!prop) return;
|
|
1982
|
+
try {
|
|
1983
|
+
const values2 = prop.deps.map((dep) => this.get(dep));
|
|
1984
|
+
const result = prop.callback(...values2);
|
|
1985
|
+
this.set(name, result, true);
|
|
1986
|
+
} catch (e) {
|
|
1987
|
+
console.error(`Computed error for ${name}:`, e);
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
load(data) {
|
|
1991
|
+
Object.keys(data).forEach((key) => {
|
|
1992
|
+
if (data[key] && typeof data[key] === "object" && !Array.isArray(data[key])) {
|
|
1993
|
+
this.rawData[key] = this.wrapReactive(data[key], key);
|
|
1994
|
+
} else {
|
|
1995
|
+
this.rawData[key] = data[key];
|
|
1996
|
+
}
|
|
1997
|
+
this.queueUpdate(key, this.rawData[key]);
|
|
1998
|
+
});
|
|
1999
|
+
this.processComputed();
|
|
2000
|
+
}
|
|
2001
|
+
reset() {
|
|
2002
|
+
this.rawData = {};
|
|
2003
|
+
this.observers = {};
|
|
2004
|
+
this.elements = {};
|
|
2005
|
+
this.computedProperties = {};
|
|
2006
|
+
this.pathTrie = new PathTrie();
|
|
2007
|
+
this.updateQueue.clear();
|
|
2008
|
+
this.snapshots = [];
|
|
2009
|
+
this.createReactiveData();
|
|
2010
|
+
this.bind();
|
|
2011
|
+
}
|
|
2012
|
+
persist(key, options = {}) {
|
|
2013
|
+
const {
|
|
2014
|
+
storage: storageType = "local",
|
|
2015
|
+
debounce: debounce2 = 0,
|
|
2016
|
+
version = 1,
|
|
2017
|
+
encrypt = false,
|
|
2018
|
+
encryptionKey = null
|
|
2019
|
+
} = options;
|
|
2020
|
+
const storage = storageType === "session" ? sessionStorage : localStorage;
|
|
2021
|
+
this.persistedKeys.set(key, {
|
|
2022
|
+
storage,
|
|
2023
|
+
debounce: debounce2,
|
|
2024
|
+
timeout: null,
|
|
2025
|
+
version,
|
|
2026
|
+
encrypt,
|
|
2027
|
+
encryptionKey
|
|
2028
|
+
});
|
|
2029
|
+
const value = this.get(key);
|
|
2030
|
+
if (value !== void 0) {
|
|
2031
|
+
this._persistSave(key, value, storage, { version, encrypt, encryptionKey });
|
|
2032
|
+
}
|
|
2033
|
+
this.observe(key, (newValue) => {
|
|
2034
|
+
const config2 = this.persistedKeys.get(key);
|
|
2035
|
+
if (!config2) return;
|
|
2036
|
+
if (config2.debounce > 0) {
|
|
2037
|
+
if (config2.timeout) clearTimeout(config2.timeout);
|
|
2038
|
+
config2.timeout = setTimeout(() => {
|
|
2039
|
+
this._persistSave(key, newValue, config2.storage, {
|
|
2040
|
+
version: config2.version,
|
|
2041
|
+
encrypt: config2.encrypt,
|
|
2042
|
+
encryptionKey: config2.encryptionKey
|
|
2043
|
+
});
|
|
2044
|
+
}, config2.debounce);
|
|
2045
|
+
} else {
|
|
2046
|
+
this._persistSave(key, newValue, config2.storage, {
|
|
2047
|
+
version: config2.version,
|
|
2048
|
+
encrypt: config2.encrypt,
|
|
2049
|
+
encryptionKey: config2.encryptionKey
|
|
2050
|
+
});
|
|
2051
|
+
}
|
|
2052
|
+
});
|
|
2053
|
+
}
|
|
2054
|
+
_persistSave(key, value, storage, options = {}) {
|
|
2055
|
+
try {
|
|
2056
|
+
const { version = 1, encrypt = false, encryptionKey = null } = options;
|
|
2057
|
+
const dataToStore = {
|
|
2058
|
+
value,
|
|
2059
|
+
version,
|
|
2060
|
+
timestamp: Date.now()
|
|
2061
|
+
};
|
|
2062
|
+
let serialized = JSON.stringify(dataToStore);
|
|
2063
|
+
if (encrypt && encryptionKey) {
|
|
2064
|
+
serialized = this._encrypt(serialized, encryptionKey);
|
|
2065
|
+
}
|
|
2066
|
+
this._ensureStorageCapacity(storage);
|
|
2067
|
+
storage.setItem(`kupola:${key}`, serialized);
|
|
2068
|
+
} catch (e) {
|
|
2069
|
+
console.warn(`Failed to persist key ${key}:`, e);
|
|
2070
|
+
if (e.name === "QuotaExceededError" && storage === localStorage) {
|
|
2071
|
+
console.warn(`localStorage quota exceeded, trying sessionStorage for key ${key}`);
|
|
2072
|
+
try {
|
|
2073
|
+
sessionStorage.setItem(`kupola:${key}`, JSON.stringify({ value, version: 1 }));
|
|
2074
|
+
} catch (e2) {
|
|
2075
|
+
console.warn(`sessionStorage also failed for key ${key}:`, e2);
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
_ensureStorageCapacity(storage) {
|
|
2081
|
+
try {
|
|
2082
|
+
const testKey = `kupola:__storage_test__`;
|
|
2083
|
+
storage.setItem(testKey, "test");
|
|
2084
|
+
storage.removeItem(testKey);
|
|
2085
|
+
} catch (e) {
|
|
2086
|
+
if (e.name === "QuotaExceededError") {
|
|
2087
|
+
this._cleanupOldStorage(storage);
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
_cleanupOldStorage(storage) {
|
|
2092
|
+
const now2 = Date.now();
|
|
2093
|
+
const maxAge = 30 * 24 * 60 * 60 * 1e3;
|
|
2094
|
+
for (let i = 0; i < storage.length; i++) {
|
|
2095
|
+
const key = storage.key(i);
|
|
2096
|
+
if (key?.startsWith("kupola:")) {
|
|
2097
|
+
try {
|
|
2098
|
+
const data = JSON.parse(storage.getItem(key));
|
|
2099
|
+
if (data.timestamp && now2 - data.timestamp > maxAge) {
|
|
2100
|
+
storage.removeItem(key);
|
|
2101
|
+
}
|
|
2102
|
+
} catch (e) {
|
|
2103
|
+
storage.removeItem(key);
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
_encrypt(text, key) {
|
|
2109
|
+
if (!window.CryptoJS) {
|
|
2110
|
+
console.warn("CryptoJS not available, encryption skipped");
|
|
2111
|
+
return text;
|
|
2112
|
+
}
|
|
2113
|
+
return window.CryptoJS.AES.encrypt(text, key).toString();
|
|
2114
|
+
}
|
|
2115
|
+
_decrypt(ciphertext, key) {
|
|
2116
|
+
if (!window.CryptoJS) {
|
|
2117
|
+
console.warn("CryptoJS not available, decryption skipped");
|
|
2118
|
+
return ciphertext;
|
|
2119
|
+
}
|
|
2120
|
+
try {
|
|
2121
|
+
const bytes = window.CryptoJS.AES.decrypt(ciphertext, key);
|
|
2122
|
+
return bytes.toString(window.CryptoJS.enc.Utf8);
|
|
2123
|
+
} catch (e) {
|
|
2124
|
+
console.warn("Decryption failed:", e);
|
|
2125
|
+
return ciphertext;
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
unpersist(key) {
|
|
2129
|
+
const config2 = this.persistedKeys.get(key);
|
|
2130
|
+
if (config2) {
|
|
2131
|
+
if (config2.timeout) clearTimeout(config2.timeout);
|
|
2132
|
+
config2.storage.removeItem(`kupola:${key}`);
|
|
2133
|
+
this.persistedKeys.delete(key);
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
loadPersisted(options = {}) {
|
|
2137
|
+
const persisted = {};
|
|
2138
|
+
const { encryptionKey = null } = options;
|
|
2139
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
2140
|
+
const key = localStorage.key(i);
|
|
2141
|
+
if (key?.startsWith("kupola:")) {
|
|
2142
|
+
const dataKey = key.replace("kupola:", "");
|
|
2143
|
+
try {
|
|
2144
|
+
const stored = localStorage.getItem(key);
|
|
2145
|
+
let parsed;
|
|
2146
|
+
if (encryptionKey) {
|
|
2147
|
+
const decrypted = this._decrypt(stored, encryptionKey);
|
|
2148
|
+
parsed = JSON.parse(decrypted);
|
|
2149
|
+
} else {
|
|
2150
|
+
parsed = JSON.parse(stored);
|
|
2151
|
+
}
|
|
2152
|
+
if (parsed.version !== void 0 && parsed.version !== options.version) {
|
|
2153
|
+
console.debug(`Skipping outdated data for ${dataKey} (version ${parsed.version})`);
|
|
2154
|
+
continue;
|
|
2155
|
+
}
|
|
2156
|
+
persisted[dataKey] = parsed.value !== void 0 ? parsed.value : parsed;
|
|
2157
|
+
} catch (e) {
|
|
2158
|
+
console.warn(`Failed to load persisted key ${dataKey}:`, e);
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
for (let i = 0; i < sessionStorage.length; i++) {
|
|
2163
|
+
const key = sessionStorage.key(i);
|
|
2164
|
+
if (key?.startsWith("kupola:")) {
|
|
2165
|
+
const dataKey = key.replace("kupola:", "");
|
|
2166
|
+
try {
|
|
2167
|
+
const stored = sessionStorage.getItem(key);
|
|
2168
|
+
let parsed;
|
|
2169
|
+
if (encryptionKey) {
|
|
2170
|
+
const decrypted = this._decrypt(stored, encryptionKey);
|
|
2171
|
+
parsed = JSON.parse(decrypted);
|
|
2172
|
+
} else {
|
|
2173
|
+
parsed = JSON.parse(stored);
|
|
2174
|
+
}
|
|
2175
|
+
if (parsed.version !== void 0 && parsed.version !== options.version) {
|
|
2176
|
+
console.debug(`Skipping outdated data for ${dataKey} (version ${parsed.version})`);
|
|
2177
|
+
continue;
|
|
2178
|
+
}
|
|
2179
|
+
persisted[dataKey] = parsed.value !== void 0 ? parsed.value : parsed;
|
|
2180
|
+
} catch (e) {
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
if (Object.keys(persisted).length > 0) {
|
|
2185
|
+
this.load(persisted);
|
|
2186
|
+
}
|
|
2187
|
+
return persisted;
|
|
2188
|
+
}
|
|
2189
|
+
_clone(data) {
|
|
2190
|
+
if (typeof structuredClone === "function") {
|
|
2191
|
+
try {
|
|
2192
|
+
return structuredClone(data);
|
|
2193
|
+
} catch (e) {
|
|
2194
|
+
console.warn("structuredClone failed, falling back to JSON:", e);
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
return JSON.parse(JSON.stringify(data));
|
|
2198
|
+
}
|
|
2199
|
+
snapshot() {
|
|
2200
|
+
const snapshot = this._clone(this.rawData);
|
|
2201
|
+
this.snapshots.push(snapshot);
|
|
2202
|
+
if (this.snapshots.length > this.snapshotLimit) {
|
|
2203
|
+
this.snapshots.shift();
|
|
2204
|
+
}
|
|
2205
|
+
return this.snapshots.length - 1;
|
|
2206
|
+
}
|
|
2207
|
+
rollback(index = -1) {
|
|
2208
|
+
if (this.snapshots.length === 0) return false;
|
|
2209
|
+
const snapshotIndex = index >= 0 ? index : this.snapshots.length - 1;
|
|
2210
|
+
const snapshot = this.snapshots[snapshotIndex];
|
|
2211
|
+
if (!snapshot) return false;
|
|
2212
|
+
this.rawData = this._clone(snapshot);
|
|
2213
|
+
this.createReactiveData();
|
|
2214
|
+
Object.keys(this.rawData).forEach((key) => {
|
|
2215
|
+
this.queueUpdate(key, this.rawData[key]);
|
|
2216
|
+
});
|
|
2217
|
+
this.processComputed();
|
|
2218
|
+
return true;
|
|
2219
|
+
}
|
|
2220
|
+
getSnapshotCount() {
|
|
2221
|
+
return this.snapshots.length;
|
|
2222
|
+
}
|
|
2223
|
+
clearSnapshots() {
|
|
2224
|
+
this.snapshots = [];
|
|
2225
|
+
}
|
|
2226
|
+
serializeForm(formElement) {
|
|
2227
|
+
const data = {};
|
|
2228
|
+
formElement.querySelectorAll("input, select, textarea").forEach((element) => {
|
|
2229
|
+
const name = element.getAttribute("data-bind");
|
|
2230
|
+
if (!name) return;
|
|
2231
|
+
const parts = name.split(":");
|
|
2232
|
+
const key = parts[1]?.trim();
|
|
2233
|
+
if (!key) return;
|
|
2234
|
+
if (element.type === "checkbox") {
|
|
2235
|
+
if (!data[key]) data[key] = [];
|
|
2236
|
+
if (element.checked) {
|
|
2237
|
+
data[key].push(element.value);
|
|
2238
|
+
}
|
|
2239
|
+
} else if (element.type === "radio") {
|
|
2240
|
+
if (element.checked) {
|
|
2241
|
+
data[key] = element.value;
|
|
2242
|
+
}
|
|
2243
|
+
} else {
|
|
2244
|
+
data[key] = element.value;
|
|
2245
|
+
}
|
|
2246
|
+
});
|
|
2247
|
+
return data;
|
|
2248
|
+
}
|
|
2249
|
+
fillForm(formElement, data) {
|
|
2250
|
+
Object.keys(data).forEach((key) => {
|
|
2251
|
+
formElement.querySelectorAll('[data-bind*=":' + key + '"]').forEach((element) => {
|
|
2252
|
+
if (element.type === "checkbox") {
|
|
2253
|
+
element.checked = Array.isArray(data[key]) ? data[key].includes(element.value) : !!data[key];
|
|
2254
|
+
} else if (element.type === "radio") {
|
|
2255
|
+
element.checked = element.value === data[key];
|
|
2256
|
+
} else {
|
|
2257
|
+
element.value = data[key] ?? "";
|
|
2258
|
+
}
|
|
2259
|
+
});
|
|
2260
|
+
});
|
|
2261
|
+
}
|
|
2262
|
+
createReactive(target, path = "") {
|
|
2263
|
+
if (target[REACTIVE_SYMBOLS.isReactive]) return target;
|
|
2264
|
+
if (this._proxyCache.has(target)) {
|
|
2265
|
+
return this._proxyCache.get(target);
|
|
2266
|
+
}
|
|
2267
|
+
const handler = {
|
|
2268
|
+
get: (target2, key, receiver2) => {
|
|
2269
|
+
if (key === "__raw__") return target2;
|
|
2270
|
+
if (key === REACTIVE_SYMBOLS.parent || key === "__parent__") return target2[REACTIVE_SYMBOLS.parent];
|
|
2271
|
+
if (key === REACTIVE_SYMBOLS.path || key === "__path__") return target2[REACTIVE_SYMBOLS.path];
|
|
2272
|
+
if (key === REACTIVE_SYMBOLS.isReactive || key === "__isReactive__") return true;
|
|
2273
|
+
const result = Reflect.get(target2, key, receiver2);
|
|
2274
|
+
if (result && typeof result === "object" && !Array.isArray(result)) {
|
|
2275
|
+
return this.wrapReactive(result, `${target2[REACTIVE_SYMBOLS.path]}${target2[REACTIVE_SYMBOLS.path] ? "." : ""}${key}`);
|
|
2276
|
+
}
|
|
2277
|
+
return result;
|
|
2278
|
+
},
|
|
2279
|
+
set: (target2, key, value, receiver2) => {
|
|
2280
|
+
if (key === REACTIVE_SYMBOLS.parent || key === REACTIVE_SYMBOLS.path || key === REACTIVE_SYMBOLS.isReactive || key === "__parent__" || key === "__path__" || key === "__isReactive__") {
|
|
2281
|
+
return true;
|
|
2282
|
+
}
|
|
2283
|
+
const oldValue = Reflect.get(target2, key, receiver2);
|
|
2284
|
+
const result = Reflect.set(target2, key, value, receiver2);
|
|
2285
|
+
const fullPath = `${target2[REACTIVE_SYMBOLS.path]}${target2[REACTIVE_SYMBOLS.path] ? "." : ""}${key}`;
|
|
2286
|
+
this.notify(fullPath, value, oldValue);
|
|
2287
|
+
this.queueUpdate(fullPath, value);
|
|
2288
|
+
return result;
|
|
2289
|
+
},
|
|
2290
|
+
deleteProperty: (target2, key) => {
|
|
2291
|
+
if (key === REACTIVE_SYMBOLS.parent || key === REACTIVE_SYMBOLS.path || key === REACTIVE_SYMBOLS.isReactive) {
|
|
2292
|
+
return false;
|
|
2293
|
+
}
|
|
2294
|
+
const oldValue = Reflect.get(target2, key);
|
|
2295
|
+
const result = Reflect.deleteProperty(target2, key);
|
|
2296
|
+
const fullPath = `${target2[REACTIVE_SYMBOLS.path]}${target2[REACTIVE_SYMBOLS.path] ? "." : ""}${key}`;
|
|
2297
|
+
this.notify(fullPath, void 0, oldValue);
|
|
2298
|
+
this.queueUpdate(fullPath, void 0);
|
|
2299
|
+
return result;
|
|
2300
|
+
},
|
|
2301
|
+
has: (target2, key) => {
|
|
2302
|
+
if (key === "__raw__" || key === REACTIVE_SYMBOLS.parent || key === REACTIVE_SYMBOLS.path || key === REACTIVE_SYMBOLS.isReactive || key === "__parent__" || key === "__path__" || key === "__isReactive__") {
|
|
2303
|
+
return true;
|
|
2304
|
+
}
|
|
2305
|
+
return key in target2;
|
|
2306
|
+
},
|
|
2307
|
+
ownKeys: (target2) => {
|
|
2308
|
+
return Reflect.ownKeys(target2).filter(
|
|
2309
|
+
(key) => key !== REACTIVE_SYMBOLS.parent && key !== REACTIVE_SYMBOLS.path && key !== REACTIVE_SYMBOLS.isReactive
|
|
2310
|
+
);
|
|
2311
|
+
},
|
|
2312
|
+
getOwnPropertyDescriptor: (target2, key) => {
|
|
2313
|
+
if (key === REACTIVE_SYMBOLS.parent || key === REACTIVE_SYMBOLS.path || key === REACTIVE_SYMBOLS.isReactive) {
|
|
2314
|
+
return {
|
|
2315
|
+
configurable: false,
|
|
2316
|
+
enumerable: false,
|
|
2317
|
+
writable: false,
|
|
2318
|
+
value: target2[key]
|
|
2319
|
+
};
|
|
2320
|
+
}
|
|
2321
|
+
return Reflect.getOwnPropertyDescriptor(target2, key);
|
|
2322
|
+
}
|
|
2323
|
+
};
|
|
2324
|
+
const proxy = new Proxy(target, handler);
|
|
2325
|
+
target[REACTIVE_SYMBOLS.parent] = target;
|
|
2326
|
+
target[REACTIVE_SYMBOLS.path] = path;
|
|
2327
|
+
target[REACTIVE_SYMBOLS.isReactive] = true;
|
|
2328
|
+
this._proxyCache.set(target, proxy);
|
|
2329
|
+
Object.keys(target).forEach((key) => {
|
|
2330
|
+
if (target[key] && typeof target[key] === "object" && !Array.isArray(target[key])) {
|
|
2331
|
+
target[key] = this.wrapReactive(target[key], `${path}${path ? "." : ""}${key}`);
|
|
2332
|
+
}
|
|
2333
|
+
});
|
|
2334
|
+
return proxy;
|
|
2335
|
+
}
|
|
2336
|
+
bind() {
|
|
2337
|
+
document.querySelectorAll("[data-bind]").forEach((element) => {
|
|
2338
|
+
this._bindElement(element);
|
|
2339
|
+
});
|
|
2340
|
+
if (!this._mutationObserver) {
|
|
2341
|
+
this._isObserving = false;
|
|
2342
|
+
this._mutationObserver = new MutationObserver((mutations) => {
|
|
2343
|
+
if (this._isObserving) return;
|
|
2344
|
+
this._isObserving = true;
|
|
2345
|
+
try {
|
|
2346
|
+
mutations.forEach((mutation) => {
|
|
2347
|
+
mutation.addedNodes.forEach((node) => {
|
|
2348
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
2349
|
+
const bindElements = node.querySelectorAll("[data-bind]");
|
|
2350
|
+
bindElements.forEach((element) => this._bindElement(element));
|
|
2351
|
+
if (node.hasAttribute && node.hasAttribute("data-bind")) {
|
|
2352
|
+
this._bindElement(node);
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
});
|
|
2356
|
+
});
|
|
2357
|
+
} finally {
|
|
2358
|
+
this._isObserving = false;
|
|
2359
|
+
}
|
|
2360
|
+
});
|
|
2361
|
+
this._mutationObserver.observe(document.body, {
|
|
2362
|
+
childList: true,
|
|
2363
|
+
subtree: true
|
|
2364
|
+
});
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
_bindElement(element) {
|
|
2368
|
+
const binding = element.getAttribute("data-bind");
|
|
2369
|
+
const parts = binding.split(":");
|
|
2370
|
+
parts[0].split("|")[0].trim();
|
|
2371
|
+
const key = parts[1]?.trim();
|
|
2372
|
+
if (!key) return;
|
|
2373
|
+
this.pathTrie.insert(key);
|
|
2374
|
+
if (!this.elements[key]) {
|
|
2375
|
+
this.elements[key] = [];
|
|
2376
|
+
}
|
|
2377
|
+
if (!this.elements[key].includes(element)) {
|
|
2378
|
+
this.elements[key].push(element);
|
|
2379
|
+
}
|
|
2380
|
+
if (element.tagName === "INPUT" || element.tagName === "TEXTAREA" || element.tagName === "SELECT") {
|
|
2381
|
+
const existingHandler = element.__kupolaBindHandler;
|
|
2382
|
+
if (existingHandler) {
|
|
2383
|
+
element.removeEventListener("input", existingHandler);
|
|
2384
|
+
}
|
|
2385
|
+
const handler = () => {
|
|
2386
|
+
const value = element.type === "checkbox" ? element.checked : element.value;
|
|
2387
|
+
if (key.includes(".")) {
|
|
2388
|
+
this.setNested(key, value);
|
|
2389
|
+
} else {
|
|
2390
|
+
this.set(key, value);
|
|
2391
|
+
}
|
|
2392
|
+
};
|
|
2393
|
+
element.__kupolaBindHandler = handler;
|
|
2394
|
+
element.addEventListener("input", handler);
|
|
2395
|
+
}
|
|
2396
|
+
if (this.rawData[key] !== void 0) {
|
|
2397
|
+
this.updateElement(element, this.rawData[key]);
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
destroy() {
|
|
2401
|
+
if (this._mutationObserver) {
|
|
2402
|
+
this._mutationObserver.disconnect();
|
|
2403
|
+
this._mutationObserver = null;
|
|
2404
|
+
}
|
|
2405
|
+
Object.values(this.elements).forEach((elements) => {
|
|
2406
|
+
elements.forEach((element) => {
|
|
2407
|
+
const handler = element.__kupolaBindHandler;
|
|
2408
|
+
if (handler) {
|
|
2409
|
+
element.removeEventListener("input", handler);
|
|
2410
|
+
delete element.__kupolaBindHandler;
|
|
2411
|
+
}
|
|
2412
|
+
});
|
|
2413
|
+
});
|
|
2414
|
+
this.persistedKeys.forEach((config2, key) => {
|
|
2415
|
+
if (config2.timeout) clearTimeout(config2.timeout);
|
|
2416
|
+
});
|
|
2417
|
+
this.persistedKeys.clear();
|
|
2418
|
+
this.rawData = {};
|
|
2419
|
+
this.observers = {};
|
|
2420
|
+
this.elements = {};
|
|
2421
|
+
this.computedProperties = {};
|
|
2422
|
+
this.pathTrie = new PathTrie();
|
|
2423
|
+
this.updateQueue.clear();
|
|
2424
|
+
this.snapshots = [];
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
class KupolaStore {
|
|
2428
|
+
constructor(name, options = {}) {
|
|
2429
|
+
this.name = name;
|
|
2430
|
+
this._stateKey = `__store_${name}__`;
|
|
2431
|
+
const initialState = options.state ? options.state() : {};
|
|
2432
|
+
this.getters = options.getters || {};
|
|
2433
|
+
this.actions = options.actions || {};
|
|
2434
|
+
this.mutations = options.mutations || {};
|
|
2435
|
+
this.observers = {};
|
|
2436
|
+
if (kupolaData) {
|
|
2437
|
+
kupolaData.set(this._stateKey, initialState);
|
|
2438
|
+
this.state = kupolaData.data?.[this._stateKey] || kupolaData.createReactive(initialState, this._stateKey);
|
|
2439
|
+
kupolaData.observe(this._stateKey, (newState) => {
|
|
2440
|
+
this.notify(newState);
|
|
2441
|
+
});
|
|
2442
|
+
} else {
|
|
2443
|
+
this.state = initialState;
|
|
2444
|
+
}
|
|
2445
|
+
this._bindGetters();
|
|
2446
|
+
this._bindActions();
|
|
2447
|
+
}
|
|
2448
|
+
_bindGetters() {
|
|
2449
|
+
Object.keys(this.getters).forEach((name) => {
|
|
2450
|
+
Object.defineProperty(this, name, {
|
|
2451
|
+
get: () => this.getters[name](this.state),
|
|
2452
|
+
enumerable: true
|
|
2453
|
+
});
|
|
2454
|
+
});
|
|
2455
|
+
}
|
|
2456
|
+
_bindActions() {
|
|
2457
|
+
Object.keys(this.actions).forEach((name) => {
|
|
2458
|
+
this[name] = (...args) => {
|
|
2459
|
+
return this.actions[name]({
|
|
2460
|
+
state: this.state,
|
|
2461
|
+
commit: this.commit.bind(this),
|
|
2462
|
+
dispatch: this.dispatch.bind(this),
|
|
2463
|
+
getters: this
|
|
2464
|
+
}, ...args);
|
|
2465
|
+
};
|
|
2466
|
+
});
|
|
2467
|
+
}
|
|
2468
|
+
commit(type, payload) {
|
|
2469
|
+
const mutation = this.mutations[type];
|
|
2470
|
+
if (!mutation) {
|
|
2471
|
+
console.warn(`Mutation ${type} not found in store ${this.name}`);
|
|
2472
|
+
return;
|
|
2473
|
+
}
|
|
2474
|
+
mutation(this.state, payload);
|
|
2475
|
+
}
|
|
2476
|
+
dispatch(type, payload) {
|
|
2477
|
+
const action = this.actions[type];
|
|
2478
|
+
if (!action) {
|
|
2479
|
+
console.warn(`Action ${type} not found in store ${this.name}`);
|
|
2480
|
+
return;
|
|
2481
|
+
}
|
|
2482
|
+
return action({
|
|
2483
|
+
state: this.state,
|
|
2484
|
+
commit: this.commit.bind(this),
|
|
2485
|
+
dispatch: this.dispatch.bind(this),
|
|
2486
|
+
getters: this
|
|
2487
|
+
}, payload);
|
|
2488
|
+
}
|
|
2489
|
+
observe(callback) {
|
|
2490
|
+
if (!this.observers["*"]) {
|
|
2491
|
+
this.observers["*"] = [];
|
|
2492
|
+
}
|
|
2493
|
+
this.observers["*"].push(callback);
|
|
2494
|
+
return callback;
|
|
2495
|
+
}
|
|
2496
|
+
unobserve(callback) {
|
|
2497
|
+
if (this.observers["*"]) {
|
|
2498
|
+
this.observers["*"] = this.observers["*"].filter((cb) => cb !== callback);
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
notify(newState) {
|
|
2502
|
+
if (this.observers["*"]) {
|
|
2503
|
+
this.observers["*"].forEach((callback) => {
|
|
2504
|
+
try {
|
|
2505
|
+
callback(newState);
|
|
2506
|
+
} catch (e) {
|
|
2507
|
+
console.error(`Observer error for store ${this.name}:`, e);
|
|
2508
|
+
}
|
|
2509
|
+
});
|
|
2510
|
+
}
|
|
2511
|
+
if (kupolaData) {
|
|
2512
|
+
kupolaData.set(this.name, newState);
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
toJSON() {
|
|
2516
|
+
return {
|
|
2517
|
+
name: this.name,
|
|
2518
|
+
state: this.state,
|
|
2519
|
+
getters: Object.keys(this.getters).reduce((acc, name) => {
|
|
2520
|
+
acc[name] = this[name];
|
|
2521
|
+
return acc;
|
|
2522
|
+
}, {})
|
|
2523
|
+
};
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
class KupolaStoreManager {
|
|
2527
|
+
constructor() {
|
|
2528
|
+
this.stores = /* @__PURE__ */ new Map();
|
|
2529
|
+
}
|
|
2530
|
+
createStore(name, options) {
|
|
2531
|
+
const store = new KupolaStore(name, options);
|
|
2532
|
+
this.stores.set(name, store);
|
|
2533
|
+
return store;
|
|
2534
|
+
}
|
|
2535
|
+
getStore(name) {
|
|
2536
|
+
return this.stores.get(name);
|
|
2537
|
+
}
|
|
2538
|
+
registerStore(store) {
|
|
2539
|
+
if (store instanceof KupolaStore) {
|
|
2540
|
+
this.stores.set(store.name, store);
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
dispose() {
|
|
2544
|
+
this.stores.clear();
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
class KupolaEventBus {
|
|
2548
|
+
constructor() {
|
|
2549
|
+
this.events = {};
|
|
2550
|
+
this.delegatedEvents = {};
|
|
2551
|
+
this.eventListeners = {};
|
|
2552
|
+
}
|
|
2553
|
+
on(eventName, callback) {
|
|
2554
|
+
if (!this.events[eventName]) {
|
|
2555
|
+
this.events[eventName] = [];
|
|
2556
|
+
}
|
|
2557
|
+
this.events[eventName].push(callback);
|
|
2558
|
+
return callback;
|
|
2559
|
+
}
|
|
2560
|
+
off(eventName, callback) {
|
|
2561
|
+
if (this.events[eventName]) {
|
|
2562
|
+
this.events[eventName] = this.events[eventName].filter((cb) => cb !== callback);
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
emit(eventName, data) {
|
|
2566
|
+
if (this.events[eventName]) {
|
|
2567
|
+
this.events[eventName].forEach((callback) => {
|
|
2568
|
+
try {
|
|
2569
|
+
callback(data);
|
|
2570
|
+
} catch (e) {
|
|
2571
|
+
console.error(`Error in event handler for ${eventName}:`, e);
|
|
2572
|
+
}
|
|
2573
|
+
});
|
|
2574
|
+
}
|
|
2575
|
+
this.events["*"]?.forEach((callback) => {
|
|
2576
|
+
try {
|
|
2577
|
+
callback(eventName, data);
|
|
2578
|
+
} catch (e) {
|
|
2579
|
+
console.error(`Error in wildcard event handler:`, e);
|
|
2580
|
+
}
|
|
2581
|
+
});
|
|
2582
|
+
}
|
|
2583
|
+
once(eventName, callback) {
|
|
2584
|
+
const onceCallback = (data) => {
|
|
2585
|
+
callback(data);
|
|
2586
|
+
this.off(eventName, onceCallback);
|
|
2587
|
+
};
|
|
2588
|
+
this.on(eventName, onceCallback);
|
|
2589
|
+
return onceCallback;
|
|
2590
|
+
}
|
|
2591
|
+
delegate(selector, eventName, callback) {
|
|
2592
|
+
if (!this.delegatedEvents[eventName]) {
|
|
2593
|
+
this.delegatedEvents[eventName] = [];
|
|
2594
|
+
const listener = (e) => {
|
|
2595
|
+
this.delegatedEvents[eventName].forEach(({ selector: sel, cb }) => {
|
|
2596
|
+
if (e.target.matches(sel) || e.target.closest(sel)) {
|
|
2597
|
+
cb(e);
|
|
2598
|
+
}
|
|
2599
|
+
});
|
|
2600
|
+
};
|
|
2601
|
+
document.addEventListener(eventName, listener);
|
|
2602
|
+
this.eventListeners[eventName] = listener;
|
|
2603
|
+
}
|
|
2604
|
+
this.delegatedEvents[eventName].push({ selector, cb: callback });
|
|
2605
|
+
return callback;
|
|
2606
|
+
}
|
|
2607
|
+
undelegate(selector, eventName) {
|
|
2608
|
+
if (this.delegatedEvents[eventName]) {
|
|
2609
|
+
this.delegatedEvents[eventName] = this.delegatedEvents[eventName].filter(
|
|
2610
|
+
(item) => item.selector !== selector
|
|
2611
|
+
);
|
|
2612
|
+
if (this.delegatedEvents[eventName].length === 0) {
|
|
2613
|
+
const listener = this.eventListeners[eventName];
|
|
2614
|
+
if (listener) {
|
|
2615
|
+
document.removeEventListener(eventName, listener);
|
|
2616
|
+
delete this.eventListeners[eventName];
|
|
2617
|
+
}
|
|
2618
|
+
delete this.delegatedEvents[eventName];
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
destroy() {
|
|
2623
|
+
Object.entries(this.eventListeners).forEach(([eventName, listener]) => {
|
|
2624
|
+
document.removeEventListener(eventName, listener);
|
|
2625
|
+
});
|
|
2626
|
+
this.events = {};
|
|
2627
|
+
this.delegatedEvents = {};
|
|
2628
|
+
this.eventListeners = {};
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
function ref(initialValue = null) {
|
|
2632
|
+
const refObj = {
|
|
2633
|
+
_value: initialValue,
|
|
2634
|
+
_subscribers: /* @__PURE__ */ new Set()
|
|
2635
|
+
};
|
|
2636
|
+
Object.defineProperty(refObj, "value", {
|
|
2637
|
+
configurable: true,
|
|
2638
|
+
enumerable: true,
|
|
2639
|
+
get() {
|
|
2640
|
+
return refObj._value;
|
|
2641
|
+
},
|
|
2642
|
+
set(newValue) {
|
|
2643
|
+
if (newValue !== refObj._value) {
|
|
2644
|
+
refObj._value = newValue;
|
|
2645
|
+
refObj._subscribers.forEach((sub) => sub(newValue));
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
});
|
|
2649
|
+
refObj.subscribe = (callback) => {
|
|
2650
|
+
refObj._subscribers.add(callback);
|
|
2651
|
+
return {
|
|
2652
|
+
unsubscribe() {
|
|
2653
|
+
refObj._subscribers.delete(callback);
|
|
2654
|
+
}
|
|
2655
|
+
};
|
|
2656
|
+
};
|
|
2657
|
+
return refObj;
|
|
2658
|
+
}
|
|
2659
|
+
const kupolaData = new KupolaDataBind();
|
|
2660
|
+
const kupolaEvents = new KupolaEventBus();
|
|
2661
|
+
const kupolaStoreManager = new KupolaStoreManager();
|
|
2662
|
+
function createStore(name, options) {
|
|
2663
|
+
return kupolaStoreManager.createStore(name, options);
|
|
2664
|
+
}
|
|
2665
|
+
function getStore(name) {
|
|
2666
|
+
return kupolaStoreManager.getStore(name);
|
|
2667
|
+
}
|
|
2668
|
+
const config = {
|
|
2669
|
+
paths: {
|
|
2670
|
+
icons: "/icons/",
|
|
2671
|
+
base: "/"
|
|
2672
|
+
},
|
|
2673
|
+
theme: {
|
|
2674
|
+
default: "dark",
|
|
2675
|
+
brand: "zengqing"
|
|
2676
|
+
},
|
|
2677
|
+
i18n: {
|
|
2678
|
+
locale: "zh-CN",
|
|
2679
|
+
fallbackLocale: "en-US"
|
|
2680
|
+
},
|
|
2681
|
+
http: {
|
|
2682
|
+
baseURL: "",
|
|
2683
|
+
timeout: 1e4,
|
|
2684
|
+
headers: {},
|
|
2685
|
+
withCredentials: false
|
|
2686
|
+
},
|
|
2687
|
+
zIndex: {
|
|
2688
|
+
modal: 1e3,
|
|
2689
|
+
dropdown: 2e3,
|
|
2690
|
+
tooltip: 2100,
|
|
2691
|
+
popover: 2200,
|
|
2692
|
+
datepicker: 2300,
|
|
2693
|
+
message: 3e3,
|
|
2694
|
+
notification: 3100,
|
|
2695
|
+
loading: 5e3
|
|
2696
|
+
},
|
|
2697
|
+
ui: {
|
|
2698
|
+
defaultSize: "md",
|
|
2699
|
+
modal: {
|
|
2700
|
+
backdropClick: true
|
|
2701
|
+
},
|
|
2702
|
+
dropdown: {
|
|
2703
|
+
closeOnClick: true
|
|
2704
|
+
},
|
|
2705
|
+
datepicker: {
|
|
2706
|
+
weekStart: 1
|
|
2707
|
+
},
|
|
2708
|
+
tooltip: {
|
|
2709
|
+
delay: 300
|
|
2710
|
+
}
|
|
2711
|
+
},
|
|
2712
|
+
performance: {
|
|
2713
|
+
lazyLoad: false,
|
|
2714
|
+
debounceDelay: 200,
|
|
2715
|
+
throttleDelay: 100,
|
|
2716
|
+
animationEnabled: true
|
|
2717
|
+
},
|
|
2718
|
+
security: {
|
|
2719
|
+
xssProtection: true,
|
|
2720
|
+
sanitizeHtml: {
|
|
2721
|
+
enabled: true,
|
|
2722
|
+
allowedTags: ["b", "i", "u", "em", "strong", "a", "br", "p", "span", "div", "img"],
|
|
2723
|
+
allowedAttributes: {
|
|
2724
|
+
"a": ["href", "target", "rel"],
|
|
2725
|
+
"img": ["src", "alt", "width", "height"],
|
|
2726
|
+
"span": ["class", "style"],
|
|
2727
|
+
"div": ["class", "style"]
|
|
2728
|
+
}
|
|
2729
|
+
},
|
|
2730
|
+
maskData: {
|
|
2731
|
+
enabled: true,
|
|
2732
|
+
patterns: {
|
|
2733
|
+
phone: { regex: "^(\\d{3})\\d{4}(\\d{4})$", replace: "$1****$2" },
|
|
2734
|
+
email: { regex: "^(.)(.*)(@.*)$", replace: "$1***$3" },
|
|
2735
|
+
idCard: { regex: "^(\\d{6})\\d{8}(\\d{4})$", replace: "$1********$2" },
|
|
2736
|
+
bankCard: { regex: "^(\\d{4})\\d{8}(\\d{4})$", replace: "$1 **** **** $2" }
|
|
2737
|
+
}
|
|
2738
|
+
},
|
|
2739
|
+
secureId: {
|
|
2740
|
+
length: 16,
|
|
2741
|
+
charset: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
|
2742
|
+
}
|
|
2743
|
+
},
|
|
2744
|
+
message: {
|
|
2745
|
+
duration: 3e3,
|
|
2746
|
+
position: "top-right",
|
|
2747
|
+
maxCount: 5
|
|
2748
|
+
},
|
|
2749
|
+
notification: {
|
|
2750
|
+
duration: 4500,
|
|
2751
|
+
position: "top-right"
|
|
2752
|
+
},
|
|
2753
|
+
validation: {
|
|
2754
|
+
defaultRules: [],
|
|
2755
|
+
showErrors: true,
|
|
2756
|
+
trigger: "blur"
|
|
2757
|
+
},
|
|
2758
|
+
components: {
|
|
2759
|
+
autoInit: true,
|
|
2760
|
+
silentErrors: false
|
|
2761
|
+
}
|
|
2762
|
+
};
|
|
2763
|
+
const configChangeListeners = [];
|
|
2764
|
+
function loadConfigFromGlobal() {
|
|
2765
|
+
if (typeof window !== "undefined" && window.kupolaConfig) {
|
|
2766
|
+
try {
|
|
2767
|
+
mergeDeep(config, window.kupolaConfig);
|
|
2768
|
+
notifyConfigChange();
|
|
2769
|
+
} catch (e) {
|
|
2770
|
+
console.warn("[Kupola] Failed to parse window.kupolaConfig:", e);
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2774
|
+
function notifyConfigChange() {
|
|
2775
|
+
configChangeListeners.forEach((listener) => {
|
|
2776
|
+
try {
|
|
2777
|
+
listener(config);
|
|
2778
|
+
} catch (e) {
|
|
2779
|
+
console.warn("[Kupola] Error in config change listener:", e);
|
|
2780
|
+
}
|
|
2781
|
+
});
|
|
2782
|
+
}
|
|
2783
|
+
function onConfigChange(listener) {
|
|
2784
|
+
if (typeof listener === "function") {
|
|
2785
|
+
configChangeListeners.push(listener);
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
function offConfigChange(listener) {
|
|
2789
|
+
const index = configChangeListeners.indexOf(listener);
|
|
2790
|
+
if (index > -1) {
|
|
2791
|
+
configChangeListeners.splice(index, 1);
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
if (typeof document !== "undefined") {
|
|
2795
|
+
if (document.readyState === "loading") {
|
|
2796
|
+
document.addEventListener("DOMContentLoaded", loadConfigFromGlobal);
|
|
2797
|
+
} else {
|
|
2798
|
+
loadConfigFromGlobal();
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
function setConfig(options) {
|
|
2802
|
+
mergeDeep(config, options);
|
|
2803
|
+
notifyConfigChange();
|
|
2804
|
+
}
|
|
2805
|
+
function getConfig(key) {
|
|
2806
|
+
if (!key) {
|
|
2807
|
+
return config;
|
|
2808
|
+
}
|
|
2809
|
+
return getNestedValue(config, key);
|
|
2810
|
+
}
|
|
2811
|
+
function getIconsPath() {
|
|
2812
|
+
return config.paths.base + config.paths.icons.replace(/^\//, "");
|
|
2813
|
+
}
|
|
2814
|
+
function getBasePath() {
|
|
2815
|
+
return config.paths.base;
|
|
2816
|
+
}
|
|
2817
|
+
function getDefaultTheme() {
|
|
2818
|
+
return config.theme.default;
|
|
2819
|
+
}
|
|
2820
|
+
function getDefaultBrand() {
|
|
2821
|
+
return config.theme.brand;
|
|
2822
|
+
}
|
|
2823
|
+
function getHttpConfig() {
|
|
2824
|
+
return config.http;
|
|
2825
|
+
}
|
|
2826
|
+
function getUiConfig() {
|
|
2827
|
+
return config.ui;
|
|
2828
|
+
}
|
|
2829
|
+
function getZIndexConfig() {
|
|
2830
|
+
return config.zIndex;
|
|
2831
|
+
}
|
|
2832
|
+
function getSecurityConfig() {
|
|
2833
|
+
return config.security;
|
|
2834
|
+
}
|
|
2835
|
+
function getPerformanceConfig() {
|
|
2836
|
+
return config.performance;
|
|
2837
|
+
}
|
|
2838
|
+
function getMessageConfig() {
|
|
2839
|
+
return config.message;
|
|
2840
|
+
}
|
|
2841
|
+
function getNotificationConfig() {
|
|
2842
|
+
return config.notification;
|
|
2843
|
+
}
|
|
2844
|
+
function getValidationConfig() {
|
|
2845
|
+
return config.validation;
|
|
2846
|
+
}
|
|
2847
|
+
function mergeDeep(target, source) {
|
|
2848
|
+
for (const key in source) {
|
|
2849
|
+
if (source[key] instanceof Object && key in target && target[key] instanceof Object) {
|
|
2850
|
+
mergeDeep(target[key], source[key]);
|
|
2851
|
+
} else {
|
|
2852
|
+
target[key] = source[key];
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
return target;
|
|
2856
|
+
}
|
|
2857
|
+
function getNestedValue(obj, key) {
|
|
2858
|
+
return key.split(".").reduce((o, k) => (o && o[k]) !== void 0 ? o[k] : void 0, obj);
|
|
2859
|
+
}
|
|
2860
|
+
const THEME_KEY = "kupola-theme";
|
|
2861
|
+
const BRAND_KEY = "kupola-brand";
|
|
2862
|
+
onConfigChange((newConfig) => {
|
|
2863
|
+
const toggleBtn = document.querySelector("[data-theme-toggle]");
|
|
2864
|
+
if (toggleBtn) {
|
|
2865
|
+
const currentTheme = getTheme();
|
|
2866
|
+
updateThemeIcon(toggleBtn);
|
|
2867
|
+
setTheme(currentTheme);
|
|
2868
|
+
}
|
|
2869
|
+
});
|
|
2870
|
+
const BRAND_OPTIONS = [
|
|
2871
|
+
{ id: "green", name: "翠绿", color: "#32F08C" },
|
|
2872
|
+
{ id: "xionghuang", name: "雄黄", color: "#FF9900" },
|
|
2873
|
+
{ id: "jianghuang", name: "姜黄", color: "#E2C027" },
|
|
2874
|
+
{ id: "lanlv", name: "蓝绿", color: "#12A182" },
|
|
2875
|
+
{ id: "kongquelan", name: "孔雀蓝", color: "#0EB0C9" },
|
|
2876
|
+
{ id: "meiguizi", name: "玫瑰紫", color: "#BA2F7B" },
|
|
2877
|
+
{ id: "shihong", name: "柿红", color: "#F2481B" },
|
|
2878
|
+
{ id: "quhong", name: "紫云", color: "#B1A6CC" },
|
|
2879
|
+
{ id: "shanchahong", name: "山茶红", color: "#F05A46" },
|
|
2880
|
+
{ id: "zengqing", name: "曾青", color: "#535164" },
|
|
2881
|
+
{ id: "roulan", name: "柔蓝", color: "#106898" }
|
|
2882
|
+
];
|
|
2883
|
+
function getTheme() {
|
|
2884
|
+
return localStorage.getItem(THEME_KEY) || getDefaultTheme();
|
|
2885
|
+
}
|
|
2886
|
+
function setTheme(theme) {
|
|
2887
|
+
if (theme !== "dark" && theme !== "light") return;
|
|
2888
|
+
var root = document.documentElement;
|
|
2889
|
+
if (root.hasAttribute("data-kupola-theme-preloaded")) {
|
|
2890
|
+
root.style.removeProperty("--bg-base-default");
|
|
2891
|
+
root.style.removeProperty("--text-default");
|
|
2892
|
+
root.removeAttribute("data-kupola-theme-preloaded");
|
|
2893
|
+
}
|
|
2894
|
+
root.setAttribute("data-theme", theme);
|
|
2895
|
+
localStorage.setItem(THEME_KEY, theme);
|
|
2896
|
+
const toggleBtn = document.querySelector("[data-theme-toggle]");
|
|
2897
|
+
if (toggleBtn) {
|
|
2898
|
+
toggleBtn.setAttribute("data-current-theme", theme);
|
|
2899
|
+
updateThemeIcon(toggleBtn);
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2902
|
+
function getBrand() {
|
|
2903
|
+
return localStorage.getItem(BRAND_KEY) || getDefaultBrand();
|
|
2904
|
+
}
|
|
2905
|
+
function setBrand(brandId) {
|
|
2906
|
+
const brand = BRAND_OPTIONS.find((b) => b.id === brandId);
|
|
2907
|
+
if (!brand) return;
|
|
2908
|
+
document.documentElement.setAttribute("data-brand", brandId);
|
|
2909
|
+
localStorage.setItem(BRAND_KEY, brandId);
|
|
2910
|
+
const brandToggle = document.querySelector("[data-brand-toggle]");
|
|
2911
|
+
if (brandToggle) {
|
|
2912
|
+
brandToggle.setAttribute("data-current-brand", brandId);
|
|
2913
|
+
const brandIcon = brandToggle.querySelector(".brand-icon");
|
|
2914
|
+
if (brandIcon) {
|
|
2915
|
+
brandIcon.style.backgroundColor = brand.color;
|
|
2916
|
+
}
|
|
2917
|
+
const brandName = brandToggle.querySelector(".brand-name");
|
|
2918
|
+
if (brandName) {
|
|
2919
|
+
brandName.textContent = brand.name;
|
|
2920
|
+
}
|
|
2921
|
+
}
|
|
2922
|
+
const brandButtons = document.querySelectorAll("[data-brand-btn]");
|
|
2923
|
+
brandButtons.forEach((btn) => {
|
|
2924
|
+
if (btn.getAttribute("data-brand-btn") === brandId) {
|
|
2925
|
+
btn.classList.add("is-active");
|
|
2926
|
+
} else {
|
|
2927
|
+
btn.classList.remove("is-active");
|
|
2928
|
+
}
|
|
2929
|
+
});
|
|
2930
|
+
}
|
|
2931
|
+
function updateThemeIcon(toggleBtn) {
|
|
2932
|
+
const iconEl = toggleBtn.querySelector(".theme-icon");
|
|
2933
|
+
if (iconEl) {
|
|
2934
|
+
const currentTheme = getTheme();
|
|
2935
|
+
const iconsPath = getIconsPath();
|
|
2936
|
+
iconEl.src = currentTheme === "dark" ? iconsPath + "sun.svg" : iconsPath + "moon.svg";
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
function _themeToggleHandler(e) {
|
|
2940
|
+
e.preventDefault();
|
|
2941
|
+
const currentTheme = getTheme();
|
|
2942
|
+
const newTheme = currentTheme === "dark" ? "light" : "dark";
|
|
2943
|
+
setTheme(newTheme);
|
|
2944
|
+
}
|
|
2945
|
+
function initTheme() {
|
|
2946
|
+
var root = document.documentElement;
|
|
2947
|
+
if (root.hasAttribute("data-kupola-theme-preloaded")) {
|
|
2948
|
+
root.style.removeProperty("--bg-base-default");
|
|
2949
|
+
root.style.removeProperty("--text-default");
|
|
2950
|
+
root.removeAttribute("data-kupola-theme-preloaded");
|
|
2951
|
+
}
|
|
2952
|
+
const toggleBtn = document.querySelector("[data-theme-toggle]");
|
|
2953
|
+
const savedTheme = getTheme();
|
|
2954
|
+
setTheme(savedTheme);
|
|
2955
|
+
const savedBrand = getBrand();
|
|
2956
|
+
setBrand(savedBrand);
|
|
2957
|
+
if (toggleBtn) {
|
|
2958
|
+
updateThemeIcon(toggleBtn);
|
|
2959
|
+
toggleBtn.removeEventListener("click", _themeToggleHandler);
|
|
2960
|
+
toggleBtn.addEventListener("click", _themeToggleHandler);
|
|
2961
|
+
}
|
|
2962
|
+
let brandPicker = document.getElementById("brand-picker");
|
|
2963
|
+
if (!brandPicker) {
|
|
2964
|
+
brandPicker = document.createElement("div");
|
|
2965
|
+
brandPicker.id = "brand-picker";
|
|
2966
|
+
brandPicker.style.position = "fixed";
|
|
2967
|
+
brandPicker.style.top = "64px";
|
|
2968
|
+
brandPicker.style.right = "16px";
|
|
2969
|
+
brandPicker.style.zIndex = "9998";
|
|
2970
|
+
brandPicker.style.display = "none";
|
|
2971
|
+
brandPicker.style.padding = "12px";
|
|
2972
|
+
brandPicker.style.width = "200px";
|
|
2973
|
+
brandPicker.style.gridTemplateColumns = "repeat(3, 1fr)";
|
|
2974
|
+
brandPicker.style.gap = "6px";
|
|
2975
|
+
brandPicker.style.backgroundColor = "var(--bg-base-secondary)";
|
|
2976
|
+
brandPicker.style.border = "1px solid var(--border-neutral-l1)";
|
|
2977
|
+
brandPicker.style.borderRadius = "8px";
|
|
2978
|
+
brandPicker.style.boxShadow = "0 4px 20px rgba(0, 0, 0, 0.2)";
|
|
2979
|
+
brandPicker.style.overflow = "hidden";
|
|
2980
|
+
BRAND_OPTIONS.forEach((brand) => {
|
|
2981
|
+
const btn = document.createElement("button");
|
|
2982
|
+
btn.setAttribute("data-brand-btn", brand.id);
|
|
2983
|
+
btn.style.display = "flex";
|
|
2984
|
+
btn.style.justifyContent = "center";
|
|
2985
|
+
btn.style.alignItems = "center";
|
|
2986
|
+
btn.style.height = "60px";
|
|
2987
|
+
btn.style.backgroundColor = brand.color;
|
|
2988
|
+
btn.style.color = ["#32F08C", "#FF9900", "#E2C027", "#0EB0C9", "#B1A6CC"].includes(brand.color) ? "#0C0C0D" : "#FFFFFF";
|
|
2989
|
+
btn.style.fontWeight = "500";
|
|
2990
|
+
btn.style.borderRadius = "4px";
|
|
2991
|
+
btn.style.border = "none";
|
|
2992
|
+
btn.style.cursor = "pointer";
|
|
2993
|
+
btn.style.margin = "0";
|
|
2994
|
+
btn.style.padding = "0";
|
|
2995
|
+
btn.textContent = brand.name;
|
|
2996
|
+
brandPicker.appendChild(btn);
|
|
2997
|
+
});
|
|
2998
|
+
document.body.appendChild(brandPicker);
|
|
2999
|
+
}
|
|
3000
|
+
const brandToggle = document.querySelector("[data-brand-toggle]");
|
|
3001
|
+
if (brandToggle && brandPicker) {
|
|
3002
|
+
brandToggle.onclick = function(e) {
|
|
3003
|
+
e.stopPropagation();
|
|
3004
|
+
e.preventDefault();
|
|
3005
|
+
const isHidden = brandPicker.style.display === "none";
|
|
3006
|
+
brandPicker.style.display = isHidden ? "grid" : "none";
|
|
3007
|
+
if (isHidden) {
|
|
3008
|
+
setTimeout(() => {
|
|
3009
|
+
document.addEventListener("click", closeBrandPicker, true);
|
|
3010
|
+
}, 0);
|
|
3011
|
+
} else {
|
|
3012
|
+
document.removeEventListener("click", closeBrandPicker, true);
|
|
3013
|
+
}
|
|
3014
|
+
};
|
|
3015
|
+
brandPicker.onclick = function(e) {
|
|
3016
|
+
e.stopPropagation();
|
|
3017
|
+
};
|
|
3018
|
+
}
|
|
3019
|
+
function closeBrandPicker(e) {
|
|
3020
|
+
if (brandPicker && brandToggle) {
|
|
3021
|
+
if (!brandPicker.contains(e.target) && !brandToggle.contains(e.target)) {
|
|
3022
|
+
brandPicker.style.display = "none";
|
|
3023
|
+
document.removeEventListener("click", closeBrandPicker, true);
|
|
3024
|
+
}
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
const brandButtons = document.querySelectorAll("[data-brand-btn]");
|
|
3028
|
+
brandButtons.forEach((btn) => {
|
|
3029
|
+
btn.addEventListener("click", (e) => {
|
|
3030
|
+
e.stopPropagation();
|
|
3031
|
+
const brandId = btn.getAttribute("data-brand-btn");
|
|
3032
|
+
setBrand(brandId);
|
|
3033
|
+
if (brandPicker) {
|
|
3034
|
+
brandPicker.style.display = "none";
|
|
3035
|
+
}
|
|
3036
|
+
});
|
|
3037
|
+
});
|
|
3038
|
+
}
|
|
3039
|
+
function createThemeToggle() {
|
|
3040
|
+
const btn = document.createElement("button");
|
|
3041
|
+
btn.setAttribute("data-theme-toggle", "");
|
|
3042
|
+
btn.setAttribute("data-current-theme", getTheme());
|
|
3043
|
+
btn.className = "ds-btn ds-btn--ghost ds-btn--sm ds-btn--icon";
|
|
3044
|
+
btn.style.position = "fixed";
|
|
3045
|
+
btn.style.top = "16px";
|
|
3046
|
+
btn.style.right = "16px";
|
|
3047
|
+
btn.style.zIndex = "9999";
|
|
3048
|
+
const icon = document.createElement("img");
|
|
3049
|
+
icon.className = "theme-icon";
|
|
3050
|
+
const iconsPath = getIconsPath();
|
|
3051
|
+
icon.src = getTheme() === "dark" ? iconsPath + "sun.svg" : iconsPath + "moon.svg";
|
|
3052
|
+
icon.width = 14;
|
|
3053
|
+
icon.height = 14;
|
|
3054
|
+
icon.alt = "Toggle theme";
|
|
3055
|
+
btn.appendChild(icon);
|
|
3056
|
+
document.body.appendChild(btn);
|
|
3057
|
+
btn.onclick = function(e) {
|
|
3058
|
+
e.preventDefault();
|
|
3059
|
+
const currentTheme = getTheme();
|
|
3060
|
+
const newTheme = currentTheme === "dark" ? "light" : "dark";
|
|
3061
|
+
setTheme(newTheme);
|
|
3062
|
+
};
|
|
3063
|
+
return btn;
|
|
3064
|
+
}
|
|
3065
|
+
function createBrandPicker() {
|
|
3066
|
+
const container = document.createElement("div");
|
|
3067
|
+
container.id = "brand-picker-auto";
|
|
3068
|
+
container.style.position = "fixed";
|
|
3069
|
+
container.style.top = "56px";
|
|
3070
|
+
container.style.right = "16px";
|
|
3071
|
+
container.style.zIndex = "9998";
|
|
3072
|
+
container.style.display = "none";
|
|
3073
|
+
container.style.padding = "12px";
|
|
3074
|
+
container.style.width = "200px";
|
|
3075
|
+
container.style.gridTemplateColumns = "repeat(3, 1fr)";
|
|
3076
|
+
container.style.gap = "6px";
|
|
3077
|
+
container.style.backgroundColor = "var(--bg-base-secondary)";
|
|
3078
|
+
container.style.border = "1px solid var(--border-neutral-l1)";
|
|
3079
|
+
container.style.borderRadius = "8px";
|
|
3080
|
+
container.style.boxShadow = "0 4px 20px rgba(0, 0, 0, 0.2)";
|
|
3081
|
+
container.style.overflow = "hidden";
|
|
3082
|
+
BRAND_OPTIONS.forEach((brand) => {
|
|
3083
|
+
const btn = document.createElement("button");
|
|
3084
|
+
btn.setAttribute("data-brand-btn", brand.id);
|
|
3085
|
+
btn.style.display = "flex";
|
|
3086
|
+
btn.style.justifyContent = "center";
|
|
3087
|
+
btn.style.alignItems = "center";
|
|
3088
|
+
btn.style.height = "60px";
|
|
3089
|
+
btn.style.backgroundColor = brand.color;
|
|
3090
|
+
btn.style.color = ["#32F08C", "#FF9900", "#E2C027", "#0EB0C9", "#B1A6CC"].includes(brand.color) ? "#0C0C0D" : "#FFFFFF";
|
|
3091
|
+
btn.style.fontWeight = "500";
|
|
3092
|
+
btn.style.borderRadius = "4px";
|
|
3093
|
+
btn.style.border = "none";
|
|
3094
|
+
btn.style.cursor = "pointer";
|
|
3095
|
+
btn.style.margin = "0";
|
|
3096
|
+
btn.style.padding = "0";
|
|
3097
|
+
btn.textContent = brand.name;
|
|
3098
|
+
container.appendChild(btn);
|
|
3099
|
+
});
|
|
3100
|
+
document.body.appendChild(container);
|
|
3101
|
+
const toggleBtn = document.createElement("button");
|
|
3102
|
+
toggleBtn.setAttribute("data-brand-toggle", "");
|
|
3103
|
+
toggleBtn.setAttribute("data-current-brand", getBrand());
|
|
3104
|
+
toggleBtn.className = "ds-btn ds-btn--ghost ds-btn--sm";
|
|
3105
|
+
toggleBtn.style.position = "fixed";
|
|
3106
|
+
toggleBtn.style.top = "16px";
|
|
3107
|
+
toggleBtn.style.right = "56px";
|
|
3108
|
+
toggleBtn.style.zIndex = "9999";
|
|
3109
|
+
toggleBtn.style.display = "flex";
|
|
3110
|
+
toggleBtn.style.alignItems = "center";
|
|
3111
|
+
toggleBtn.style.gap = "6px";
|
|
3112
|
+
const brandIcon = document.createElement("span");
|
|
3113
|
+
brandIcon.className = "brand-icon";
|
|
3114
|
+
brandIcon.style.width = "12px";
|
|
3115
|
+
brandIcon.style.height = "12px";
|
|
3116
|
+
brandIcon.style.borderRadius = "50%";
|
|
3117
|
+
brandIcon.style.backgroundColor = BRAND_OPTIONS.find((b) => b.id === getBrand()).color;
|
|
3118
|
+
const brandName = document.createElement("span");
|
|
3119
|
+
brandName.className = "brand-name";
|
|
3120
|
+
brandName.style.fontSize = "11px";
|
|
3121
|
+
brandName.textContent = BRAND_OPTIONS.find((b) => b.id === getBrand()).name;
|
|
3122
|
+
toggleBtn.appendChild(brandIcon);
|
|
3123
|
+
toggleBtn.appendChild(brandName);
|
|
3124
|
+
document.body.appendChild(toggleBtn);
|
|
3125
|
+
toggleBtn.onclick = function(e) {
|
|
3126
|
+
e.stopPropagation();
|
|
3127
|
+
e.preventDefault();
|
|
3128
|
+
const isHidden = container.style.display === "none";
|
|
3129
|
+
container.style.display = isHidden ? "grid" : "none";
|
|
3130
|
+
if (isHidden) {
|
|
3131
|
+
setTimeout(() => {
|
|
3132
|
+
document.addEventListener("click", closeBrandPickerAuto, true);
|
|
3133
|
+
}, 0);
|
|
3134
|
+
} else {
|
|
3135
|
+
document.removeEventListener("click", closeBrandPickerAuto, true);
|
|
3136
|
+
}
|
|
3137
|
+
};
|
|
3138
|
+
container.onclick = function(e) {
|
|
3139
|
+
e.stopPropagation();
|
|
3140
|
+
};
|
|
3141
|
+
function closeBrandPickerAuto(e) {
|
|
3142
|
+
if (!container.contains(e.target) && !toggleBtn.contains(e.target)) {
|
|
3143
|
+
container.style.display = "none";
|
|
3144
|
+
document.removeEventListener("click", closeBrandPickerAuto, true);
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
const brandButtons = container.querySelectorAll("[data-brand-btn]");
|
|
3148
|
+
brandButtons.forEach((btn) => {
|
|
3149
|
+
btn.addEventListener("click", (e) => {
|
|
3150
|
+
e.stopPropagation();
|
|
3151
|
+
const brandId = btn.getAttribute("data-brand-btn");
|
|
3152
|
+
setBrand(brandId);
|
|
3153
|
+
container.style.display = "none";
|
|
3154
|
+
});
|
|
3155
|
+
});
|
|
3156
|
+
return { toggleBtn, container };
|
|
3157
|
+
}
|
|
3158
|
+
function sanitizeHtml(html, options = {}) {
|
|
3159
|
+
const securityConfig = getSecurityConfig();
|
|
3160
|
+
const sanitizeConfig = securityConfig?.sanitizeHtml || {};
|
|
3161
|
+
if (!sanitizeConfig.enabled && !options.force) {
|
|
3162
|
+
return html;
|
|
3163
|
+
}
|
|
3164
|
+
const allowedTags = options.allowedTags || sanitizeConfig.allowedTags || [];
|
|
3165
|
+
const allowedAttributes = options.allowedAttributes || sanitizeConfig.allowedAttributes || {};
|
|
3166
|
+
if (typeof html !== "string") {
|
|
3167
|
+
return html;
|
|
3168
|
+
}
|
|
3169
|
+
const domParser = new DOMParser();
|
|
3170
|
+
const doc = domParser.parseFromString(html, "text/html");
|
|
3171
|
+
const elements = doc.body.querySelectorAll("*");
|
|
3172
|
+
elements.forEach((element) => {
|
|
3173
|
+
const tagName = element.tagName.toLowerCase();
|
|
3174
|
+
if (!allowedTags.includes(tagName)) {
|
|
3175
|
+
element.remove();
|
|
3176
|
+
return;
|
|
3177
|
+
}
|
|
3178
|
+
Array.from(element.attributes).forEach((attr) => {
|
|
3179
|
+
const attrName = attr.name.toLowerCase();
|
|
3180
|
+
const tagAllowedAttrs = allowedAttributes[tagName] || [];
|
|
3181
|
+
if (!tagAllowedAttrs.includes(attrName)) {
|
|
3182
|
+
element.removeAttribute(attr.name);
|
|
3183
|
+
}
|
|
3184
|
+
});
|
|
3185
|
+
});
|
|
3186
|
+
return doc.body.innerHTML;
|
|
3187
|
+
}
|
|
3188
|
+
function escapeHtml(text) {
|
|
3189
|
+
if (typeof text !== "string") {
|
|
3190
|
+
return text;
|
|
3191
|
+
}
|
|
3192
|
+
const div = document.createElement("div");
|
|
3193
|
+
div.textContent = text;
|
|
3194
|
+
return div.innerHTML;
|
|
3195
|
+
}
|
|
3196
|
+
function stripHtml(html) {
|
|
3197
|
+
if (typeof html !== "string") {
|
|
3198
|
+
return html;
|
|
3199
|
+
}
|
|
3200
|
+
const domParser = new DOMParser();
|
|
3201
|
+
const doc = domParser.parseFromString(html, "text/html");
|
|
3202
|
+
return doc.body.textContent || "";
|
|
3203
|
+
}
|
|
3204
|
+
function maskData(value, type, options = {}) {
|
|
3205
|
+
const securityConfig = getSecurityConfig();
|
|
3206
|
+
const maskConfig = securityConfig?.maskData || {};
|
|
3207
|
+
if (!maskConfig.enabled && !options.force) {
|
|
3208
|
+
return value;
|
|
3209
|
+
}
|
|
3210
|
+
if (value === null || value === void 0) {
|
|
3211
|
+
return value;
|
|
3212
|
+
}
|
|
3213
|
+
const patterns = options.patterns || maskConfig.patterns || {};
|
|
3214
|
+
const pattern = patterns[type];
|
|
3215
|
+
if (!pattern) {
|
|
3216
|
+
return value;
|
|
3217
|
+
}
|
|
3218
|
+
const regex = typeof pattern.regex === "string" ? new RegExp(pattern.regex) : pattern.regex;
|
|
3219
|
+
return String(value).replace(regex, pattern.replace);
|
|
3220
|
+
}
|
|
3221
|
+
function generateSecureId(length, prefix) {
|
|
3222
|
+
const securityConfig = getSecurityConfig();
|
|
3223
|
+
const secureIdConfig = securityConfig?.secureId || {};
|
|
3224
|
+
const idLength = length || secureIdConfig.length || 16;
|
|
3225
|
+
const charset = secureIdConfig.charset || "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
3226
|
+
if (typeof crypto === "undefined" || !crypto.getRandomValues) {
|
|
3227
|
+
let result2 = "";
|
|
3228
|
+
for (let i = 0; i < idLength; i++) {
|
|
3229
|
+
result2 += charset[Math.floor(Math.random() * charset.length)];
|
|
3230
|
+
}
|
|
3231
|
+
return prefix ? `${prefix}_${result2}` : result2;
|
|
3232
|
+
}
|
|
3233
|
+
const array = new Uint32Array(idLength);
|
|
3234
|
+
crypto.getRandomValues(array);
|
|
3235
|
+
let result = "";
|
|
3236
|
+
for (let i = 0; i < idLength; i++) {
|
|
3237
|
+
result += charset[array[i] % charset.length];
|
|
3238
|
+
}
|
|
3239
|
+
return prefix ? `${prefix}_${result}` : result;
|
|
3240
|
+
}
|
|
3241
|
+
class ComponentInitializerRegistry {
|
|
3242
|
+
constructor() {
|
|
3243
|
+
this.initializers = /* @__PURE__ */ new Map();
|
|
3244
|
+
this.cleanupFunctions = /* @__PURE__ */ new Map();
|
|
3245
|
+
this.processedElements = /* @__PURE__ */ new WeakSet();
|
|
3246
|
+
this._dataAttrs = ["data-component"];
|
|
3247
|
+
this._cssClasses = [];
|
|
3248
|
+
this._cachedSelector = null;
|
|
3249
|
+
}
|
|
3250
|
+
/**
|
|
3251
|
+
* Register a component initializer.
|
|
3252
|
+
* @param {string} name - Component name
|
|
3253
|
+
* @param {Function} initFn - Init function called with the DOM element
|
|
3254
|
+
* @param {Function} [cleanupFn] - Optional cleanup/destroy function
|
|
3255
|
+
* @param {Object} [options] - { dataAttribute, cssClass } for auto-discovery
|
|
3256
|
+
*/
|
|
3257
|
+
register(name, initFn, cleanupFn = null, options = {}) {
|
|
3258
|
+
this.initializers.set(name, initFn);
|
|
3259
|
+
if (cleanupFn) {
|
|
3260
|
+
this.cleanupFunctions.set(name, cleanupFn);
|
|
3261
|
+
}
|
|
3262
|
+
if (options.dataAttribute && !this._dataAttrs.includes(options.dataAttribute)) {
|
|
3263
|
+
this._dataAttrs.push(options.dataAttribute);
|
|
3264
|
+
this._cachedSelector = null;
|
|
3265
|
+
}
|
|
3266
|
+
if (options.cssClass && !this._cssClasses.includes(options.cssClass)) {
|
|
3267
|
+
this._cssClasses.push(options.cssClass);
|
|
3268
|
+
this._cachedSelector = null;
|
|
3269
|
+
}
|
|
3270
|
+
}
|
|
3271
|
+
unregister(name) {
|
|
3272
|
+
this.initializers.delete(name);
|
|
3273
|
+
this.cleanupFunctions.delete(name);
|
|
3274
|
+
}
|
|
3275
|
+
has(name) {
|
|
3276
|
+
return this.initializers.has(name);
|
|
3277
|
+
}
|
|
3278
|
+
get(name) {
|
|
3279
|
+
return this.initializers.get(name);
|
|
3280
|
+
}
|
|
3281
|
+
_buildSelector() {
|
|
3282
|
+
if (this._cachedSelector !== null) return this._cachedSelector;
|
|
3283
|
+
const parts = this._dataAttrs.map((a) => `[${a}]`);
|
|
3284
|
+
for (const cls of this._cssClasses) {
|
|
3285
|
+
parts.push(`.${cls}`);
|
|
3286
|
+
}
|
|
3287
|
+
this._cachedSelector = parts.join(", ");
|
|
3288
|
+
return this._cachedSelector;
|
|
3289
|
+
}
|
|
3290
|
+
/** Initialize a single DOM element if it matches a registered component. */
|
|
3291
|
+
async initialize(element) {
|
|
3292
|
+
if (this.processedElements.has(element)) return;
|
|
3293
|
+
for (const attr of this._dataAttrs) {
|
|
3294
|
+
const value = element.getAttribute(attr);
|
|
3295
|
+
if (value !== null) {
|
|
3296
|
+
const name = value || attr.replace("data-", "");
|
|
3297
|
+
const initFn = this.initializers.get(name) || this.initializers.get(attr.replace("data-", ""));
|
|
3298
|
+
if (initFn) {
|
|
3299
|
+
try {
|
|
3300
|
+
await initFn(element);
|
|
3301
|
+
this.processedElements.add(element);
|
|
3302
|
+
} catch (error) {
|
|
3303
|
+
console.error(`[ComponentInitializerRegistry] Error initializing "${name}":`, error);
|
|
3304
|
+
}
|
|
3305
|
+
return;
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
const className = element.className;
|
|
3310
|
+
if (typeof className === "string") {
|
|
3311
|
+
for (const cls of this._cssClasses) {
|
|
3312
|
+
const regex = new RegExp(`(^|\\s)${cls}(\\s|$)`);
|
|
3313
|
+
if (regex.test(className)) {
|
|
3314
|
+
const name = cls.replace("ds-", "");
|
|
3315
|
+
const initFn = this.initializers.get(name) || this.initializers.get(cls);
|
|
3316
|
+
if (initFn) {
|
|
3317
|
+
try {
|
|
3318
|
+
await initFn(element);
|
|
3319
|
+
this.processedElements.add(element);
|
|
3320
|
+
} catch (error) {
|
|
3321
|
+
console.error(`[ComponentInitializerRegistry] Error initializing "${name}":`, error);
|
|
3322
|
+
}
|
|
3323
|
+
return;
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
}
|
|
3329
|
+
/** Run cleanup/destroy for a single DOM element. */
|
|
3330
|
+
cleanup(element) {
|
|
3331
|
+
for (const attr of this._dataAttrs) {
|
|
3332
|
+
const value = element.getAttribute(attr);
|
|
3333
|
+
if (value !== null) {
|
|
3334
|
+
const name = value || attr.replace("data-", "");
|
|
3335
|
+
const cleanupFn = this.cleanupFunctions.get(name) || this.cleanupFunctions.get(attr.replace("data-", ""));
|
|
3336
|
+
if (cleanupFn) {
|
|
3337
|
+
try {
|
|
3338
|
+
cleanupFn(element);
|
|
3339
|
+
} catch (error) {
|
|
3340
|
+
console.error(`[ComponentInitializerRegistry] Error cleaning up "${name}":`, error);
|
|
3341
|
+
}
|
|
3342
|
+
this.processedElements.delete(element);
|
|
3343
|
+
return;
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
}
|
|
3347
|
+
const className = element.className;
|
|
3348
|
+
if (typeof className === "string") {
|
|
3349
|
+
for (const cls of this._cssClasses) {
|
|
3350
|
+
const regex = new RegExp(`(^|\\s)${cls}(\\s|$)`);
|
|
3351
|
+
if (regex.test(className)) {
|
|
3352
|
+
const name = cls.replace("ds-", "");
|
|
3353
|
+
const cleanupFn = this.cleanupFunctions.get(name) || this.cleanupFunctions.get(cls);
|
|
3354
|
+
if (cleanupFn) {
|
|
3355
|
+
try {
|
|
3356
|
+
cleanupFn(element);
|
|
3357
|
+
} catch (error) {
|
|
3358
|
+
console.error(`[ComponentInitializerRegistry] Error cleaning up "${name}":`, error);
|
|
3359
|
+
}
|
|
3360
|
+
this.processedElements.delete(element);
|
|
3361
|
+
return;
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3367
|
+
/** Scan and initialize all matching components. Uses MutationObserver for dynamic content. */
|
|
3368
|
+
async initializeAll(root = document) {
|
|
3369
|
+
const selector = this._buildSelector();
|
|
3370
|
+
if (!selector) return;
|
|
3371
|
+
const elements = root.querySelectorAll(selector);
|
|
3372
|
+
const promises = [];
|
|
3373
|
+
elements.forEach((element) => {
|
|
3374
|
+
if (!this.processedElements.has(element)) {
|
|
3375
|
+
promises.push(this.initialize(element));
|
|
3376
|
+
}
|
|
3377
|
+
});
|
|
3378
|
+
await Promise.all(promises);
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
const kupolaInitializer = new ComponentInitializerRegistry();
|
|
3382
|
+
const knownComponents = [
|
|
3383
|
+
{ attr: "data-dropdown", cls: "ds-dropdown" },
|
|
3384
|
+
{ attr: "data-select", cls: "ds-select" },
|
|
3385
|
+
{ attr: "data-datepicker", cls: "ds-datepicker" },
|
|
3386
|
+
{ attr: "data-timepicker", cls: "ds-timepicker" },
|
|
3387
|
+
{ attr: "data-slider", cls: "ds-slider" },
|
|
3388
|
+
{ attr: "data-carousel", cls: "ds-carousel" },
|
|
3389
|
+
{ attr: "data-drawer", cls: "ds-drawer" },
|
|
3390
|
+
{ attr: "data-modal", cls: "ds-modal" },
|
|
3391
|
+
{ attr: "data-dialog", cls: "ds-dialog" },
|
|
3392
|
+
{ attr: "data-color-picker", cls: "ds-color-picker" },
|
|
3393
|
+
{ attr: "data-calendar", cls: "ds-calendar" },
|
|
3394
|
+
{ attr: "data-slide-captcha", cls: "ds-slide-captcha" },
|
|
3395
|
+
{ attr: "data-heatmap", cls: "ds-heatmap" },
|
|
3396
|
+
{ cls: "ds-tooltip" },
|
|
3397
|
+
{ cls: "ds-tag" },
|
|
3398
|
+
{ cls: "ds-statcard" },
|
|
3399
|
+
{ cls: "ds-collapse" },
|
|
3400
|
+
{ cls: "ds-fileupload" },
|
|
3401
|
+
{ cls: "ds-notification" },
|
|
3402
|
+
{ cls: "ds-message" }
|
|
3403
|
+
];
|
|
3404
|
+
for (const comp of knownComponents) {
|
|
3405
|
+
if (comp.attr && !kupolaInitializer._dataAttrs.includes(comp.attr)) {
|
|
3406
|
+
kupolaInitializer._dataAttrs.push(comp.attr);
|
|
3407
|
+
}
|
|
3408
|
+
if (comp.cls && !kupolaInitializer._cssClasses.includes(comp.cls)) {
|
|
3409
|
+
kupolaInitializer._cssClasses.push(comp.cls);
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
class KupolaComponent {
|
|
3413
|
+
constructor(element) {
|
|
3414
|
+
this.element = element;
|
|
3415
|
+
this.isMounted = false;
|
|
3416
|
+
this.isDestroyed = false;
|
|
3417
|
+
this.props = this._parseProps();
|
|
3418
|
+
this.state = {};
|
|
3419
|
+
this.slots = this._parseSlots();
|
|
3420
|
+
this._eventListeners = {};
|
|
3421
|
+
this._appliedMixins = [];
|
|
3422
|
+
this.lifecycle = new KupolaLifecycle();
|
|
3423
|
+
this.setupContext = null;
|
|
3424
|
+
}
|
|
3425
|
+
_parseProps() {
|
|
3426
|
+
const props = {};
|
|
3427
|
+
for (const attr of this.element.attributes) {
|
|
3428
|
+
if (attr.name.startsWith("data-prop-")) {
|
|
3429
|
+
const name = attr.name.replace("data-prop-", "");
|
|
3430
|
+
let value = attr.value;
|
|
3431
|
+
try {
|
|
3432
|
+
value = JSON.parse(value);
|
|
3433
|
+
} catch (e) {
|
|
3434
|
+
}
|
|
3435
|
+
props[name] = value;
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
return props;
|
|
3439
|
+
}
|
|
3440
|
+
_parseSlots() {
|
|
3441
|
+
const slots = {};
|
|
3442
|
+
const slotElements = this.element.querySelectorAll("[data-slot]");
|
|
3443
|
+
slotElements.forEach((slotEl) => {
|
|
3444
|
+
const slotName = slotEl.getAttribute("data-slot") || "default";
|
|
3445
|
+
slots[slotName] = slotEl.innerHTML.trim();
|
|
3446
|
+
slotEl.remove();
|
|
3447
|
+
});
|
|
3448
|
+
if (!slots.default && this.element.children.length > 0) {
|
|
3449
|
+
slots.default = this.element.innerHTML.trim();
|
|
3450
|
+
}
|
|
3451
|
+
return slots;
|
|
3452
|
+
}
|
|
3453
|
+
$slot(name = "default") {
|
|
3454
|
+
return this.slots[name] || "";
|
|
3455
|
+
}
|
|
3456
|
+
$emit(eventName, data) {
|
|
3457
|
+
const handlers = this._eventListeners[eventName] || [];
|
|
3458
|
+
handlers.forEach((handler) => {
|
|
3459
|
+
try {
|
|
3460
|
+
handler(data);
|
|
3461
|
+
} catch (e) {
|
|
3462
|
+
console.error(`Error in event handler for ${eventName}:`, e);
|
|
3463
|
+
}
|
|
3464
|
+
});
|
|
3465
|
+
if (this.element) {
|
|
3466
|
+
const customEvent = new CustomEvent(`kupola:${eventName}`, {
|
|
3467
|
+
detail: data,
|
|
3468
|
+
bubbles: true,
|
|
3469
|
+
cancelable: true
|
|
3470
|
+
});
|
|
3471
|
+
this.element.dispatchEvent(customEvent);
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3474
|
+
$on(eventName, handler) {
|
|
3475
|
+
if (!this._eventListeners[eventName]) {
|
|
3476
|
+
this._eventListeners[eventName] = [];
|
|
3477
|
+
}
|
|
3478
|
+
this._eventListeners[eventName].push(handler);
|
|
3479
|
+
return handler;
|
|
3480
|
+
}
|
|
3481
|
+
$off(eventName, handler) {
|
|
3482
|
+
if (this._eventListeners[eventName]) {
|
|
3483
|
+
this._eventListeners[eventName] = this._eventListeners[eventName].filter((cb) => cb !== handler);
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
async setProps(newProps) {
|
|
3487
|
+
try {
|
|
3488
|
+
this.props = { ...this.props, ...newProps };
|
|
3489
|
+
await this.lifecycle.update();
|
|
3490
|
+
this.setupContext?._executeUpdated();
|
|
3491
|
+
} catch (error) {
|
|
3492
|
+
console.error(`[KupolaComponent] Error in setProps for "${this.constructor.name}":`, error);
|
|
3493
|
+
if (this.lifecycle && typeof this.lifecycle._handleError === "function") {
|
|
3494
|
+
await this.lifecycle._handleError({ phase: "update", hook: "setProps", error, args: [newProps] });
|
|
3495
|
+
}
|
|
3496
|
+
}
|
|
3497
|
+
}
|
|
3498
|
+
async setState(newState) {
|
|
3499
|
+
try {
|
|
3500
|
+
this.state = { ...this.state, ...newState };
|
|
3501
|
+
await this.lifecycle.update();
|
|
3502
|
+
this.setupContext?._executeUpdated();
|
|
3503
|
+
} catch (error) {
|
|
3504
|
+
console.error(`[KupolaComponent] Error in setState for "${this.constructor.name}":`, error);
|
|
3505
|
+
if (this.lifecycle && typeof this.lifecycle._handleError === "function") {
|
|
3506
|
+
await this.lifecycle._handleError({ phase: "update", hook: "setState", error, args: [newState] });
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3510
|
+
async mount() {
|
|
3511
|
+
if (this.isMounted || this.isDestroyed) return;
|
|
3512
|
+
try {
|
|
3513
|
+
this._bindLifecycleHooks();
|
|
3514
|
+
await this.lifecycle.bootstrap();
|
|
3515
|
+
if (typeof this.setup === "function") {
|
|
3516
|
+
const result = this.setup();
|
|
3517
|
+
if (result instanceof Promise) {
|
|
3518
|
+
await result;
|
|
3519
|
+
}
|
|
3520
|
+
}
|
|
3521
|
+
this.isMounted = true;
|
|
3522
|
+
await this.lifecycle.mount();
|
|
3523
|
+
this.setupContext?._executeMounted();
|
|
3524
|
+
} catch (error) {
|
|
3525
|
+
console.error(`[KupolaComponent] Error mounting component "${this.constructor.name}":`, error);
|
|
3526
|
+
if (this.lifecycle && typeof this.lifecycle._handleError === "function") {
|
|
3527
|
+
await this.lifecycle._handleError({ phase: "mount", hook: "component", error, args: [] });
|
|
3528
|
+
}
|
|
3529
|
+
if (typeof this.renderError === "function") {
|
|
3530
|
+
try {
|
|
3531
|
+
this.renderError(error);
|
|
3532
|
+
} catch (e) {
|
|
3533
|
+
console.error(`[KupolaComponent] Error in renderError for "${this.constructor.name}":`, e);
|
|
3534
|
+
}
|
|
3535
|
+
} else {
|
|
3536
|
+
this.element.innerHTML = `
|
|
3537
|
+
<div style="padding: 16px; background: #fee2e2; border: 1px solid #fecaca; border-radius: 8px; color: #991b1b;">
|
|
3538
|
+
<div style="font-weight: bold; margin-bottom: 8px;">Component Error</div>
|
|
3539
|
+
<div style="font-size: 12px; white-space: pre-wrap;">${error.message}</div>
|
|
3540
|
+
</div>
|
|
3541
|
+
`;
|
|
3542
|
+
}
|
|
3543
|
+
}
|
|
3544
|
+
}
|
|
3545
|
+
_bindLifecycleHooks() {
|
|
3546
|
+
if (this._hooksBound) return;
|
|
3547
|
+
const hookMap = {
|
|
3548
|
+
beforeMount: "beforeMount",
|
|
3549
|
+
render: ["mount", "update"],
|
|
3550
|
+
afterMount: "afterMount",
|
|
3551
|
+
updated: "afterUpdate",
|
|
3552
|
+
beforeUnmount: "beforeUnmount",
|
|
3553
|
+
afterUnmount: "afterUnmount",
|
|
3554
|
+
renderError: "errorBoundary"
|
|
3555
|
+
};
|
|
3556
|
+
let proto = Object.getPrototypeOf(this);
|
|
3557
|
+
const registeredHooks = /* @__PURE__ */ new Set();
|
|
3558
|
+
while (proto && proto.constructor !== Object && proto.constructor !== KupolaComponent) {
|
|
3559
|
+
for (const [methodName, lifecyclePhase] of Object.entries(hookMap)) {
|
|
3560
|
+
if (registeredHooks.has(methodName)) continue;
|
|
3561
|
+
if (proto.hasOwnProperty(methodName)) {
|
|
3562
|
+
if (Array.isArray(lifecyclePhase)) {
|
|
3563
|
+
lifecyclePhase.forEach((phase) => {
|
|
3564
|
+
if (methodName === "render") {
|
|
3565
|
+
this.lifecycle.on(phase, () => this.render?.());
|
|
3566
|
+
}
|
|
3567
|
+
});
|
|
3568
|
+
} else if (methodName === "renderError") {
|
|
3569
|
+
this.lifecycle.on(lifecyclePhase, (errorInfo) => {
|
|
3570
|
+
this.renderError(errorInfo.error);
|
|
3571
|
+
return "handled";
|
|
3572
|
+
});
|
|
3573
|
+
} else {
|
|
3574
|
+
this.lifecycle.on(lifecyclePhase, () => this[methodName]?.());
|
|
3575
|
+
}
|
|
3576
|
+
registeredHooks.add(methodName);
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3579
|
+
proto = Object.getPrototypeOf(proto);
|
|
3580
|
+
}
|
|
3581
|
+
this._hooksBound = true;
|
|
3582
|
+
}
|
|
3583
|
+
async unmount() {
|
|
3584
|
+
if (!this.isMounted || this.isDestroyed) return;
|
|
3585
|
+
try {
|
|
3586
|
+
this.setupContext?._executeUnmounted();
|
|
3587
|
+
await this.lifecycle.unmount();
|
|
3588
|
+
this.isMounted = false;
|
|
3589
|
+
this.isDestroyed = true;
|
|
3590
|
+
await this.lifecycle.destroy();
|
|
3591
|
+
} catch (error) {
|
|
3592
|
+
console.error(`[KupolaComponent] Error unmounting component "${this.constructor.name}":`, error);
|
|
3593
|
+
if (this.lifecycle && typeof this.lifecycle._handleError === "function") {
|
|
3594
|
+
await this.lifecycle._handleError({ phase: "unmount", hook: "component", error, args: [] });
|
|
3595
|
+
}
|
|
3596
|
+
this.isMounted = false;
|
|
3597
|
+
this.isDestroyed = true;
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
beforeMount() {
|
|
3601
|
+
}
|
|
3602
|
+
afterMount() {
|
|
3603
|
+
}
|
|
3604
|
+
beforeUnmount() {
|
|
3605
|
+
}
|
|
3606
|
+
afterUnmount() {
|
|
3607
|
+
}
|
|
3608
|
+
render() {
|
|
3609
|
+
}
|
|
3610
|
+
renderError(error) {
|
|
3611
|
+
}
|
|
3612
|
+
updated() {
|
|
3613
|
+
}
|
|
3614
|
+
setup() {
|
|
3615
|
+
}
|
|
3616
|
+
}
|
|
3617
|
+
function applyMixin(componentClass, mixin) {
|
|
3618
|
+
Object.keys(mixin).forEach((key) => {
|
|
3619
|
+
if (key === "constructor") return;
|
|
3620
|
+
if (typeof mixin[key] === "function") {
|
|
3621
|
+
const original = componentClass.prototype[key];
|
|
3622
|
+
if (original) {
|
|
3623
|
+
componentClass.prototype[key] = function(...args) {
|
|
3624
|
+
mixin[key].apply(this, args);
|
|
3625
|
+
return original.apply(this, args);
|
|
3626
|
+
};
|
|
3627
|
+
} else {
|
|
3628
|
+
componentClass.prototype[key] = mixin[key];
|
|
3629
|
+
}
|
|
3630
|
+
} else {
|
|
3631
|
+
componentClass.prototype[key] = mixin[key];
|
|
3632
|
+
}
|
|
3633
|
+
});
|
|
3634
|
+
}
|
|
3635
|
+
class KupolaComponentRegistry {
|
|
3636
|
+
constructor() {
|
|
3637
|
+
this.components = /* @__PURE__ */ new Map();
|
|
3638
|
+
this.lazyComponents = /* @__PURE__ */ new Map();
|
|
3639
|
+
this.loadedComponents = /* @__PURE__ */ new Map();
|
|
3640
|
+
this.instances = /* @__PURE__ */ new Map();
|
|
3641
|
+
this.observer = null;
|
|
3642
|
+
this.mixins = /* @__PURE__ */ new Map();
|
|
3643
|
+
this.loadingPromises = /* @__PURE__ */ new Map();
|
|
3644
|
+
}
|
|
3645
|
+
register(name, componentClass) {
|
|
3646
|
+
if (!(componentClass.prototype instanceof KupolaComponent)) {
|
|
3647
|
+
throw new Error(`Component ${name} must extend KupolaComponent`);
|
|
3648
|
+
}
|
|
3649
|
+
this.components.set(name, componentClass);
|
|
3650
|
+
}
|
|
3651
|
+
registerLazy(name, loader) {
|
|
3652
|
+
this.lazyComponents.set(name, loader);
|
|
3653
|
+
}
|
|
3654
|
+
unregister(name) {
|
|
3655
|
+
this.components.delete(name);
|
|
3656
|
+
this.lazyComponents.delete(name);
|
|
3657
|
+
this.loadedComponents.delete(name);
|
|
3658
|
+
this.loadingPromises.delete(name);
|
|
3659
|
+
}
|
|
3660
|
+
get(name) {
|
|
3661
|
+
return this.components.get(name) || this.loadedComponents.get(name);
|
|
3662
|
+
}
|
|
3663
|
+
async getAsync(name) {
|
|
3664
|
+
const cached = this.components.get(name) || this.loadedComponents.get(name);
|
|
3665
|
+
if (cached) return cached;
|
|
3666
|
+
if (this.loadingPromises.has(name)) {
|
|
3667
|
+
return this.loadingPromises.get(name);
|
|
3668
|
+
}
|
|
3669
|
+
const loader = this.lazyComponents.get(name);
|
|
3670
|
+
if (!loader) {
|
|
3671
|
+
throw new Error(`Component ${name} not found`);
|
|
3672
|
+
}
|
|
3673
|
+
const loadingPromise = (async () => {
|
|
3674
|
+
try {
|
|
3675
|
+
const result = await loader();
|
|
3676
|
+
const componentClass = result.default || result;
|
|
3677
|
+
if (!(componentClass.prototype instanceof KupolaComponent)) {
|
|
3678
|
+
throw new Error(`Component ${name} must extend KupolaComponent`);
|
|
3679
|
+
}
|
|
3680
|
+
this.loadedComponents.set(name, componentClass);
|
|
3681
|
+
return componentClass;
|
|
3682
|
+
} catch (e) {
|
|
3683
|
+
this.loadingPromises.delete(name);
|
|
3684
|
+
throw e;
|
|
3685
|
+
}
|
|
3686
|
+
})();
|
|
3687
|
+
this.loadingPromises.set(name, loadingPromise);
|
|
3688
|
+
return loadingPromise;
|
|
3689
|
+
}
|
|
3690
|
+
defineMixin(name, mixin) {
|
|
3691
|
+
this.mixins.set(name, mixin);
|
|
3692
|
+
}
|
|
3693
|
+
useMixin(componentClass, ...mixinNames) {
|
|
3694
|
+
mixinNames.forEach((name) => {
|
|
3695
|
+
const mixin = this.mixins.get(name);
|
|
3696
|
+
if (mixin) {
|
|
3697
|
+
applyMixin(componentClass, mixin);
|
|
3698
|
+
}
|
|
3699
|
+
});
|
|
3700
|
+
}
|
|
3701
|
+
async bootstrap(root = document) {
|
|
3702
|
+
await this._upgradeElements(root);
|
|
3703
|
+
this._startObserver(root);
|
|
3704
|
+
}
|
|
3705
|
+
async _upgradeElements(root) {
|
|
3706
|
+
const elements = root.querySelectorAll("[data-component]");
|
|
3707
|
+
const promises = [];
|
|
3708
|
+
elements.forEach((element) => {
|
|
3709
|
+
promises.push(this._upgradeElement(element));
|
|
3710
|
+
});
|
|
3711
|
+
await Promise.all(promises);
|
|
3712
|
+
}
|
|
3713
|
+
async _upgradeElement(element) {
|
|
3714
|
+
if (element.__kupolaInstance || element.__kupolaUpgrading) return;
|
|
3715
|
+
element.__kupolaUpgrading = true;
|
|
3716
|
+
try {
|
|
3717
|
+
const componentName = element.getAttribute("data-component");
|
|
3718
|
+
if (componentName) {
|
|
3719
|
+
const initializer = kupolaInitializer.get(componentName);
|
|
3720
|
+
if (initializer) {
|
|
3721
|
+
try {
|
|
3722
|
+
await initializer(element);
|
|
3723
|
+
return;
|
|
3724
|
+
} catch (e) {
|
|
3725
|
+
console.warn(`[KupolaComponentRegistry] Initializer for "${componentName}" failed, trying component class:`, e);
|
|
3726
|
+
}
|
|
3727
|
+
}
|
|
3728
|
+
}
|
|
3729
|
+
let ComponentClass = this.components.get(componentName);
|
|
3730
|
+
if (!ComponentClass) {
|
|
3731
|
+
try {
|
|
3732
|
+
ComponentClass = await this.getAsync(componentName);
|
|
3733
|
+
} catch (e) {
|
|
3734
|
+
console.error(`Failed to load component ${componentName}:`, e);
|
|
3735
|
+
return;
|
|
3736
|
+
}
|
|
3737
|
+
if (!element.isConnected) {
|
|
3738
|
+
return;
|
|
3739
|
+
}
|
|
3740
|
+
}
|
|
3741
|
+
const mixinNames = element.getAttribute("data-mixins");
|
|
3742
|
+
const componentClass = ComponentClass;
|
|
3743
|
+
if (mixinNames) {
|
|
3744
|
+
mixinNames.split(",").forEach((name) => {
|
|
3745
|
+
const mixin = this.mixins.get(name.trim());
|
|
3746
|
+
if (mixin) {
|
|
3747
|
+
applyMixin(componentClass, mixin);
|
|
3748
|
+
}
|
|
3749
|
+
});
|
|
3750
|
+
}
|
|
3751
|
+
const instance = new componentClass(element);
|
|
3752
|
+
element.__kupolaInstance = instance;
|
|
3753
|
+
this.instances.set(element, instance);
|
|
3754
|
+
instance.mount();
|
|
3755
|
+
} finally {
|
|
3756
|
+
element.__kupolaUpgrading = false;
|
|
3757
|
+
}
|
|
3758
|
+
}
|
|
3759
|
+
_startObserver(root) {
|
|
3760
|
+
if (this.observer) return;
|
|
3761
|
+
this.observer = new MutationObserver((mutations) => {
|
|
3762
|
+
mutations.forEach((mutation) => {
|
|
3763
|
+
mutation.addedNodes.forEach((node) => {
|
|
3764
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
3765
|
+
if (node.hasAttribute("data-component")) {
|
|
3766
|
+
this._upgradeElement(node).catch((e) => console.error(e));
|
|
3767
|
+
}
|
|
3768
|
+
this._upgradeElements(node).catch((e) => console.error(e));
|
|
3769
|
+
kupolaInitializer.initialize(node).catch(() => {
|
|
3770
|
+
});
|
|
3771
|
+
const selector = kupolaInitializer._buildSelector();
|
|
3772
|
+
if (selector) {
|
|
3773
|
+
node.querySelectorAll?.(selector).forEach((child) => {
|
|
3774
|
+
kupolaInitializer.initialize(child).catch(() => {
|
|
3775
|
+
});
|
|
3776
|
+
});
|
|
3777
|
+
}
|
|
3778
|
+
}
|
|
3779
|
+
});
|
|
3780
|
+
mutation.removedNodes.forEach((node) => {
|
|
3781
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
3782
|
+
const instance = this.instances.get(node);
|
|
3783
|
+
if (instance) {
|
|
3784
|
+
instance.unmount();
|
|
3785
|
+
this.instances.delete(node);
|
|
3786
|
+
}
|
|
3787
|
+
node.querySelectorAll("[data-component]").forEach((child) => {
|
|
3788
|
+
const childInstance = this.instances.get(child);
|
|
3789
|
+
if (childInstance) {
|
|
3790
|
+
childInstance.unmount();
|
|
3791
|
+
this.instances.delete(child);
|
|
3792
|
+
}
|
|
3793
|
+
});
|
|
3794
|
+
kupolaInitializer.cleanup(node);
|
|
3795
|
+
node.querySelectorAll?.("*").forEach((child) => {
|
|
3796
|
+
kupolaInitializer.cleanup(child);
|
|
3797
|
+
});
|
|
3798
|
+
}
|
|
3799
|
+
});
|
|
3800
|
+
});
|
|
3801
|
+
});
|
|
3802
|
+
this.observer.observe(root, {
|
|
3803
|
+
childList: true,
|
|
3804
|
+
subtree: true
|
|
3805
|
+
});
|
|
3806
|
+
}
|
|
3807
|
+
destroy() {
|
|
3808
|
+
if (this.observer) {
|
|
3809
|
+
this.observer.disconnect();
|
|
3810
|
+
this.observer = null;
|
|
3811
|
+
}
|
|
3812
|
+
this.instances.forEach((instance) => {
|
|
3813
|
+
instance.unmount();
|
|
3814
|
+
});
|
|
3815
|
+
this.instances.clear();
|
|
3816
|
+
this.components.clear();
|
|
3817
|
+
this.mixins.clear();
|
|
3818
|
+
}
|
|
3819
|
+
}
|
|
3820
|
+
let kupolaRegistry = null;
|
|
3821
|
+
if (typeof window !== "undefined") {
|
|
3822
|
+
kupolaRegistry = new KupolaComponentRegistry();
|
|
3823
|
+
}
|
|
3824
|
+
function _applySecurityHeaders() {
|
|
3825
|
+
const securityConfig = getSecurityConfig();
|
|
3826
|
+
if (securityConfig.xssProtection && typeof document !== "undefined") {
|
|
3827
|
+
let metaTag = document.querySelector('meta[http-equiv="X-XSS-Protection"]');
|
|
3828
|
+
if (!metaTag) {
|
|
3829
|
+
metaTag = document.createElement("meta");
|
|
3830
|
+
metaTag.setAttribute("http-equiv", "X-XSS-Protection");
|
|
3831
|
+
metaTag.setAttribute("content", "1; mode=block");
|
|
3832
|
+
document.head.insertBefore(metaTag, document.head.firstChild);
|
|
3833
|
+
}
|
|
3834
|
+
metaTag = document.querySelector('meta[http-equiv="X-Content-Type-Options"]');
|
|
3835
|
+
if (!metaTag) {
|
|
3836
|
+
metaTag = document.createElement("meta");
|
|
3837
|
+
metaTag.setAttribute("http-equiv", "X-Content-Type-Options");
|
|
3838
|
+
metaTag.setAttribute("content", "nosniff");
|
|
3839
|
+
document.head.insertBefore(metaTag, document.head.firstChild);
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
3842
|
+
}
|
|
3843
|
+
async function kupolaBootstrap() {
|
|
3844
|
+
if (typeof window !== "undefined") {
|
|
3845
|
+
_applySecurityHeaders();
|
|
3846
|
+
const config2 = getConfig();
|
|
3847
|
+
kupolaData.loadPersisted();
|
|
3848
|
+
kupolaData.bind();
|
|
3849
|
+
initTheme();
|
|
3850
|
+
if (config2.components?.autoInit !== false) {
|
|
3851
|
+
await kupolaInitializer.initializeAll();
|
|
3852
|
+
if (kupolaRegistry) {
|
|
3853
|
+
await kupolaRegistry.bootstrap();
|
|
3854
|
+
}
|
|
3855
|
+
}
|
|
3856
|
+
}
|
|
3857
|
+
}
|
|
3858
|
+
if (typeof document !== "undefined" && document.readyState === "loading") {
|
|
3859
|
+
document.addEventListener("DOMContentLoaded", kupolaBootstrap);
|
|
3860
|
+
} else if (typeof window !== "undefined") {
|
|
3861
|
+
setTimeout(kupolaBootstrap, 0);
|
|
3862
|
+
}
|
|
3863
|
+
function registerComponent(name, componentClass) {
|
|
3864
|
+
if (kupolaRegistry) {
|
|
3865
|
+
kupolaRegistry.register(name, componentClass);
|
|
3866
|
+
}
|
|
3867
|
+
}
|
|
3868
|
+
function registerLazyComponent(name, loader) {
|
|
3869
|
+
if (kupolaRegistry) {
|
|
3870
|
+
kupolaRegistry.registerLazy(name, loader);
|
|
3871
|
+
}
|
|
3872
|
+
}
|
|
3873
|
+
function bootstrapComponents(root) {
|
|
3874
|
+
if (kupolaRegistry) {
|
|
3875
|
+
return kupolaRegistry.bootstrap(root);
|
|
3876
|
+
}
|
|
3877
|
+
return Promise.resolve();
|
|
3878
|
+
}
|
|
3879
|
+
function defineMixin(name, mixin) {
|
|
3880
|
+
if (kupolaRegistry) {
|
|
3881
|
+
kupolaRegistry.defineMixin(name, mixin);
|
|
3882
|
+
}
|
|
3883
|
+
}
|
|
3884
|
+
function useMixin(componentClass, ...mixinNames) {
|
|
3885
|
+
if (kupolaRegistry) {
|
|
3886
|
+
kupolaRegistry.useMixin(componentClass, ...mixinNames);
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3889
|
+
function defineComponent(name, options) {
|
|
3890
|
+
if (!options || typeof options !== "object") {
|
|
3891
|
+
throw new Error(`defineComponent("${name}"): options must be an object`);
|
|
3892
|
+
}
|
|
3893
|
+
if (options.componentClass) {
|
|
3894
|
+
if (kupolaRegistry) {
|
|
3895
|
+
kupolaRegistry.register(name, options.componentClass);
|
|
3896
|
+
}
|
|
3897
|
+
} else if (options.lazy) {
|
|
3898
|
+
if (kupolaRegistry) {
|
|
3899
|
+
kupolaRegistry.registerLazy(name, options.lazy);
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3902
|
+
if (options.init) {
|
|
3903
|
+
kupolaInitializer.register(name, options.init, options.cleanup || null, {
|
|
3904
|
+
dataAttribute: options.dataAttribute,
|
|
3905
|
+
cssClass: options.cssClass
|
|
3906
|
+
});
|
|
3907
|
+
} else if (options.dataAttribute || options.cssClass) {
|
|
3908
|
+
kupolaInitializer.register(name, () => {
|
|
3909
|
+
}, null, {
|
|
3910
|
+
dataAttribute: options.dataAttribute,
|
|
3911
|
+
cssClass: options.cssClass
|
|
3912
|
+
});
|
|
3913
|
+
}
|
|
3914
|
+
}
|
|
3915
|
+
class KupolaI18n {
|
|
3916
|
+
constructor(options = {}) {
|
|
3917
|
+
const config2 = getConfig();
|
|
3918
|
+
this.locales = options.locales || {};
|
|
3919
|
+
this.currentLocale = options.defaultLocale || config2.i18n?.locale || "zh-CN";
|
|
3920
|
+
this.fallbackLocale = options.fallbackLocale || config2.i18n?.fallbackLocale || "en-US";
|
|
3921
|
+
this.delimiter = options.delimiter || ".";
|
|
3922
|
+
this.missingHandler = options.missingHandler || ((key) => {
|
|
3923
|
+
console.warn(`Missing translation: ${key}`);
|
|
3924
|
+
return key;
|
|
3925
|
+
});
|
|
3926
|
+
this._initFromDOM();
|
|
3927
|
+
}
|
|
3928
|
+
_initFromDOM() {
|
|
3929
|
+
const scriptTags = document.querySelectorAll('script[type="application/json"][data-kupola-i18n]');
|
|
3930
|
+
scriptTags.forEach((script) => {
|
|
3931
|
+
const locale = script.dataset.kupolaI18n;
|
|
3932
|
+
if (locale) {
|
|
3933
|
+
try {
|
|
3934
|
+
const data = JSON.parse(script.textContent);
|
|
3935
|
+
this.addLocale(locale, data);
|
|
3936
|
+
} catch (e) {
|
|
3937
|
+
console.error("Failed to parse i18n data:", e);
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
});
|
|
3941
|
+
const htmlLang = document.documentElement.lang;
|
|
3942
|
+
if (htmlLang && this.locales[htmlLang]) {
|
|
3943
|
+
this.currentLocale = htmlLang;
|
|
3944
|
+
}
|
|
3945
|
+
}
|
|
3946
|
+
addLocale(locale, data) {
|
|
3947
|
+
if (!this.locales[locale]) {
|
|
3948
|
+
this.locales[locale] = {};
|
|
3949
|
+
}
|
|
3950
|
+
this._mergeDeep(this.locales[locale], data);
|
|
3951
|
+
}
|
|
3952
|
+
_mergeDeep(target, source) {
|
|
3953
|
+
for (const key of Object.keys(source)) {
|
|
3954
|
+
if (source[key] instanceof Object && key in target) {
|
|
3955
|
+
this._mergeDeep(target[key], source[key]);
|
|
3956
|
+
} else {
|
|
3957
|
+
target[key] = source[key];
|
|
3958
|
+
}
|
|
3959
|
+
}
|
|
3960
|
+
}
|
|
3961
|
+
setLocale(locale) {
|
|
3962
|
+
if (this.locales[locale]) {
|
|
3963
|
+
this.currentLocale = locale;
|
|
3964
|
+
document.documentElement.lang = locale;
|
|
3965
|
+
this._emitChange();
|
|
3966
|
+
return true;
|
|
3967
|
+
}
|
|
3968
|
+
return false;
|
|
3969
|
+
}
|
|
3970
|
+
getLocale() {
|
|
3971
|
+
return this.currentLocale;
|
|
3972
|
+
}
|
|
3973
|
+
t(key, params = {}) {
|
|
3974
|
+
let value = this._getTranslation(key, this.currentLocale);
|
|
3975
|
+
if (!value) {
|
|
3976
|
+
value = this._getTranslation(key, this.fallbackLocale);
|
|
3977
|
+
}
|
|
3978
|
+
if (!value) {
|
|
3979
|
+
return this.missingHandler(key);
|
|
3980
|
+
}
|
|
3981
|
+
return this._interpolate(value, params);
|
|
3982
|
+
}
|
|
3983
|
+
_getTranslation(key, locale) {
|
|
3984
|
+
if (!this.locales[locale]) return null;
|
|
3985
|
+
const keys2 = key.split(this.delimiter);
|
|
3986
|
+
let value = this.locales[locale];
|
|
3987
|
+
for (const k of keys2) {
|
|
3988
|
+
if (value && typeof value === "object" && k in value) {
|
|
3989
|
+
value = value[k];
|
|
3990
|
+
} else {
|
|
3991
|
+
return null;
|
|
3992
|
+
}
|
|
3993
|
+
}
|
|
3994
|
+
return typeof value === "string" ? value : null;
|
|
3995
|
+
}
|
|
3996
|
+
_interpolate(str, params) {
|
|
3997
|
+
return str.replace(/\{(\w+)\}/g, (match, key) => {
|
|
3998
|
+
return params[key] !== void 0 ? params[key] : match;
|
|
3999
|
+
});
|
|
4000
|
+
}
|
|
4001
|
+
n(key, count, params = {}) {
|
|
4002
|
+
const value = this.t(key, { ...params, count });
|
|
4003
|
+
if (!value) return value;
|
|
4004
|
+
const parts = value.split("|");
|
|
4005
|
+
if (parts.length === 1) {
|
|
4006
|
+
return value.replace("{count}", count);
|
|
4007
|
+
}
|
|
4008
|
+
if (parts.length === 2) {
|
|
4009
|
+
return count === 1 ? parts[0] : parts[1];
|
|
4010
|
+
}
|
|
4011
|
+
if (parts.length >= 3) {
|
|
4012
|
+
if (count === 0) return parts[0];
|
|
4013
|
+
if (count === 1) return parts[1];
|
|
4014
|
+
return parts[2];
|
|
4015
|
+
}
|
|
4016
|
+
return value;
|
|
4017
|
+
}
|
|
4018
|
+
_emitChange() {
|
|
4019
|
+
const event = new CustomEvent("kupola:i18n:change", {
|
|
4020
|
+
detail: { locale: this.currentLocale },
|
|
4021
|
+
bubbles: true
|
|
4022
|
+
});
|
|
4023
|
+
document.dispatchEvent(event);
|
|
4024
|
+
}
|
|
4025
|
+
async loadLocale(locale, url) {
|
|
4026
|
+
try {
|
|
4027
|
+
const response = await fetch(url);
|
|
4028
|
+
const data = await response.json();
|
|
4029
|
+
this.addLocale(locale, data);
|
|
4030
|
+
return true;
|
|
4031
|
+
} catch (error) {
|
|
4032
|
+
console.error("Failed to load locale:", error);
|
|
4033
|
+
return false;
|
|
4034
|
+
}
|
|
4035
|
+
}
|
|
4036
|
+
getAvailableLocales() {
|
|
4037
|
+
return Object.keys(this.locales);
|
|
4038
|
+
}
|
|
4039
|
+
hasLocale(locale) {
|
|
4040
|
+
return !!this.locales[locale];
|
|
4041
|
+
}
|
|
4042
|
+
formatDate(date, options = {}) {
|
|
4043
|
+
const locale = options.locale || this.currentLocale;
|
|
4044
|
+
const dateObj = typeof date === "string" ? new Date(date) : date;
|
|
4045
|
+
return new Intl.DateTimeFormat(locale, options).format(dateObj);
|
|
4046
|
+
}
|
|
4047
|
+
formatNumber(number, options = {}) {
|
|
4048
|
+
const locale = options.locale || this.currentLocale;
|
|
4049
|
+
return new Intl.NumberFormat(locale, options).format(number);
|
|
4050
|
+
}
|
|
4051
|
+
formatCurrency(number, currency, options = {}) {
|
|
4052
|
+
const locale = options.locale || this.currentLocale;
|
|
4053
|
+
return new Intl.NumberFormat(locale, {
|
|
4054
|
+
style: "currency",
|
|
4055
|
+
currency,
|
|
4056
|
+
...options
|
|
4057
|
+
}).format(number);
|
|
4058
|
+
}
|
|
4059
|
+
formatRelativeTime(value, unit, options = {}) {
|
|
4060
|
+
const locale = options.locale || this.currentLocale;
|
|
4061
|
+
return new Intl.RelativeTimeFormat(locale, options).format(value, unit);
|
|
4062
|
+
}
|
|
4063
|
+
}
|
|
4064
|
+
const kupolaI18n = new KupolaI18n();
|
|
4065
|
+
function createI18n(options) {
|
|
4066
|
+
return new KupolaI18n(options);
|
|
4067
|
+
}
|
|
4068
|
+
function t(key, params = {}) {
|
|
4069
|
+
return kupolaI18n.t(key, params);
|
|
4070
|
+
}
|
|
4071
|
+
function n(key, count, params = {}) {
|
|
4072
|
+
return kupolaI18n.n(key, count, params);
|
|
4073
|
+
}
|
|
4074
|
+
function setLocale(locale) {
|
|
4075
|
+
return kupolaI18n.setLocale(locale);
|
|
4076
|
+
}
|
|
4077
|
+
function getLocale() {
|
|
4078
|
+
return kupolaI18n.getLocale();
|
|
4079
|
+
}
|
|
4080
|
+
function formatDate(date, options = {}) {
|
|
4081
|
+
return kupolaI18n.formatDate(date, options);
|
|
4082
|
+
}
|
|
4083
|
+
function formatNumber(number, options = {}) {
|
|
4084
|
+
return kupolaI18n.formatNumber(number, options);
|
|
4085
|
+
}
|
|
4086
|
+
function formatCurrency(number, currency, options = {}) {
|
|
4087
|
+
return kupolaI18n.formatCurrency(number, currency, options);
|
|
4088
|
+
}
|
|
4089
|
+
class GlobalEvents {
|
|
4090
|
+
constructor() {
|
|
4091
|
+
this._listeners = /* @__PURE__ */ new Map();
|
|
4092
|
+
this._scopeListeners = /* @__PURE__ */ new Map();
|
|
4093
|
+
}
|
|
4094
|
+
on(target, eventName, handler, options = {}) {
|
|
4095
|
+
const { scope = null, once: once2 = false, passive = false, capture = false } = options;
|
|
4096
|
+
const listenerId = this._generateId();
|
|
4097
|
+
const listener = {
|
|
4098
|
+
id: listenerId,
|
|
4099
|
+
target,
|
|
4100
|
+
eventName,
|
|
4101
|
+
handler,
|
|
4102
|
+
scope,
|
|
4103
|
+
once: once2,
|
|
4104
|
+
wrappedHandler: null
|
|
4105
|
+
};
|
|
4106
|
+
listener.wrappedHandler = (event) => {
|
|
4107
|
+
if (once2) {
|
|
4108
|
+
this.offById(listenerId);
|
|
4109
|
+
}
|
|
4110
|
+
handler.call(target, event);
|
|
4111
|
+
};
|
|
4112
|
+
const eventKey = this._getEventKey(target, eventName);
|
|
4113
|
+
if (!this._listeners.has(eventKey)) {
|
|
4114
|
+
this._listeners.set(eventKey, []);
|
|
4115
|
+
}
|
|
4116
|
+
this._listeners.get(eventKey).push(listener);
|
|
4117
|
+
if (scope) {
|
|
4118
|
+
if (!this._scopeListeners.has(scope)) {
|
|
4119
|
+
this._scopeListeners.set(scope, []);
|
|
4120
|
+
}
|
|
4121
|
+
this._scopeListeners.get(scope).push(listenerId);
|
|
4122
|
+
}
|
|
4123
|
+
target.addEventListener(eventName, listener.wrappedHandler, {
|
|
4124
|
+
passive,
|
|
4125
|
+
capture
|
|
4126
|
+
});
|
|
4127
|
+
return {
|
|
4128
|
+
unsubscribe: () => this.offById(listenerId)
|
|
4129
|
+
};
|
|
4130
|
+
}
|
|
4131
|
+
once(target, eventName, handler, options = {}) {
|
|
4132
|
+
return this.on(target, eventName, handler, { ...options, once: true });
|
|
4133
|
+
}
|
|
4134
|
+
off(target, eventName, handler) {
|
|
4135
|
+
const eventKey = this._getEventKey(target, eventName);
|
|
4136
|
+
if (!this._listeners.has(eventKey)) return;
|
|
4137
|
+
const listeners = this._listeners.get(eventKey);
|
|
4138
|
+
const filtered = listeners.filter((listener) => listener.handler !== handler);
|
|
4139
|
+
listeners.forEach((listener) => {
|
|
4140
|
+
if (listener.handler === handler) {
|
|
4141
|
+
target.removeEventListener(eventName, listener.wrappedHandler);
|
|
4142
|
+
this._removeFromScope(listener);
|
|
4143
|
+
}
|
|
4144
|
+
});
|
|
4145
|
+
if (filtered.length === 0) {
|
|
4146
|
+
this._listeners.delete(eventKey);
|
|
4147
|
+
} else {
|
|
4148
|
+
this._listeners.set(eventKey, filtered);
|
|
4149
|
+
}
|
|
4150
|
+
}
|
|
4151
|
+
offById(listenerId) {
|
|
4152
|
+
for (const [eventKey, listeners] of this._listeners) {
|
|
4153
|
+
const index = listeners.findIndex((l) => l.id === listenerId);
|
|
4154
|
+
if (index !== -1) {
|
|
4155
|
+
const listener = listeners[index];
|
|
4156
|
+
listener.target.removeEventListener(listener.eventName, listener.wrappedHandler);
|
|
4157
|
+
listeners.splice(index, 1);
|
|
4158
|
+
if (listeners.length === 0) {
|
|
4159
|
+
this._listeners.delete(eventKey);
|
|
4160
|
+
}
|
|
4161
|
+
this._removeFromScope(listener);
|
|
4162
|
+
return true;
|
|
4163
|
+
}
|
|
4164
|
+
}
|
|
4165
|
+
return false;
|
|
4166
|
+
}
|
|
4167
|
+
offByScope(scope) {
|
|
4168
|
+
if (!this._scopeListeners.has(scope)) return;
|
|
4169
|
+
const listenerIds = this._scopeListeners.get(scope);
|
|
4170
|
+
listenerIds.forEach((listenerId) => {
|
|
4171
|
+
this.offById(listenerId);
|
|
4172
|
+
});
|
|
4173
|
+
this._scopeListeners.delete(scope);
|
|
4174
|
+
}
|
|
4175
|
+
offAll(target, eventName = null) {
|
|
4176
|
+
if (eventName) {
|
|
4177
|
+
const eventKey = this._getEventKey(target, eventName);
|
|
4178
|
+
if (!this._listeners.has(eventKey)) return;
|
|
4179
|
+
const listeners = this._listeners.get(eventKey);
|
|
4180
|
+
listeners.forEach((listener) => {
|
|
4181
|
+
target.removeEventListener(eventName, listener.wrappedHandler);
|
|
4182
|
+
this._removeFromScope(listener);
|
|
4183
|
+
});
|
|
4184
|
+
this._listeners.delete(eventKey);
|
|
4185
|
+
} else {
|
|
4186
|
+
for (const [eventKey, listeners] of this._listeners) {
|
|
4187
|
+
const [targetId] = eventKey.split(":");
|
|
4188
|
+
if (this._getTargetId(target) === targetId) {
|
|
4189
|
+
listeners.forEach((listener) => {
|
|
4190
|
+
target.removeEventListener(listener.eventName, listener.wrappedHandler);
|
|
4191
|
+
this._removeFromScope(listener);
|
|
4192
|
+
});
|
|
4193
|
+
this._listeners.delete(eventKey);
|
|
4194
|
+
}
|
|
4195
|
+
}
|
|
4196
|
+
}
|
|
4197
|
+
}
|
|
4198
|
+
emit(target, eventName, detail = {}) {
|
|
4199
|
+
const event = new CustomEvent(eventName, {
|
|
4200
|
+
detail,
|
|
4201
|
+
bubbles: true,
|
|
4202
|
+
cancelable: true
|
|
4203
|
+
});
|
|
4204
|
+
target.dispatchEvent(event);
|
|
4205
|
+
return event;
|
|
4206
|
+
}
|
|
4207
|
+
emitGlobal(eventName, detail = {}) {
|
|
4208
|
+
return this.emit(document, eventName, detail);
|
|
4209
|
+
}
|
|
4210
|
+
emitToScope(scope, eventName, detail = {}) {
|
|
4211
|
+
if (!this._scopeListeners.has(scope)) return;
|
|
4212
|
+
const listenerIds = this._scopeListeners.get(scope);
|
|
4213
|
+
const targets = /* @__PURE__ */ new Set();
|
|
4214
|
+
for (const [eventKey, listeners] of this._listeners) {
|
|
4215
|
+
listeners.forEach((listener) => {
|
|
4216
|
+
if (listenerIds.includes(listener.id)) {
|
|
4217
|
+
targets.add(listener.target);
|
|
4218
|
+
}
|
|
4219
|
+
});
|
|
4220
|
+
}
|
|
4221
|
+
targets.forEach((target) => {
|
|
4222
|
+
this.emit(target, eventName, detail);
|
|
4223
|
+
});
|
|
4224
|
+
}
|
|
4225
|
+
getListenerCount(target, eventName = null) {
|
|
4226
|
+
if (eventName) {
|
|
4227
|
+
const eventKey = this._getEventKey(target, eventName);
|
|
4228
|
+
return this._listeners.has(eventKey) ? this._listeners.get(eventKey).length : 0;
|
|
4229
|
+
}
|
|
4230
|
+
let count = 0;
|
|
4231
|
+
const targetId = this._getTargetId(target);
|
|
4232
|
+
for (const [eventKey, listeners] of this._listeners) {
|
|
4233
|
+
const [keyTargetId] = eventKey.split(":");
|
|
4234
|
+
if (keyTargetId === targetId) {
|
|
4235
|
+
count += listeners.length;
|
|
4236
|
+
}
|
|
4237
|
+
}
|
|
4238
|
+
return count;
|
|
4239
|
+
}
|
|
4240
|
+
getScopeListenerCount(scope) {
|
|
4241
|
+
return this._scopeListeners.has(scope) ? this._scopeListeners.get(scope).length : 0;
|
|
4242
|
+
}
|
|
4243
|
+
hasListeners(target, eventName = null) {
|
|
4244
|
+
return this.getListenerCount(target, eventName) > 0;
|
|
4245
|
+
}
|
|
4246
|
+
_getEventKey(target, eventName) {
|
|
4247
|
+
return `${this._getTargetId(target)}:${eventName}`;
|
|
4248
|
+
}
|
|
4249
|
+
_getTargetId(target) {
|
|
4250
|
+
if (target === document) return "document";
|
|
4251
|
+
if (target === window) return "window";
|
|
4252
|
+
if (target === document.body) return "body";
|
|
4253
|
+
if (target._kupolaId) return target._kupolaId;
|
|
4254
|
+
target._kupolaId = this._generateId();
|
|
4255
|
+
return target._kupolaId;
|
|
4256
|
+
}
|
|
4257
|
+
_generateId() {
|
|
4258
|
+
return `ge-${Math.random().toString(36).substr(2, 9)}-${Date.now()}`;
|
|
4259
|
+
}
|
|
4260
|
+
_removeFromScope(listener) {
|
|
4261
|
+
if (!listener.scope || !this._scopeListeners.has(listener.scope)) return;
|
|
4262
|
+
const scopeListeners = this._scopeListeners.get(listener.scope);
|
|
4263
|
+
const index = scopeListeners.indexOf(listener.id);
|
|
4264
|
+
if (index !== -1) {
|
|
4265
|
+
scopeListeners.splice(index, 1);
|
|
4266
|
+
if (scopeListeners.length === 0) {
|
|
4267
|
+
this._scopeListeners.delete(listener.scope);
|
|
4268
|
+
}
|
|
4269
|
+
}
|
|
4270
|
+
}
|
|
4271
|
+
destroy() {
|
|
4272
|
+
for (const [eventKey, listeners] of this._listeners) {
|
|
4273
|
+
listeners.forEach((listener) => {
|
|
4274
|
+
listener.target.removeEventListener(listener.eventName, listener.wrappedHandler);
|
|
4275
|
+
});
|
|
4276
|
+
}
|
|
4277
|
+
this._listeners.clear();
|
|
4278
|
+
this._scopeListeners.clear();
|
|
4279
|
+
}
|
|
4280
|
+
}
|
|
4281
|
+
const globalEvents = new GlobalEvents();
|
|
4282
|
+
function on(target, eventName, handler, options) {
|
|
4283
|
+
return globalEvents.on(target, eventName, handler, options);
|
|
4284
|
+
}
|
|
4285
|
+
function once(target, eventName, handler, options) {
|
|
4286
|
+
return globalEvents.once(target, eventName, handler, options);
|
|
4287
|
+
}
|
|
4288
|
+
function off(target, eventName, handler) {
|
|
4289
|
+
globalEvents.off(target, eventName, handler);
|
|
4290
|
+
}
|
|
4291
|
+
function emit(target, eventName, detail) {
|
|
4292
|
+
return globalEvents.emit(target, eventName, detail);
|
|
4293
|
+
}
|
|
4294
|
+
function emitGlobal(eventName, detail) {
|
|
4295
|
+
return globalEvents.emitGlobal(eventName, detail);
|
|
4296
|
+
}
|
|
4297
|
+
function offByScope(scope) {
|
|
4298
|
+
globalEvents.offByScope(scope);
|
|
4299
|
+
}
|
|
4300
|
+
function offAll(target, eventName) {
|
|
4301
|
+
globalEvents.offAll(target, eventName);
|
|
4302
|
+
}
|
|
4303
|
+
function getListenerCount(target, eventName) {
|
|
4304
|
+
return globalEvents.getListenerCount(target, eventName);
|
|
4305
|
+
}
|
|
4306
|
+
class Scheduler {
|
|
4307
|
+
constructor() {
|
|
4308
|
+
this._queue = /* @__PURE__ */ new Set();
|
|
4309
|
+
this._scheduled = false;
|
|
4310
|
+
this._flushDepth = 0;
|
|
4311
|
+
this._maxDepth = 10;
|
|
4312
|
+
}
|
|
4313
|
+
schedule(fn) {
|
|
4314
|
+
this._queue.add(fn);
|
|
4315
|
+
if (!this._scheduled) {
|
|
4316
|
+
this._scheduled = true;
|
|
4317
|
+
queueMicrotask(() => this._flush());
|
|
4318
|
+
}
|
|
4319
|
+
}
|
|
4320
|
+
_flush() {
|
|
4321
|
+
if (this._flushDepth >= this._maxDepth) {
|
|
4322
|
+
console.warn("[Kupola Scheduler] Max flush depth reached, possible infinite loop detected");
|
|
4323
|
+
this._queue.clear();
|
|
4324
|
+
this._scheduled = false;
|
|
4325
|
+
return;
|
|
4326
|
+
}
|
|
4327
|
+
const tasks = Array.from(this._queue);
|
|
4328
|
+
this._queue.clear();
|
|
4329
|
+
this._scheduled = false;
|
|
4330
|
+
this._flushDepth++;
|
|
4331
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4332
|
+
for (const task of tasks) {
|
|
4333
|
+
if (!seen.has(task)) {
|
|
4334
|
+
seen.add(task);
|
|
4335
|
+
try {
|
|
4336
|
+
task();
|
|
4337
|
+
} catch (e) {
|
|
4338
|
+
console.error("[DependsScheduler]", e);
|
|
4339
|
+
}
|
|
4340
|
+
}
|
|
4341
|
+
}
|
|
4342
|
+
this._flushDepth--;
|
|
4343
|
+
}
|
|
4344
|
+
}
|
|
4345
|
+
const globalScheduler = new Scheduler();
|
|
4346
|
+
class CacheEntry {
|
|
4347
|
+
constructor(data, ttl) {
|
|
4348
|
+
this.data = data;
|
|
4349
|
+
this.createdAt = Date.now();
|
|
4350
|
+
this.ttl = ttl;
|
|
4351
|
+
}
|
|
4352
|
+
get isFresh() {
|
|
4353
|
+
return Date.now() - this.createdAt < this.ttl;
|
|
4354
|
+
}
|
|
4355
|
+
get isStale() {
|
|
4356
|
+
return !this.isFresh;
|
|
4357
|
+
}
|
|
4358
|
+
}
|
|
4359
|
+
class CacheManager {
|
|
4360
|
+
constructor() {
|
|
4361
|
+
this._store = /* @__PURE__ */ new Map();
|
|
4362
|
+
}
|
|
4363
|
+
get(key) {
|
|
4364
|
+
const entry = this._store.get(key);
|
|
4365
|
+
if (!entry) return null;
|
|
4366
|
+
return entry;
|
|
4367
|
+
}
|
|
4368
|
+
set(key, data, ttl = 6e4) {
|
|
4369
|
+
this._store.set(key, new CacheEntry(data, ttl));
|
|
4370
|
+
}
|
|
4371
|
+
has(key) {
|
|
4372
|
+
return this._store.has(key);
|
|
4373
|
+
}
|
|
4374
|
+
delete(key) {
|
|
4375
|
+
this._store.delete(key);
|
|
4376
|
+
}
|
|
4377
|
+
clear() {
|
|
4378
|
+
this._store.clear();
|
|
4379
|
+
}
|
|
4380
|
+
// Get data regardless of freshness (for stale-while-revalidate)
|
|
4381
|
+
getStale(key) {
|
|
4382
|
+
const entry = this._store.get(key);
|
|
4383
|
+
return entry ? entry.data : null;
|
|
4384
|
+
}
|
|
4385
|
+
}
|
|
4386
|
+
class DependsError extends Error {
|
|
4387
|
+
constructor(message, code, cause) {
|
|
4388
|
+
super(message);
|
|
4389
|
+
this.name = "DependsError";
|
|
4390
|
+
this.code = code;
|
|
4391
|
+
this.cause = cause;
|
|
4392
|
+
this.timestamp = Date.now();
|
|
4393
|
+
}
|
|
4394
|
+
}
|
|
4395
|
+
let _httpClientFetch = typeof globalThis !== "undefined" && globalThis.fetch ? globalThis.fetch.bind(globalThis) : typeof window !== "undefined" && window.fetch ? window.fetch.bind(window) : null;
|
|
4396
|
+
function configureHttpClient(client) {
|
|
4397
|
+
if (!client || typeof client.fetch !== "function") {
|
|
4398
|
+
throw new TypeError("[Kupola] configureHttpClient: client must provide a fetch function");
|
|
4399
|
+
}
|
|
4400
|
+
_httpClientFetch = client.fetch.bind(client);
|
|
4401
|
+
}
|
|
4402
|
+
function getHttpClient() {
|
|
4403
|
+
return _httpClientFetch;
|
|
4404
|
+
}
|
|
4405
|
+
function resetHttpClient() {
|
|
4406
|
+
_httpClientFetch = typeof globalThis !== "undefined" && globalThis.fetch ? globalThis.fetch.bind(globalThis) : typeof window !== "undefined" && window.fetch ? window.fetch.bind(window) : null;
|
|
4407
|
+
}
|
|
4408
|
+
class DependsSource {
|
|
4409
|
+
constructor(config2, cacheManager) {
|
|
4410
|
+
this.config = config2;
|
|
4411
|
+
this.cacheKey = config2.cacheKey || String(config2.source);
|
|
4412
|
+
this.staleTime = config2.staleTime ?? 6e4;
|
|
4413
|
+
this.cache = cacheManager;
|
|
4414
|
+
this.subscribers = [];
|
|
4415
|
+
this.pending = null;
|
|
4416
|
+
this.retryCount = config2.retry ?? 0;
|
|
4417
|
+
this.retryDelay = config2.retryDelay ?? 1e3;
|
|
4418
|
+
this.onError = config2.onError || null;
|
|
4419
|
+
}
|
|
4420
|
+
subscribe(callback) {
|
|
4421
|
+
this.subscribers.push(callback);
|
|
4422
|
+
return () => {
|
|
4423
|
+
const idx = this.subscribers.indexOf(callback);
|
|
4424
|
+
if (idx > -1) this.subscribers.splice(idx, 1);
|
|
4425
|
+
};
|
|
4426
|
+
}
|
|
4427
|
+
notify() {
|
|
4428
|
+
globalScheduler.schedule(() => {
|
|
4429
|
+
this.subscribers.forEach((cb) => {
|
|
4430
|
+
try {
|
|
4431
|
+
cb();
|
|
4432
|
+
} catch (e) {
|
|
4433
|
+
console.error("[DependsSource.notify]", e);
|
|
4434
|
+
}
|
|
4435
|
+
});
|
|
4436
|
+
});
|
|
4437
|
+
}
|
|
4438
|
+
async fetch(params) {
|
|
4439
|
+
throw new DependsError("Source fetch not implemented", "NOT_IMPLEMENTED");
|
|
4440
|
+
}
|
|
4441
|
+
async getValue(params) {
|
|
4442
|
+
const cached = this.cache.get(this.cacheKey);
|
|
4443
|
+
if (cached && cached.isFresh) {
|
|
4444
|
+
return cached.data;
|
|
4445
|
+
}
|
|
4446
|
+
if (cached && cached.isStale) {
|
|
4447
|
+
this._revalidate(params);
|
|
4448
|
+
return cached.data;
|
|
4449
|
+
}
|
|
4450
|
+
return this._fetchWithRetry(params);
|
|
4451
|
+
}
|
|
4452
|
+
async _fetchWithRetry(params, attempt = 0) {
|
|
4453
|
+
try {
|
|
4454
|
+
const result = await this.fetch(params);
|
|
4455
|
+
this.cache.set(this.cacheKey, result, this.staleTime);
|
|
4456
|
+
this.notify();
|
|
4457
|
+
return result;
|
|
4458
|
+
} catch (error) {
|
|
4459
|
+
if (attempt < this.retryCount) {
|
|
4460
|
+
const baseDelay = this.retryDelay * Math.pow(2, attempt);
|
|
4461
|
+
const jitter = Math.random() * baseDelay * 0.5;
|
|
4462
|
+
const delay = baseDelay + jitter;
|
|
4463
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
4464
|
+
return this._fetchWithRetry(params, attempt + 1);
|
|
4465
|
+
}
|
|
4466
|
+
const depError = error instanceof DependsError ? error : new DependsError(error.message || "Fetch failed", "FETCH_ERROR", error);
|
|
4467
|
+
if (this.onError) {
|
|
4468
|
+
try {
|
|
4469
|
+
this.onError(depError);
|
|
4470
|
+
} catch (_) {
|
|
4471
|
+
}
|
|
4472
|
+
}
|
|
4473
|
+
throw depError;
|
|
4474
|
+
}
|
|
4475
|
+
}
|
|
4476
|
+
async _revalidate(params) {
|
|
4477
|
+
try {
|
|
4478
|
+
await this._fetchWithRetry(params);
|
|
4479
|
+
} catch (_) {
|
|
4480
|
+
}
|
|
4481
|
+
}
|
|
4482
|
+
invalidate() {
|
|
4483
|
+
this.cache.delete(this.cacheKey);
|
|
4484
|
+
this.pending = null;
|
|
4485
|
+
}
|
|
4486
|
+
destroy() {
|
|
4487
|
+
this.subscribers = [];
|
|
4488
|
+
this.pending = null;
|
|
4489
|
+
}
|
|
4490
|
+
}
|
|
4491
|
+
class FetchedSource extends DependsSource {
|
|
4492
|
+
constructor(config2, cacheManager) {
|
|
4493
|
+
super(config2, cacheManager);
|
|
4494
|
+
this.method = config2.method || "GET";
|
|
4495
|
+
this.headers = config2.headers || {};
|
|
4496
|
+
this.queryParams = config2.query || {};
|
|
4497
|
+
}
|
|
4498
|
+
async fetch(params) {
|
|
4499
|
+
let url = this.config.source;
|
|
4500
|
+
const httpConfig = getConfig("http");
|
|
4501
|
+
if (httpConfig?.baseURL && !url.startsWith("http://") && !url.startsWith("https://")) {
|
|
4502
|
+
url = httpConfig.baseURL + url.replace(/^\//, "");
|
|
4503
|
+
}
|
|
4504
|
+
for (const key in params) {
|
|
4505
|
+
url = url.replace(`:${key}`, encodeURIComponent(params[key]));
|
|
4506
|
+
}
|
|
4507
|
+
const queryParts = [];
|
|
4508
|
+
for (const [k, v] of Object.entries(this.queryParams || {})) {
|
|
4509
|
+
queryParts.push(`${encodeURIComponent(k)}=${encodeURIComponent(v)}`);
|
|
4510
|
+
}
|
|
4511
|
+
for (const key in params) {
|
|
4512
|
+
if (!this.config.source.includes(`:${key}`)) {
|
|
4513
|
+
queryParts.push(`${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`);
|
|
4514
|
+
}
|
|
4515
|
+
}
|
|
4516
|
+
if (queryParts.length > 0) {
|
|
4517
|
+
url += (url.includes("?") ? "&" : "?") + queryParts.join("&");
|
|
4518
|
+
}
|
|
4519
|
+
const globalHeaders = httpConfig?.headers || {};
|
|
4520
|
+
const fetchOptions = {
|
|
4521
|
+
method: this.method.toUpperCase(),
|
|
4522
|
+
headers: { "Content-Type": "application/json", ...globalHeaders, ...this.headers }
|
|
4523
|
+
};
|
|
4524
|
+
if (httpConfig?.withCredentials) {
|
|
4525
|
+
fetchOptions.credentials = "include";
|
|
4526
|
+
}
|
|
4527
|
+
if (["POST", "PUT", "PATCH"].includes(fetchOptions.method)) {
|
|
4528
|
+
fetchOptions.body = JSON.stringify(params);
|
|
4529
|
+
}
|
|
4530
|
+
const clientFetch = _httpClientFetch;
|
|
4531
|
+
if (!clientFetch) {
|
|
4532
|
+
throw new DependsError("No HTTP client available. Use configureHttpClient() to set one.", "NO_HTTP_CLIENT");
|
|
4533
|
+
}
|
|
4534
|
+
const response = await clientFetch(url, fetchOptions);
|
|
4535
|
+
const ok = typeof response.ok === "boolean" ? response.ok : response.status >= 200 && response.status < 300;
|
|
4536
|
+
const status = typeof response.status === "number" ? response.status : 0;
|
|
4537
|
+
if (!ok) {
|
|
4538
|
+
throw new DependsError(`HTTP ${status}`, "HTTP_ERROR");
|
|
4539
|
+
}
|
|
4540
|
+
const data = typeof response.json === "function" ? await response.json() : response.data !== void 0 ? response.data : response;
|
|
4541
|
+
return data;
|
|
4542
|
+
}
|
|
4543
|
+
}
|
|
4544
|
+
class StorageSource extends DependsSource {
|
|
4545
|
+
constructor(config2, cacheManager) {
|
|
4546
|
+
super(config2, cacheManager);
|
|
4547
|
+
this.storageKey = config2.source.replace("localStorage:", "");
|
|
4548
|
+
this.defaultValue = config2.default;
|
|
4549
|
+
this.sync = config2.sync !== false;
|
|
4550
|
+
if (this.sync && typeof window !== "undefined") {
|
|
4551
|
+
this._storageHandler = (e) => {
|
|
4552
|
+
if (e.key === this.storageKey) {
|
|
4553
|
+
this.cache.delete(this.cacheKey);
|
|
4554
|
+
this.notify();
|
|
4555
|
+
}
|
|
4556
|
+
};
|
|
4557
|
+
window.addEventListener("storage", this._storageHandler);
|
|
4558
|
+
}
|
|
4559
|
+
}
|
|
4560
|
+
async fetch() {
|
|
4561
|
+
try {
|
|
4562
|
+
const value = localStorage.getItem(this.storageKey);
|
|
4563
|
+
if (value === null) return this.defaultValue;
|
|
4564
|
+
try {
|
|
4565
|
+
return JSON.parse(value);
|
|
4566
|
+
} catch (_) {
|
|
4567
|
+
return value;
|
|
4568
|
+
}
|
|
4569
|
+
} catch (e) {
|
|
4570
|
+
return this.defaultValue;
|
|
4571
|
+
}
|
|
4572
|
+
}
|
|
4573
|
+
setValue(value) {
|
|
4574
|
+
const serialized = typeof value === "string" ? value : JSON.stringify(value);
|
|
4575
|
+
localStorage.setItem(this.storageKey, serialized);
|
|
4576
|
+
this.cache.delete(this.cacheKey);
|
|
4577
|
+
this.notify();
|
|
4578
|
+
}
|
|
4579
|
+
destroy() {
|
|
4580
|
+
super.destroy();
|
|
4581
|
+
if (this._storageHandler) {
|
|
4582
|
+
window.removeEventListener("storage", this._storageHandler);
|
|
4583
|
+
}
|
|
4584
|
+
}
|
|
4585
|
+
}
|
|
4586
|
+
class RouteSource extends DependsSource {
|
|
4587
|
+
constructor(config2, cacheManager) {
|
|
4588
|
+
super(config2, cacheManager);
|
|
4589
|
+
this.paramName = config2.source.replace("route:", "");
|
|
4590
|
+
}
|
|
4591
|
+
async fetch() {
|
|
4592
|
+
if (typeof window === "undefined") return "";
|
|
4593
|
+
const hash = location.hash.slice(1);
|
|
4594
|
+
const match = hash.match(new RegExp(`/${this.paramName}/([^/]+)`));
|
|
4595
|
+
if (match) return decodeURIComponent(match[1]);
|
|
4596
|
+
const urlParams = new URLSearchParams(location.search);
|
|
4597
|
+
return urlParams.get(this.paramName) || "";
|
|
4598
|
+
}
|
|
4599
|
+
}
|
|
4600
|
+
class FunctionSource extends DependsSource {
|
|
4601
|
+
async fetch(params) {
|
|
4602
|
+
const result = await this.config.source(params);
|
|
4603
|
+
return result;
|
|
4604
|
+
}
|
|
4605
|
+
}
|
|
4606
|
+
class StaticSource extends DependsSource {
|
|
4607
|
+
async fetch() {
|
|
4608
|
+
return this.config.source;
|
|
4609
|
+
}
|
|
4610
|
+
}
|
|
4611
|
+
class WebSocketSource extends DependsSource {
|
|
4612
|
+
constructor(config2, cacheManager) {
|
|
4613
|
+
super(config2, cacheManager);
|
|
4614
|
+
this.ws = null;
|
|
4615
|
+
this.reconnect = config2.reconnect !== false;
|
|
4616
|
+
this.reconnectDelay = config2.reconnectDelay || 3e3;
|
|
4617
|
+
this._reconnectAttempt = 0;
|
|
4618
|
+
this._maxReconnectDelay = config2.maxReconnectDelay || 3e4;
|
|
4619
|
+
this.messageHandler = null;
|
|
4620
|
+
this._connected = false;
|
|
4621
|
+
this._destroyed = false;
|
|
4622
|
+
}
|
|
4623
|
+
async fetch() {
|
|
4624
|
+
return new Promise((resolve, reject) => {
|
|
4625
|
+
try {
|
|
4626
|
+
this.ws = new WebSocket(this.config.source);
|
|
4627
|
+
this.ws.onopen = () => {
|
|
4628
|
+
this._connected = true;
|
|
4629
|
+
this._reconnectAttempt = 0;
|
|
4630
|
+
resolve(this.cache.getStale(this.cacheKey));
|
|
4631
|
+
};
|
|
4632
|
+
this.messageHandler = (event) => {
|
|
4633
|
+
let data;
|
|
4634
|
+
try {
|
|
4635
|
+
data = JSON.parse(event.data);
|
|
4636
|
+
} catch (_) {
|
|
4637
|
+
data = event.data;
|
|
4638
|
+
}
|
|
4639
|
+
this.cache.set(this.cacheKey, data, this.staleTime);
|
|
4640
|
+
this.notify();
|
|
4641
|
+
};
|
|
4642
|
+
this.ws.onmessage = this.messageHandler;
|
|
4643
|
+
this.ws.onerror = (error) => {
|
|
4644
|
+
if (!this._connected) {
|
|
4645
|
+
reject(new DependsError("WebSocket connection failed", "WS_ERROR", error));
|
|
4646
|
+
}
|
|
4647
|
+
};
|
|
4648
|
+
this.ws.onclose = () => {
|
|
4649
|
+
this._connected = false;
|
|
4650
|
+
if (this.reconnect && !this._destroyed) {
|
|
4651
|
+
const baseDelay = this.reconnectDelay * Math.pow(2, this._reconnectAttempt);
|
|
4652
|
+
const jitter = Math.random() * baseDelay * 0.3;
|
|
4653
|
+
const delay = Math.min(baseDelay + jitter, this._maxReconnectDelay);
|
|
4654
|
+
this._reconnectAttempt++;
|
|
4655
|
+
setTimeout(() => {
|
|
4656
|
+
if (!this._destroyed) this.fetch().catch(() => {
|
|
4657
|
+
});
|
|
4658
|
+
}, delay);
|
|
4659
|
+
}
|
|
4660
|
+
};
|
|
4661
|
+
} catch (e) {
|
|
4662
|
+
reject(new DependsError("WebSocket creation failed", "WS_ERROR", e));
|
|
4663
|
+
}
|
|
4664
|
+
});
|
|
4665
|
+
}
|
|
4666
|
+
send(data) {
|
|
4667
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
4668
|
+
this.ws.send(typeof data === "string" ? data : JSON.stringify(data));
|
|
4669
|
+
}
|
|
4670
|
+
}
|
|
4671
|
+
destroy() {
|
|
4672
|
+
this._destroyed = true;
|
|
4673
|
+
super.destroy();
|
|
4674
|
+
if (this.ws) {
|
|
4675
|
+
this.ws.onmessage = null;
|
|
4676
|
+
this.ws.onclose = null;
|
|
4677
|
+
this.ws.close();
|
|
4678
|
+
this.ws = null;
|
|
4679
|
+
}
|
|
4680
|
+
}
|
|
4681
|
+
}
|
|
4682
|
+
function createSource(config2, cacheManager) {
|
|
4683
|
+
const source = config2.source;
|
|
4684
|
+
if (typeof source === "function") {
|
|
4685
|
+
return new FunctionSource(config2, cacheManager);
|
|
4686
|
+
} else if (typeof source === "string" && (source.startsWith("ws://") || source.startsWith("wss://"))) {
|
|
4687
|
+
return new WebSocketSource(config2, cacheManager);
|
|
4688
|
+
} else if (typeof source === "string" && (source.startsWith("/") || source.startsWith("http"))) {
|
|
4689
|
+
return new FetchedSource(config2, cacheManager);
|
|
4690
|
+
} else if (typeof source === "string" && source.startsWith("localStorage:")) {
|
|
4691
|
+
return new StorageSource(config2, cacheManager);
|
|
4692
|
+
} else if (typeof source === "string" && source.startsWith("route:")) {
|
|
4693
|
+
return new RouteSource(config2, cacheManager);
|
|
4694
|
+
} else {
|
|
4695
|
+
return new StaticSource(config2, cacheManager);
|
|
4696
|
+
}
|
|
4697
|
+
}
|
|
4698
|
+
function useDeps(props, depsConfig) {
|
|
4699
|
+
const result = {};
|
|
4700
|
+
const cacheManager = new CacheManager();
|
|
4701
|
+
const unsubscribers = [];
|
|
4702
|
+
for (const key in depsConfig) {
|
|
4703
|
+
let getParams = function() {
|
|
4704
|
+
return _extractPropValues(props);
|
|
4705
|
+
};
|
|
4706
|
+
let config2 = depsConfig[key];
|
|
4707
|
+
if (typeof config2 === "string") {
|
|
4708
|
+
config2 = { source: config2 };
|
|
4709
|
+
}
|
|
4710
|
+
const depConfig = {
|
|
4711
|
+
...config2,
|
|
4712
|
+
cacheKey: config2.cacheKey || `${key}-${JSON.stringify(_extractPropValues(props))}`
|
|
4713
|
+
};
|
|
4714
|
+
const source = createSource(depConfig, cacheManager);
|
|
4715
|
+
const data = ref(null);
|
|
4716
|
+
const loading = ref(true);
|
|
4717
|
+
const error = ref(null);
|
|
4718
|
+
const lastUpdated = ref(null);
|
|
4719
|
+
let _loadId = 0;
|
|
4720
|
+
async function load() {
|
|
4721
|
+
const thisLoadId = ++_loadId;
|
|
4722
|
+
loading.value = true;
|
|
4723
|
+
error.value = null;
|
|
4724
|
+
try {
|
|
4725
|
+
const value = await source.getValue(getParams());
|
|
4726
|
+
if (thisLoadId !== _loadId) return;
|
|
4727
|
+
data.value = value;
|
|
4728
|
+
lastUpdated.value = Date.now();
|
|
4729
|
+
} catch (e) {
|
|
4730
|
+
if (thisLoadId !== _loadId) return;
|
|
4731
|
+
error.value = e.message || "Unknown error";
|
|
4732
|
+
} finally {
|
|
4733
|
+
if (thisLoadId === _loadId) {
|
|
4734
|
+
loading.value = false;
|
|
4735
|
+
}
|
|
4736
|
+
}
|
|
4737
|
+
}
|
|
4738
|
+
load();
|
|
4739
|
+
const unsubscribeSource = source.subscribe(() => {
|
|
4740
|
+
const cached = cacheManager.getStale(source.cacheKey);
|
|
4741
|
+
if (cached !== null && cached !== void 0) {
|
|
4742
|
+
data.value = cached;
|
|
4743
|
+
lastUpdated.value = Date.now();
|
|
4744
|
+
}
|
|
4745
|
+
});
|
|
4746
|
+
unsubscribers.push(unsubscribeSource);
|
|
4747
|
+
const propKeys = Object.keys(props);
|
|
4748
|
+
if (propKeys.length > 0) {
|
|
4749
|
+
propKeys.forEach((k) => {
|
|
4750
|
+
const prop = props[k];
|
|
4751
|
+
if (prop && typeof prop === "object" && "value" in prop && prop._subscribers) {
|
|
4752
|
+
const handler = () => {
|
|
4753
|
+
source.invalidate();
|
|
4754
|
+
load();
|
|
4755
|
+
};
|
|
4756
|
+
prop._subscribers.add(handler);
|
|
4757
|
+
unsubscribers.push(() => prop._subscribers.delete(handler));
|
|
4758
|
+
}
|
|
4759
|
+
});
|
|
4760
|
+
}
|
|
4761
|
+
result[key] = {
|
|
4762
|
+
data,
|
|
4763
|
+
loading,
|
|
4764
|
+
error,
|
|
4765
|
+
lastUpdated,
|
|
4766
|
+
refresh() {
|
|
4767
|
+
source.invalidate();
|
|
4768
|
+
return load();
|
|
4769
|
+
},
|
|
4770
|
+
// For StorageSource
|
|
4771
|
+
setValue(value) {
|
|
4772
|
+
if (source instanceof StorageSource) {
|
|
4773
|
+
source.setValue(value);
|
|
4774
|
+
data.value = value;
|
|
4775
|
+
}
|
|
4776
|
+
},
|
|
4777
|
+
// For WebSocketSource
|
|
4778
|
+
send(msg) {
|
|
4779
|
+
if (source instanceof WebSocketSource) {
|
|
4780
|
+
source.send(msg);
|
|
4781
|
+
}
|
|
4782
|
+
},
|
|
4783
|
+
_source: source
|
|
4784
|
+
};
|
|
4785
|
+
}
|
|
4786
|
+
result._dispose = () => {
|
|
4787
|
+
unsubscribers.forEach((fn) => fn());
|
|
4788
|
+
if (window.__kupolaDepInstances) {
|
|
4789
|
+
const idx = window.__kupolaDepInstances.indexOf(result);
|
|
4790
|
+
if (idx !== -1) window.__kupolaDepInstances.splice(idx, 1);
|
|
4791
|
+
}
|
|
4792
|
+
};
|
|
4793
|
+
if (!window.__kupolaDepInstances) window.__kupolaDepInstances = [];
|
|
4794
|
+
window.__kupolaDepInstances.push(result);
|
|
4795
|
+
return result;
|
|
4796
|
+
}
|
|
4797
|
+
function useQuery(config2) {
|
|
4798
|
+
const cacheManager = new CacheManager();
|
|
4799
|
+
const source = createSource(config2, cacheManager);
|
|
4800
|
+
const data = ref(null);
|
|
4801
|
+
const loading = ref(true);
|
|
4802
|
+
const error = ref(null);
|
|
4803
|
+
let _loadId = 0;
|
|
4804
|
+
async function load() {
|
|
4805
|
+
const thisLoadId = ++_loadId;
|
|
4806
|
+
loading.value = true;
|
|
4807
|
+
error.value = null;
|
|
4808
|
+
try {
|
|
4809
|
+
const value = await source.getValue(config2.params || {});
|
|
4810
|
+
if (thisLoadId !== _loadId) return;
|
|
4811
|
+
data.value = value;
|
|
4812
|
+
} catch (e) {
|
|
4813
|
+
if (thisLoadId !== _loadId) return;
|
|
4814
|
+
error.value = e.message || "Unknown error";
|
|
4815
|
+
} finally {
|
|
4816
|
+
if (thisLoadId === _loadId) loading.value = false;
|
|
4817
|
+
}
|
|
4818
|
+
}
|
|
4819
|
+
load();
|
|
4820
|
+
source.subscribe(() => {
|
|
4821
|
+
const cached = cacheManager.getStale(source.cacheKey);
|
|
4822
|
+
if (cached !== null && cached !== void 0) {
|
|
4823
|
+
data.value = cached;
|
|
4824
|
+
}
|
|
4825
|
+
});
|
|
4826
|
+
return {
|
|
4827
|
+
data,
|
|
4828
|
+
loading,
|
|
4829
|
+
error,
|
|
4830
|
+
refresh() {
|
|
4831
|
+
source.invalidate();
|
|
4832
|
+
return load();
|
|
4833
|
+
}
|
|
4834
|
+
};
|
|
4835
|
+
}
|
|
4836
|
+
function _extractPropValues(props) {
|
|
4837
|
+
const result = {};
|
|
4838
|
+
for (const key in props) {
|
|
4839
|
+
const value = props[key];
|
|
4840
|
+
if (value && typeof value === "object" && "value" in value) {
|
|
4841
|
+
result[key] = value.value;
|
|
4842
|
+
} else {
|
|
4843
|
+
result[key] = value;
|
|
4844
|
+
}
|
|
4845
|
+
}
|
|
4846
|
+
return result;
|
|
4847
|
+
}
|
|
4848
|
+
function clearCache() {
|
|
4849
|
+
}
|
|
4850
|
+
export {
|
|
4851
|
+
formatNumber as $,
|
|
4852
|
+
clearCache as A,
|
|
4853
|
+
BRAND_OPTIONS as B,
|
|
4854
|
+
CacheEntry as C,
|
|
4855
|
+
DependsError as D,
|
|
4856
|
+
configureHttpClient as E,
|
|
4857
|
+
FetchedSource as F,
|
|
4858
|
+
GlobalEvents as G,
|
|
4859
|
+
createBrandPicker as H,
|
|
4860
|
+
createI18n as I,
|
|
4861
|
+
createLifecycle as J,
|
|
4862
|
+
KupolaComponent as K,
|
|
4863
|
+
createSource as L,
|
|
4864
|
+
createStore as M,
|
|
4865
|
+
createThemeToggle as N,
|
|
4866
|
+
cryptoUtils as O,
|
|
4867
|
+
dateUtils as P,
|
|
4868
|
+
debounce as Q,
|
|
4869
|
+
RouteSource as R,
|
|
4870
|
+
Scheduler as S,
|
|
4871
|
+
defineComponent as T,
|
|
4872
|
+
defineMixin as U,
|
|
4873
|
+
emit as V,
|
|
4874
|
+
WebSocketSource as W,
|
|
4875
|
+
emitGlobal as X,
|
|
4876
|
+
escapeHtml as Y,
|
|
4877
|
+
formatCurrency as Z,
|
|
4878
|
+
formatDate as _,
|
|
4879
|
+
globalEvents as a,
|
|
4880
|
+
generateSecureId as a0,
|
|
4881
|
+
getBasePath as a1,
|
|
4882
|
+
getBrand as a2,
|
|
4883
|
+
getConfig as a3,
|
|
4884
|
+
getDefaultBrand as a4,
|
|
4885
|
+
getDefaultTheme as a5,
|
|
4886
|
+
getHttpClient as a6,
|
|
4887
|
+
getHttpConfig as a7,
|
|
4888
|
+
getIconsPath as a8,
|
|
4889
|
+
getListenerCount as a9,
|
|
4890
|
+
resetHttpClient as aA,
|
|
4891
|
+
sanitizeHtml as aB,
|
|
4892
|
+
setBrand as aC,
|
|
4893
|
+
setConfig as aD,
|
|
4894
|
+
setLocale as aE,
|
|
4895
|
+
setTheme as aF,
|
|
4896
|
+
stringUtils as aG,
|
|
4897
|
+
stripHtml as aH,
|
|
4898
|
+
t as aI,
|
|
4899
|
+
throttle as aJ,
|
|
4900
|
+
useDeps as aK,
|
|
4901
|
+
useMixin as aL,
|
|
4902
|
+
useQuery as aM,
|
|
4903
|
+
validatorUtils as aN,
|
|
4904
|
+
getLocale as aa,
|
|
4905
|
+
getSecurityConfig as ab,
|
|
4906
|
+
getStore as ac,
|
|
4907
|
+
getTheme as ad,
|
|
4908
|
+
initTheme as ae,
|
|
4909
|
+
kupolaBootstrap as af,
|
|
4910
|
+
kupolaData as ag,
|
|
4911
|
+
kupolaEvents as ah,
|
|
4912
|
+
kupolaI18n as ai,
|
|
4913
|
+
kupolaLifecycle as aj,
|
|
4914
|
+
kupolaRegistry as ak,
|
|
4915
|
+
kupolaStoreManager as al,
|
|
4916
|
+
maskData as am,
|
|
4917
|
+
n as an,
|
|
4918
|
+
numberUtils as ao,
|
|
4919
|
+
objectUtils as ap,
|
|
4920
|
+
off as aq,
|
|
4921
|
+
offAll as ar,
|
|
4922
|
+
offByScope as as,
|
|
4923
|
+
offConfigChange as at,
|
|
4924
|
+
on as au,
|
|
4925
|
+
onConfigChange as av,
|
|
4926
|
+
once as aw,
|
|
4927
|
+
preloadUtils as ax,
|
|
4928
|
+
registerComponent as ay,
|
|
4929
|
+
registerLazyComponent as az,
|
|
4930
|
+
getZIndexConfig as b,
|
|
4931
|
+
getNotificationConfig as c,
|
|
4932
|
+
getMessageConfig as d,
|
|
4933
|
+
getValidationConfig as e,
|
|
4934
|
+
getPerformanceConfig as f,
|
|
4935
|
+
getUiConfig as g,
|
|
4936
|
+
CacheManager as h,
|
|
4937
|
+
ComponentInitializerRegistry as i,
|
|
4938
|
+
DependsSource as j,
|
|
4939
|
+
kupolaInitializer as k,
|
|
4940
|
+
FunctionSource as l,
|
|
4941
|
+
KupolaComponentRegistry as m,
|
|
4942
|
+
KupolaDataBind as n,
|
|
4943
|
+
KupolaEventBus as o,
|
|
4944
|
+
KupolaI18n as p,
|
|
4945
|
+
KupolaLifecycle as q,
|
|
4946
|
+
ref as r,
|
|
4947
|
+
KupolaStore as s,
|
|
4948
|
+
KupolaStoreManager as t,
|
|
4949
|
+
KupolaUtils as u,
|
|
4950
|
+
StaticSource as v,
|
|
4951
|
+
StorageSource as w,
|
|
4952
|
+
applyMixin as x,
|
|
4953
|
+
arrayUtils as y,
|
|
4954
|
+
bootstrapComponents as z
|
|
4955
|
+
};
|