@zaflun/lumio-sdk 0.1.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/LICENSE.md +21 -0
- package/README.md +120 -0
- package/dist/index.d.ts +489 -0
- package/dist/index.js +876 -0
- package/dist/lumio-runtime.global.js +28607 -0
- package/dist/server.d.ts +138 -0
- package/dist/server.js +131 -0
- package/package.json +72 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,876 @@
|
|
|
1
|
+
// src/core/lumio.tsx
|
|
2
|
+
import { createRoot } from "react-dom/client";
|
|
3
|
+
var RENDER_TIMEOUT_MS = 1e4;
|
|
4
|
+
var hasRendered = false;
|
|
5
|
+
var renderTimeout = null;
|
|
6
|
+
function startRenderTimeout() {
|
|
7
|
+
if (renderTimeout) return;
|
|
8
|
+
renderTimeout = setTimeout(() => {
|
|
9
|
+
if (!hasRendered) {
|
|
10
|
+
console.error(
|
|
11
|
+
"[Lumio SDK] Extension did not call Lumio.render() within 10 seconds. Terminating."
|
|
12
|
+
);
|
|
13
|
+
window.parent.postMessage(
|
|
14
|
+
{
|
|
15
|
+
type: "ERROR",
|
|
16
|
+
code: "RENDER_TIMEOUT",
|
|
17
|
+
message: "Extension did not render within 10 seconds"
|
|
18
|
+
},
|
|
19
|
+
"*"
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
}, RENDER_TIMEOUT_MS);
|
|
23
|
+
}
|
|
24
|
+
if (typeof window !== "undefined") {
|
|
25
|
+
startRenderTimeout();
|
|
26
|
+
}
|
|
27
|
+
var Lumio = {
|
|
28
|
+
render(element, options) {
|
|
29
|
+
hasRendered = true;
|
|
30
|
+
if (renderTimeout) {
|
|
31
|
+
clearTimeout(renderTimeout);
|
|
32
|
+
renderTimeout = null;
|
|
33
|
+
}
|
|
34
|
+
let rootElement = document.getElementById("lumio-root");
|
|
35
|
+
if (!rootElement) {
|
|
36
|
+
rootElement = document.createElement("div");
|
|
37
|
+
rootElement.id = "lumio-root";
|
|
38
|
+
document.body.appendChild(rootElement);
|
|
39
|
+
}
|
|
40
|
+
const root = createRoot(rootElement);
|
|
41
|
+
root.render(element);
|
|
42
|
+
window.parent.postMessage({ type: "READY" }, "*");
|
|
43
|
+
void options;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// src/bridge/WorkerBridge.ts
|
|
48
|
+
var WorkerBridge = class {
|
|
49
|
+
notificationHandlers = /* @__PURE__ */ new Map();
|
|
50
|
+
requestHandlers = /* @__PURE__ */ new Map();
|
|
51
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
52
|
+
requestCounter = 0;
|
|
53
|
+
requestTimeout;
|
|
54
|
+
messageListener = null;
|
|
55
|
+
destroyed = false;
|
|
56
|
+
constructor(options) {
|
|
57
|
+
this.requestTimeout = options?.requestTimeout ?? 3e4;
|
|
58
|
+
this.messageListener = this.handleMessage.bind(this);
|
|
59
|
+
globalThis.addEventListener("message", this.messageListener);
|
|
60
|
+
}
|
|
61
|
+
notify(method, params) {
|
|
62
|
+
const msg = {
|
|
63
|
+
jsonrpc: "2.0",
|
|
64
|
+
method,
|
|
65
|
+
...params !== void 0 ? { params } : {}
|
|
66
|
+
};
|
|
67
|
+
globalThis.postMessage(msg);
|
|
68
|
+
}
|
|
69
|
+
request(method, params) {
|
|
70
|
+
const id = `req_${++this.requestCounter}`;
|
|
71
|
+
const msg = {
|
|
72
|
+
jsonrpc: "2.0",
|
|
73
|
+
id,
|
|
74
|
+
method,
|
|
75
|
+
...params !== void 0 ? { params } : {}
|
|
76
|
+
};
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
const timer = setTimeout(() => {
|
|
79
|
+
this.pendingRequests.delete(id);
|
|
80
|
+
reject(new Error("Request timeout"));
|
|
81
|
+
}, this.requestTimeout);
|
|
82
|
+
this.pendingRequests.set(id, { resolve, reject, timer });
|
|
83
|
+
globalThis.postMessage(msg);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
onNotification(method, handler) {
|
|
87
|
+
if (!this.notificationHandlers.has(method)) {
|
|
88
|
+
this.notificationHandlers.set(method, /* @__PURE__ */ new Set());
|
|
89
|
+
}
|
|
90
|
+
this.notificationHandlers.get(method).add(handler);
|
|
91
|
+
return () => {
|
|
92
|
+
this.notificationHandlers.get(method)?.delete(handler);
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
onRequest(method, handler) {
|
|
96
|
+
this.requestHandlers.set(method, handler);
|
|
97
|
+
}
|
|
98
|
+
destroy() {
|
|
99
|
+
this.destroyed = true;
|
|
100
|
+
if (this.messageListener) {
|
|
101
|
+
globalThis.removeEventListener("message", this.messageListener);
|
|
102
|
+
this.messageListener = null;
|
|
103
|
+
}
|
|
104
|
+
for (const { timer, reject } of this.pendingRequests.values()) {
|
|
105
|
+
if (timer) clearTimeout(timer);
|
|
106
|
+
reject(new Error("Bridge destroyed"));
|
|
107
|
+
}
|
|
108
|
+
this.pendingRequests.clear();
|
|
109
|
+
this.notificationHandlers.clear();
|
|
110
|
+
this.requestHandlers.clear();
|
|
111
|
+
}
|
|
112
|
+
handleMessage(event) {
|
|
113
|
+
if (this.destroyed) return;
|
|
114
|
+
const data = event.data;
|
|
115
|
+
if (!data || typeof data !== "object" || data.jsonrpc !== "2.0") return;
|
|
116
|
+
if ("id" in data && !("method" in data)) {
|
|
117
|
+
const pending = this.pendingRequests.get(data.id);
|
|
118
|
+
if (pending) {
|
|
119
|
+
this.pendingRequests.delete(data.id);
|
|
120
|
+
if (pending.timer) clearTimeout(pending.timer);
|
|
121
|
+
if (data.error) {
|
|
122
|
+
pending.reject(new Error(data.error.message));
|
|
123
|
+
} else {
|
|
124
|
+
pending.resolve(data.result);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if ("id" in data && "method" in data) {
|
|
130
|
+
const handler = this.requestHandlers.get(data.method);
|
|
131
|
+
if (handler) {
|
|
132
|
+
handler(data.params).then((result) => {
|
|
133
|
+
const response = { jsonrpc: "2.0", id: data.id, result };
|
|
134
|
+
globalThis.postMessage(response);
|
|
135
|
+
}).catch((err) => {
|
|
136
|
+
const response = {
|
|
137
|
+
jsonrpc: "2.0",
|
|
138
|
+
id: data.id,
|
|
139
|
+
error: { code: -32603, message: err.message }
|
|
140
|
+
};
|
|
141
|
+
globalThis.postMessage(response);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if ("method" in data && !("id" in data)) {
|
|
147
|
+
const handlers = this.notificationHandlers.get(data.method);
|
|
148
|
+
if (handlers) {
|
|
149
|
+
for (const handler of handlers) {
|
|
150
|
+
handler(data.params);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// src/components/Box.tsx
|
|
158
|
+
import React from "react";
|
|
159
|
+
function Box({ children, style }) {
|
|
160
|
+
return React.createElement("Box", { style }, children);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// src/components/VStack.tsx
|
|
164
|
+
import React2 from "react";
|
|
165
|
+
function VStack({ children, gap, style }) {
|
|
166
|
+
return React2.createElement("VStack", { gap, style }, children);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// src/components/HStack.tsx
|
|
170
|
+
import React3 from "react";
|
|
171
|
+
function HStack({ children, gap, style }) {
|
|
172
|
+
return React3.createElement("HStack", { gap, style }, children);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/components/CompactView.tsx
|
|
176
|
+
import React4 from "react";
|
|
177
|
+
function CompactView({ title, children }) {
|
|
178
|
+
return React4.createElement("CompactView", { title }, children);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// src/components/List.tsx
|
|
182
|
+
import React5 from "react";
|
|
183
|
+
function List({ items, renderItem, emptyText }) {
|
|
184
|
+
if (items.length === 0) {
|
|
185
|
+
return React5.createElement("List", { emptyText, items: [] });
|
|
186
|
+
}
|
|
187
|
+
const children = items.map((item, index) => renderItem(item, index));
|
|
188
|
+
return React5.createElement("List", { items, emptyText }, ...children);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// src/components/Text.tsx
|
|
192
|
+
import React6 from "react";
|
|
193
|
+
function Text({ content, variant = "default" }) {
|
|
194
|
+
return React6.createElement("Text", { content, variant });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/components/Image.tsx
|
|
198
|
+
import React7 from "react";
|
|
199
|
+
function Image({ src, alt, objectFit }) {
|
|
200
|
+
return React7.createElement("Image", { src, alt, objectFit });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// src/components/Button.tsx
|
|
204
|
+
import React8 from "react";
|
|
205
|
+
function Button({ label, onClick, variant = "default", disabled = false }) {
|
|
206
|
+
return React8.createElement("Button", { label, onClick, variant, disabled });
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/components/Toggle.tsx
|
|
210
|
+
import React9 from "react";
|
|
211
|
+
function Toggle({ label, checked, onChange }) {
|
|
212
|
+
return React9.createElement("Toggle", { label, checked, onChange });
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// src/components/TextField.tsx
|
|
216
|
+
import React10 from "react";
|
|
217
|
+
function TextField({ label, value, onChange, placeholder }) {
|
|
218
|
+
return React10.createElement("TextField", { label, value, onChange, placeholder });
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// src/components/TextArea.tsx
|
|
222
|
+
import React11 from "react";
|
|
223
|
+
function TextArea({ label, value, onChange, placeholder, rows = 3 }) {
|
|
224
|
+
return React11.createElement("TextArea", { label, value, onChange, placeholder, rows });
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/components/NumberField.tsx
|
|
228
|
+
import React12 from "react";
|
|
229
|
+
function NumberField({ label, value, onChange, min, max, step }) {
|
|
230
|
+
return React12.createElement("NumberField", { label, value, onChange, min, max, step });
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// src/components/Dropdown.tsx
|
|
234
|
+
import React13 from "react";
|
|
235
|
+
function Dropdown({ label, value, options, onChange }) {
|
|
236
|
+
return React13.createElement("Dropdown", { label, value, options, onChange });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/components/Slider.tsx
|
|
240
|
+
import React14 from "react";
|
|
241
|
+
function Slider({ label, value, onChange, min, max, step = 1 }) {
|
|
242
|
+
return React14.createElement("Slider", { label, value, onChange, min, max, step });
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// src/hooks/useExtensionStorage.ts
|
|
246
|
+
import { useState, useEffect, useCallback } from "react";
|
|
247
|
+
|
|
248
|
+
// src/renderer/bridge-context.ts
|
|
249
|
+
var bridgeInstance = null;
|
|
250
|
+
function getBridge() {
|
|
251
|
+
if (!bridgeInstance) {
|
|
252
|
+
bridgeInstance = new WorkerBridge();
|
|
253
|
+
}
|
|
254
|
+
return bridgeInstance;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// src/hooks/useExtensionStorage.ts
|
|
258
|
+
import { HOST_METHODS, WORKER_METHODS } from "@zaflun/lumio-protocol";
|
|
259
|
+
function useExtensionStorage(key, initialValue) {
|
|
260
|
+
const [storage, setStorageState] = useState(initialValue);
|
|
261
|
+
useEffect(() => {
|
|
262
|
+
const bridge = getBridge();
|
|
263
|
+
const unsub = bridge.onNotification(
|
|
264
|
+
HOST_METHODS.STORAGE_UPDATE,
|
|
265
|
+
(params) => {
|
|
266
|
+
const p = params;
|
|
267
|
+
if (p.key === key) {
|
|
268
|
+
setStorageState(p.value);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
);
|
|
272
|
+
bridge.request(WORKER_METHODS.STORAGE_GET, { key }).then((result) => {
|
|
273
|
+
const r = result;
|
|
274
|
+
if (r && r.value !== void 0) {
|
|
275
|
+
setStorageState(r.value);
|
|
276
|
+
}
|
|
277
|
+
}).catch(() => {
|
|
278
|
+
});
|
|
279
|
+
return unsub;
|
|
280
|
+
}, [key]);
|
|
281
|
+
const setStorage = useCallback(
|
|
282
|
+
(value) => {
|
|
283
|
+
setStorageState(value);
|
|
284
|
+
const bridge = getBridge();
|
|
285
|
+
bridge.notify(WORKER_METHODS.STORAGE_SET, { key, value });
|
|
286
|
+
},
|
|
287
|
+
[key]
|
|
288
|
+
);
|
|
289
|
+
return [storage, setStorage];
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// src/hooks/useVisionState.ts
|
|
293
|
+
import { useState as useState2, useCallback as useCallback2 } from "react";
|
|
294
|
+
import { WORKER_METHODS as WORKER_METHODS2 } from "@zaflun/lumio-protocol";
|
|
295
|
+
function useVisionState(key, defaultValue) {
|
|
296
|
+
const [value, setValueState] = useState2(defaultValue);
|
|
297
|
+
const setValue = useCallback2(
|
|
298
|
+
(newValue) => {
|
|
299
|
+
setValueState(newValue);
|
|
300
|
+
const bridge = getBridge();
|
|
301
|
+
bridge.notify(WORKER_METHODS2.STORAGE_SET, { key, value: newValue });
|
|
302
|
+
},
|
|
303
|
+
[key]
|
|
304
|
+
);
|
|
305
|
+
return [value, setValue];
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// src/hooks/useExtensionContext.ts
|
|
309
|
+
import { useState as useState3, useEffect as useEffect2 } from "react";
|
|
310
|
+
import { HOST_METHODS as HOST_METHODS2 } from "@zaflun/lumio-protocol";
|
|
311
|
+
var defaultContext = {
|
|
312
|
+
extensionId: null,
|
|
313
|
+
target: null,
|
|
314
|
+
overlayId: null,
|
|
315
|
+
interactiveUrl: null
|
|
316
|
+
};
|
|
317
|
+
function useExtensionContext() {
|
|
318
|
+
const [context, setContext] = useState3(defaultContext);
|
|
319
|
+
useEffect2(() => {
|
|
320
|
+
const bridge = getBridge();
|
|
321
|
+
const unsub = bridge.onNotification(
|
|
322
|
+
HOST_METHODS2.LIFECYCLE_INIT,
|
|
323
|
+
(params) => {
|
|
324
|
+
const p = params;
|
|
325
|
+
setContext({
|
|
326
|
+
extensionId: p.extensionId ?? null,
|
|
327
|
+
target: p.target ?? null,
|
|
328
|
+
overlayId: p.installId ?? null,
|
|
329
|
+
interactiveUrl: p.interactiveUrl ?? null
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
);
|
|
333
|
+
return unsub;
|
|
334
|
+
}, []);
|
|
335
|
+
return context;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// src/hooks/useLumioEvent.ts
|
|
339
|
+
import { useState as useState4, useEffect as useEffect3 } from "react";
|
|
340
|
+
import { HOST_METHODS as HOST_METHODS3 } from "@zaflun/lumio-protocol";
|
|
341
|
+
function useLumioEvent(eventType) {
|
|
342
|
+
const [eventData, setEventData] = useState4(null);
|
|
343
|
+
useEffect3(() => {
|
|
344
|
+
const bridge = getBridge();
|
|
345
|
+
const unsub = bridge.onNotification(
|
|
346
|
+
HOST_METHODS3.EVENT_PLATFORM,
|
|
347
|
+
(params) => {
|
|
348
|
+
const p = params;
|
|
349
|
+
if (p.eventType === eventType) {
|
|
350
|
+
setEventData(p.data ?? null);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
);
|
|
354
|
+
return unsub;
|
|
355
|
+
}, [eventType]);
|
|
356
|
+
return eventData;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// src/hooks/useLumioConfig.ts
|
|
360
|
+
import { useState as useState5, useEffect as useEffect4, useCallback as useCallback3 } from "react";
|
|
361
|
+
import { HOST_METHODS as HOST_METHODS4 } from "@zaflun/lumio-protocol";
|
|
362
|
+
function useLumioConfig() {
|
|
363
|
+
const [config, setConfigState] = useState5({});
|
|
364
|
+
useEffect4(() => {
|
|
365
|
+
const bridge = getBridge();
|
|
366
|
+
const unsub = bridge.onNotification(
|
|
367
|
+
HOST_METHODS4.LIFECYCLE_INIT,
|
|
368
|
+
(params) => {
|
|
369
|
+
const p = params;
|
|
370
|
+
setConfigState({
|
|
371
|
+
extension_id: p.extensionId,
|
|
372
|
+
install_id: p.installId,
|
|
373
|
+
account_id: p.accountId,
|
|
374
|
+
target: p.target,
|
|
375
|
+
permissions: p.permissions,
|
|
376
|
+
settings: p.settings
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
);
|
|
380
|
+
return unsub;
|
|
381
|
+
}, []);
|
|
382
|
+
const setConfig = useCallback3(
|
|
383
|
+
(newConfig) => {
|
|
384
|
+
setConfigState(newConfig);
|
|
385
|
+
},
|
|
386
|
+
[]
|
|
387
|
+
);
|
|
388
|
+
return [config, setConfig];
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// src/hooks/useLumioTheme.ts
|
|
392
|
+
import { useState as useState6, useEffect as useEffect5 } from "react";
|
|
393
|
+
import { HOST_METHODS as HOST_METHODS5 } from "@zaflun/lumio-protocol";
|
|
394
|
+
var DEFAULT_THEME = {
|
|
395
|
+
colors: {
|
|
396
|
+
primary: "#6366f1",
|
|
397
|
+
secondary: "#8b5cf6",
|
|
398
|
+
accent: "#06b6d4",
|
|
399
|
+
background: "transparent",
|
|
400
|
+
surface: "rgba(15, 23, 42, 0.8)",
|
|
401
|
+
text: "#f8fafc",
|
|
402
|
+
muted: "#94a3b8",
|
|
403
|
+
border: "rgba(148, 163, 184, 0.2)"
|
|
404
|
+
},
|
|
405
|
+
fonts: {
|
|
406
|
+
heading: "inherit",
|
|
407
|
+
body: "inherit"
|
|
408
|
+
},
|
|
409
|
+
borderRadius: {
|
|
410
|
+
sm: "4px",
|
|
411
|
+
md: "8px",
|
|
412
|
+
lg: "12px"
|
|
413
|
+
},
|
|
414
|
+
spacing: 8
|
|
415
|
+
};
|
|
416
|
+
function useLumioTheme() {
|
|
417
|
+
const [theme, setTheme] = useState6(DEFAULT_THEME);
|
|
418
|
+
useEffect5(() => {
|
|
419
|
+
const bridge = getBridge();
|
|
420
|
+
const unsubInit = bridge.onNotification(
|
|
421
|
+
HOST_METHODS5.LIFECYCLE_INIT,
|
|
422
|
+
(params) => {
|
|
423
|
+
const p = params;
|
|
424
|
+
if (p.theme) {
|
|
425
|
+
setTheme((prev) => ({ ...prev, ...p.theme }));
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
);
|
|
429
|
+
const unsubTheme = bridge.onNotification(
|
|
430
|
+
HOST_METHODS5.THEME_CHANGE,
|
|
431
|
+
(params) => {
|
|
432
|
+
const p = params;
|
|
433
|
+
if (p.theme) {
|
|
434
|
+
setTheme((prev) => ({ ...prev, ...p.theme }));
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
);
|
|
438
|
+
return () => {
|
|
439
|
+
unsubInit();
|
|
440
|
+
unsubTheme();
|
|
441
|
+
};
|
|
442
|
+
}, []);
|
|
443
|
+
return theme;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// src/hooks/useLumioIdentity.ts
|
|
447
|
+
import { useState as useState7, useEffect as useEffect6 } from "react";
|
|
448
|
+
import { HOST_METHODS as HOST_METHODS6 } from "@zaflun/lumio-protocol";
|
|
449
|
+
var defaultIdentity = {
|
|
450
|
+
userId: null,
|
|
451
|
+
accountId: null,
|
|
452
|
+
role: null
|
|
453
|
+
};
|
|
454
|
+
function useLumioIdentity() {
|
|
455
|
+
const [identity, setIdentity] = useState7(defaultIdentity);
|
|
456
|
+
useEffect6(() => {
|
|
457
|
+
const bridge = getBridge();
|
|
458
|
+
const unsub = bridge.onNotification(
|
|
459
|
+
HOST_METHODS6.LIFECYCLE_INIT,
|
|
460
|
+
(params) => {
|
|
461
|
+
const p = params;
|
|
462
|
+
setIdentity({
|
|
463
|
+
userId: p.userId ?? null,
|
|
464
|
+
accountId: p.accountId ?? null,
|
|
465
|
+
role: p.role ?? null
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
);
|
|
469
|
+
return unsub;
|
|
470
|
+
}, []);
|
|
471
|
+
return identity;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// src/hooks/useLumioAction.ts
|
|
475
|
+
import { useState as useState8, useCallback as useCallback4, useRef } from "react";
|
|
476
|
+
function useLumioAction(action) {
|
|
477
|
+
const [data, setData] = useState8(null);
|
|
478
|
+
const [isLoading, setIsLoading] = useState8(false);
|
|
479
|
+
const [error, setError] = useState8(null);
|
|
480
|
+
const actionRef = useRef(action);
|
|
481
|
+
actionRef.current = action;
|
|
482
|
+
const execute = useCallback4(
|
|
483
|
+
(params = {}) => {
|
|
484
|
+
setIsLoading(true);
|
|
485
|
+
setError(null);
|
|
486
|
+
const bridge = getBridge();
|
|
487
|
+
bridge.request("action.execute", { action, params }).then((result) => {
|
|
488
|
+
setIsLoading(false);
|
|
489
|
+
setData(result ?? null);
|
|
490
|
+
setError(null);
|
|
491
|
+
}).catch((err) => {
|
|
492
|
+
setIsLoading(false);
|
|
493
|
+
setError(err instanceof Error ? err.message : "Unknown error");
|
|
494
|
+
setData(null);
|
|
495
|
+
});
|
|
496
|
+
},
|
|
497
|
+
[action]
|
|
498
|
+
);
|
|
499
|
+
return { execute, data, isLoading, error };
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// src/hooks/useQuery.ts
|
|
503
|
+
import { useState as useState9, useEffect as useEffect7, useCallback as useCallback5, useRef as useRef2 } from "react";
|
|
504
|
+
import { WORKER_METHODS as WORKER_METHODS3 } from "@zaflun/lumio-protocol";
|
|
505
|
+
function useQuery(functionName, params) {
|
|
506
|
+
const [data, setData] = useState9(null);
|
|
507
|
+
const [isLoading, setIsLoading] = useState9(false);
|
|
508
|
+
const [error, setError] = useState9(null);
|
|
509
|
+
const fnRef = useRef2(functionName);
|
|
510
|
+
fnRef.current = functionName;
|
|
511
|
+
const sendQuery = useCallback5(() => {
|
|
512
|
+
setIsLoading(true);
|
|
513
|
+
setError(null);
|
|
514
|
+
const bridge = getBridge();
|
|
515
|
+
bridge.request(WORKER_METHODS3.SERVER_QUERY, { fn: functionName, params: params ?? {} }).then((result) => {
|
|
516
|
+
setIsLoading(false);
|
|
517
|
+
setData(result ?? null);
|
|
518
|
+
setError(null);
|
|
519
|
+
}).catch((err) => {
|
|
520
|
+
setIsLoading(false);
|
|
521
|
+
setError(err instanceof Error ? err.message : "Unknown error");
|
|
522
|
+
setData(null);
|
|
523
|
+
});
|
|
524
|
+
}, [functionName, params]);
|
|
525
|
+
useEffect7(() => {
|
|
526
|
+
sendQuery();
|
|
527
|
+
}, [functionName]);
|
|
528
|
+
return { data, isLoading, error, refetch: sendQuery };
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// src/hooks/useMutation.ts
|
|
532
|
+
import { useState as useState10, useCallback as useCallback6 } from "react";
|
|
533
|
+
import { WORKER_METHODS as WORKER_METHODS4 } from "@zaflun/lumio-protocol";
|
|
534
|
+
function useMutation(functionName) {
|
|
535
|
+
const [data, setData] = useState10(null);
|
|
536
|
+
const [isLoading, setIsLoading] = useState10(false);
|
|
537
|
+
const [error, setError] = useState10(null);
|
|
538
|
+
const sendMutation = useCallback6(
|
|
539
|
+
(params = {}) => {
|
|
540
|
+
setIsLoading(true);
|
|
541
|
+
setError(null);
|
|
542
|
+
const bridge = getBridge();
|
|
543
|
+
return bridge.request(WORKER_METHODS4.SERVER_MUTATION, { fn: functionName, params }).then((result) => {
|
|
544
|
+
setIsLoading(false);
|
|
545
|
+
setData(result ?? null);
|
|
546
|
+
setError(null);
|
|
547
|
+
return result;
|
|
548
|
+
}).catch((err) => {
|
|
549
|
+
setIsLoading(false);
|
|
550
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
551
|
+
setError(message);
|
|
552
|
+
setData(null);
|
|
553
|
+
throw err;
|
|
554
|
+
});
|
|
555
|
+
},
|
|
556
|
+
[functionName]
|
|
557
|
+
);
|
|
558
|
+
const mutateAsync = useCallback6(
|
|
559
|
+
(params) => sendMutation(params),
|
|
560
|
+
[sendMutation]
|
|
561
|
+
);
|
|
562
|
+
const mutate = useCallback6(
|
|
563
|
+
(params) => {
|
|
564
|
+
sendMutation(params).catch(() => {
|
|
565
|
+
});
|
|
566
|
+
},
|
|
567
|
+
[sendMutation]
|
|
568
|
+
);
|
|
569
|
+
return { mutate, mutateAsync, data, isLoading, error };
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// src/animation/keyframes.ts
|
|
573
|
+
var registry = /* @__PURE__ */ new Map();
|
|
574
|
+
var counter = 0;
|
|
575
|
+
function camelToKebab(str) {
|
|
576
|
+
return str.replace(/([A-Z])/g, (match) => `-${match.toLowerCase()}`);
|
|
577
|
+
}
|
|
578
|
+
function keyframesToCSS(name, def) {
|
|
579
|
+
const stops = Object.entries(def).map(([stop, block]) => {
|
|
580
|
+
const props = Object.entries(block).map(([prop, value]) => ` ${camelToKebab(prop)}: ${value};`).join("\n");
|
|
581
|
+
return ` ${stop} {
|
|
582
|
+
${props}
|
|
583
|
+
}`;
|
|
584
|
+
}).join("\n");
|
|
585
|
+
return `@keyframes ${name} {
|
|
586
|
+
${stops}
|
|
587
|
+
}`;
|
|
588
|
+
}
|
|
589
|
+
function defineKeyframes(definition) {
|
|
590
|
+
const hash = JSON.stringify(definition);
|
|
591
|
+
const existing = registry.get(hash);
|
|
592
|
+
if (existing !== void 0) {
|
|
593
|
+
return existing;
|
|
594
|
+
}
|
|
595
|
+
const name = `lumio-kf-${counter++}`;
|
|
596
|
+
registry.set(hash, name);
|
|
597
|
+
return name;
|
|
598
|
+
}
|
|
599
|
+
function getRegisteredCSS() {
|
|
600
|
+
const parts = [];
|
|
601
|
+
for (const [hash, name] of registry.entries()) {
|
|
602
|
+
const def = JSON.parse(hash);
|
|
603
|
+
parts.push(keyframesToCSS(name, def));
|
|
604
|
+
}
|
|
605
|
+
return parts.join("\n\n");
|
|
606
|
+
}
|
|
607
|
+
function flushKeyframes() {
|
|
608
|
+
const css = getRegisteredCSS();
|
|
609
|
+
if (!css) return;
|
|
610
|
+
try {
|
|
611
|
+
getBridge().notify("ui.keyframes", { css });
|
|
612
|
+
} catch {
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// src/animation/presets.ts
|
|
617
|
+
var ANIMATION_PRESETS = {
|
|
618
|
+
// Basic
|
|
619
|
+
fadeIn: { from: { opacity: "0" }, to: { opacity: "1" } },
|
|
620
|
+
fadeOut: { from: { opacity: "1" }, to: { opacity: "0" } },
|
|
621
|
+
zoomIn: { from: { opacity: "0", transform: "scale(0.5)" }, to: { opacity: "1", transform: "scale(1)" } },
|
|
622
|
+
zoomOut: { from: { opacity: "1", transform: "scale(1)" }, to: { opacity: "0", transform: "scale(0.5)" } },
|
|
623
|
+
// Slide In/Out
|
|
624
|
+
slideLeftIn: { from: { opacity: "0", transform: "translateX(-100%)" }, to: { opacity: "1", transform: "translateX(0)" } },
|
|
625
|
+
slideLeftOut: { from: { opacity: "1", transform: "translateX(0)" }, to: { opacity: "0", transform: "translateX(-100%)" } },
|
|
626
|
+
slideRightIn: { from: { opacity: "0", transform: "translateX(100%)" }, to: { opacity: "1", transform: "translateX(0)" } },
|
|
627
|
+
slideRightOut: { from: { opacity: "1", transform: "translateX(0)" }, to: { opacity: "0", transform: "translateX(100%)" } },
|
|
628
|
+
slideUpIn: { from: { opacity: "0", transform: "translateY(-100%)" }, to: { opacity: "1", transform: "translateY(0)" } },
|
|
629
|
+
slideUpOut: { from: { opacity: "1", transform: "translateY(0)" }, to: { opacity: "0", transform: "translateY(-100%)" } },
|
|
630
|
+
slideDownIn: { from: { opacity: "0", transform: "translateY(100%)" }, to: { opacity: "1", transform: "translateY(0)" } },
|
|
631
|
+
slideDownOut: { from: { opacity: "1", transform: "translateY(0)" }, to: { opacity: "0", transform: "translateY(100%)" } },
|
|
632
|
+
// Push In/Out (no opacity change)
|
|
633
|
+
pushLeftIn: { from: { transform: "translateX(100%)" }, to: { transform: "translateX(0)" } },
|
|
634
|
+
pushLeftOut: { from: { transform: "translateX(0)" }, to: { transform: "translateX(-100%)" } },
|
|
635
|
+
pushRightIn: { from: { transform: "translateX(-100%)" }, to: { transform: "translateX(0)" } },
|
|
636
|
+
pushRightOut: { from: { transform: "translateX(0)" }, to: { transform: "translateX(100%)" } },
|
|
637
|
+
pushUpIn: { from: { transform: "translateY(100%)" }, to: { transform: "translateY(0)" } },
|
|
638
|
+
pushUpOut: { from: { transform: "translateY(0)" }, to: { transform: "translateY(-100%)" } },
|
|
639
|
+
pushDownIn: { from: { transform: "translateY(-100%)" }, to: { transform: "translateY(0)" } },
|
|
640
|
+
pushDownOut: { from: { transform: "translateY(0)" }, to: { transform: "translateY(100%)" } },
|
|
641
|
+
// Loop
|
|
642
|
+
pulse: {
|
|
643
|
+
"0%": { transform: "scale(1)" },
|
|
644
|
+
"50%": { transform: "scale(1.05)" },
|
|
645
|
+
"100%": { transform: "scale(1)" }
|
|
646
|
+
},
|
|
647
|
+
bounce: {
|
|
648
|
+
"0%": { transform: "translateY(0)" },
|
|
649
|
+
"50%": { transform: "translateY(-10px)" },
|
|
650
|
+
"100%": { transform: "translateY(0)" }
|
|
651
|
+
}
|
|
652
|
+
};
|
|
653
|
+
function getPreset(name) {
|
|
654
|
+
return defineKeyframes(ANIMATION_PRESETS[name]);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// src/animation/Animated.tsx
|
|
658
|
+
import React15, { useEffect as useEffect8, useRef as useRef3, useState as useState11 } from "react";
|
|
659
|
+
function resolveAnimation(name) {
|
|
660
|
+
if (name === void 0) return void 0;
|
|
661
|
+
try {
|
|
662
|
+
return getPreset(name);
|
|
663
|
+
} catch {
|
|
664
|
+
return name;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
function Animated({
|
|
668
|
+
children,
|
|
669
|
+
in: inProp,
|
|
670
|
+
out: outProp,
|
|
671
|
+
loop: loopProp,
|
|
672
|
+
duration = 300,
|
|
673
|
+
loopDuration = 1e3,
|
|
674
|
+
easing = "ease",
|
|
675
|
+
delay = 0,
|
|
676
|
+
visible = true,
|
|
677
|
+
onAnimationEnd
|
|
678
|
+
}) {
|
|
679
|
+
const [phase, setPhase] = useState11(() => visible ? "in" : "idle");
|
|
680
|
+
const prevVisibleRef = useRef3(visible);
|
|
681
|
+
const timerRef = useRef3(null);
|
|
682
|
+
function clearTimer() {
|
|
683
|
+
if (timerRef.current !== null) {
|
|
684
|
+
clearTimeout(timerRef.current);
|
|
685
|
+
timerRef.current = null;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
useEffect8(() => {
|
|
689
|
+
const prevVisible = prevVisibleRef.current;
|
|
690
|
+
prevVisibleRef.current = visible;
|
|
691
|
+
if (visible && !prevVisible) {
|
|
692
|
+
clearTimer();
|
|
693
|
+
setPhase("in");
|
|
694
|
+
} else if (!visible && prevVisible) {
|
|
695
|
+
clearTimer();
|
|
696
|
+
setPhase("out");
|
|
697
|
+
}
|
|
698
|
+
}, [visible]);
|
|
699
|
+
useEffect8(() => {
|
|
700
|
+
if (phase === "in") {
|
|
701
|
+
const totalDuration = duration + delay;
|
|
702
|
+
if (loopProp !== void 0) {
|
|
703
|
+
timerRef.current = setTimeout(() => {
|
|
704
|
+
setPhase("loop");
|
|
705
|
+
}, totalDuration);
|
|
706
|
+
} else {
|
|
707
|
+
if (onAnimationEnd) {
|
|
708
|
+
timerRef.current = setTimeout(() => {
|
|
709
|
+
onAnimationEnd();
|
|
710
|
+
}, totalDuration);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
} else if (phase === "out" && onAnimationEnd) {
|
|
714
|
+
const totalDuration = duration + delay;
|
|
715
|
+
timerRef.current = setTimeout(() => {
|
|
716
|
+
onAnimationEnd();
|
|
717
|
+
}, totalDuration);
|
|
718
|
+
}
|
|
719
|
+
return () => clearTimer();
|
|
720
|
+
}, [phase]);
|
|
721
|
+
const animationStyles = {};
|
|
722
|
+
if (phase === "in" && inProp !== void 0) {
|
|
723
|
+
const animName = resolveAnimation(inProp);
|
|
724
|
+
if (animName !== void 0) {
|
|
725
|
+
animationStyles.animationName = animName;
|
|
726
|
+
animationStyles.animationDuration = `${duration}ms`;
|
|
727
|
+
animationStyles.animationTimingFunction = easing;
|
|
728
|
+
animationStyles.animationDelay = `${delay}ms`;
|
|
729
|
+
animationStyles.animationFillMode = "forwards";
|
|
730
|
+
animationStyles.animationIterationCount = 1;
|
|
731
|
+
}
|
|
732
|
+
} else if (phase === "loop" && loopProp !== void 0) {
|
|
733
|
+
const animName = resolveAnimation(loopProp);
|
|
734
|
+
if (animName !== void 0) {
|
|
735
|
+
animationStyles.animationName = animName;
|
|
736
|
+
animationStyles.animationDuration = `${loopDuration}ms`;
|
|
737
|
+
animationStyles.animationTimingFunction = easing;
|
|
738
|
+
animationStyles.animationDelay = "0ms";
|
|
739
|
+
animationStyles.animationFillMode = "forwards";
|
|
740
|
+
animationStyles.animationIterationCount = "infinite";
|
|
741
|
+
}
|
|
742
|
+
} else if (phase === "out" && outProp !== void 0) {
|
|
743
|
+
const animName = resolveAnimation(outProp);
|
|
744
|
+
if (animName !== void 0) {
|
|
745
|
+
animationStyles.animationName = animName;
|
|
746
|
+
animationStyles.animationDuration = `${duration}ms`;
|
|
747
|
+
animationStyles.animationTimingFunction = easing;
|
|
748
|
+
animationStyles.animationDelay = `${delay}ms`;
|
|
749
|
+
animationStyles.animationFillMode = "forwards";
|
|
750
|
+
animationStyles.animationIterationCount = 1;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
return React15.createElement("Box", { style: animationStyles }, children);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// src/animation/useAnimation.ts
|
|
757
|
+
import { useState as useState12, useCallback as useCallback7 } from "react";
|
|
758
|
+
function resolvePreset(name) {
|
|
759
|
+
try {
|
|
760
|
+
return getPreset(name);
|
|
761
|
+
} catch {
|
|
762
|
+
return name;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
function useAnimation() {
|
|
766
|
+
const [isAnimating, setIsAnimating] = useState12(false);
|
|
767
|
+
const [currentAnimation, setCurrentAnimation] = useState12(null);
|
|
768
|
+
const [style, setStyle] = useState12({});
|
|
769
|
+
const trigger = useCallback7((preset, duration = 300) => {
|
|
770
|
+
const animationName = resolvePreset(preset);
|
|
771
|
+
setIsAnimating(true);
|
|
772
|
+
setCurrentAnimation(preset);
|
|
773
|
+
setStyle({
|
|
774
|
+
animationName,
|
|
775
|
+
animationDuration: `${duration}ms`,
|
|
776
|
+
animationFillMode: "forwards"
|
|
777
|
+
});
|
|
778
|
+
return animationName;
|
|
779
|
+
}, []);
|
|
780
|
+
const reset = useCallback7(() => {
|
|
781
|
+
setIsAnimating(false);
|
|
782
|
+
setCurrentAnimation(null);
|
|
783
|
+
setStyle({});
|
|
784
|
+
}, []);
|
|
785
|
+
return { trigger, reset, isAnimating, currentAnimation, style };
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// src/fonts/localFont.ts
|
|
789
|
+
var registeredFonts = [];
|
|
790
|
+
function formatFromPath(path) {
|
|
791
|
+
const lower = path.toLowerCase();
|
|
792
|
+
if (lower.endsWith(".woff2")) return "woff2";
|
|
793
|
+
if (lower.endsWith(".woff")) return "woff";
|
|
794
|
+
return "truetype";
|
|
795
|
+
}
|
|
796
|
+
function buildFontFace(family, src, display, weight, style) {
|
|
797
|
+
const format = formatFromPath(src);
|
|
798
|
+
const lines = [
|
|
799
|
+
"@font-face {",
|
|
800
|
+
` font-family: "${family}";`,
|
|
801
|
+
` src: url("${src}") format("${format}");`,
|
|
802
|
+
` font-display: ${display};`
|
|
803
|
+
];
|
|
804
|
+
if (weight !== void 0) {
|
|
805
|
+
lines.push(` font-weight: ${weight};`);
|
|
806
|
+
}
|
|
807
|
+
if (style !== void 0) {
|
|
808
|
+
lines.push(` font-style: ${style};`);
|
|
809
|
+
}
|
|
810
|
+
lines.push("}");
|
|
811
|
+
return lines.join("\n");
|
|
812
|
+
}
|
|
813
|
+
function localFont(options) {
|
|
814
|
+
const { src, family, weight, style, display = "swap", fallback } = options;
|
|
815
|
+
const blocks = [];
|
|
816
|
+
if (typeof src === "string") {
|
|
817
|
+
blocks.push(buildFontFace(family, src, display, weight, style));
|
|
818
|
+
} else {
|
|
819
|
+
for (const source of src) {
|
|
820
|
+
blocks.push(
|
|
821
|
+
buildFontFace(
|
|
822
|
+
family,
|
|
823
|
+
source.path,
|
|
824
|
+
display,
|
|
825
|
+
source.weight ?? weight,
|
|
826
|
+
source.style ?? style
|
|
827
|
+
)
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
for (const block of blocks) {
|
|
832
|
+
registeredFonts.push(block);
|
|
833
|
+
}
|
|
834
|
+
const css = blocks.join("\n\n");
|
|
835
|
+
try {
|
|
836
|
+
getBridge().notify("ui.keyframes", { css });
|
|
837
|
+
} catch {
|
|
838
|
+
}
|
|
839
|
+
const families = [family, ...fallback ?? []].join(", ");
|
|
840
|
+
return { fontFamily: families };
|
|
841
|
+
}
|
|
842
|
+
export {
|
|
843
|
+
ANIMATION_PRESETS,
|
|
844
|
+
Animated,
|
|
845
|
+
Box,
|
|
846
|
+
Button,
|
|
847
|
+
CompactView,
|
|
848
|
+
Dropdown,
|
|
849
|
+
HStack,
|
|
850
|
+
Image,
|
|
851
|
+
List,
|
|
852
|
+
Lumio,
|
|
853
|
+
NumberField,
|
|
854
|
+
Slider,
|
|
855
|
+
Text,
|
|
856
|
+
TextArea,
|
|
857
|
+
TextField,
|
|
858
|
+
Toggle,
|
|
859
|
+
VStack,
|
|
860
|
+
WorkerBridge,
|
|
861
|
+
defineKeyframes,
|
|
862
|
+
flushKeyframes,
|
|
863
|
+
getPreset,
|
|
864
|
+
localFont,
|
|
865
|
+
useAnimation,
|
|
866
|
+
useExtensionContext,
|
|
867
|
+
useExtensionStorage,
|
|
868
|
+
useLumioAction,
|
|
869
|
+
useLumioConfig,
|
|
870
|
+
useLumioEvent,
|
|
871
|
+
useLumioIdentity,
|
|
872
|
+
useLumioTheme,
|
|
873
|
+
useMutation,
|
|
874
|
+
useQuery,
|
|
875
|
+
useVisionState
|
|
876
|
+
};
|