@stacksjs/desktop 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +563 -0
- package/dist/alerts.d.ts +164 -0
- package/dist/components.d.ts +942 -0
- package/dist/dialogs.d.ts +213 -0
- package/dist/index.d.ts +183 -0
- package/dist/index.js +3015 -0
- package/dist/index.js.map +15 -0
- package/dist/modals.d.ts +201 -0
- package/dist/system-tray.d.ts +121 -0
- package/dist/types.d.ts +171 -0
- package/dist/window.d.ts +111 -0
- package/package.json +63 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,3015 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var __require = import.meta.require;
|
|
3
|
+
|
|
4
|
+
// src/alerts.ts
|
|
5
|
+
function hasNativeNotificationSupport() {
|
|
6
|
+
if (typeof Notification !== "undefined" && Notification.permission === "granted") {
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
function isBrowser() {
|
|
12
|
+
return typeof window !== "undefined" && typeof document !== "undefined";
|
|
13
|
+
}
|
|
14
|
+
async function requestNotificationPermission() {
|
|
15
|
+
if (typeof Notification === "undefined") {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
if (Notification.permission === "granted") {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
if (Notification.permission === "denied") {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
const permission = await Notification.requestPermission();
|
|
25
|
+
return permission === "granted";
|
|
26
|
+
}
|
|
27
|
+
var activeAlerts = new Map;
|
|
28
|
+
function generateAlertId() {
|
|
29
|
+
return `alert-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
30
|
+
}
|
|
31
|
+
function getAlertIcon(type) {
|
|
32
|
+
switch (type) {
|
|
33
|
+
case "info":
|
|
34
|
+
return "ℹ";
|
|
35
|
+
case "success":
|
|
36
|
+
return "✔";
|
|
37
|
+
case "warning":
|
|
38
|
+
return "⚠";
|
|
39
|
+
case "error":
|
|
40
|
+
return "✖";
|
|
41
|
+
default:
|
|
42
|
+
return "ℹ";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function getToastContainer(position = "top-right") {
|
|
46
|
+
if (!isBrowser()) {
|
|
47
|
+
throw new Error("Toast container requires browser environment");
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const existingContainer = document.querySelector(`.stx-toast-container[data-position="${position}"]`);
|
|
51
|
+
if (existingContainer) {
|
|
52
|
+
return existingContainer;
|
|
53
|
+
}
|
|
54
|
+
} catch {}
|
|
55
|
+
const container = document.createElement("div");
|
|
56
|
+
container.className = `stx-toast-container ${position}`;
|
|
57
|
+
container.dataset.position = position;
|
|
58
|
+
try {
|
|
59
|
+
if (document.body) {
|
|
60
|
+
document.body.appendChild(container);
|
|
61
|
+
}
|
|
62
|
+
} catch {}
|
|
63
|
+
return container;
|
|
64
|
+
}
|
|
65
|
+
function createToastHTML(state) {
|
|
66
|
+
const { options } = state;
|
|
67
|
+
const icon = getAlertIcon(options.type);
|
|
68
|
+
const typeClass = options.type || "info";
|
|
69
|
+
const hasClose = options.duration !== 0;
|
|
70
|
+
return `
|
|
71
|
+
<div class="stx-toast ${typeClass}" data-alert-id="${state.id}" role="alert" aria-live="polite">
|
|
72
|
+
<div class="stx-toast-icon">${icon}</div>
|
|
73
|
+
<div class="stx-toast-content">
|
|
74
|
+
${options.title ? `<div class="stx-toast-title">${escapeHtml(options.title)}</div>` : ""}
|
|
75
|
+
<div class="stx-toast-message">${escapeHtml(options.message)}</div>
|
|
76
|
+
</div>
|
|
77
|
+
${hasClose ? '<button class="stx-toast-close" aria-label="Close">×</button>' : ""}
|
|
78
|
+
</div>
|
|
79
|
+
`;
|
|
80
|
+
}
|
|
81
|
+
function escapeHtml(str) {
|
|
82
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
83
|
+
}
|
|
84
|
+
function dismissAlert(state) {
|
|
85
|
+
if (state.timeout) {
|
|
86
|
+
clearTimeout(state.timeout);
|
|
87
|
+
}
|
|
88
|
+
activeAlerts.delete(state.id);
|
|
89
|
+
if (state.element && isBrowser()) {
|
|
90
|
+
state.element.classList.add("dismissing");
|
|
91
|
+
setTimeout(() => {
|
|
92
|
+
state.element?.remove();
|
|
93
|
+
}, 200);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function showAlert(options) {
|
|
97
|
+
const id = generateAlertId();
|
|
98
|
+
const hasNative = hasNativeNotificationSupport();
|
|
99
|
+
const duration = options.duration ?? 5000;
|
|
100
|
+
const state = {
|
|
101
|
+
id,
|
|
102
|
+
options
|
|
103
|
+
};
|
|
104
|
+
activeAlerts.set(id, state);
|
|
105
|
+
if (hasNative && options.type !== "error") {
|
|
106
|
+
try {
|
|
107
|
+
const notification = new Notification(options.title || "Notification", {
|
|
108
|
+
body: options.message,
|
|
109
|
+
icon: options.type === "success" ? "\u2713" : options.type === "warning" ? "\u26A0" : "\u2139",
|
|
110
|
+
tag: id
|
|
111
|
+
});
|
|
112
|
+
notification.onclick = () => {
|
|
113
|
+
if (options.onClick) {
|
|
114
|
+
options.onClick();
|
|
115
|
+
}
|
|
116
|
+
notification.close();
|
|
117
|
+
};
|
|
118
|
+
if (duration > 0) {
|
|
119
|
+
state.timeout = setTimeout(() => {
|
|
120
|
+
notification.close();
|
|
121
|
+
activeAlerts.delete(id);
|
|
122
|
+
}, duration);
|
|
123
|
+
}
|
|
124
|
+
return;
|
|
125
|
+
} catch {}
|
|
126
|
+
}
|
|
127
|
+
if (isBrowser()) {
|
|
128
|
+
try {
|
|
129
|
+
const container = getToastContainer(options.position);
|
|
130
|
+
const wrapper = document.createElement("div");
|
|
131
|
+
wrapper.innerHTML = createToastHTML(state);
|
|
132
|
+
const toast = wrapper.firstElementChild;
|
|
133
|
+
if (!toast) {
|
|
134
|
+
const typeLabel = (options.type || "info").toUpperCase();
|
|
135
|
+
console.log(`[stx-alert] ${typeLabel}: ${options.title || ""}`);
|
|
136
|
+
console.log(`[stx-alert] ${options.message}`);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
state.element = toast;
|
|
140
|
+
try {
|
|
141
|
+
container.appendChild(toast);
|
|
142
|
+
} catch {
|
|
143
|
+
const typeLabel = (options.type || "info").toUpperCase();
|
|
144
|
+
console.log(`[stx-alert] ${typeLabel}: ${options.title || ""}`);
|
|
145
|
+
console.log(`[stx-alert] ${options.message}`);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (typeof requestAnimationFrame === "function") {
|
|
149
|
+
requestAnimationFrame(() => {
|
|
150
|
+
try {
|
|
151
|
+
toast.classList.add("visible");
|
|
152
|
+
} catch {}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
toast.addEventListener("click", (e) => {
|
|
157
|
+
if (e.target.classList?.contains("stx-toast-close")) {
|
|
158
|
+
dismissAlert(state);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (options.onClick) {
|
|
162
|
+
options.onClick();
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
} catch {}
|
|
166
|
+
if (duration > 0) {
|
|
167
|
+
state.timeout = setTimeout(() => {
|
|
168
|
+
dismissAlert(state);
|
|
169
|
+
}, duration);
|
|
170
|
+
}
|
|
171
|
+
} catch {
|
|
172
|
+
const typeLabel = (options.type || "info").toUpperCase();
|
|
173
|
+
console.log(`[stx-alert] ${typeLabel}: ${options.title || ""}`);
|
|
174
|
+
console.log(`[stx-alert] ${options.message}`);
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
const typeLabel = (options.type || "info").toUpperCase();
|
|
178
|
+
console.log(`[stx-alert] ${typeLabel}: ${options.title || ""}`);
|
|
179
|
+
console.log(`[stx-alert] ${options.message}`);
|
|
180
|
+
if (duration > 0) {
|
|
181
|
+
state.timeout = setTimeout(() => {
|
|
182
|
+
activeAlerts.delete(id);
|
|
183
|
+
}, duration);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async function showToast(options) {
|
|
188
|
+
return showAlert({
|
|
189
|
+
...options,
|
|
190
|
+
position: options.position || "top-right"
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
async function showInfoToast(message, duration = 3000) {
|
|
194
|
+
return showToast({ message, type: "info", duration });
|
|
195
|
+
}
|
|
196
|
+
async function showSuccessToast(message, duration = 3000) {
|
|
197
|
+
return showToast({ message, type: "success", duration });
|
|
198
|
+
}
|
|
199
|
+
async function showWarningToast(message, duration = 3000) {
|
|
200
|
+
return showToast({ message, type: "warning", duration });
|
|
201
|
+
}
|
|
202
|
+
async function showErrorToast(message, duration = 5000) {
|
|
203
|
+
return showToast({ message, type: "error", duration });
|
|
204
|
+
}
|
|
205
|
+
async function notify(title, message, type = "info") {
|
|
206
|
+
return showAlert({ title, message, type });
|
|
207
|
+
}
|
|
208
|
+
function dismissAlertById(id) {
|
|
209
|
+
const state = activeAlerts.get(id);
|
|
210
|
+
if (state) {
|
|
211
|
+
dismissAlert(state);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function dismissAllAlerts() {
|
|
215
|
+
for (const state of activeAlerts.values()) {
|
|
216
|
+
dismissAlert(state);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function getActiveAlertCount() {
|
|
220
|
+
return activeAlerts.size;
|
|
221
|
+
}
|
|
222
|
+
var TOAST_STYLES = `
|
|
223
|
+
.stx-toast-container {
|
|
224
|
+
position: fixed;
|
|
225
|
+
z-index: 10001;
|
|
226
|
+
display: flex;
|
|
227
|
+
flex-direction: column;
|
|
228
|
+
gap: 8px;
|
|
229
|
+
max-width: 400px;
|
|
230
|
+
pointer-events: none;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.stx-toast-container.top-left { top: 16px; left: 16px; }
|
|
234
|
+
.stx-toast-container.top-center { top: 16px; left: 50%; transform: translateX(-50%); }
|
|
235
|
+
.stx-toast-container.top-right { top: 16px; right: 16px; }
|
|
236
|
+
.stx-toast-container.bottom-left { bottom: 16px; left: 16px; }
|
|
237
|
+
.stx-toast-container.bottom-center { bottom: 16px; left: 50%; transform: translateX(-50%); }
|
|
238
|
+
.stx-toast-container.bottom-right { bottom: 16px; right: 16px; }
|
|
239
|
+
|
|
240
|
+
.stx-toast {
|
|
241
|
+
display: flex;
|
|
242
|
+
align-items: flex-start;
|
|
243
|
+
gap: 12px;
|
|
244
|
+
padding: 12px 16px;
|
|
245
|
+
background: #fff;
|
|
246
|
+
border-radius: 8px;
|
|
247
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
248
|
+
pointer-events: auto;
|
|
249
|
+
cursor: pointer;
|
|
250
|
+
opacity: 0;
|
|
251
|
+
transform: translateY(-10px);
|
|
252
|
+
transition: opacity 0.2s, transform 0.2s;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.stx-toast.visible {
|
|
256
|
+
opacity: 1;
|
|
257
|
+
transform: translateY(0);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.stx-toast.dismissing {
|
|
261
|
+
opacity: 0;
|
|
262
|
+
transform: translateX(100%);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
@media (prefers-color-scheme: dark) {
|
|
266
|
+
.stx-toast {
|
|
267
|
+
background: #2d2d2d;
|
|
268
|
+
color: #fff;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.stx-toast-icon {
|
|
273
|
+
font-size: 20px;
|
|
274
|
+
flex-shrink: 0;
|
|
275
|
+
margin-top: 2px;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.stx-toast.info .stx-toast-icon { color: #3498db; }
|
|
279
|
+
.stx-toast.success .stx-toast-icon { color: #27ae60; }
|
|
280
|
+
.stx-toast.warning .stx-toast-icon { color: #f39c12; }
|
|
281
|
+
.stx-toast.error .stx-toast-icon { color: #e74c3c; }
|
|
282
|
+
|
|
283
|
+
.stx-toast-content {
|
|
284
|
+
flex: 1;
|
|
285
|
+
min-width: 0;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.stx-toast-title {
|
|
289
|
+
font-weight: 600;
|
|
290
|
+
margin-bottom: 4px;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.stx-toast-message {
|
|
294
|
+
color: #666;
|
|
295
|
+
font-size: 14px;
|
|
296
|
+
line-height: 1.4;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
@media (prefers-color-scheme: dark) {
|
|
300
|
+
.stx-toast-message { color: #aaa; }
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.stx-toast-close {
|
|
304
|
+
background: none;
|
|
305
|
+
border: none;
|
|
306
|
+
font-size: 20px;
|
|
307
|
+
cursor: pointer;
|
|
308
|
+
opacity: 0.5;
|
|
309
|
+
padding: 0;
|
|
310
|
+
line-height: 1;
|
|
311
|
+
color: inherit;
|
|
312
|
+
transition: opacity 0.15s;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.stx-toast-close:hover {
|
|
316
|
+
opacity: 1;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/* Border accent for different types */
|
|
320
|
+
.stx-toast.info { border-left: 4px solid #3498db; }
|
|
321
|
+
.stx-toast.success { border-left: 4px solid #27ae60; }
|
|
322
|
+
.stx-toast.warning { border-left: 4px solid #f39c12; }
|
|
323
|
+
.stx-toast.error { border-left: 4px solid #e74c3c; }
|
|
324
|
+
`;
|
|
325
|
+
// src/components.ts
|
|
326
|
+
function generateId(prefix = "stx") {
|
|
327
|
+
return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
328
|
+
}
|
|
329
|
+
function escapeHtml2(str) {
|
|
330
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
331
|
+
}
|
|
332
|
+
function buildClasses(...classes) {
|
|
333
|
+
return classes.filter(Boolean).join(" ");
|
|
334
|
+
}
|
|
335
|
+
function buildStyles(styles) {
|
|
336
|
+
if (!styles)
|
|
337
|
+
return "";
|
|
338
|
+
return Object.entries(styles).map(([key, value]) => `${key.replace(/([A-Z])/g, "-$1").toLowerCase()}: ${value}`).join("; ");
|
|
339
|
+
}
|
|
340
|
+
function createButton(props) {
|
|
341
|
+
const {
|
|
342
|
+
text,
|
|
343
|
+
variant = "primary",
|
|
344
|
+
size = "medium",
|
|
345
|
+
disabled = false,
|
|
346
|
+
loading = false,
|
|
347
|
+
icon,
|
|
348
|
+
iconPosition = "left",
|
|
349
|
+
id = generateId("btn"),
|
|
350
|
+
className,
|
|
351
|
+
style
|
|
352
|
+
} = props;
|
|
353
|
+
const classes = buildClasses("stx-button", `stx-button--${variant}`, `stx-button--${size}`, disabled && "stx-button--disabled", loading && "stx-button--loading", className);
|
|
354
|
+
const styleStr = buildStyles(style);
|
|
355
|
+
const iconHtml = icon ? `<span class="stx-button-icon">${icon}</span>` : "";
|
|
356
|
+
const loadingHtml = loading ? '<span class="stx-button-spinner"></span>' : "";
|
|
357
|
+
const content = iconPosition === "left" ? `${iconHtml}${loadingHtml}<span class="stx-button-text">${escapeHtml2(text)}</span>` : `<span class="stx-button-text">${escapeHtml2(text)}</span>${iconHtml}${loadingHtml}`;
|
|
358
|
+
return `<button id="${id}" class="${classes}" ${disabled || loading ? "disabled" : ""} ${styleStr ? `style="${styleStr}"` : ""}>${content}</button>`;
|
|
359
|
+
}
|
|
360
|
+
function createTextInput(props) {
|
|
361
|
+
const {
|
|
362
|
+
value = "",
|
|
363
|
+
placeholder = "",
|
|
364
|
+
type = "text",
|
|
365
|
+
disabled = false,
|
|
366
|
+
readonly = false,
|
|
367
|
+
maxLength,
|
|
368
|
+
minLength,
|
|
369
|
+
pattern,
|
|
370
|
+
required = false,
|
|
371
|
+
autocomplete,
|
|
372
|
+
label,
|
|
373
|
+
error,
|
|
374
|
+
hint,
|
|
375
|
+
id = generateId("input"),
|
|
376
|
+
className,
|
|
377
|
+
style
|
|
378
|
+
} = props;
|
|
379
|
+
const classes = buildClasses("stx-input", error && "stx-input--error", disabled && "stx-input--disabled", className);
|
|
380
|
+
const styleStr = buildStyles(style);
|
|
381
|
+
const attrs = [
|
|
382
|
+
`type="${type}"`,
|
|
383
|
+
`id="${id}"`,
|
|
384
|
+
`class="${classes}"`,
|
|
385
|
+
value && `value="${escapeHtml2(value)}"`,
|
|
386
|
+
placeholder && `placeholder="${escapeHtml2(placeholder)}"`,
|
|
387
|
+
disabled && "disabled",
|
|
388
|
+
readonly && "readonly",
|
|
389
|
+
required && "required",
|
|
390
|
+
maxLength && `maxlength="${maxLength}"`,
|
|
391
|
+
minLength && `minlength="${minLength}"`,
|
|
392
|
+
pattern && `pattern="${escapeHtml2(pattern)}"`,
|
|
393
|
+
autocomplete && `autocomplete="${autocomplete}"`,
|
|
394
|
+
styleStr && `style="${styleStr}"`
|
|
395
|
+
].filter(Boolean).join(" ");
|
|
396
|
+
let html = "";
|
|
397
|
+
if (label) {
|
|
398
|
+
html += `<label class="stx-input-label" for="${id}">${escapeHtml2(label)}</label>`;
|
|
399
|
+
}
|
|
400
|
+
html += `<input ${attrs} />`;
|
|
401
|
+
if (error) {
|
|
402
|
+
html += `<div class="stx-input-error">${escapeHtml2(error)}</div>`;
|
|
403
|
+
} else if (hint) {
|
|
404
|
+
html += `<div class="stx-input-hint">${escapeHtml2(hint)}</div>`;
|
|
405
|
+
}
|
|
406
|
+
return `<div class="stx-input-wrapper">${html}</div>`;
|
|
407
|
+
}
|
|
408
|
+
function createCheckbox(props) {
|
|
409
|
+
const {
|
|
410
|
+
checked = false,
|
|
411
|
+
label,
|
|
412
|
+
disabled = false,
|
|
413
|
+
indeterminate = false,
|
|
414
|
+
id = generateId("checkbox"),
|
|
415
|
+
className,
|
|
416
|
+
style
|
|
417
|
+
} = props;
|
|
418
|
+
const classes = buildClasses("stx-checkbox", disabled && "stx-checkbox--disabled", className);
|
|
419
|
+
const styleStr = buildStyles(style);
|
|
420
|
+
const inputAttrs = [
|
|
421
|
+
'type="checkbox"',
|
|
422
|
+
`id="${id}"`,
|
|
423
|
+
checked && "checked",
|
|
424
|
+
disabled && "disabled",
|
|
425
|
+
indeterminate && 'data-indeterminate="true"'
|
|
426
|
+
].filter(Boolean).join(" ");
|
|
427
|
+
return `
|
|
428
|
+
<label class="${classes}" ${styleStr ? `style="${styleStr}"` : ""}>
|
|
429
|
+
<input ${inputAttrs} />
|
|
430
|
+
<span class="stx-checkbox-box"></span>
|
|
431
|
+
${label ? `<span class="stx-checkbox-label">${escapeHtml2(label)}</span>` : ""}
|
|
432
|
+
</label>
|
|
433
|
+
`;
|
|
434
|
+
}
|
|
435
|
+
function createSlider(props) {
|
|
436
|
+
const {
|
|
437
|
+
value = 50,
|
|
438
|
+
min = 0,
|
|
439
|
+
max = 100,
|
|
440
|
+
step = 1,
|
|
441
|
+
disabled = false,
|
|
442
|
+
label,
|
|
443
|
+
showValue = true,
|
|
444
|
+
id = generateId("slider"),
|
|
445
|
+
className,
|
|
446
|
+
style
|
|
447
|
+
} = props;
|
|
448
|
+
const classes = buildClasses("stx-slider", disabled && "stx-slider--disabled", className);
|
|
449
|
+
const styleStr = buildStyles(style);
|
|
450
|
+
const inputAttrs = [
|
|
451
|
+
'type="range"',
|
|
452
|
+
`id="${id}"`,
|
|
453
|
+
`value="${value}"`,
|
|
454
|
+
`min="${min}"`,
|
|
455
|
+
`max="${max}"`,
|
|
456
|
+
`step="${step}"`,
|
|
457
|
+
disabled && "disabled"
|
|
458
|
+
].filter(Boolean).join(" ");
|
|
459
|
+
let html = "";
|
|
460
|
+
if (label) {
|
|
461
|
+
html += `<label class="stx-slider-label" for="${id}">${escapeHtml2(label)}</label>`;
|
|
462
|
+
}
|
|
463
|
+
html += `<div class="stx-slider-track"><input ${inputAttrs} /></div>`;
|
|
464
|
+
if (showValue) {
|
|
465
|
+
html += `<span class="stx-slider-value">${value}</span>`;
|
|
466
|
+
}
|
|
467
|
+
return `<div class="${classes}" ${styleStr ? `style="${styleStr}"` : ""}>${html}</div>`;
|
|
468
|
+
}
|
|
469
|
+
function createProgressBar(props) {
|
|
470
|
+
const {
|
|
471
|
+
value,
|
|
472
|
+
max = 100,
|
|
473
|
+
variant = "default",
|
|
474
|
+
size = "medium",
|
|
475
|
+
showLabel = false,
|
|
476
|
+
indeterminate = false,
|
|
477
|
+
id = generateId("progress"),
|
|
478
|
+
className,
|
|
479
|
+
style
|
|
480
|
+
} = props;
|
|
481
|
+
const percentage = Math.min(100, Math.max(0, value / max * 100));
|
|
482
|
+
const classes = buildClasses("stx-progress", `stx-progress--${variant}`, `stx-progress--${size}`, indeterminate && "stx-progress--indeterminate", className);
|
|
483
|
+
const styleStr = buildStyles(style);
|
|
484
|
+
return `
|
|
485
|
+
<div id="${id}" class="${classes}" role="progressbar" aria-valuenow="${value}" aria-valuemin="0" aria-valuemax="${max}" ${styleStr ? `style="${styleStr}"` : ""}>
|
|
486
|
+
<div class="stx-progress-bar" style="width: ${indeterminate ? "100%" : `${percentage}%`}"></div>
|
|
487
|
+
${showLabel ? `<span class="stx-progress-label">${Math.round(percentage)}%</span>` : ""}
|
|
488
|
+
</div>
|
|
489
|
+
`;
|
|
490
|
+
}
|
|
491
|
+
function createBadge(props) {
|
|
492
|
+
const {
|
|
493
|
+
text,
|
|
494
|
+
variant = "default",
|
|
495
|
+
size = "medium",
|
|
496
|
+
id = generateId("badge"),
|
|
497
|
+
className,
|
|
498
|
+
style
|
|
499
|
+
} = props;
|
|
500
|
+
const classes = buildClasses("stx-badge", `stx-badge--${variant}`, `stx-badge--${size}`, className);
|
|
501
|
+
const styleStr = buildStyles(style);
|
|
502
|
+
return `<span id="${id}" class="${classes}" ${styleStr ? `style="${styleStr}"` : ""}>${escapeHtml2(text)}</span>`;
|
|
503
|
+
}
|
|
504
|
+
function createAvatar(props) {
|
|
505
|
+
const {
|
|
506
|
+
src,
|
|
507
|
+
alt = "",
|
|
508
|
+
name,
|
|
509
|
+
size = "medium",
|
|
510
|
+
shape = "circle",
|
|
511
|
+
id = generateId("avatar"),
|
|
512
|
+
className,
|
|
513
|
+
style
|
|
514
|
+
} = props;
|
|
515
|
+
const sizeClass = typeof size === "number" ? "" : `stx-avatar--${size}`;
|
|
516
|
+
const sizeStyle = typeof size === "number" ? `width: ${size}px; height: ${size}px;` : "";
|
|
517
|
+
const classes = buildClasses("stx-avatar", sizeClass, `stx-avatar--${shape}`, className);
|
|
518
|
+
const combinedStyle = [sizeStyle, buildStyles(style)].filter(Boolean).join(" ");
|
|
519
|
+
const initials = name ? name.split(" ").map((n) => n[0]).join("").toUpperCase().slice(0, 2) : "";
|
|
520
|
+
if (src) {
|
|
521
|
+
return `<div id="${id}" class="${classes}" ${combinedStyle ? `style="${combinedStyle}"` : ""}>
|
|
522
|
+
<img src="${escapeHtml2(src)}" alt="${escapeHtml2(alt)}" class="stx-avatar-img" />
|
|
523
|
+
</div>`;
|
|
524
|
+
}
|
|
525
|
+
return `<div id="${id}" class="${classes}" ${combinedStyle ? `style="${combinedStyle}"` : ""}>
|
|
526
|
+
<span class="stx-avatar-initials">${initials}</span>
|
|
527
|
+
</div>`;
|
|
528
|
+
}
|
|
529
|
+
function createCard(props) {
|
|
530
|
+
const {
|
|
531
|
+
title,
|
|
532
|
+
subtitle,
|
|
533
|
+
content,
|
|
534
|
+
image,
|
|
535
|
+
footer,
|
|
536
|
+
variant = "default",
|
|
537
|
+
id = generateId("card"),
|
|
538
|
+
className,
|
|
539
|
+
style
|
|
540
|
+
} = props;
|
|
541
|
+
const classes = buildClasses("stx-card", `stx-card--${variant}`, className);
|
|
542
|
+
const styleStr = buildStyles(style);
|
|
543
|
+
let html = "";
|
|
544
|
+
if (image) {
|
|
545
|
+
html += `<div class="stx-card-image"><img src="${escapeHtml2(image)}" alt="" /></div>`;
|
|
546
|
+
}
|
|
547
|
+
html += '<div class="stx-card-body">';
|
|
548
|
+
if (title) {
|
|
549
|
+
html += `<h3 class="stx-card-title">${escapeHtml2(title)}</h3>`;
|
|
550
|
+
}
|
|
551
|
+
if (subtitle) {
|
|
552
|
+
html += `<p class="stx-card-subtitle">${escapeHtml2(subtitle)}</p>`;
|
|
553
|
+
}
|
|
554
|
+
if (content) {
|
|
555
|
+
html += `<div class="stx-card-content">${escapeHtml2(content)}</div>`;
|
|
556
|
+
}
|
|
557
|
+
html += "</div>";
|
|
558
|
+
if (footer) {
|
|
559
|
+
html += `<div class="stx-card-footer">${escapeHtml2(footer)}</div>`;
|
|
560
|
+
}
|
|
561
|
+
return `<div id="${id}" class="${classes}" ${styleStr ? `style="${styleStr}"` : ""}>${html}</div>`;
|
|
562
|
+
}
|
|
563
|
+
function createTabs(props) {
|
|
564
|
+
const {
|
|
565
|
+
tabs,
|
|
566
|
+
activeTab,
|
|
567
|
+
variant = "default",
|
|
568
|
+
id = generateId("tabs"),
|
|
569
|
+
className,
|
|
570
|
+
style
|
|
571
|
+
} = props;
|
|
572
|
+
const classes = buildClasses("stx-tabs", `stx-tabs--${variant}`, className);
|
|
573
|
+
const styleStr = buildStyles(style);
|
|
574
|
+
const tabsHtml = tabs.map((tab, index) => {
|
|
575
|
+
const isActive = activeTab ? tab.value === activeTab : index === 0;
|
|
576
|
+
const tabClasses = buildClasses("stx-tab", isActive && "stx-tab--active", tab.disabled && "stx-tab--disabled");
|
|
577
|
+
return `<button class="${tabClasses}" data-value="${escapeHtml2(tab.value)}" ${tab.disabled ? "disabled" : ""} role="tab" aria-selected="${isActive}">${escapeHtml2(tab.label)}</button>`;
|
|
578
|
+
}).join("");
|
|
579
|
+
return `<div id="${id}" class="${classes}" role="tablist" ${styleStr ? `style="${styleStr}"` : ""}>${tabsHtml}</div>`;
|
|
580
|
+
}
|
|
581
|
+
function createDropdown(props) {
|
|
582
|
+
const {
|
|
583
|
+
options,
|
|
584
|
+
value,
|
|
585
|
+
placeholder = "Select...",
|
|
586
|
+
disabled = false,
|
|
587
|
+
id = generateId("dropdown"),
|
|
588
|
+
className,
|
|
589
|
+
style
|
|
590
|
+
} = props;
|
|
591
|
+
const classes = buildClasses("stx-dropdown", disabled && "stx-dropdown--disabled", className);
|
|
592
|
+
const styleStr = buildStyles(style);
|
|
593
|
+
const optionsHtml = options.map((opt) => {
|
|
594
|
+
const selected = opt.value === value ? "selected" : "";
|
|
595
|
+
const disabledAttr = opt.disabled ? "disabled" : "";
|
|
596
|
+
return `<option value="${escapeHtml2(opt.value)}" ${selected} ${disabledAttr}>${escapeHtml2(opt.label)}</option>`;
|
|
597
|
+
}).join("");
|
|
598
|
+
const placeholderOption = value ? "" : `<option value="" disabled selected>${escapeHtml2(placeholder)}</option>`;
|
|
599
|
+
return `<select id="${id}" class="${classes}" ${disabled ? "disabled" : ""} ${styleStr ? `style="${styleStr}"` : ""}>${placeholderOption}${optionsHtml}</select>`;
|
|
600
|
+
}
|
|
601
|
+
function createRating(props) {
|
|
602
|
+
const {
|
|
603
|
+
value = 0,
|
|
604
|
+
max = 5,
|
|
605
|
+
readonly = false,
|
|
606
|
+
size = "medium",
|
|
607
|
+
allowHalf = false,
|
|
608
|
+
id = generateId("rating"),
|
|
609
|
+
className,
|
|
610
|
+
style
|
|
611
|
+
} = props;
|
|
612
|
+
const classes = buildClasses("stx-rating", `stx-rating--${size}`, readonly && "stx-rating--readonly", className);
|
|
613
|
+
const styleStr = buildStyles(style);
|
|
614
|
+
let starsHtml = "";
|
|
615
|
+
for (let i = 1;i <= max; i++) {
|
|
616
|
+
const filled = i <= value;
|
|
617
|
+
const halfFilled = allowHalf && i - 0.5 === value;
|
|
618
|
+
const starClass = filled ? "stx-star--filled" : halfFilled ? "stx-star--half" : "";
|
|
619
|
+
starsHtml += `<span class="stx-star ${starClass}" data-value="${i}">\u2605</span>`;
|
|
620
|
+
}
|
|
621
|
+
return `<div id="${id}" class="${classes}" role="slider" aria-valuenow="${value}" aria-valuemin="0" aria-valuemax="${max}" ${styleStr ? `style="${styleStr}"` : ""}>${starsHtml}</div>`;
|
|
622
|
+
}
|
|
623
|
+
function createRadioButton(props) {
|
|
624
|
+
const {
|
|
625
|
+
value,
|
|
626
|
+
name,
|
|
627
|
+
checked = false,
|
|
628
|
+
label,
|
|
629
|
+
disabled = false,
|
|
630
|
+
id = generateId("radio"),
|
|
631
|
+
className,
|
|
632
|
+
style
|
|
633
|
+
} = props;
|
|
634
|
+
const classes = buildClasses("stx-radio", disabled && "stx-radio--disabled", className);
|
|
635
|
+
const styleStr = buildStyles(style);
|
|
636
|
+
const inputAttrs = [
|
|
637
|
+
'type="radio"',
|
|
638
|
+
`id="${id}"`,
|
|
639
|
+
`name="${escapeHtml2(name)}"`,
|
|
640
|
+
`value="${escapeHtml2(value)}"`,
|
|
641
|
+
checked && "checked",
|
|
642
|
+
disabled && "disabled"
|
|
643
|
+
].filter(Boolean).join(" ");
|
|
644
|
+
return `
|
|
645
|
+
<label class="${classes}" ${styleStr ? `style="${styleStr}"` : ""}>
|
|
646
|
+
<input ${inputAttrs} />
|
|
647
|
+
<span class="stx-radio-circle"></span>
|
|
648
|
+
${label ? `<span class="stx-radio-label">${escapeHtml2(label)}</span>` : ""}
|
|
649
|
+
</label>
|
|
650
|
+
`;
|
|
651
|
+
}
|
|
652
|
+
function createColorPicker(props) {
|
|
653
|
+
const {
|
|
654
|
+
value = "#3498db",
|
|
655
|
+
disabled = false,
|
|
656
|
+
label,
|
|
657
|
+
presetColors = ["#e74c3c", "#f39c12", "#27ae60", "#3498db", "#9b59b6", "#1abc9c", "#34495e", "#000000"],
|
|
658
|
+
id = generateId("color"),
|
|
659
|
+
className,
|
|
660
|
+
style
|
|
661
|
+
} = props;
|
|
662
|
+
const classes = buildClasses("stx-color-picker", disabled && "stx-color-picker--disabled", className);
|
|
663
|
+
const styleStr = buildStyles(style);
|
|
664
|
+
let html = "";
|
|
665
|
+
if (label) {
|
|
666
|
+
html += `<label class="stx-color-picker-label" for="${id}">${escapeHtml2(label)}</label>`;
|
|
667
|
+
}
|
|
668
|
+
html += `<div class="stx-color-picker-input">
|
|
669
|
+
<input type="color" id="${id}" value="${escapeHtml2(value)}" ${disabled ? "disabled" : ""} />
|
|
670
|
+
<span class="stx-color-picker-value">${escapeHtml2(value)}</span>
|
|
671
|
+
</div>`;
|
|
672
|
+
if (presetColors.length > 0) {
|
|
673
|
+
html += '<div class="stx-color-picker-presets">';
|
|
674
|
+
for (const color of presetColors) {
|
|
675
|
+
html += `<button class="stx-color-preset" style="background: ${escapeHtml2(color)}" data-color="${escapeHtml2(color)}" ${disabled ? "disabled" : ""}></button>`;
|
|
676
|
+
}
|
|
677
|
+
html += "</div>";
|
|
678
|
+
}
|
|
679
|
+
return `<div class="${classes}" ${styleStr ? `style="${styleStr}"` : ""}>${html}</div>`;
|
|
680
|
+
}
|
|
681
|
+
function createDatePicker(props) {
|
|
682
|
+
const {
|
|
683
|
+
value,
|
|
684
|
+
min,
|
|
685
|
+
max,
|
|
686
|
+
disabled = false,
|
|
687
|
+
label,
|
|
688
|
+
placeholder = "Select date",
|
|
689
|
+
showClear = true,
|
|
690
|
+
id = generateId("date"),
|
|
691
|
+
className,
|
|
692
|
+
style
|
|
693
|
+
} = props;
|
|
694
|
+
const classes = buildClasses("stx-date-picker", disabled && "stx-date-picker--disabled", className);
|
|
695
|
+
const styleStr = buildStyles(style);
|
|
696
|
+
const dateValue = value instanceof Date ? value.toISOString().split("T")[0] : value || "";
|
|
697
|
+
const minValue = min instanceof Date ? min.toISOString().split("T")[0] : min;
|
|
698
|
+
const maxValue = max instanceof Date ? max.toISOString().split("T")[0] : max;
|
|
699
|
+
const inputAttrs = [
|
|
700
|
+
'type="date"',
|
|
701
|
+
`id="${id}"`,
|
|
702
|
+
dateValue && `value="${dateValue}"`,
|
|
703
|
+
minValue && `min="${minValue}"`,
|
|
704
|
+
maxValue && `max="${maxValue}"`,
|
|
705
|
+
disabled && "disabled",
|
|
706
|
+
`placeholder="${escapeHtml2(placeholder)}"`
|
|
707
|
+
].filter(Boolean).join(" ");
|
|
708
|
+
let html = "";
|
|
709
|
+
if (label) {
|
|
710
|
+
html += `<label class="stx-date-picker-label" for="${id}">${escapeHtml2(label)}</label>`;
|
|
711
|
+
}
|
|
712
|
+
html += `<div class="stx-date-picker-input">
|
|
713
|
+
<input ${inputAttrs} />
|
|
714
|
+
${showClear && dateValue ? '<button class="stx-date-picker-clear" type="button">×</button>' : ""}
|
|
715
|
+
</div>`;
|
|
716
|
+
return `<div class="${classes}" ${styleStr ? `style="${styleStr}"` : ""}>${html}</div>`;
|
|
717
|
+
}
|
|
718
|
+
function createTimePicker(props) {
|
|
719
|
+
const {
|
|
720
|
+
value = "",
|
|
721
|
+
disabled = false,
|
|
722
|
+
label,
|
|
723
|
+
step = 60,
|
|
724
|
+
min,
|
|
725
|
+
max,
|
|
726
|
+
id = generateId("time"),
|
|
727
|
+
className,
|
|
728
|
+
style
|
|
729
|
+
} = props;
|
|
730
|
+
const classes = buildClasses("stx-time-picker", disabled && "stx-time-picker--disabled", className);
|
|
731
|
+
const styleStr = buildStyles(style);
|
|
732
|
+
const inputAttrs = [
|
|
733
|
+
'type="time"',
|
|
734
|
+
`id="${id}"`,
|
|
735
|
+
value && `value="${escapeHtml2(value)}"`,
|
|
736
|
+
`step="${step}"`,
|
|
737
|
+
min && `min="${escapeHtml2(min)}"`,
|
|
738
|
+
max && `max="${escapeHtml2(max)}"`,
|
|
739
|
+
disabled && "disabled"
|
|
740
|
+
].filter(Boolean).join(" ");
|
|
741
|
+
let html = "";
|
|
742
|
+
if (label) {
|
|
743
|
+
html += `<label class="stx-time-picker-label" for="${id}">${escapeHtml2(label)}</label>`;
|
|
744
|
+
}
|
|
745
|
+
html += `<input ${inputAttrs} class="stx-time-picker-input" />`;
|
|
746
|
+
return `<div class="${classes}" ${styleStr ? `style="${styleStr}"` : ""}>${html}</div>`;
|
|
747
|
+
}
|
|
748
|
+
function createAutocomplete(props) {
|
|
749
|
+
const {
|
|
750
|
+
options,
|
|
751
|
+
value = "",
|
|
752
|
+
placeholder = "Search...",
|
|
753
|
+
disabled = false,
|
|
754
|
+
label,
|
|
755
|
+
loading = false,
|
|
756
|
+
id = generateId("autocomplete"),
|
|
757
|
+
className,
|
|
758
|
+
style
|
|
759
|
+
} = props;
|
|
760
|
+
const classes = buildClasses("stx-autocomplete", disabled && "stx-autocomplete--disabled", className);
|
|
761
|
+
const styleStr = buildStyles(style);
|
|
762
|
+
const selectedOption = options.find((opt) => opt.value === value);
|
|
763
|
+
const displayValue = selectedOption ? selectedOption.label : value;
|
|
764
|
+
let html = "";
|
|
765
|
+
if (label) {
|
|
766
|
+
html += `<label class="stx-autocomplete-label" for="${id}">${escapeHtml2(label)}</label>`;
|
|
767
|
+
}
|
|
768
|
+
html += `<div class="stx-autocomplete-input-wrapper">
|
|
769
|
+
<input type="text" id="${id}" value="${escapeHtml2(displayValue)}" placeholder="${escapeHtml2(placeholder)}" ${disabled ? "disabled" : ""} autocomplete="off" class="stx-autocomplete-input" />
|
|
770
|
+
${loading ? '<span class="stx-autocomplete-spinner"></span>' : ""}
|
|
771
|
+
</div>`;
|
|
772
|
+
html += '<ul class="stx-autocomplete-list">';
|
|
773
|
+
for (const opt of options) {
|
|
774
|
+
html += `<li class="stx-autocomplete-item ${opt.value === value ? "stx-autocomplete-item--selected" : ""}" data-value="${escapeHtml2(opt.value)}">${escapeHtml2(opt.label)}</li>`;
|
|
775
|
+
}
|
|
776
|
+
html += "</ul>";
|
|
777
|
+
return `<div class="${classes}" ${styleStr ? `style="${styleStr}"` : ""}>${html}</div>`;
|
|
778
|
+
}
|
|
779
|
+
function createLabel(props) {
|
|
780
|
+
const {
|
|
781
|
+
text,
|
|
782
|
+
htmlFor,
|
|
783
|
+
required = false,
|
|
784
|
+
size = "medium",
|
|
785
|
+
id = generateId("label"),
|
|
786
|
+
className,
|
|
787
|
+
style
|
|
788
|
+
} = props;
|
|
789
|
+
const classes = buildClasses("stx-label", `stx-label--${size}`, className);
|
|
790
|
+
const styleStr = buildStyles(style);
|
|
791
|
+
return `<label id="${id}" class="${classes}" ${htmlFor ? `for="${escapeHtml2(htmlFor)}"` : ""} ${styleStr ? `style="${styleStr}"` : ""}>${escapeHtml2(text)}${required ? '<span class="stx-label-required">*</span>' : ""}</label>`;
|
|
792
|
+
}
|
|
793
|
+
function createImageView(props) {
|
|
794
|
+
const {
|
|
795
|
+
src,
|
|
796
|
+
alt = "",
|
|
797
|
+
width,
|
|
798
|
+
height,
|
|
799
|
+
objectFit = "cover",
|
|
800
|
+
fallback,
|
|
801
|
+
loading = "lazy",
|
|
802
|
+
id = generateId("image"),
|
|
803
|
+
className,
|
|
804
|
+
style
|
|
805
|
+
} = props;
|
|
806
|
+
const classes = buildClasses("stx-image", className);
|
|
807
|
+
const imageStyles = { objectFit };
|
|
808
|
+
if (width)
|
|
809
|
+
imageStyles.width = typeof width === "number" ? `${width}px` : width;
|
|
810
|
+
if (height)
|
|
811
|
+
imageStyles.height = typeof height === "number" ? `${height}px` : height;
|
|
812
|
+
const combinedStyle = [buildStyles(imageStyles), buildStyles(style)].filter(Boolean).join("; ");
|
|
813
|
+
const fallbackAttr = fallback ? `onerror="this.src='${escapeHtml2(fallback)}'"` : "";
|
|
814
|
+
return `<img id="${id}" class="${classes}" src="${escapeHtml2(src)}" alt="${escapeHtml2(alt)}" loading="${loading}" ${combinedStyle ? `style="${combinedStyle}"` : ""} ${fallbackAttr} />`;
|
|
815
|
+
}
|
|
816
|
+
function createChip(props) {
|
|
817
|
+
const {
|
|
818
|
+
text,
|
|
819
|
+
variant = "default",
|
|
820
|
+
size = "medium",
|
|
821
|
+
removable = false,
|
|
822
|
+
icon,
|
|
823
|
+
id = generateId("chip"),
|
|
824
|
+
className,
|
|
825
|
+
style
|
|
826
|
+
} = props;
|
|
827
|
+
const classes = buildClasses("stx-chip", `stx-chip--${variant}`, `stx-chip--${size}`, className);
|
|
828
|
+
const styleStr = buildStyles(style);
|
|
829
|
+
let html = "";
|
|
830
|
+
if (icon) {
|
|
831
|
+
html += `<span class="stx-chip-icon">${icon}</span>`;
|
|
832
|
+
}
|
|
833
|
+
html += `<span class="stx-chip-text">${escapeHtml2(text)}</span>`;
|
|
834
|
+
if (removable) {
|
|
835
|
+
html += '<button class="stx-chip-remove" type="button">×</button>';
|
|
836
|
+
}
|
|
837
|
+
return `<span id="${id}" class="${classes}" ${styleStr ? `style="${styleStr}"` : ""}>${html}</span>`;
|
|
838
|
+
}
|
|
839
|
+
function createTooltip(props) {
|
|
840
|
+
const {
|
|
841
|
+
text,
|
|
842
|
+
position = "top",
|
|
843
|
+
delay = 200,
|
|
844
|
+
children,
|
|
845
|
+
id = generateId("tooltip"),
|
|
846
|
+
className,
|
|
847
|
+
style
|
|
848
|
+
} = props;
|
|
849
|
+
const classes = buildClasses("stx-tooltip-wrapper", className);
|
|
850
|
+
const styleStr = buildStyles(style);
|
|
851
|
+
return `<div id="${id}" class="${classes}" ${styleStr ? `style="${styleStr}"` : ""} data-tooltip="${escapeHtml2(text)}" data-position="${position}" data-delay="${delay}">
|
|
852
|
+
${children}
|
|
853
|
+
<span class="stx-tooltip stx-tooltip--${position}">${escapeHtml2(text)}</span>
|
|
854
|
+
</div>`;
|
|
855
|
+
}
|
|
856
|
+
function createScrollView(props) {
|
|
857
|
+
const {
|
|
858
|
+
direction = "vertical",
|
|
859
|
+
showScrollbar = true,
|
|
860
|
+
smooth = true,
|
|
861
|
+
height,
|
|
862
|
+
width,
|
|
863
|
+
children,
|
|
864
|
+
id = generateId("scroll"),
|
|
865
|
+
className,
|
|
866
|
+
style
|
|
867
|
+
} = props;
|
|
868
|
+
const classes = buildClasses("stx-scroll-view", `stx-scroll-view--${direction}`, !showScrollbar && "stx-scroll-view--hide-scrollbar", smooth && "stx-scroll-view--smooth", className);
|
|
869
|
+
const scrollStyles = {};
|
|
870
|
+
if (height)
|
|
871
|
+
scrollStyles.height = typeof height === "number" ? `${height}px` : height;
|
|
872
|
+
if (width)
|
|
873
|
+
scrollStyles.width = typeof width === "number" ? `${width}px` : width;
|
|
874
|
+
const combinedStyle = [buildStyles(scrollStyles), buildStyles(style)].filter(Boolean).join("; ");
|
|
875
|
+
return `<div id="${id}" class="${classes}" ${combinedStyle ? `style="${combinedStyle}"` : ""}>${children}</div>`;
|
|
876
|
+
}
|
|
877
|
+
function createSplitView(props) {
|
|
878
|
+
const {
|
|
879
|
+
direction = "horizontal",
|
|
880
|
+
sizes = [50, 50],
|
|
881
|
+
resizable = true,
|
|
882
|
+
children,
|
|
883
|
+
id = generateId("split"),
|
|
884
|
+
className,
|
|
885
|
+
style
|
|
886
|
+
} = props;
|
|
887
|
+
const classes = buildClasses("stx-split-view", `stx-split-view--${direction}`, resizable && "stx-split-view--resizable", className);
|
|
888
|
+
const styleStr = buildStyles(style);
|
|
889
|
+
const sizeUnit = direction === "horizontal" ? "width" : "height";
|
|
890
|
+
return `<div id="${id}" class="${classes}" ${styleStr ? `style="${styleStr}"` : ""}>
|
|
891
|
+
<div class="stx-split-pane" style="${sizeUnit}: ${sizes[0]}%">${children[0]}</div>
|
|
892
|
+
${resizable ? '<div class="stx-split-divider"></div>' : ""}
|
|
893
|
+
<div class="stx-split-pane" style="${sizeUnit}: ${sizes[1]}%">${children[1]}</div>
|
|
894
|
+
</div>`;
|
|
895
|
+
}
|
|
896
|
+
function createAccordion(props) {
|
|
897
|
+
const {
|
|
898
|
+
items,
|
|
899
|
+
multiple = false,
|
|
900
|
+
defaultOpen = [],
|
|
901
|
+
id = generateId("accordion"),
|
|
902
|
+
className,
|
|
903
|
+
style
|
|
904
|
+
} = props;
|
|
905
|
+
const classes = buildClasses("stx-accordion", className);
|
|
906
|
+
const styleStr = buildStyles(style);
|
|
907
|
+
let html = "";
|
|
908
|
+
items.forEach((item, index) => {
|
|
909
|
+
const isOpen = defaultOpen.includes(index);
|
|
910
|
+
const itemClasses = buildClasses("stx-accordion-item", isOpen && "stx-accordion-item--open", item.disabled && "stx-accordion-item--disabled");
|
|
911
|
+
html += `<div class="${itemClasses}" data-index="${index}">
|
|
912
|
+
<button class="stx-accordion-header" ${item.disabled ? "disabled" : ""} aria-expanded="${isOpen}" data-multiple="${multiple}">
|
|
913
|
+
<span class="stx-accordion-title">${escapeHtml2(item.title)}</span>
|
|
914
|
+
<span class="stx-accordion-icon">\u25BC</span>
|
|
915
|
+
</button>
|
|
916
|
+
<div class="stx-accordion-content" ${isOpen ? "" : "hidden"}>
|
|
917
|
+
${escapeHtml2(item.content)}
|
|
918
|
+
</div>
|
|
919
|
+
</div>`;
|
|
920
|
+
});
|
|
921
|
+
return `<div id="${id}" class="${classes}" ${styleStr ? `style="${styleStr}"` : ""}>${html}</div>`;
|
|
922
|
+
}
|
|
923
|
+
function createStepper(props) {
|
|
924
|
+
const {
|
|
925
|
+
steps,
|
|
926
|
+
currentStep,
|
|
927
|
+
orientation = "horizontal",
|
|
928
|
+
id = generateId("stepper"),
|
|
929
|
+
className,
|
|
930
|
+
style
|
|
931
|
+
} = props;
|
|
932
|
+
const classes = buildClasses("stx-stepper", `stx-stepper--${orientation}`, className);
|
|
933
|
+
const styleStr = buildStyles(style);
|
|
934
|
+
let html = "";
|
|
935
|
+
steps.forEach((step, index) => {
|
|
936
|
+
const stepClasses = buildClasses("stx-step", index < currentStep && "stx-step--completed", index === currentStep && "stx-step--active");
|
|
937
|
+
html += `<div class="${stepClasses}" data-step="${index}">
|
|
938
|
+
<div class="stx-step-indicator">
|
|
939
|
+
${index < currentStep ? "\u2713" : index + 1}
|
|
940
|
+
</div>
|
|
941
|
+
<div class="stx-step-content">
|
|
942
|
+
<div class="stx-step-label">${escapeHtml2(step.label)}</div>
|
|
943
|
+
${step.description ? `<div class="stx-step-description">${escapeHtml2(step.description)}</div>` : ""}
|
|
944
|
+
</div>
|
|
945
|
+
</div>`;
|
|
946
|
+
if (index < steps.length - 1) {
|
|
947
|
+
html += '<div class="stx-step-connector"></div>';
|
|
948
|
+
}
|
|
949
|
+
});
|
|
950
|
+
return `<div id="${id}" class="${classes}" ${styleStr ? `style="${styleStr}"` : ""}>${html}</div>`;
|
|
951
|
+
}
|
|
952
|
+
function createModalComponent(props) {
|
|
953
|
+
const {
|
|
954
|
+
open = false,
|
|
955
|
+
title,
|
|
956
|
+
size = "medium",
|
|
957
|
+
closable = true,
|
|
958
|
+
children,
|
|
959
|
+
footer,
|
|
960
|
+
id = generateId("modal"),
|
|
961
|
+
className,
|
|
962
|
+
style
|
|
963
|
+
} = props;
|
|
964
|
+
const classes = buildClasses("stx-modal-component", `stx-modal-component--${size}`, className);
|
|
965
|
+
const styleStr = buildStyles(style);
|
|
966
|
+
if (!open) {
|
|
967
|
+
return `<div id="${id}" class="${classes} stx-modal-component--hidden" ${styleStr ? `style="${styleStr}"` : ""}></div>`;
|
|
968
|
+
}
|
|
969
|
+
let html = '<div class="stx-modal-component-overlay">';
|
|
970
|
+
html += '<div class="stx-modal-component-dialog">';
|
|
971
|
+
if (title || closable) {
|
|
972
|
+
html += '<div class="stx-modal-component-header">';
|
|
973
|
+
if (title) {
|
|
974
|
+
html += `<h3 class="stx-modal-component-title">${escapeHtml2(title)}</h3>`;
|
|
975
|
+
}
|
|
976
|
+
if (closable) {
|
|
977
|
+
html += '<button class="stx-modal-component-close">×</button>';
|
|
978
|
+
}
|
|
979
|
+
html += "</div>";
|
|
980
|
+
}
|
|
981
|
+
html += `<div class="stx-modal-component-body">${children}</div>`;
|
|
982
|
+
if (footer) {
|
|
983
|
+
html += `<div class="stx-modal-component-footer">${footer}</div>`;
|
|
984
|
+
}
|
|
985
|
+
html += "</div></div>";
|
|
986
|
+
return `<div id="${id}" class="${classes}" ${styleStr ? `style="${styleStr}"` : ""}>${html}</div>`;
|
|
987
|
+
}
|
|
988
|
+
function createListView(props) {
|
|
989
|
+
const {
|
|
990
|
+
items,
|
|
991
|
+
selectable = false,
|
|
992
|
+
multiSelect = false,
|
|
993
|
+
emptyMessage = "No items",
|
|
994
|
+
id = generateId("list"),
|
|
995
|
+
className,
|
|
996
|
+
style
|
|
997
|
+
} = props;
|
|
998
|
+
const classes = buildClasses("stx-list-view", selectable && "stx-list-view--selectable", multiSelect && "stx-list-view--multi", className);
|
|
999
|
+
const styleStr = buildStyles(style);
|
|
1000
|
+
if (items.length === 0) {
|
|
1001
|
+
return `<div id="${id}" class="${classes}" ${styleStr ? `style="${styleStr}"` : ""}>
|
|
1002
|
+
<div class="stx-list-view-empty">${escapeHtml2(emptyMessage)}</div>
|
|
1003
|
+
</div>`;
|
|
1004
|
+
}
|
|
1005
|
+
let html = '<ul class="stx-list-view-items">';
|
|
1006
|
+
for (const item of items) {
|
|
1007
|
+
const itemClasses = buildClasses("stx-list-view-item", item.selected && "stx-list-view-item--selected");
|
|
1008
|
+
html += `<li class="${itemClasses}" data-id="${escapeHtml2(item.id)}" ${selectable ? 'tabindex="0"' : ""}>${item.content}</li>`;
|
|
1009
|
+
}
|
|
1010
|
+
html += "</ul>";
|
|
1011
|
+
return `<div id="${id}" class="${classes}" ${styleStr ? `style="${styleStr}"` : ""}>${html}</div>`;
|
|
1012
|
+
}
|
|
1013
|
+
function createTable(props) {
|
|
1014
|
+
const {
|
|
1015
|
+
columns,
|
|
1016
|
+
data,
|
|
1017
|
+
striped = false,
|
|
1018
|
+
bordered = false,
|
|
1019
|
+
hoverable = true,
|
|
1020
|
+
sortBy,
|
|
1021
|
+
sortOrder,
|
|
1022
|
+
id = generateId("table"),
|
|
1023
|
+
className,
|
|
1024
|
+
style
|
|
1025
|
+
} = props;
|
|
1026
|
+
const classes = buildClasses("stx-table", striped && "stx-table--striped", bordered && "stx-table--bordered", hoverable && "stx-table--hoverable", className);
|
|
1027
|
+
const styleStr = buildStyles(style);
|
|
1028
|
+
let html = "<table>";
|
|
1029
|
+
html += "<thead><tr>";
|
|
1030
|
+
for (const col of columns) {
|
|
1031
|
+
const isSorted = col.key === sortBy;
|
|
1032
|
+
const sortClass = isSorted ? `stx-table-sorted stx-table-sorted--${sortOrder}` : "";
|
|
1033
|
+
const sortable = col.sortable ? 'data-sortable="true"' : "";
|
|
1034
|
+
const widthStyle = col.width ? `style="width: ${col.width}"` : "";
|
|
1035
|
+
html += `<th class="${sortClass}" ${sortable} ${widthStyle} data-key="${escapeHtml2(col.key)}">${escapeHtml2(col.label)}${col.sortable ? '<span class="stx-table-sort-icon"></span>' : ""}</th>`;
|
|
1036
|
+
}
|
|
1037
|
+
html += "</tr></thead>";
|
|
1038
|
+
html += "<tbody>";
|
|
1039
|
+
for (const row of data) {
|
|
1040
|
+
html += "<tr>";
|
|
1041
|
+
for (const col of columns) {
|
|
1042
|
+
const cellValue = row[col.key];
|
|
1043
|
+
html += `<td>${cellValue != null ? escapeHtml2(String(cellValue)) : ""}</td>`;
|
|
1044
|
+
}
|
|
1045
|
+
html += "</tr>";
|
|
1046
|
+
}
|
|
1047
|
+
html += "</tbody></table>";
|
|
1048
|
+
return `<div id="${id}" class="${classes}" ${styleStr ? `style="${styleStr}"` : ""}>${html}</div>`;
|
|
1049
|
+
}
|
|
1050
|
+
function renderTreeNode(node, expandedKeys, selectedKeys, checkable) {
|
|
1051
|
+
const isExpanded = expandedKeys.includes(node.key);
|
|
1052
|
+
const isSelected = selectedKeys.includes(node.key);
|
|
1053
|
+
const hasChildren = node.children && node.children.length > 0;
|
|
1054
|
+
const nodeClasses = buildClasses("stx-tree-node", isSelected && "stx-tree-node--selected", node.disabled && "stx-tree-node--disabled");
|
|
1055
|
+
let html = `<div class="${nodeClasses}" data-key="${escapeHtml2(node.key)}">`;
|
|
1056
|
+
html += '<div class="stx-tree-node-content">';
|
|
1057
|
+
if (hasChildren) {
|
|
1058
|
+
html += `<span class="stx-tree-toggle ${isExpanded ? "stx-tree-toggle--expanded" : ""}">\u25B6</span>`;
|
|
1059
|
+
} else {
|
|
1060
|
+
html += '<span class="stx-tree-toggle-placeholder"></span>';
|
|
1061
|
+
}
|
|
1062
|
+
if (checkable) {
|
|
1063
|
+
html += `<input type="checkbox" class="stx-tree-checkbox" ${isSelected ? "checked" : ""} ${node.disabled ? "disabled" : ""} />`;
|
|
1064
|
+
}
|
|
1065
|
+
if (node.icon) {
|
|
1066
|
+
html += `<span class="stx-tree-icon">${node.icon}</span>`;
|
|
1067
|
+
}
|
|
1068
|
+
html += `<span class="stx-tree-label">${escapeHtml2(node.label)}</span>`;
|
|
1069
|
+
html += "</div>";
|
|
1070
|
+
if (hasChildren && isExpanded) {
|
|
1071
|
+
html += '<div class="stx-tree-children">';
|
|
1072
|
+
for (const child of node.children) {
|
|
1073
|
+
html += renderTreeNode(child, expandedKeys, selectedKeys, checkable);
|
|
1074
|
+
}
|
|
1075
|
+
html += "</div>";
|
|
1076
|
+
}
|
|
1077
|
+
html += "</div>";
|
|
1078
|
+
return html;
|
|
1079
|
+
}
|
|
1080
|
+
function createTreeView(props) {
|
|
1081
|
+
const {
|
|
1082
|
+
nodes,
|
|
1083
|
+
expandedKeys = [],
|
|
1084
|
+
selectedKeys = [],
|
|
1085
|
+
checkable = false,
|
|
1086
|
+
id = generateId("tree"),
|
|
1087
|
+
className,
|
|
1088
|
+
style
|
|
1089
|
+
} = props;
|
|
1090
|
+
const classes = buildClasses("stx-tree-view", checkable && "stx-tree-view--checkable", className);
|
|
1091
|
+
const styleStr = buildStyles(style);
|
|
1092
|
+
let html = "";
|
|
1093
|
+
for (const node of nodes) {
|
|
1094
|
+
html += renderTreeNode(node, expandedKeys, selectedKeys, checkable);
|
|
1095
|
+
}
|
|
1096
|
+
return `<div id="${id}" class="${classes}" role="tree" ${styleStr ? `style="${styleStr}"` : ""}>${html}</div>`;
|
|
1097
|
+
}
|
|
1098
|
+
function createDataGrid(props) {
|
|
1099
|
+
const {
|
|
1100
|
+
columns,
|
|
1101
|
+
data,
|
|
1102
|
+
pagination,
|
|
1103
|
+
selectable = false,
|
|
1104
|
+
id = generateId("datagrid"),
|
|
1105
|
+
className,
|
|
1106
|
+
style
|
|
1107
|
+
} = props;
|
|
1108
|
+
const classes = buildClasses("stx-data-grid", selectable && "stx-data-grid--selectable", className);
|
|
1109
|
+
const styleStr = buildStyles(style);
|
|
1110
|
+
let html = '<div class="stx-data-grid-table-wrapper">';
|
|
1111
|
+
html += '<table class="stx-data-grid-table">';
|
|
1112
|
+
html += "<thead><tr>";
|
|
1113
|
+
if (selectable) {
|
|
1114
|
+
html += '<th class="stx-data-grid-select-all"><input type="checkbox" /></th>';
|
|
1115
|
+
}
|
|
1116
|
+
for (const col of columns) {
|
|
1117
|
+
const widthStyle = col.width ? `style="width: ${col.width}"` : "";
|
|
1118
|
+
html += `<th ${widthStyle}>
|
|
1119
|
+
<div class="stx-data-grid-header-cell">
|
|
1120
|
+
<span>${escapeHtml2(col.label)}</span>
|
|
1121
|
+
${col.sortable ? '<button class="stx-data-grid-sort">\u21C5</button>' : ""}
|
|
1122
|
+
${col.filterable ? '<button class="stx-data-grid-filter">\u22BB</button>' : ""}
|
|
1123
|
+
</div>
|
|
1124
|
+
</th>`;
|
|
1125
|
+
}
|
|
1126
|
+
html += "</tr></thead>";
|
|
1127
|
+
html += "<tbody>";
|
|
1128
|
+
for (const row of data) {
|
|
1129
|
+
html += "<tr>";
|
|
1130
|
+
if (selectable) {
|
|
1131
|
+
html += '<td class="stx-data-grid-select"><input type="checkbox" /></td>';
|
|
1132
|
+
}
|
|
1133
|
+
for (const col of columns) {
|
|
1134
|
+
const cellValue = row[col.key];
|
|
1135
|
+
html += `<td>${cellValue != null ? escapeHtml2(String(cellValue)) : ""}</td>`;
|
|
1136
|
+
}
|
|
1137
|
+
html += "</tr>";
|
|
1138
|
+
}
|
|
1139
|
+
html += "</tbody></table></div>";
|
|
1140
|
+
if (pagination) {
|
|
1141
|
+
const totalPages = Math.ceil(pagination.total / pagination.pageSize);
|
|
1142
|
+
html += `<div class="stx-data-grid-pagination">
|
|
1143
|
+
<span class="stx-data-grid-page-info">Page ${pagination.page} of ${totalPages} (${pagination.total} items)</span>
|
|
1144
|
+
<div class="stx-data-grid-page-buttons">
|
|
1145
|
+
<button class="stx-data-grid-page-btn" ${pagination.page <= 1 ? "disabled" : ""}>\u25C0</button>
|
|
1146
|
+
<button class="stx-data-grid-page-btn" ${pagination.page >= totalPages ? "disabled" : ""}>\u25B6</button>
|
|
1147
|
+
</div>
|
|
1148
|
+
</div>`;
|
|
1149
|
+
}
|
|
1150
|
+
return `<div id="${id}" class="${classes}" ${styleStr ? `style="${styleStr}"` : ""}>${html}</div>`;
|
|
1151
|
+
}
|
|
1152
|
+
function createChart(props) {
|
|
1153
|
+
const {
|
|
1154
|
+
type,
|
|
1155
|
+
data,
|
|
1156
|
+
options = {},
|
|
1157
|
+
width = "100%",
|
|
1158
|
+
height = 300,
|
|
1159
|
+
id = generateId("chart"),
|
|
1160
|
+
className,
|
|
1161
|
+
style
|
|
1162
|
+
} = props;
|
|
1163
|
+
const classes = buildClasses("stx-chart", `stx-chart--${type}`, className);
|
|
1164
|
+
const chartStyles = {
|
|
1165
|
+
width: typeof width === "number" ? `${width}px` : width,
|
|
1166
|
+
height: typeof height === "number" ? `${height}px` : height
|
|
1167
|
+
};
|
|
1168
|
+
const combinedStyle = [buildStyles(chartStyles), buildStyles(style)].filter(Boolean).join("; ");
|
|
1169
|
+
const chartData = JSON.stringify({ type, data, options });
|
|
1170
|
+
return `<div id="${id}" class="${classes}" style="${combinedStyle}" data-chart='${escapeHtml2(chartData)}'>
|
|
1171
|
+
<div class="stx-chart-placeholder">
|
|
1172
|
+
${options.title ? `<div class="stx-chart-title">${escapeHtml2(options.title)}</div>` : ""}
|
|
1173
|
+
<div class="stx-chart-type">${type.charAt(0).toUpperCase() + type.slice(1)} Chart</div>
|
|
1174
|
+
<div class="stx-chart-info">${data.datasets.length} dataset(s), ${data.labels.length} labels</div>
|
|
1175
|
+
</div>
|
|
1176
|
+
</div>`;
|
|
1177
|
+
}
|
|
1178
|
+
function createCodeEditor(props) {
|
|
1179
|
+
const {
|
|
1180
|
+
value = "",
|
|
1181
|
+
language = "plaintext",
|
|
1182
|
+
theme = "dark",
|
|
1183
|
+
lineNumbers = true,
|
|
1184
|
+
readOnly = false,
|
|
1185
|
+
height = 300,
|
|
1186
|
+
tabSize = 2,
|
|
1187
|
+
wordWrap = false,
|
|
1188
|
+
id = generateId("code"),
|
|
1189
|
+
className,
|
|
1190
|
+
style
|
|
1191
|
+
} = props;
|
|
1192
|
+
const classes = buildClasses("stx-code-editor", `stx-code-editor--${theme}`, lineNumbers && "stx-code-editor--line-numbers", readOnly && "stx-code-editor--readonly", wordWrap && "stx-code-editor--wrap", className);
|
|
1193
|
+
const editorStyles = {
|
|
1194
|
+
height: typeof height === "number" ? `${height}px` : height
|
|
1195
|
+
};
|
|
1196
|
+
const combinedStyle = [buildStyles(editorStyles), buildStyles(style)].filter(Boolean).join("; ");
|
|
1197
|
+
const lines = value.split(`
|
|
1198
|
+
`);
|
|
1199
|
+
let codeHtml = "";
|
|
1200
|
+
if (lineNumbers) {
|
|
1201
|
+
codeHtml += '<div class="stx-code-editor-gutter">';
|
|
1202
|
+
for (let i = 1;i <= lines.length; i++) {
|
|
1203
|
+
codeHtml += `<div class="stx-code-editor-line-number">${i}</div>`;
|
|
1204
|
+
}
|
|
1205
|
+
codeHtml += "</div>";
|
|
1206
|
+
}
|
|
1207
|
+
codeHtml += `<pre class="stx-code-editor-content"><code class="language-${escapeHtml2(language)}">${escapeHtml2(value)}</code></pre>`;
|
|
1208
|
+
return `<div id="${id}" class="${classes}" style="${combinedStyle}" data-language="${escapeHtml2(language)}" data-tab-size="${tabSize}">${codeHtml}</div>`;
|
|
1209
|
+
}
|
|
1210
|
+
function createMediaPlayer(props) {
|
|
1211
|
+
const {
|
|
1212
|
+
src,
|
|
1213
|
+
type = "video",
|
|
1214
|
+
poster,
|
|
1215
|
+
autoplay = false,
|
|
1216
|
+
loop = false,
|
|
1217
|
+
muted = false,
|
|
1218
|
+
controls = true,
|
|
1219
|
+
width,
|
|
1220
|
+
height,
|
|
1221
|
+
id = generateId("media"),
|
|
1222
|
+
className,
|
|
1223
|
+
style
|
|
1224
|
+
} = props;
|
|
1225
|
+
const classes = buildClasses("stx-media-player", `stx-media-player--${type}`, className);
|
|
1226
|
+
const mediaStyles = {};
|
|
1227
|
+
if (width)
|
|
1228
|
+
mediaStyles.width = typeof width === "number" ? `${width}px` : width;
|
|
1229
|
+
if (height)
|
|
1230
|
+
mediaStyles.height = typeof height === "number" ? `${height}px` : height;
|
|
1231
|
+
const combinedStyle = [buildStyles(mediaStyles), buildStyles(style)].filter(Boolean).join("; ");
|
|
1232
|
+
const mediaAttrs = [
|
|
1233
|
+
`id="${id}-media"`,
|
|
1234
|
+
`src="${escapeHtml2(src)}"`,
|
|
1235
|
+
controls && "controls",
|
|
1236
|
+
autoplay && "autoplay",
|
|
1237
|
+
loop && "loop",
|
|
1238
|
+
muted && "muted",
|
|
1239
|
+
type === "video" && poster && `poster="${escapeHtml2(poster)}"`
|
|
1240
|
+
].filter(Boolean).join(" ");
|
|
1241
|
+
const mediaElement = type === "video" ? `<video ${mediaAttrs}>Your browser does not support the video tag.</video>` : `<audio ${mediaAttrs}>Your browser does not support the audio tag.</audio>`;
|
|
1242
|
+
return `<div id="${id}" class="${classes}" ${combinedStyle ? `style="${combinedStyle}"` : ""}>${mediaElement}</div>`;
|
|
1243
|
+
}
|
|
1244
|
+
function formatFileSize(bytes) {
|
|
1245
|
+
if (bytes === undefined)
|
|
1246
|
+
return "";
|
|
1247
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
1248
|
+
let size = bytes;
|
|
1249
|
+
let unitIndex = 0;
|
|
1250
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
1251
|
+
size /= 1024;
|
|
1252
|
+
unitIndex++;
|
|
1253
|
+
}
|
|
1254
|
+
return `${size.toFixed(1)} ${units[unitIndex]}`;
|
|
1255
|
+
}
|
|
1256
|
+
function createFileExplorer(props) {
|
|
1257
|
+
const {
|
|
1258
|
+
files,
|
|
1259
|
+
viewMode = "list",
|
|
1260
|
+
showHidden = false,
|
|
1261
|
+
selectable = true,
|
|
1262
|
+
id = generateId("files"),
|
|
1263
|
+
className,
|
|
1264
|
+
style
|
|
1265
|
+
} = props;
|
|
1266
|
+
const classes = buildClasses("stx-file-explorer", `stx-file-explorer--${viewMode}`, className);
|
|
1267
|
+
const styleStr = buildStyles(style);
|
|
1268
|
+
const filteredFiles = showHidden ? files : files.filter((f) => !f.name.startsWith("."));
|
|
1269
|
+
let html = "";
|
|
1270
|
+
if (viewMode === "list") {
|
|
1271
|
+
html = '<table class="stx-file-explorer-list"><thead><tr><th>Name</th><th>Size</th><th>Modified</th></tr></thead><tbody>';
|
|
1272
|
+
for (const file of filteredFiles) {
|
|
1273
|
+
const icon = file.icon || (file.type === "folder" ? "\uD83D\uDCC1" : "\uD83D\uDCC4");
|
|
1274
|
+
const size = file.type === "file" ? formatFileSize(file.size) : "";
|
|
1275
|
+
const modified = file.modified ? file.modified.toLocaleDateString() : "";
|
|
1276
|
+
html += `<tr class="stx-file-explorer-item ${selectable ? "stx-file-explorer-item--selectable" : ""}" data-path="${escapeHtml2(file.path)}" data-type="${file.type}">
|
|
1277
|
+
<td><span class="stx-file-explorer-icon">${icon}</span>${escapeHtml2(file.name)}</td>
|
|
1278
|
+
<td>${size}</td>
|
|
1279
|
+
<td>${modified}</td>
|
|
1280
|
+
</tr>`;
|
|
1281
|
+
}
|
|
1282
|
+
html += "</tbody></table>";
|
|
1283
|
+
} else {
|
|
1284
|
+
html = '<div class="stx-file-explorer-grid">';
|
|
1285
|
+
for (const file of filteredFiles) {
|
|
1286
|
+
const icon = file.icon || (file.type === "folder" ? "\uD83D\uDCC1" : "\uD83D\uDCC4");
|
|
1287
|
+
html += `<div class="stx-file-explorer-item ${selectable ? "stx-file-explorer-item--selectable" : ""}" data-path="${escapeHtml2(file.path)}" data-type="${file.type}">
|
|
1288
|
+
<div class="stx-file-explorer-icon">${icon}</div>
|
|
1289
|
+
<div class="stx-file-explorer-name">${escapeHtml2(file.name)}</div>
|
|
1290
|
+
</div>`;
|
|
1291
|
+
}
|
|
1292
|
+
html += "</div>";
|
|
1293
|
+
}
|
|
1294
|
+
return `<div id="${id}" class="${classes}" ${styleStr ? `style="${styleStr}"` : ""}>${html}</div>`;
|
|
1295
|
+
}
|
|
1296
|
+
function createWebView(props) {
|
|
1297
|
+
const {
|
|
1298
|
+
url,
|
|
1299
|
+
width = "100%",
|
|
1300
|
+
height = 400,
|
|
1301
|
+
sandbox = true,
|
|
1302
|
+
allowScripts = true,
|
|
1303
|
+
id = generateId("webview"),
|
|
1304
|
+
className,
|
|
1305
|
+
style
|
|
1306
|
+
} = props;
|
|
1307
|
+
const classes = buildClasses("stx-webview", className);
|
|
1308
|
+
const webviewStyles = {
|
|
1309
|
+
width: typeof width === "number" ? `${width}px` : width,
|
|
1310
|
+
height: typeof height === "number" ? `${height}px` : height
|
|
1311
|
+
};
|
|
1312
|
+
const combinedStyle = [buildStyles(webviewStyles), buildStyles(style)].filter(Boolean).join("; ");
|
|
1313
|
+
const sandboxAttrs = [];
|
|
1314
|
+
if (sandbox) {
|
|
1315
|
+
sandboxAttrs.push("allow-same-origin");
|
|
1316
|
+
if (allowScripts) {
|
|
1317
|
+
sandboxAttrs.push("allow-scripts");
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
const sandboxAttr = sandbox ? `sandbox="${sandboxAttrs.join(" ")}"` : "";
|
|
1321
|
+
return `<iframe id="${id}" class="${classes}" src="${escapeHtml2(url)}" style="${combinedStyle}" ${sandboxAttr} loading="lazy" frameborder="0"></iframe>`;
|
|
1322
|
+
}
|
|
1323
|
+
var AVAILABLE_COMPONENTS = [
|
|
1324
|
+
"Button",
|
|
1325
|
+
"TextInput",
|
|
1326
|
+
"Checkbox",
|
|
1327
|
+
"RadioButton",
|
|
1328
|
+
"Slider",
|
|
1329
|
+
"ColorPicker",
|
|
1330
|
+
"DatePicker",
|
|
1331
|
+
"TimePicker",
|
|
1332
|
+
"Autocomplete",
|
|
1333
|
+
"Label",
|
|
1334
|
+
"ImageView",
|
|
1335
|
+
"ProgressBar",
|
|
1336
|
+
"Avatar",
|
|
1337
|
+
"Badge",
|
|
1338
|
+
"Chip",
|
|
1339
|
+
"Card",
|
|
1340
|
+
"Tooltip",
|
|
1341
|
+
"Toast",
|
|
1342
|
+
"ScrollView",
|
|
1343
|
+
"SplitView",
|
|
1344
|
+
"Accordion",
|
|
1345
|
+
"Stepper",
|
|
1346
|
+
"Modal",
|
|
1347
|
+
"Tabs",
|
|
1348
|
+
"Dropdown",
|
|
1349
|
+
"ListView",
|
|
1350
|
+
"Table",
|
|
1351
|
+
"TreeView",
|
|
1352
|
+
"DataGrid",
|
|
1353
|
+
"Chart",
|
|
1354
|
+
"Rating",
|
|
1355
|
+
"CodeEditor",
|
|
1356
|
+
"MediaPlayer",
|
|
1357
|
+
"FileExplorer",
|
|
1358
|
+
"WebView"
|
|
1359
|
+
];
|
|
1360
|
+
var COMPONENT_STYLES = `
|
|
1361
|
+
/* Button */
|
|
1362
|
+
.stx-button {
|
|
1363
|
+
display: inline-flex;
|
|
1364
|
+
align-items: center;
|
|
1365
|
+
justify-content: center;
|
|
1366
|
+
gap: 8px;
|
|
1367
|
+
padding: 8px 16px;
|
|
1368
|
+
border: none;
|
|
1369
|
+
border-radius: 6px;
|
|
1370
|
+
font-size: 14px;
|
|
1371
|
+
font-weight: 500;
|
|
1372
|
+
cursor: pointer;
|
|
1373
|
+
transition: all 0.15s;
|
|
1374
|
+
}
|
|
1375
|
+
.stx-button--primary { background: #3498db; color: #fff; }
|
|
1376
|
+
.stx-button--primary:hover { background: #2980b9; }
|
|
1377
|
+
.stx-button--secondary { background: #6c757d; color: #fff; }
|
|
1378
|
+
.stx-button--outline { background: transparent; border: 1px solid #3498db; color: #3498db; }
|
|
1379
|
+
.stx-button--ghost { background: transparent; color: #3498db; }
|
|
1380
|
+
.stx-button--destructive { background: #e74c3c; color: #fff; }
|
|
1381
|
+
.stx-button--small { padding: 4px 8px; font-size: 12px; }
|
|
1382
|
+
.stx-button--large { padding: 12px 24px; font-size: 16px; }
|
|
1383
|
+
.stx-button--disabled { opacity: 0.5; cursor: not-allowed; }
|
|
1384
|
+
.stx-button--loading { pointer-events: none; }
|
|
1385
|
+
.stx-button-spinner { width: 16px; height: 16px; border: 2px solid transparent; border-top-color: currentColor; border-radius: 50%; animation: spin 0.6s linear infinite; }
|
|
1386
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
1387
|
+
|
|
1388
|
+
/* Input */
|
|
1389
|
+
.stx-input-wrapper { display: flex; flex-direction: column; gap: 4px; }
|
|
1390
|
+
.stx-input-label { font-size: 14px; font-weight: 500; color: #333; }
|
|
1391
|
+
.stx-input { padding: 8px 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; transition: border-color 0.15s; }
|
|
1392
|
+
.stx-input:focus { outline: none; border-color: #3498db; }
|
|
1393
|
+
.stx-input--error { border-color: #e74c3c; }
|
|
1394
|
+
.stx-input-error { font-size: 12px; color: #e74c3c; }
|
|
1395
|
+
.stx-input-hint { font-size: 12px; color: #666; }
|
|
1396
|
+
|
|
1397
|
+
/* Checkbox */
|
|
1398
|
+
.stx-checkbox { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; }
|
|
1399
|
+
.stx-checkbox input { display: none; }
|
|
1400
|
+
.stx-checkbox-box { width: 18px; height: 18px; border: 2px solid #ddd; border-radius: 4px; transition: all 0.15s; }
|
|
1401
|
+
.stx-checkbox input:checked + .stx-checkbox-box { background: #3498db; border-color: #3498db; }
|
|
1402
|
+
.stx-checkbox--disabled { opacity: 0.5; cursor: not-allowed; }
|
|
1403
|
+
|
|
1404
|
+
/* Progress */
|
|
1405
|
+
.stx-progress { height: 8px; background: #e0e0e0; border-radius: 4px; overflow: hidden; position: relative; }
|
|
1406
|
+
.stx-progress-bar { height: 100%; background: #3498db; transition: width 0.3s; }
|
|
1407
|
+
.stx-progress--success .stx-progress-bar { background: #27ae60; }
|
|
1408
|
+
.stx-progress--warning .stx-progress-bar { background: #f39c12; }
|
|
1409
|
+
.stx-progress--error .stx-progress-bar { background: #e74c3c; }
|
|
1410
|
+
.stx-progress--indeterminate .stx-progress-bar { animation: progress-indeterminate 1.5s infinite; }
|
|
1411
|
+
@keyframes progress-indeterminate { 0% { transform: translateX(-100%); } 100% { transform: translateX(200%); } }
|
|
1412
|
+
|
|
1413
|
+
/* Badge */
|
|
1414
|
+
.stx-badge { display: inline-flex; padding: 2px 8px; border-radius: 12px; font-size: 12px; font-weight: 500; }
|
|
1415
|
+
.stx-badge--default { background: #e0e0e0; color: #333; }
|
|
1416
|
+
.stx-badge--primary { background: #3498db; color: #fff; }
|
|
1417
|
+
.stx-badge--success { background: #27ae60; color: #fff; }
|
|
1418
|
+
.stx-badge--warning { background: #f39c12; color: #fff; }
|
|
1419
|
+
.stx-badge--error { background: #e74c3c; color: #fff; }
|
|
1420
|
+
|
|
1421
|
+
/* Avatar */
|
|
1422
|
+
.stx-avatar { display: inline-flex; align-items: center; justify-content: center; background: #3498db; color: #fff; overflow: hidden; }
|
|
1423
|
+
.stx-avatar--circle { border-radius: 50%; }
|
|
1424
|
+
.stx-avatar--square { border-radius: 0; }
|
|
1425
|
+
.stx-avatar--rounded { border-radius: 8px; }
|
|
1426
|
+
.stx-avatar--small { width: 32px; height: 32px; font-size: 12px; }
|
|
1427
|
+
.stx-avatar--medium { width: 40px; height: 40px; font-size: 14px; }
|
|
1428
|
+
.stx-avatar--large { width: 56px; height: 56px; font-size: 18px; }
|
|
1429
|
+
.stx-avatar-img { width: 100%; height: 100%; object-fit: cover; }
|
|
1430
|
+
|
|
1431
|
+
/* Card */
|
|
1432
|
+
.stx-card { background: #fff; border-radius: 8px; overflow: hidden; }
|
|
1433
|
+
.stx-card--outlined { border: 1px solid #e0e0e0; }
|
|
1434
|
+
.stx-card--elevated { box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
|
|
1435
|
+
.stx-card-image img { width: 100%; display: block; }
|
|
1436
|
+
.stx-card-body { padding: 16px; }
|
|
1437
|
+
.stx-card-title { margin: 0 0 4px; font-size: 18px; }
|
|
1438
|
+
.stx-card-subtitle { margin: 0 0 8px; color: #666; font-size: 14px; }
|
|
1439
|
+
.stx-card-footer { padding: 12px 16px; border-top: 1px solid #e0e0e0; }
|
|
1440
|
+
|
|
1441
|
+
/* Tabs */
|
|
1442
|
+
.stx-tabs { display: flex; gap: 4px; border-bottom: 1px solid #e0e0e0; }
|
|
1443
|
+
.stx-tab { padding: 8px 16px; border: none; background: none; cursor: pointer; color: #666; border-bottom: 2px solid transparent; transition: all 0.15s; }
|
|
1444
|
+
.stx-tab:hover { color: #333; }
|
|
1445
|
+
.stx-tab--active { color: #3498db; border-bottom-color: #3498db; }
|
|
1446
|
+
.stx-tab--disabled { opacity: 0.5; cursor: not-allowed; }
|
|
1447
|
+
.stx-tabs--pills { border-bottom: none; }
|
|
1448
|
+
.stx-tabs--pills .stx-tab { border-radius: 20px; border-bottom: none; }
|
|
1449
|
+
.stx-tabs--pills .stx-tab--active { background: #3498db; color: #fff; }
|
|
1450
|
+
|
|
1451
|
+
/* Dropdown */
|
|
1452
|
+
.stx-dropdown { padding: 8px 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; background: #fff; cursor: pointer; min-width: 150px; }
|
|
1453
|
+
.stx-dropdown:focus { outline: none; border-color: #3498db; }
|
|
1454
|
+
.stx-dropdown--disabled { opacity: 0.5; cursor: not-allowed; }
|
|
1455
|
+
|
|
1456
|
+
/* Rating */
|
|
1457
|
+
.stx-rating { display: inline-flex; gap: 4px; }
|
|
1458
|
+
.stx-star { color: #ddd; cursor: pointer; transition: color 0.15s; }
|
|
1459
|
+
.stx-star--filled { color: #f1c40f; }
|
|
1460
|
+
.stx-star--half { background: linear-gradient(90deg, #f1c40f 50%, #ddd 50%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
|
|
1461
|
+
.stx-rating--readonly .stx-star { cursor: default; }
|
|
1462
|
+
.stx-rating--small .stx-star { font-size: 16px; }
|
|
1463
|
+
.stx-rating--medium .stx-star { font-size: 24px; }
|
|
1464
|
+
.stx-rating--large .stx-star { font-size: 32px; }
|
|
1465
|
+
|
|
1466
|
+
/* Slider */
|
|
1467
|
+
.stx-slider { display: flex; align-items: center; gap: 12px; }
|
|
1468
|
+
.stx-slider-track { flex: 1; }
|
|
1469
|
+
.stx-slider input[type="range"] { width: 100%; }
|
|
1470
|
+
.stx-slider-value { min-width: 40px; text-align: right; font-size: 14px; }
|
|
1471
|
+
|
|
1472
|
+
/* Radio Button */
|
|
1473
|
+
.stx-radio { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; }
|
|
1474
|
+
.stx-radio input { display: none; }
|
|
1475
|
+
.stx-radio-circle { width: 18px; height: 18px; border: 2px solid #ddd; border-radius: 50%; position: relative; transition: all 0.15s; }
|
|
1476
|
+
.stx-radio input:checked + .stx-radio-circle { border-color: #3498db; }
|
|
1477
|
+
.stx-radio input:checked + .stx-radio-circle::after { content: ''; position: absolute; top: 3px; left: 3px; width: 8px; height: 8px; background: #3498db; border-radius: 50%; }
|
|
1478
|
+
.stx-radio--disabled { opacity: 0.5; cursor: not-allowed; }
|
|
1479
|
+
|
|
1480
|
+
/* Color Picker */
|
|
1481
|
+
.stx-color-picker { display: flex; flex-direction: column; gap: 8px; }
|
|
1482
|
+
.stx-color-picker-input { display: flex; align-items: center; gap: 8px; }
|
|
1483
|
+
.stx-color-picker-input input[type="color"] { width: 40px; height: 40px; border: none; cursor: pointer; }
|
|
1484
|
+
.stx-color-picker-value { font-family: monospace; }
|
|
1485
|
+
.stx-color-picker-presets { display: flex; gap: 4px; flex-wrap: wrap; }
|
|
1486
|
+
.stx-color-preset { width: 24px; height: 24px; border: 2px solid transparent; border-radius: 4px; cursor: pointer; }
|
|
1487
|
+
.stx-color-preset:hover { border-color: #333; }
|
|
1488
|
+
|
|
1489
|
+
/* Date/Time Picker */
|
|
1490
|
+
.stx-date-picker, .stx-time-picker { display: flex; flex-direction: column; gap: 4px; }
|
|
1491
|
+
.stx-date-picker-input, .stx-time-picker-input { display: flex; align-items: center; gap: 4px; }
|
|
1492
|
+
.stx-date-picker-input input, .stx-time-picker-input { padding: 8px 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; }
|
|
1493
|
+
.stx-date-picker-clear { background: none; border: none; cursor: pointer; font-size: 16px; }
|
|
1494
|
+
|
|
1495
|
+
/* Autocomplete */
|
|
1496
|
+
.stx-autocomplete { position: relative; }
|
|
1497
|
+
.stx-autocomplete-input-wrapper { position: relative; }
|
|
1498
|
+
.stx-autocomplete-input { width: 100%; padding: 8px 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; }
|
|
1499
|
+
.stx-autocomplete-spinner { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); width: 16px; height: 16px; border: 2px solid #ddd; border-top-color: #3498db; border-radius: 50%; animation: spin 0.6s linear infinite; }
|
|
1500
|
+
.stx-autocomplete-list { position: absolute; top: 100%; left: 0; right: 0; background: #fff; border: 1px solid #ddd; border-radius: 6px; max-height: 200px; overflow-y: auto; display: none; z-index: 10; }
|
|
1501
|
+
.stx-autocomplete:focus-within .stx-autocomplete-list { display: block; }
|
|
1502
|
+
.stx-autocomplete-item { padding: 8px 12px; cursor: pointer; }
|
|
1503
|
+
.stx-autocomplete-item:hover { background: #f5f5f5; }
|
|
1504
|
+
.stx-autocomplete-item--selected { background: #e8f4fc; }
|
|
1505
|
+
|
|
1506
|
+
/* Label */
|
|
1507
|
+
.stx-label { display: inline-block; font-weight: 500; }
|
|
1508
|
+
.stx-label--small { font-size: 12px; }
|
|
1509
|
+
.stx-label--medium { font-size: 14px; }
|
|
1510
|
+
.stx-label--large { font-size: 16px; }
|
|
1511
|
+
.stx-label-required { color: #e74c3c; margin-left: 2px; }
|
|
1512
|
+
|
|
1513
|
+
/* Image */
|
|
1514
|
+
.stx-image { display: block; max-width: 100%; }
|
|
1515
|
+
|
|
1516
|
+
/* Chip */
|
|
1517
|
+
.stx-chip { display: inline-flex; align-items: center; gap: 4px; padding: 4px 12px; border-radius: 16px; font-size: 13px; }
|
|
1518
|
+
.stx-chip--default { background: #e0e0e0; color: #333; }
|
|
1519
|
+
.stx-chip--primary { background: #e8f4fc; color: #3498db; }
|
|
1520
|
+
.stx-chip--success { background: #e8f8f0; color: #27ae60; }
|
|
1521
|
+
.stx-chip--warning { background: #fef5e7; color: #f39c12; }
|
|
1522
|
+
.stx-chip--error { background: #fdebeb; color: #e74c3c; }
|
|
1523
|
+
.stx-chip--small { padding: 2px 8px; font-size: 11px; }
|
|
1524
|
+
.stx-chip--large { padding: 6px 16px; font-size: 15px; }
|
|
1525
|
+
.stx-chip-remove { background: none; border: none; cursor: pointer; opacity: 0.6; padding: 0 2px; }
|
|
1526
|
+
.stx-chip-remove:hover { opacity: 1; }
|
|
1527
|
+
|
|
1528
|
+
/* Tooltip */
|
|
1529
|
+
.stx-tooltip-wrapper { position: relative; display: inline-block; }
|
|
1530
|
+
.stx-tooltip { position: absolute; background: #333; color: #fff; padding: 4px 8px; border-radius: 4px; font-size: 12px; white-space: nowrap; opacity: 0; visibility: hidden; transition: opacity 0.2s, visibility 0.2s; z-index: 1000; }
|
|
1531
|
+
.stx-tooltip-wrapper:hover .stx-tooltip { opacity: 1; visibility: visible; }
|
|
1532
|
+
.stx-tooltip--top { bottom: 100%; left: 50%; transform: translateX(-50%); margin-bottom: 8px; }
|
|
1533
|
+
.stx-tooltip--bottom { top: 100%; left: 50%; transform: translateX(-50%); margin-top: 8px; }
|
|
1534
|
+
.stx-tooltip--left { right: 100%; top: 50%; transform: translateY(-50%); margin-right: 8px; }
|
|
1535
|
+
.stx-tooltip--right { left: 100%; top: 50%; transform: translateY(-50%); margin-left: 8px; }
|
|
1536
|
+
|
|
1537
|
+
/* Scroll View */
|
|
1538
|
+
.stx-scroll-view { overflow: auto; }
|
|
1539
|
+
.stx-scroll-view--vertical { overflow-x: hidden; overflow-y: auto; }
|
|
1540
|
+
.stx-scroll-view--horizontal { overflow-x: auto; overflow-y: hidden; }
|
|
1541
|
+
.stx-scroll-view--hide-scrollbar::-webkit-scrollbar { display: none; }
|
|
1542
|
+
.stx-scroll-view--hide-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
|
|
1543
|
+
.stx-scroll-view--smooth { scroll-behavior: smooth; }
|
|
1544
|
+
|
|
1545
|
+
/* Split View */
|
|
1546
|
+
.stx-split-view { display: flex; }
|
|
1547
|
+
.stx-split-view--horizontal { flex-direction: row; }
|
|
1548
|
+
.stx-split-view--vertical { flex-direction: column; }
|
|
1549
|
+
.stx-split-pane { overflow: auto; }
|
|
1550
|
+
.stx-split-divider { background: #e0e0e0; flex-shrink: 0; }
|
|
1551
|
+
.stx-split-view--horizontal .stx-split-divider { width: 4px; cursor: col-resize; }
|
|
1552
|
+
.stx-split-view--vertical .stx-split-divider { height: 4px; cursor: row-resize; }
|
|
1553
|
+
.stx-split-divider:hover { background: #3498db; }
|
|
1554
|
+
|
|
1555
|
+
/* Accordion */
|
|
1556
|
+
.stx-accordion { border: 1px solid #e0e0e0; border-radius: 8px; overflow: hidden; }
|
|
1557
|
+
.stx-accordion-item { border-bottom: 1px solid #e0e0e0; }
|
|
1558
|
+
.stx-accordion-item:last-child { border-bottom: none; }
|
|
1559
|
+
.stx-accordion-header { width: 100%; display: flex; justify-content: space-between; align-items: center; padding: 12px 16px; background: none; border: none; cursor: pointer; font-size: 14px; }
|
|
1560
|
+
.stx-accordion-header:hover { background: #f5f5f5; }
|
|
1561
|
+
.stx-accordion-icon { transition: transform 0.2s; }
|
|
1562
|
+
.stx-accordion-item--open .stx-accordion-icon { transform: rotate(180deg); }
|
|
1563
|
+
.stx-accordion-content { padding: 0 16px 16px; }
|
|
1564
|
+
.stx-accordion-item--disabled { opacity: 0.5; }
|
|
1565
|
+
.stx-accordion-item--disabled .stx-accordion-header { cursor: not-allowed; }
|
|
1566
|
+
|
|
1567
|
+
/* Stepper */
|
|
1568
|
+
.stx-stepper { display: flex; }
|
|
1569
|
+
.stx-stepper--horizontal { flex-direction: row; align-items: flex-start; }
|
|
1570
|
+
.stx-stepper--vertical { flex-direction: column; }
|
|
1571
|
+
.stx-step { display: flex; align-items: center; }
|
|
1572
|
+
.stx-stepper--vertical .stx-step { flex-direction: row; }
|
|
1573
|
+
.stx-step-indicator { width: 32px; height: 32px; border-radius: 50%; background: #e0e0e0; display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: 500; flex-shrink: 0; }
|
|
1574
|
+
.stx-step--completed .stx-step-indicator { background: #27ae60; color: #fff; }
|
|
1575
|
+
.stx-step--active .stx-step-indicator { background: #3498db; color: #fff; }
|
|
1576
|
+
.stx-step-content { margin-left: 12px; }
|
|
1577
|
+
.stx-step-label { font-size: 14px; font-weight: 500; }
|
|
1578
|
+
.stx-step-description { font-size: 12px; color: #666; }
|
|
1579
|
+
.stx-step-connector { flex: 1; height: 2px; background: #e0e0e0; margin: 0 8px; min-width: 20px; }
|
|
1580
|
+
.stx-stepper--vertical .stx-step-connector { width: 2px; height: 20px; margin: 8px 0 8px 15px; }
|
|
1581
|
+
|
|
1582
|
+
/* Modal Component */
|
|
1583
|
+
.stx-modal-component--hidden { display: none; }
|
|
1584
|
+
.stx-modal-component-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 10000; }
|
|
1585
|
+
.stx-modal-component-dialog { background: #fff; border-radius: 12px; max-height: 90vh; overflow: hidden; display: flex; flex-direction: column; }
|
|
1586
|
+
.stx-modal-component--small .stx-modal-component-dialog { width: 300px; }
|
|
1587
|
+
.stx-modal-component--medium .stx-modal-component-dialog { width: 500px; }
|
|
1588
|
+
.stx-modal-component--large .stx-modal-component-dialog { width: 800px; }
|
|
1589
|
+
.stx-modal-component--fullscreen .stx-modal-component-dialog { width: 100%; height: 100%; border-radius: 0; }
|
|
1590
|
+
.stx-modal-component-header { display: flex; justify-content: space-between; align-items: center; padding: 16px; border-bottom: 1px solid #e0e0e0; }
|
|
1591
|
+
.stx-modal-component-title { margin: 0; font-size: 18px; }
|
|
1592
|
+
.stx-modal-component-close { background: none; border: none; font-size: 24px; cursor: pointer; opacity: 0.6; }
|
|
1593
|
+
.stx-modal-component-close:hover { opacity: 1; }
|
|
1594
|
+
.stx-modal-component-body { padding: 16px; overflow-y: auto; flex: 1; }
|
|
1595
|
+
.stx-modal-component-footer { padding: 16px; border-top: 1px solid #e0e0e0; display: flex; justify-content: flex-end; gap: 8px; }
|
|
1596
|
+
|
|
1597
|
+
/* List View */
|
|
1598
|
+
.stx-list-view { border: 1px solid #e0e0e0; border-radius: 8px; overflow: hidden; }
|
|
1599
|
+
.stx-list-view-items { list-style: none; margin: 0; padding: 0; }
|
|
1600
|
+
.stx-list-view-item { padding: 12px 16px; border-bottom: 1px solid #e0e0e0; }
|
|
1601
|
+
.stx-list-view-item:last-child { border-bottom: none; }
|
|
1602
|
+
.stx-list-view--selectable .stx-list-view-item { cursor: pointer; }
|
|
1603
|
+
.stx-list-view--selectable .stx-list-view-item:hover { background: #f5f5f5; }
|
|
1604
|
+
.stx-list-view-item--selected { background: #e8f4fc; }
|
|
1605
|
+
.stx-list-view-empty { padding: 24px; text-align: center; color: #666; }
|
|
1606
|
+
|
|
1607
|
+
/* Table */
|
|
1608
|
+
.stx-table { overflow-x: auto; }
|
|
1609
|
+
.stx-table table { width: 100%; border-collapse: collapse; }
|
|
1610
|
+
.stx-table th, .stx-table td { padding: 12px; text-align: left; }
|
|
1611
|
+
.stx-table th { font-weight: 600; background: #f5f5f5; }
|
|
1612
|
+
.stx-table--bordered th, .stx-table--bordered td { border: 1px solid #e0e0e0; }
|
|
1613
|
+
.stx-table--striped tbody tr:nth-child(even) { background: #f9f9f9; }
|
|
1614
|
+
.stx-table--hoverable tbody tr:hover { background: #f5f5f5; }
|
|
1615
|
+
.stx-table th[data-sortable] { cursor: pointer; }
|
|
1616
|
+
.stx-table-sort-icon::after { content: '\u21C5'; margin-left: 4px; opacity: 0.3; }
|
|
1617
|
+
.stx-table-sorted .stx-table-sort-icon::after { opacity: 1; }
|
|
1618
|
+
.stx-table-sorted--asc .stx-table-sort-icon::after { content: '\u2191'; }
|
|
1619
|
+
.stx-table-sorted--desc .stx-table-sort-icon::after { content: '\u2193'; }
|
|
1620
|
+
|
|
1621
|
+
/* Tree View */
|
|
1622
|
+
.stx-tree-view { font-size: 14px; }
|
|
1623
|
+
.stx-tree-node { padding-left: 20px; }
|
|
1624
|
+
.stx-tree-node-content { display: flex; align-items: center; gap: 4px; padding: 4px; cursor: pointer; border-radius: 4px; }
|
|
1625
|
+
.stx-tree-node-content:hover { background: #f5f5f5; }
|
|
1626
|
+
.stx-tree-node--selected > .stx-tree-node-content { background: #e8f4fc; }
|
|
1627
|
+
.stx-tree-node--disabled { opacity: 0.5; }
|
|
1628
|
+
.stx-tree-toggle { width: 16px; cursor: pointer; transition: transform 0.2s; }
|
|
1629
|
+
.stx-tree-toggle--expanded { transform: rotate(90deg); }
|
|
1630
|
+
.stx-tree-toggle-placeholder { width: 16px; }
|
|
1631
|
+
.stx-tree-children { padding-left: 8px; }
|
|
1632
|
+
|
|
1633
|
+
/* Data Grid */
|
|
1634
|
+
.stx-data-grid { border: 1px solid #e0e0e0; border-radius: 8px; overflow: hidden; }
|
|
1635
|
+
.stx-data-grid-table-wrapper { overflow-x: auto; }
|
|
1636
|
+
.stx-data-grid-table { width: 100%; border-collapse: collapse; }
|
|
1637
|
+
.stx-data-grid-table th, .stx-data-grid-table td { padding: 12px; text-align: left; border-bottom: 1px solid #e0e0e0; }
|
|
1638
|
+
.stx-data-grid-table th { background: #f5f5f5; }
|
|
1639
|
+
.stx-data-grid-header-cell { display: flex; align-items: center; gap: 8px; }
|
|
1640
|
+
.stx-data-grid-sort, .stx-data-grid-filter { background: none; border: none; cursor: pointer; opacity: 0.5; }
|
|
1641
|
+
.stx-data-grid-sort:hover, .stx-data-grid-filter:hover { opacity: 1; }
|
|
1642
|
+
.stx-data-grid-pagination { display: flex; justify-content: space-between; align-items: center; padding: 12px 16px; border-top: 1px solid #e0e0e0; }
|
|
1643
|
+
.stx-data-grid-page-buttons { display: flex; gap: 4px; }
|
|
1644
|
+
.stx-data-grid-page-btn { padding: 4px 8px; border: 1px solid #e0e0e0; background: #fff; cursor: pointer; border-radius: 4px; }
|
|
1645
|
+
.stx-data-grid-page-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
1646
|
+
|
|
1647
|
+
/* Chart */
|
|
1648
|
+
.stx-chart { background: #f9f9f9; border-radius: 8px; display: flex; align-items: center; justify-content: center; }
|
|
1649
|
+
.stx-chart-placeholder { text-align: center; padding: 20px; }
|
|
1650
|
+
.stx-chart-title { font-size: 16px; font-weight: 600; margin-bottom: 8px; }
|
|
1651
|
+
.stx-chart-type { font-size: 14px; color: #666; }
|
|
1652
|
+
.stx-chart-info { font-size: 12px; color: #999; margin-top: 4px; }
|
|
1653
|
+
|
|
1654
|
+
/* Code Editor */
|
|
1655
|
+
.stx-code-editor { display: flex; font-family: 'Monaco', 'Menlo', monospace; font-size: 13px; line-height: 1.5; overflow: hidden; border-radius: 8px; }
|
|
1656
|
+
.stx-code-editor--dark { background: #1e1e1e; color: #d4d4d4; }
|
|
1657
|
+
.stx-code-editor--light { background: #fff; color: #333; border: 1px solid #e0e0e0; }
|
|
1658
|
+
.stx-code-editor-gutter { padding: 12px 8px; background: rgba(0,0,0,0.1); text-align: right; user-select: none; }
|
|
1659
|
+
.stx-code-editor-line-number { color: #858585; }
|
|
1660
|
+
.stx-code-editor-content { flex: 1; padding: 12px; overflow: auto; }
|
|
1661
|
+
.stx-code-editor-content pre { margin: 0; }
|
|
1662
|
+
.stx-code-editor--wrap .stx-code-editor-content { white-space: pre-wrap; word-wrap: break-word; }
|
|
1663
|
+
|
|
1664
|
+
/* Media Player */
|
|
1665
|
+
.stx-media-player video, .stx-media-player audio { width: 100%; display: block; }
|
|
1666
|
+
.stx-media-player--video { background: #000; }
|
|
1667
|
+
|
|
1668
|
+
/* File Explorer */
|
|
1669
|
+
.stx-file-explorer { border: 1px solid #e0e0e0; border-radius: 8px; overflow: hidden; }
|
|
1670
|
+
.stx-file-explorer-list { width: 100%; border-collapse: collapse; }
|
|
1671
|
+
.stx-file-explorer-list th { text-align: left; padding: 8px 12px; background: #f5f5f5; font-weight: 500; }
|
|
1672
|
+
.stx-file-explorer-list td { padding: 8px 12px; border-top: 1px solid #e0e0e0; }
|
|
1673
|
+
.stx-file-explorer-item--selectable { cursor: pointer; }
|
|
1674
|
+
.stx-file-explorer-item--selectable:hover { background: #f5f5f5; }
|
|
1675
|
+
.stx-file-explorer-icon { margin-right: 8px; }
|
|
1676
|
+
.stx-file-explorer-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); gap: 16px; padding: 16px; }
|
|
1677
|
+
.stx-file-explorer--grid .stx-file-explorer-item { text-align: center; padding: 12px; border-radius: 8px; }
|
|
1678
|
+
.stx-file-explorer--grid .stx-file-explorer-icon { font-size: 32px; display: block; margin-bottom: 8px; }
|
|
1679
|
+
.stx-file-explorer--grid .stx-file-explorer-name { font-size: 12px; word-break: break-all; }
|
|
1680
|
+
|
|
1681
|
+
/* WebView */
|
|
1682
|
+
.stx-webview { border: none; display: block; }
|
|
1683
|
+
|
|
1684
|
+
/* Dark mode support */
|
|
1685
|
+
@media (prefers-color-scheme: dark) {
|
|
1686
|
+
.stx-input-label { color: #eee; }
|
|
1687
|
+
.stx-input { background: #333; border-color: #555; color: #fff; }
|
|
1688
|
+
.stx-card { background: #2d2d2d; color: #fff; }
|
|
1689
|
+
.stx-card-subtitle { color: #aaa; }
|
|
1690
|
+
.stx-progress { background: #444; }
|
|
1691
|
+
.stx-dropdown { background: #333; border-color: #555; color: #fff; }
|
|
1692
|
+
.stx-accordion { border-color: #444; }
|
|
1693
|
+
.stx-accordion-item { border-color: #444; }
|
|
1694
|
+
.stx-accordion-header:hover { background: #333; }
|
|
1695
|
+
.stx-list-view { border-color: #444; }
|
|
1696
|
+
.stx-list-view-item { border-color: #444; }
|
|
1697
|
+
.stx-list-view--selectable .stx-list-view-item:hover { background: #333; }
|
|
1698
|
+
.stx-table th { background: #333; }
|
|
1699
|
+
.stx-table--bordered th, .stx-table--bordered td { border-color: #444; }
|
|
1700
|
+
.stx-tree-node-content:hover { background: #333; }
|
|
1701
|
+
.stx-data-grid { border-color: #444; }
|
|
1702
|
+
.stx-data-grid-table th, .stx-data-grid-table td { border-color: #444; }
|
|
1703
|
+
.stx-data-grid-table th { background: #333; }
|
|
1704
|
+
.stx-modal-component-dialog { background: #2d2d2d; color: #fff; }
|
|
1705
|
+
.stx-modal-component-header, .stx-modal-component-footer { border-color: #444; }
|
|
1706
|
+
.stx-file-explorer { border-color: #444; }
|
|
1707
|
+
.stx-file-explorer-list th { background: #333; }
|
|
1708
|
+
.stx-file-explorer-list td { border-color: #444; }
|
|
1709
|
+
.stx-autocomplete-list { background: #333; border-color: #444; }
|
|
1710
|
+
.stx-autocomplete-item:hover { background: #444; }
|
|
1711
|
+
.stx-chip--default { background: #444; color: #eee; }
|
|
1712
|
+
.stx-chip--primary { background: #1a3a52; }
|
|
1713
|
+
.stx-chip--success { background: #1a3a2a; }
|
|
1714
|
+
.stx-chip--warning { background: #3a2a1a; }
|
|
1715
|
+
.stx-chip--error { background: #3a1a1a; }
|
|
1716
|
+
}
|
|
1717
|
+
`;
|
|
1718
|
+
// src/modals.ts
|
|
1719
|
+
function hasNativeDialogSupport() {
|
|
1720
|
+
return false;
|
|
1721
|
+
}
|
|
1722
|
+
function isBrowser2() {
|
|
1723
|
+
return typeof window !== "undefined" && typeof document !== "undefined";
|
|
1724
|
+
}
|
|
1725
|
+
var activeModals = [];
|
|
1726
|
+
function generateModalId() {
|
|
1727
|
+
return `modal-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
1728
|
+
}
|
|
1729
|
+
function getModalIcon(type) {
|
|
1730
|
+
switch (type) {
|
|
1731
|
+
case "info":
|
|
1732
|
+
return "ℹ";
|
|
1733
|
+
case "warning":
|
|
1734
|
+
return "⚠";
|
|
1735
|
+
case "error":
|
|
1736
|
+
return "✖";
|
|
1737
|
+
case "success":
|
|
1738
|
+
return "✔";
|
|
1739
|
+
case "question":
|
|
1740
|
+
return "❓";
|
|
1741
|
+
default:
|
|
1742
|
+
return "ℹ";
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
function getDefaultButtons(type) {
|
|
1746
|
+
if (type === "question") {
|
|
1747
|
+
return [
|
|
1748
|
+
{ label: "No", style: "default" },
|
|
1749
|
+
{ label: "Yes", style: "primary" }
|
|
1750
|
+
];
|
|
1751
|
+
}
|
|
1752
|
+
return [{ label: "OK", style: "primary" }];
|
|
1753
|
+
}
|
|
1754
|
+
function createModalHTML(state) {
|
|
1755
|
+
const { options } = state;
|
|
1756
|
+
const icon = getModalIcon(options.type);
|
|
1757
|
+
const buttons = options.buttons || getDefaultButtons(options.type);
|
|
1758
|
+
const typeClass = options.type || "info";
|
|
1759
|
+
let buttonsHtml = "";
|
|
1760
|
+
buttons.forEach((btn, index) => {
|
|
1761
|
+
const styleClass = btn.style === "destructive" ? "destructive" : btn.style === "primary" ? "primary" : "default";
|
|
1762
|
+
const autoFocus = index === (options.defaultButton ?? buttons.length - 1) ? "autofocus" : "";
|
|
1763
|
+
buttonsHtml += `<button class="stx-modal-btn ${styleClass}" data-index="${index}" ${autoFocus}>${btn.label}</button>`;
|
|
1764
|
+
});
|
|
1765
|
+
return `
|
|
1766
|
+
<div class="stx-modal-overlay" data-modal-id="${state.id}">
|
|
1767
|
+
<div class="stx-modal ${typeClass}" role="dialog" aria-modal="true" aria-labelledby="${state.id}-title">
|
|
1768
|
+
<div class="stx-modal-icon">${icon}</div>
|
|
1769
|
+
<div class="stx-modal-content">
|
|
1770
|
+
${options.title ? `<h2 id="${state.id}-title" class="stx-modal-title">${escapeHtml3(options.title)}</h2>` : ""}
|
|
1771
|
+
<p class="stx-modal-message">${escapeHtml3(options.message)}</p>
|
|
1772
|
+
</div>
|
|
1773
|
+
<div class="stx-modal-buttons">${buttonsHtml}</div>
|
|
1774
|
+
</div>
|
|
1775
|
+
</div>
|
|
1776
|
+
`;
|
|
1777
|
+
}
|
|
1778
|
+
function escapeHtml3(str) {
|
|
1779
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1780
|
+
}
|
|
1781
|
+
function closeModal(state, buttonIndex, cancelled = false) {
|
|
1782
|
+
const index = activeModals.indexOf(state);
|
|
1783
|
+
if (index > -1) {
|
|
1784
|
+
activeModals.splice(index, 1);
|
|
1785
|
+
}
|
|
1786
|
+
if (state.element && isBrowser2()) {
|
|
1787
|
+
state.element.remove();
|
|
1788
|
+
}
|
|
1789
|
+
const buttons = state.options.buttons || getDefaultButtons(state.options.type);
|
|
1790
|
+
const button = buttons[buttonIndex];
|
|
1791
|
+
if (button?.action) {
|
|
1792
|
+
button.action();
|
|
1793
|
+
}
|
|
1794
|
+
state.resolve({ buttonIndex, cancelled });
|
|
1795
|
+
}
|
|
1796
|
+
async function showModal(options) {
|
|
1797
|
+
const hasNative = hasNativeDialogSupport();
|
|
1798
|
+
const id = generateModalId();
|
|
1799
|
+
return new Promise((resolve) => {
|
|
1800
|
+
const state = {
|
|
1801
|
+
id,
|
|
1802
|
+
options,
|
|
1803
|
+
resolve
|
|
1804
|
+
};
|
|
1805
|
+
activeModals.push(state);
|
|
1806
|
+
if (hasNative) {
|
|
1807
|
+
console.log(`[stx-modal] Showing native modal: ${options.title || "Modal"}`);
|
|
1808
|
+
setTimeout(() => {
|
|
1809
|
+
closeModal(state, options.defaultButton ?? 0, false);
|
|
1810
|
+
}, 0);
|
|
1811
|
+
} else if (isBrowser2()) {
|
|
1812
|
+
try {
|
|
1813
|
+
const container = document.createElement("div");
|
|
1814
|
+
container.innerHTML = createModalHTML(state);
|
|
1815
|
+
const overlay = container.firstElementChild;
|
|
1816
|
+
if (!overlay) {
|
|
1817
|
+
console.log(`[stx-modal] ${options.type?.toUpperCase() || "INFO"}: ${options.title || "Modal"}`);
|
|
1818
|
+
console.log(`[stx-modal] ${options.message}`);
|
|
1819
|
+
setTimeout(() => {
|
|
1820
|
+
closeModal(state, options.defaultButton ?? 0, false);
|
|
1821
|
+
}, 0);
|
|
1822
|
+
return;
|
|
1823
|
+
}
|
|
1824
|
+
state.element = overlay;
|
|
1825
|
+
try {
|
|
1826
|
+
document.body.appendChild(overlay);
|
|
1827
|
+
} catch {
|
|
1828
|
+
console.log(`[stx-modal] ${options.type?.toUpperCase() || "INFO"}: ${options.title || "Modal"}`);
|
|
1829
|
+
console.log(`[stx-modal] ${options.message}`);
|
|
1830
|
+
setTimeout(() => {
|
|
1831
|
+
closeModal(state, options.defaultButton ?? 0, false);
|
|
1832
|
+
}, 0);
|
|
1833
|
+
return;
|
|
1834
|
+
}
|
|
1835
|
+
try {
|
|
1836
|
+
overlay.querySelectorAll(".stx-modal-btn").forEach((btn) => {
|
|
1837
|
+
btn.addEventListener("click", () => {
|
|
1838
|
+
const index = Number.parseInt(btn.dataset.index || "0", 10);
|
|
1839
|
+
closeModal(state, index, false);
|
|
1840
|
+
});
|
|
1841
|
+
});
|
|
1842
|
+
} catch {}
|
|
1843
|
+
try {
|
|
1844
|
+
overlay.addEventListener("click", (e) => {
|
|
1845
|
+
if (e.target === overlay) {
|
|
1846
|
+
const cancelIndex = options.cancelButton ?? 0;
|
|
1847
|
+
closeModal(state, cancelIndex, true);
|
|
1848
|
+
}
|
|
1849
|
+
});
|
|
1850
|
+
} catch {}
|
|
1851
|
+
try {
|
|
1852
|
+
const handleKeydown = (e) => {
|
|
1853
|
+
if (e.key === "Escape") {
|
|
1854
|
+
const cancelIndex = options.cancelButton ?? 0;
|
|
1855
|
+
closeModal(state, cancelIndex, true);
|
|
1856
|
+
document.removeEventListener("keydown", handleKeydown);
|
|
1857
|
+
} else if (e.key === "Enter") {
|
|
1858
|
+
const defaultIndex = options.defaultButton ?? (options.buttons || getDefaultButtons(options.type)).length - 1;
|
|
1859
|
+
closeModal(state, defaultIndex, false);
|
|
1860
|
+
document.removeEventListener("keydown", handleKeydown);
|
|
1861
|
+
}
|
|
1862
|
+
};
|
|
1863
|
+
document.addEventListener("keydown", handleKeydown);
|
|
1864
|
+
} catch {}
|
|
1865
|
+
try {
|
|
1866
|
+
const firstButton = overlay.querySelector(".stx-modal-btn[autofocus]");
|
|
1867
|
+
if (firstButton) {
|
|
1868
|
+
firstButton.focus();
|
|
1869
|
+
}
|
|
1870
|
+
} catch {}
|
|
1871
|
+
} catch {
|
|
1872
|
+
console.log(`[stx-modal] ${options.type?.toUpperCase() || "INFO"}: ${options.title || "Modal"}`);
|
|
1873
|
+
console.log(`[stx-modal] ${options.message}`);
|
|
1874
|
+
setTimeout(() => {
|
|
1875
|
+
closeModal(state, options.defaultButton ?? 0, false);
|
|
1876
|
+
}, 0);
|
|
1877
|
+
}
|
|
1878
|
+
} else {
|
|
1879
|
+
console.log(`[stx-modal] ${options.type?.toUpperCase() || "INFO"}: ${options.title || "Modal"}`);
|
|
1880
|
+
console.log(`[stx-modal] ${options.message}`);
|
|
1881
|
+
const buttons = options.buttons || getDefaultButtons(options.type);
|
|
1882
|
+
console.log(`[stx-modal] Buttons: ${buttons.map((b) => b.label).join(", ")}`);
|
|
1883
|
+
setTimeout(() => {
|
|
1884
|
+
closeModal(state, options.defaultButton ?? 0, false);
|
|
1885
|
+
}, 0);
|
|
1886
|
+
}
|
|
1887
|
+
});
|
|
1888
|
+
}
|
|
1889
|
+
async function showInfoModal(title, message) {
|
|
1890
|
+
return showModal({ title, message, type: "info" });
|
|
1891
|
+
}
|
|
1892
|
+
async function showWarningModal(title, message) {
|
|
1893
|
+
return showModal({ title, message, type: "warning" });
|
|
1894
|
+
}
|
|
1895
|
+
async function showErrorModal(title, message) {
|
|
1896
|
+
return showModal({ title, message, type: "error" });
|
|
1897
|
+
}
|
|
1898
|
+
async function showSuccessModal(title, message) {
|
|
1899
|
+
return showModal({ title, message, type: "success" });
|
|
1900
|
+
}
|
|
1901
|
+
async function showQuestionModal(title, message) {
|
|
1902
|
+
return showModal({
|
|
1903
|
+
title,
|
|
1904
|
+
message,
|
|
1905
|
+
type: "question",
|
|
1906
|
+
buttons: [
|
|
1907
|
+
{ label: "No", style: "default" },
|
|
1908
|
+
{ label: "Yes", style: "primary" }
|
|
1909
|
+
],
|
|
1910
|
+
defaultButton: 1,
|
|
1911
|
+
cancelButton: 0
|
|
1912
|
+
});
|
|
1913
|
+
}
|
|
1914
|
+
async function confirm2(message, title = "Confirm") {
|
|
1915
|
+
const result = await showQuestionModal(title, message);
|
|
1916
|
+
return result.buttonIndex === 1;
|
|
1917
|
+
}
|
|
1918
|
+
async function alert2(message, title = "Alert") {
|
|
1919
|
+
await showInfoModal(title, message);
|
|
1920
|
+
}
|
|
1921
|
+
async function prompt2(message, defaultValue = "", title = "Input") {
|
|
1922
|
+
if (isBrowser2() && typeof window.prompt === "function") {
|
|
1923
|
+
return window.prompt(message, defaultValue);
|
|
1924
|
+
}
|
|
1925
|
+
console.log(`[stx-modal] PROMPT: ${title}`);
|
|
1926
|
+
console.log(`[stx-modal] ${message}`);
|
|
1927
|
+
console.log(`[stx-modal] Default: ${defaultValue}`);
|
|
1928
|
+
return defaultValue;
|
|
1929
|
+
}
|
|
1930
|
+
function getActiveModalCount() {
|
|
1931
|
+
return activeModals.length;
|
|
1932
|
+
}
|
|
1933
|
+
function closeAllModals() {
|
|
1934
|
+
while (activeModals.length > 0) {
|
|
1935
|
+
const state = activeModals[activeModals.length - 1];
|
|
1936
|
+
closeModal(state, 0, true);
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
var MODAL_STYLES = `
|
|
1940
|
+
.stx-modal-overlay {
|
|
1941
|
+
position: fixed;
|
|
1942
|
+
inset: 0;
|
|
1943
|
+
background: rgba(0, 0, 0, 0.5);
|
|
1944
|
+
display: flex;
|
|
1945
|
+
align-items: center;
|
|
1946
|
+
justify-content: center;
|
|
1947
|
+
z-index: 10000;
|
|
1948
|
+
animation: stx-modal-fade-in 0.15s ease-out;
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
@keyframes stx-modal-fade-in {
|
|
1952
|
+
from { opacity: 0; }
|
|
1953
|
+
to { opacity: 1; }
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
.stx-modal {
|
|
1957
|
+
background: #fff;
|
|
1958
|
+
border-radius: 12px;
|
|
1959
|
+
padding: 24px;
|
|
1960
|
+
max-width: 400px;
|
|
1961
|
+
width: 90%;
|
|
1962
|
+
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
|
|
1963
|
+
animation: stx-modal-slide-up 0.2s ease-out;
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
@keyframes stx-modal-slide-up {
|
|
1967
|
+
from { transform: translateY(20px); opacity: 0; }
|
|
1968
|
+
to { transform: translateY(0); opacity: 1; }
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
@media (prefers-color-scheme: dark) {
|
|
1972
|
+
.stx-modal {
|
|
1973
|
+
background: #2d2d2d;
|
|
1974
|
+
color: #fff;
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
.stx-modal-icon {
|
|
1979
|
+
font-size: 48px;
|
|
1980
|
+
text-align: center;
|
|
1981
|
+
margin-bottom: 16px;
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
.stx-modal.info .stx-modal-icon { color: #3498db; }
|
|
1985
|
+
.stx-modal.warning .stx-modal-icon { color: #f39c12; }
|
|
1986
|
+
.stx-modal.error .stx-modal-icon { color: #e74c3c; }
|
|
1987
|
+
.stx-modal.success .stx-modal-icon { color: #27ae60; }
|
|
1988
|
+
.stx-modal.question .stx-modal-icon { color: #9b59b6; }
|
|
1989
|
+
|
|
1990
|
+
.stx-modal-content {
|
|
1991
|
+
text-align: center;
|
|
1992
|
+
margin-bottom: 24px;
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
.stx-modal-title {
|
|
1996
|
+
margin: 0 0 8px;
|
|
1997
|
+
font-size: 20px;
|
|
1998
|
+
font-weight: 600;
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
.stx-modal-message {
|
|
2002
|
+
margin: 0;
|
|
2003
|
+
color: #666;
|
|
2004
|
+
line-height: 1.5;
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
@media (prefers-color-scheme: dark) {
|
|
2008
|
+
.stx-modal-message { color: #aaa; }
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
.stx-modal-buttons {
|
|
2012
|
+
display: flex;
|
|
2013
|
+
gap: 8px;
|
|
2014
|
+
justify-content: center;
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
.stx-modal-btn {
|
|
2018
|
+
padding: 10px 24px;
|
|
2019
|
+
border-radius: 6px;
|
|
2020
|
+
font-size: 14px;
|
|
2021
|
+
font-weight: 500;
|
|
2022
|
+
cursor: pointer;
|
|
2023
|
+
border: none;
|
|
2024
|
+
transition: background 0.15s, transform 0.1s;
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
.stx-modal-btn:hover {
|
|
2028
|
+
transform: translateY(-1px);
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
.stx-modal-btn:active {
|
|
2032
|
+
transform: translateY(0);
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
.stx-modal-btn.default {
|
|
2036
|
+
background: #e0e0e0;
|
|
2037
|
+
color: #333;
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
.stx-modal-btn.default:hover {
|
|
2041
|
+
background: #d0d0d0;
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
.stx-modal-btn.primary {
|
|
2045
|
+
background: #3498db;
|
|
2046
|
+
color: #fff;
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
.stx-modal-btn.primary:hover {
|
|
2050
|
+
background: #2980b9;
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
.stx-modal-btn.destructive {
|
|
2054
|
+
background: #e74c3c;
|
|
2055
|
+
color: #fff;
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
.stx-modal-btn.destructive:hover {
|
|
2059
|
+
background: #c0392b;
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
@media (prefers-color-scheme: dark) {
|
|
2063
|
+
.stx-modal-btn.default {
|
|
2064
|
+
background: #444;
|
|
2065
|
+
color: #fff;
|
|
2066
|
+
}
|
|
2067
|
+
.stx-modal-btn.default:hover {
|
|
2068
|
+
background: #555;
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
`;
|
|
2072
|
+
// src/system-tray.ts
|
|
2073
|
+
import process from "process";
|
|
2074
|
+
function getPlatform() {
|
|
2075
|
+
if (process.platform) {
|
|
2076
|
+
return process.platform;
|
|
2077
|
+
}
|
|
2078
|
+
return "unknown";
|
|
2079
|
+
}
|
|
2080
|
+
function isInCraftWindow() {
|
|
2081
|
+
if (typeof window !== "undefined" && window.craft?.tray) {
|
|
2082
|
+
return true;
|
|
2083
|
+
}
|
|
2084
|
+
return false;
|
|
2085
|
+
}
|
|
2086
|
+
function convertMenuItem(item, index) {
|
|
2087
|
+
if (item.type === "separator") {
|
|
2088
|
+
return {
|
|
2089
|
+
id: `sep-${index}`,
|
|
2090
|
+
label: "",
|
|
2091
|
+
type: "separator"
|
|
2092
|
+
};
|
|
2093
|
+
}
|
|
2094
|
+
const menuItem = {
|
|
2095
|
+
id: `item-${index}-${item.label.replace(/\s+/g, "-").toLowerCase()}`,
|
|
2096
|
+
label: item.label,
|
|
2097
|
+
type: item.type || "normal",
|
|
2098
|
+
enabled: item.enabled !== false
|
|
2099
|
+
};
|
|
2100
|
+
if (item.accelerator) {
|
|
2101
|
+
menuItem.shortcut = item.accelerator;
|
|
2102
|
+
}
|
|
2103
|
+
if (item.type === "checkbox") {
|
|
2104
|
+
menuItem.checked = item.checked || false;
|
|
2105
|
+
}
|
|
2106
|
+
if (item.type === "submenu" && item.submenu) {
|
|
2107
|
+
menuItem.submenu = item.submenu.map((subItem, subIndex) => convertMenuItem(subItem, subIndex));
|
|
2108
|
+
}
|
|
2109
|
+
return menuItem;
|
|
2110
|
+
}
|
|
2111
|
+
function convertMenuItems(items) {
|
|
2112
|
+
return items.map((item, index) => convertMenuItem(item, index));
|
|
2113
|
+
}
|
|
2114
|
+
function renderMenuItem(item, level = 0) {
|
|
2115
|
+
if (item.type === "separator") {
|
|
2116
|
+
return '<hr class="stx-tray-separator" />';
|
|
2117
|
+
}
|
|
2118
|
+
const indent = " ".repeat(level);
|
|
2119
|
+
const disabled = item.enabled === false ? "disabled" : "";
|
|
2120
|
+
const checked = item.checked ? "\u2713 " : "";
|
|
2121
|
+
const accelerator = item.accelerator ? `<span class="accelerator">${item.accelerator}</span>` : "";
|
|
2122
|
+
let html = `${indent}<div class="stx-tray-item ${disabled}" data-action="${item.label}">`;
|
|
2123
|
+
html += `<span class="label">${checked}${item.label}</span>${accelerator}`;
|
|
2124
|
+
if (item.type === "submenu" && item.submenu) {
|
|
2125
|
+
html += '<span class="arrow">\u25B8</span>';
|
|
2126
|
+
html += '<div class="stx-tray-submenu">';
|
|
2127
|
+
for (const subItem of item.submenu) {
|
|
2128
|
+
html += renderMenuItem(subItem, level + 1);
|
|
2129
|
+
}
|
|
2130
|
+
html += "</div>";
|
|
2131
|
+
}
|
|
2132
|
+
html += "</div>";
|
|
2133
|
+
return html;
|
|
2134
|
+
}
|
|
2135
|
+
function renderTrayMenu(menu) {
|
|
2136
|
+
let html = '<div class="stx-tray-menu">';
|
|
2137
|
+
for (const item of menu) {
|
|
2138
|
+
html += renderMenuItem(item);
|
|
2139
|
+
}
|
|
2140
|
+
html += "</div>";
|
|
2141
|
+
return html;
|
|
2142
|
+
}
|
|
2143
|
+
var activeTrayInstances = new Map;
|
|
2144
|
+
function generateTrayId() {
|
|
2145
|
+
return `tray-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
2146
|
+
}
|
|
2147
|
+
var craftTrayListenerSetup = false;
|
|
2148
|
+
function setupCraftTrayListener() {
|
|
2149
|
+
if (craftTrayListenerSetup || typeof window === "undefined")
|
|
2150
|
+
return;
|
|
2151
|
+
window.addEventListener("craft:tray:menu", (event) => {
|
|
2152
|
+
const { action } = event.detail;
|
|
2153
|
+
for (const instance of activeTrayInstances.values()) {
|
|
2154
|
+
const handler = instance.handlers.get(action);
|
|
2155
|
+
if (handler) {
|
|
2156
|
+
handler();
|
|
2157
|
+
break;
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
});
|
|
2161
|
+
craftTrayListenerSetup = true;
|
|
2162
|
+
}
|
|
2163
|
+
async function createSystemTray(options = {}) {
|
|
2164
|
+
const id = generateTrayId();
|
|
2165
|
+
const platform = getPlatform();
|
|
2166
|
+
const inCraft = isInCraftWindow();
|
|
2167
|
+
const state = {
|
|
2168
|
+
icon: options.icon || "",
|
|
2169
|
+
tooltip: options.tooltip || "stx Application",
|
|
2170
|
+
menu: options.menu || [],
|
|
2171
|
+
visible: true
|
|
2172
|
+
};
|
|
2173
|
+
const handlers = new Map;
|
|
2174
|
+
for (const item of options.menu || []) {
|
|
2175
|
+
if (item.type !== "separator" && item.onClick) {
|
|
2176
|
+
handlers.set(item.label, item.onClick);
|
|
2177
|
+
}
|
|
2178
|
+
if (item.type === "submenu" && item.submenu) {
|
|
2179
|
+
for (const subItem of item.submenu) {
|
|
2180
|
+
if (subItem.type !== "separator" && subItem.onClick) {
|
|
2181
|
+
handlers.set(subItem.label, subItem.onClick);
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
activeTrayInstances.set(id, { state, handlers });
|
|
2187
|
+
if (inCraft) {
|
|
2188
|
+
setupCraftTrayListener();
|
|
2189
|
+
const craftWindow = window;
|
|
2190
|
+
try {
|
|
2191
|
+
if (state.tooltip) {
|
|
2192
|
+
await craftWindow.craft.tray.setTitle({ title: state.tooltip });
|
|
2193
|
+
await craftWindow.craft.tray.setTooltip({ tooltip: state.tooltip });
|
|
2194
|
+
}
|
|
2195
|
+
if (state.icon) {
|
|
2196
|
+
await craftWindow.craft.tray.setIcon({ icon: state.icon });
|
|
2197
|
+
}
|
|
2198
|
+
const craftMenu = convertMenuItems(state.menu);
|
|
2199
|
+
await craftWindow.craft.tray.setMenu({ items: craftMenu });
|
|
2200
|
+
console.log(`[stx-tray] Native tray created for ${platform}`);
|
|
2201
|
+
} catch (error) {
|
|
2202
|
+
console.warn("[stx-tray] Failed to create native tray:", error);
|
|
2203
|
+
}
|
|
2204
|
+
} else {
|
|
2205
|
+
console.log(`[stx-tray] Created simulated tray (not in Craft window)`);
|
|
2206
|
+
console.log(`[stx-tray] ID: ${id}`);
|
|
2207
|
+
console.log(`[stx-tray] Tooltip: ${state.tooltip}`);
|
|
2208
|
+
console.log(`[stx-tray] Menu items: ${state.menu.length}`);
|
|
2209
|
+
}
|
|
2210
|
+
const instance = {
|
|
2211
|
+
id,
|
|
2212
|
+
setIcon: (icon) => {
|
|
2213
|
+
state.icon = icon;
|
|
2214
|
+
if (inCraft) {
|
|
2215
|
+
const craftWindow = window;
|
|
2216
|
+
craftWindow.craft.tray.setIcon({ icon }).catch((e) => {
|
|
2217
|
+
console.warn("[stx-tray] Failed to set icon:", e);
|
|
2218
|
+
});
|
|
2219
|
+
}
|
|
2220
|
+
console.log(`[stx-tray:${id}] Icon updated`);
|
|
2221
|
+
},
|
|
2222
|
+
setTooltip: (tooltip) => {
|
|
2223
|
+
state.tooltip = tooltip;
|
|
2224
|
+
if (inCraft) {
|
|
2225
|
+
const craftWindow = window;
|
|
2226
|
+
craftWindow.craft.tray.setTooltip({ tooltip }).catch((e) => {
|
|
2227
|
+
console.warn("[stx-tray] Failed to set tooltip:", e);
|
|
2228
|
+
});
|
|
2229
|
+
}
|
|
2230
|
+
console.log(`[stx-tray:${id}] Tooltip: ${tooltip}`);
|
|
2231
|
+
},
|
|
2232
|
+
setMenu: (menu) => {
|
|
2233
|
+
state.menu = menu;
|
|
2234
|
+
handlers.clear();
|
|
2235
|
+
for (const item of menu) {
|
|
2236
|
+
if (item.type !== "separator" && item.onClick) {
|
|
2237
|
+
handlers.set(item.label, item.onClick);
|
|
2238
|
+
}
|
|
2239
|
+
if (item.type === "submenu" && item.submenu) {
|
|
2240
|
+
for (const subItem of item.submenu) {
|
|
2241
|
+
if (subItem.type !== "separator" && subItem.onClick) {
|
|
2242
|
+
handlers.set(subItem.label, subItem.onClick);
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
if (inCraft) {
|
|
2248
|
+
const craftWindow = window;
|
|
2249
|
+
const craftMenu = convertMenuItems(menu);
|
|
2250
|
+
craftWindow.craft.tray.setMenu({ items: craftMenu }).catch((e) => {
|
|
2251
|
+
console.warn("[stx-tray] Failed to set menu:", e);
|
|
2252
|
+
});
|
|
2253
|
+
}
|
|
2254
|
+
console.log(`[stx-tray:${id}] Menu updated (${menu.length} items)`);
|
|
2255
|
+
},
|
|
2256
|
+
destroy: () => {
|
|
2257
|
+
state.visible = false;
|
|
2258
|
+
activeTrayInstances.delete(id);
|
|
2259
|
+
if (inCraft) {
|
|
2260
|
+
const craftWindow = window;
|
|
2261
|
+
craftWindow.craft.tray.destroy?.().catch((e) => {
|
|
2262
|
+
console.warn("[stx-tray] Failed to destroy tray:", e);
|
|
2263
|
+
});
|
|
2264
|
+
}
|
|
2265
|
+
console.log(`[stx-tray:${id}] Destroyed`);
|
|
2266
|
+
}
|
|
2267
|
+
};
|
|
2268
|
+
return instance;
|
|
2269
|
+
}
|
|
2270
|
+
var createMenubar = createSystemTray;
|
|
2271
|
+
function getActiveTrayInstances() {
|
|
2272
|
+
return Array.from(activeTrayInstances.keys());
|
|
2273
|
+
}
|
|
2274
|
+
function getTrayInstance(id) {
|
|
2275
|
+
const instance = activeTrayInstances.get(id);
|
|
2276
|
+
if (!instance)
|
|
2277
|
+
return null;
|
|
2278
|
+
const inCraft = isInCraftWindow();
|
|
2279
|
+
return {
|
|
2280
|
+
id,
|
|
2281
|
+
setIcon: (icon) => {
|
|
2282
|
+
instance.state.icon = icon;
|
|
2283
|
+
if (inCraft) {
|
|
2284
|
+
const craftWindow = window;
|
|
2285
|
+
craftWindow.craft.tray.setIcon({ icon }).catch(() => {});
|
|
2286
|
+
}
|
|
2287
|
+
},
|
|
2288
|
+
setTooltip: (tooltip) => {
|
|
2289
|
+
instance.state.tooltip = tooltip;
|
|
2290
|
+
if (inCraft) {
|
|
2291
|
+
const craftWindow = window;
|
|
2292
|
+
craftWindow.craft.tray.setTooltip({ tooltip }).catch(() => {});
|
|
2293
|
+
}
|
|
2294
|
+
},
|
|
2295
|
+
setMenu: (menu) => {
|
|
2296
|
+
instance.state.menu = menu;
|
|
2297
|
+
if (inCraft) {
|
|
2298
|
+
const craftWindow = window;
|
|
2299
|
+
const craftMenu = convertMenuItems(menu);
|
|
2300
|
+
craftWindow.craft.tray.setMenu({ items: craftMenu }).catch(() => {});
|
|
2301
|
+
}
|
|
2302
|
+
},
|
|
2303
|
+
destroy: () => {
|
|
2304
|
+
activeTrayInstances.delete(id);
|
|
2305
|
+
if (inCraft) {
|
|
2306
|
+
const craftWindow = window;
|
|
2307
|
+
craftWindow.craft.tray.destroy?.().catch(() => {});
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
};
|
|
2311
|
+
}
|
|
2312
|
+
function triggerTrayAction(trayId, actionLabel) {
|
|
2313
|
+
const instance = activeTrayInstances.get(trayId);
|
|
2314
|
+
if (!instance)
|
|
2315
|
+
return false;
|
|
2316
|
+
const handler = instance.handlers.get(actionLabel);
|
|
2317
|
+
if (handler) {
|
|
2318
|
+
handler();
|
|
2319
|
+
return true;
|
|
2320
|
+
}
|
|
2321
|
+
return false;
|
|
2322
|
+
}
|
|
2323
|
+
function getSimulatedTrayHTML(trayId) {
|
|
2324
|
+
const instance = activeTrayInstances.get(trayId);
|
|
2325
|
+
if (!instance)
|
|
2326
|
+
return null;
|
|
2327
|
+
return renderTrayMenu(instance.state.menu);
|
|
2328
|
+
}
|
|
2329
|
+
function getTrayBridgeScript() {
|
|
2330
|
+
return `
|
|
2331
|
+
// STX Desktop Tray Bridge
|
|
2332
|
+
// Provides convenient wrappers around window.craft.tray APIs
|
|
2333
|
+
window.stxTray = {
|
|
2334
|
+
setTitle: (title) => window.craft?.tray?.setTitle({ title }),
|
|
2335
|
+
setTooltip: (tooltip) => window.craft?.tray?.setTooltip({ tooltip }),
|
|
2336
|
+
setIcon: (icon) => window.craft?.tray?.setIcon({ icon }),
|
|
2337
|
+
setMenu: (items) => window.craft?.tray?.setMenu({ items }),
|
|
2338
|
+
destroy: () => window.craft?.tray?.destroy(),
|
|
2339
|
+
|
|
2340
|
+
// Check if tray is available
|
|
2341
|
+
isAvailable: () => typeof window.craft?.tray !== 'undefined',
|
|
2342
|
+
};
|
|
2343
|
+
`;
|
|
2344
|
+
}
|
|
2345
|
+
var TRAY_MENU_STYLES = `
|
|
2346
|
+
.stx-tray-menu {
|
|
2347
|
+
background: #2d2d2d;
|
|
2348
|
+
border-radius: 8px;
|
|
2349
|
+
padding: 4px 0;
|
|
2350
|
+
min-width: 200px;
|
|
2351
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
2352
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
2353
|
+
font-size: 13px;
|
|
2354
|
+
color: #fff;
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
.stx-tray-item {
|
|
2358
|
+
padding: 6px 12px;
|
|
2359
|
+
display: flex;
|
|
2360
|
+
align-items: center;
|
|
2361
|
+
justify-content: space-between;
|
|
2362
|
+
cursor: pointer;
|
|
2363
|
+
position: relative;
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2366
|
+
.stx-tray-item:hover {
|
|
2367
|
+
background: #3d3d3d;
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
.stx-tray-item.disabled {
|
|
2371
|
+
opacity: 0.5;
|
|
2372
|
+
cursor: not-allowed;
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
.stx-tray-item .label {
|
|
2376
|
+
flex: 1;
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
.stx-tray-item .accelerator {
|
|
2380
|
+
margin-left: 20px;
|
|
2381
|
+
opacity: 0.6;
|
|
2382
|
+
font-size: 12px;
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
.stx-tray-item .arrow {
|
|
2386
|
+
margin-left: 8px;
|
|
2387
|
+
font-size: 10px;
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
.stx-tray-separator {
|
|
2391
|
+
margin: 4px 0;
|
|
2392
|
+
border: none;
|
|
2393
|
+
border-top: 1px solid #444;
|
|
2394
|
+
}
|
|
2395
|
+
|
|
2396
|
+
.stx-tray-submenu {
|
|
2397
|
+
position: absolute;
|
|
2398
|
+
left: 100%;
|
|
2399
|
+
top: 0;
|
|
2400
|
+
background: #2d2d2d;
|
|
2401
|
+
border-radius: 8px;
|
|
2402
|
+
padding: 4px 0;
|
|
2403
|
+
min-width: 150px;
|
|
2404
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
2405
|
+
display: none;
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
.stx-tray-item:hover > .stx-tray-submenu {
|
|
2409
|
+
display: block;
|
|
2410
|
+
}
|
|
2411
|
+
`;
|
|
2412
|
+
// src/dialogs.ts
|
|
2413
|
+
function isInCraftWindow2() {
|
|
2414
|
+
if (typeof window !== "undefined" && window.craft?.dialog) {
|
|
2415
|
+
return true;
|
|
2416
|
+
}
|
|
2417
|
+
return false;
|
|
2418
|
+
}
|
|
2419
|
+
async function showOpenDialog(options = {}) {
|
|
2420
|
+
if (isInCraftWindow2()) {
|
|
2421
|
+
const craftWindow = window;
|
|
2422
|
+
try {
|
|
2423
|
+
return await craftWindow.craft.dialog.showOpenDialog(options);
|
|
2424
|
+
} catch (error) {
|
|
2425
|
+
console.warn("[stx-dialog] Failed to show native open dialog:", error);
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
return new Promise((resolve) => {
|
|
2429
|
+
if (typeof document === "undefined") {
|
|
2430
|
+
resolve({ canceled: true, filePaths: [] });
|
|
2431
|
+
return;
|
|
2432
|
+
}
|
|
2433
|
+
const input = document.createElement("input");
|
|
2434
|
+
input.type = "file";
|
|
2435
|
+
input.multiple = options.multiSelections ?? false;
|
|
2436
|
+
if (options.filters?.length) {
|
|
2437
|
+
const extensions = options.filters.flatMap((f) => f.extensions.map((e) => `.${e}`));
|
|
2438
|
+
input.accept = extensions.join(",");
|
|
2439
|
+
}
|
|
2440
|
+
if (options.canChooseDirectories && !options.canChooseFiles) {
|
|
2441
|
+
input.webkitdirectory = true;
|
|
2442
|
+
}
|
|
2443
|
+
input.onchange = () => {
|
|
2444
|
+
const files = Array.from(input.files || []);
|
|
2445
|
+
if (files.length === 0) {
|
|
2446
|
+
resolve({ canceled: true, filePaths: [] });
|
|
2447
|
+
} else {
|
|
2448
|
+
const filePaths = files.map((f) => f.name);
|
|
2449
|
+
resolve({ canceled: false, filePaths });
|
|
2450
|
+
}
|
|
2451
|
+
};
|
|
2452
|
+
input.oncancel = () => {
|
|
2453
|
+
resolve({ canceled: true, filePaths: [] });
|
|
2454
|
+
};
|
|
2455
|
+
input.click();
|
|
2456
|
+
});
|
|
2457
|
+
}
|
|
2458
|
+
async function showSaveDialog(options = {}) {
|
|
2459
|
+
if (isInCraftWindow2()) {
|
|
2460
|
+
const craftWindow = window;
|
|
2461
|
+
try {
|
|
2462
|
+
return await craftWindow.craft.dialog.showSaveDialog(options);
|
|
2463
|
+
} catch (error) {
|
|
2464
|
+
console.warn("[stx-dialog] Failed to show native save dialog:", error);
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
if (typeof window !== "undefined" && "showSaveFilePicker" in window) {
|
|
2468
|
+
try {
|
|
2469
|
+
const fileTypes = options.filters?.map((f) => ({
|
|
2470
|
+
description: f.name,
|
|
2471
|
+
accept: {
|
|
2472
|
+
"*/*": f.extensions.map((e) => `.${e}`)
|
|
2473
|
+
}
|
|
2474
|
+
}));
|
|
2475
|
+
const handle = await window.showSaveFilePicker({
|
|
2476
|
+
suggestedName: options.defaultPath,
|
|
2477
|
+
types: fileTypes
|
|
2478
|
+
});
|
|
2479
|
+
return { canceled: false, filePath: handle.name };
|
|
2480
|
+
} catch (error) {
|
|
2481
|
+
return { canceled: true };
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
console.warn("[stx-dialog] Save dialog not available, using prompt fallback");
|
|
2485
|
+
const filename = prompt("Enter filename:", options.defaultPath || "file.txt");
|
|
2486
|
+
if (filename) {
|
|
2487
|
+
return { canceled: false, filePath: filename };
|
|
2488
|
+
}
|
|
2489
|
+
return { canceled: true };
|
|
2490
|
+
}
|
|
2491
|
+
async function showMessageBox(options) {
|
|
2492
|
+
if (isInCraftWindow2()) {
|
|
2493
|
+
const craftWindow = window;
|
|
2494
|
+
try {
|
|
2495
|
+
return await craftWindow.craft.dialog.showMessageBox(options);
|
|
2496
|
+
} catch (error) {
|
|
2497
|
+
console.warn("[stx-dialog] Failed to show native message box:", error);
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
const buttons = options.buttons || ["OK"];
|
|
2501
|
+
if (buttons.length === 1) {
|
|
2502
|
+
alert(options.message);
|
|
2503
|
+
return { response: 0 };
|
|
2504
|
+
}
|
|
2505
|
+
if (buttons.length === 2 && options.type === "question") {
|
|
2506
|
+
const confirmed2 = confirm(options.message);
|
|
2507
|
+
return { response: confirmed2 ? 1 : 0 };
|
|
2508
|
+
}
|
|
2509
|
+
console.warn("[stx-dialog] Complex message box not fully supported in browser, using confirm");
|
|
2510
|
+
const confirmed = confirm(`${options.message}
|
|
2511
|
+
|
|
2512
|
+
${buttons.join(" / ")}`);
|
|
2513
|
+
return { response: confirmed ? buttons.length - 1 : 0 };
|
|
2514
|
+
}
|
|
2515
|
+
async function showColorPicker(options = {}) {
|
|
2516
|
+
if (isInCraftWindow2()) {
|
|
2517
|
+
const craftWindow = window;
|
|
2518
|
+
try {
|
|
2519
|
+
return await craftWindow.craft.dialog.showColorPicker(options);
|
|
2520
|
+
} catch (error) {
|
|
2521
|
+
console.warn("[stx-dialog] Failed to show native color picker:", error);
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
return new Promise((resolve) => {
|
|
2525
|
+
if (typeof document === "undefined") {
|
|
2526
|
+
resolve({ canceled: true });
|
|
2527
|
+
return;
|
|
2528
|
+
}
|
|
2529
|
+
const input = document.createElement("input");
|
|
2530
|
+
input.type = "color";
|
|
2531
|
+
input.value = options.color || "#000000";
|
|
2532
|
+
input.onchange = () => {
|
|
2533
|
+
resolve({ canceled: false, color: input.value });
|
|
2534
|
+
};
|
|
2535
|
+
input.oncancel = () => {
|
|
2536
|
+
resolve({ canceled: true });
|
|
2537
|
+
};
|
|
2538
|
+
input.click();
|
|
2539
|
+
});
|
|
2540
|
+
}
|
|
2541
|
+
async function showAlertDialog(message, title) {
|
|
2542
|
+
await showMessageBox({
|
|
2543
|
+
type: "info",
|
|
2544
|
+
title: title || "Alert",
|
|
2545
|
+
message,
|
|
2546
|
+
buttons: ["OK"]
|
|
2547
|
+
});
|
|
2548
|
+
}
|
|
2549
|
+
async function showConfirmDialog(message, title) {
|
|
2550
|
+
const result = await showMessageBox({
|
|
2551
|
+
type: "question",
|
|
2552
|
+
title: title || "Confirm",
|
|
2553
|
+
message,
|
|
2554
|
+
buttons: ["Cancel", "OK"],
|
|
2555
|
+
defaultButton: 1,
|
|
2556
|
+
cancelButton: 0
|
|
2557
|
+
});
|
|
2558
|
+
return result.response === 1;
|
|
2559
|
+
}
|
|
2560
|
+
async function showErrorDialog(message, title) {
|
|
2561
|
+
await showMessageBox({
|
|
2562
|
+
type: "error",
|
|
2563
|
+
title: title || "Error",
|
|
2564
|
+
message,
|
|
2565
|
+
buttons: ["OK"]
|
|
2566
|
+
});
|
|
2567
|
+
}
|
|
2568
|
+
async function showWarningDialog(message, title) {
|
|
2569
|
+
await showMessageBox({
|
|
2570
|
+
type: "warning",
|
|
2571
|
+
title: title || "Warning",
|
|
2572
|
+
message,
|
|
2573
|
+
buttons: ["OK"]
|
|
2574
|
+
});
|
|
2575
|
+
}
|
|
2576
|
+
function getDialogBridgeScript() {
|
|
2577
|
+
return `
|
|
2578
|
+
// STX Desktop Dialog Bridge
|
|
2579
|
+
// Provides convenient wrappers around window.craft.dialog APIs
|
|
2580
|
+
window.stxDialog = {
|
|
2581
|
+
// File dialogs
|
|
2582
|
+
showOpenDialog: (options) => window.craft?.dialog?.showOpenDialog(options),
|
|
2583
|
+
showSaveDialog: (options) => window.craft?.dialog?.showSaveDialog(options),
|
|
2584
|
+
|
|
2585
|
+
// Message dialogs
|
|
2586
|
+
showMessageBox: (options) => window.craft?.dialog?.showMessageBox(options),
|
|
2587
|
+
|
|
2588
|
+
// Color picker
|
|
2589
|
+
showColorPicker: (options) => window.craft?.dialog?.showColorPicker(options),
|
|
2590
|
+
|
|
2591
|
+
// Font picker
|
|
2592
|
+
showFontPicker: (options) => window.craft?.dialog?.showFontPicker(options),
|
|
2593
|
+
|
|
2594
|
+
// Convenience functions
|
|
2595
|
+
alert: async (message, title) => {
|
|
2596
|
+
return window.craft?.dialog?.showMessageBox({
|
|
2597
|
+
type: 'info',
|
|
2598
|
+
title: title || 'Alert',
|
|
2599
|
+
message,
|
|
2600
|
+
buttons: ['OK'],
|
|
2601
|
+
});
|
|
2602
|
+
},
|
|
2603
|
+
|
|
2604
|
+
confirm: async (message, title) => {
|
|
2605
|
+
const result = await window.craft?.dialog?.showMessageBox({
|
|
2606
|
+
type: 'question',
|
|
2607
|
+
title: title || 'Confirm',
|
|
2608
|
+
message,
|
|
2609
|
+
buttons: ['Cancel', 'OK'],
|
|
2610
|
+
});
|
|
2611
|
+
return result?.response === 1;
|
|
2612
|
+
},
|
|
2613
|
+
|
|
2614
|
+
error: async (message, title) => {
|
|
2615
|
+
return window.craft?.dialog?.showMessageBox({
|
|
2616
|
+
type: 'error',
|
|
2617
|
+
title: title || 'Error',
|
|
2618
|
+
message,
|
|
2619
|
+
buttons: ['OK'],
|
|
2620
|
+
});
|
|
2621
|
+
},
|
|
2622
|
+
|
|
2623
|
+
// Check if dialog is available
|
|
2624
|
+
isAvailable: () => typeof window.craft?.dialog !== 'undefined',
|
|
2625
|
+
};
|
|
2626
|
+
`;
|
|
2627
|
+
}
|
|
2628
|
+
// src/window.ts
|
|
2629
|
+
import { existsSync } from "fs";
|
|
2630
|
+
import { join } from "path";
|
|
2631
|
+
import process2 from "process";
|
|
2632
|
+
var currentConfig = {};
|
|
2633
|
+
function setDesktopConfig(config) {
|
|
2634
|
+
currentConfig = { ...currentConfig, ...config };
|
|
2635
|
+
}
|
|
2636
|
+
function getDesktopConfig() {
|
|
2637
|
+
return { ...currentConfig };
|
|
2638
|
+
}
|
|
2639
|
+
function resetDesktopConfig() {
|
|
2640
|
+
currentConfig = {};
|
|
2641
|
+
}
|
|
2642
|
+
var DEFAULT_SEARCH_PATHS = [
|
|
2643
|
+
join(process2.env.HOME || "", ".bun/bin/craft"),
|
|
2644
|
+
join(process2.env.HOME || "", "Code/Tools/craft/packages/zig/zig-out/bin/craft"),
|
|
2645
|
+
join(process2.env.HOME || "", "Code/craft/packages/zig/zig-out/bin/craft"),
|
|
2646
|
+
join(process2.cwd(), "../../craft/packages/zig/zig-out/bin/craft"),
|
|
2647
|
+
join(process2.cwd(), "../../craft/packages/zig/zig-out/bin/craft-minimal"),
|
|
2648
|
+
join(process2.cwd(), "../../../craft/packages/zig/zig-out/bin/craft"),
|
|
2649
|
+
join(process2.cwd(), "../../../craft/packages/zig/zig-out/bin/craft-minimal"),
|
|
2650
|
+
join(process2.cwd(), "../craft/packages/zig/zig-out/bin/craft"),
|
|
2651
|
+
join(process2.cwd(), "../craft/packages/zig/zig-out/bin/craft-minimal"),
|
|
2652
|
+
join(process2.cwd(), "node_modules/.bin/craft")
|
|
2653
|
+
];
|
|
2654
|
+
function getCraftBinaryPath() {
|
|
2655
|
+
const envPath = process2.env.CRAFT_BINARY_PATH;
|
|
2656
|
+
if (envPath && existsSync(envPath)) {
|
|
2657
|
+
return envPath;
|
|
2658
|
+
}
|
|
2659
|
+
if (currentConfig.craftBinaryPath && existsSync(currentConfig.craftBinaryPath)) {
|
|
2660
|
+
return currentConfig.craftBinaryPath;
|
|
2661
|
+
}
|
|
2662
|
+
const additionalPaths = currentConfig.additionalSearchPaths || [];
|
|
2663
|
+
for (const searchPath of additionalPaths) {
|
|
2664
|
+
if (existsSync(searchPath)) {
|
|
2665
|
+
return searchPath;
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
for (const searchPath of DEFAULT_SEARCH_PATHS) {
|
|
2669
|
+
if (existsSync(searchPath)) {
|
|
2670
|
+
return searchPath;
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
return;
|
|
2674
|
+
}
|
|
2675
|
+
var activeWindows = new Map;
|
|
2676
|
+
function getActiveWindowIds() {
|
|
2677
|
+
return Array.from(activeWindows.keys());
|
|
2678
|
+
}
|
|
2679
|
+
function getWindow(id) {
|
|
2680
|
+
const windowData = activeWindows.get(id);
|
|
2681
|
+
if (!windowData)
|
|
2682
|
+
return null;
|
|
2683
|
+
return createWindowInstance(id, windowData.app);
|
|
2684
|
+
}
|
|
2685
|
+
function closeAllWindows() {
|
|
2686
|
+
for (const [id, windowData] of activeWindows) {
|
|
2687
|
+
try {
|
|
2688
|
+
windowData.app?.close?.();
|
|
2689
|
+
} catch (e) {
|
|
2690
|
+
console.warn(`Failed to close window ${id}:`, e);
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
activeWindows.clear();
|
|
2694
|
+
}
|
|
2695
|
+
function createWindowInstance(id, app) {
|
|
2696
|
+
return {
|
|
2697
|
+
id,
|
|
2698
|
+
show: () => {
|
|
2699
|
+
console.log(`[stx/desktop] Window ${id} shown`);
|
|
2700
|
+
},
|
|
2701
|
+
hide: () => {
|
|
2702
|
+
console.log(`[stx/desktop] To hide window, use window.craft.window.hide() from inside the webview`);
|
|
2703
|
+
},
|
|
2704
|
+
close: () => {
|
|
2705
|
+
const windowData = activeWindows.get(id);
|
|
2706
|
+
if (windowData?.app) {
|
|
2707
|
+
windowData.app.close();
|
|
2708
|
+
activeWindows.delete(id);
|
|
2709
|
+
console.log(`[stx/desktop] Window ${id} closed`);
|
|
2710
|
+
}
|
|
2711
|
+
},
|
|
2712
|
+
focus: () => {
|
|
2713
|
+
console.log(`[stx/desktop] To focus window, use window.craft.window.focus() from inside the webview`);
|
|
2714
|
+
},
|
|
2715
|
+
minimize: () => {
|
|
2716
|
+
console.log(`[stx/desktop] To minimize window, use window.craft.window.minimize() from inside the webview`);
|
|
2717
|
+
},
|
|
2718
|
+
maximize: () => {
|
|
2719
|
+
console.log(`[stx/desktop] To maximize window, use window.craft.window.maximize() from inside the webview`);
|
|
2720
|
+
},
|
|
2721
|
+
restore: () => {
|
|
2722
|
+
console.log(`[stx/desktop] To restore window, use window.craft.window.show() from inside the webview`);
|
|
2723
|
+
},
|
|
2724
|
+
setTitle: (title) => {
|
|
2725
|
+
console.log(`[stx/desktop] To set title, use window.craft.window.setTitle({ title: "${title}" }) from inside the webview`);
|
|
2726
|
+
},
|
|
2727
|
+
loadURL: (url) => {
|
|
2728
|
+
console.log(`[stx/desktop] To navigate, use window.location.href = "${url}" from inside the webview`);
|
|
2729
|
+
},
|
|
2730
|
+
reload: () => {
|
|
2731
|
+
console.log(`[stx/desktop] To reload, use window.craft.window.reload() from inside the webview`);
|
|
2732
|
+
}
|
|
2733
|
+
};
|
|
2734
|
+
}
|
|
2735
|
+
async function createWindow(url, options = {}) {
|
|
2736
|
+
const {
|
|
2737
|
+
title = "stx Desktop",
|
|
2738
|
+
width = 1200,
|
|
2739
|
+
height = 800,
|
|
2740
|
+
darkMode = false,
|
|
2741
|
+
hotReload = false,
|
|
2742
|
+
resizable = true,
|
|
2743
|
+
frameless = false,
|
|
2744
|
+
alwaysOnTop = false
|
|
2745
|
+
} = options;
|
|
2746
|
+
try {
|
|
2747
|
+
const { createApp } = await import("ts-craft");
|
|
2748
|
+
const craftPath = getCraftBinaryPath();
|
|
2749
|
+
const app = createApp({
|
|
2750
|
+
url,
|
|
2751
|
+
craftPath,
|
|
2752
|
+
window: {
|
|
2753
|
+
title,
|
|
2754
|
+
width,
|
|
2755
|
+
height,
|
|
2756
|
+
darkMode,
|
|
2757
|
+
hotReload,
|
|
2758
|
+
resizable,
|
|
2759
|
+
frameless,
|
|
2760
|
+
alwaysOnTop
|
|
2761
|
+
}
|
|
2762
|
+
});
|
|
2763
|
+
const id = `craft-window-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
2764
|
+
activeWindows.set(id, { app, url, options });
|
|
2765
|
+
await app.show();
|
|
2766
|
+
return createWindowInstance(id, app);
|
|
2767
|
+
} catch (error) {
|
|
2768
|
+
console.error("Failed to create native window:", error);
|
|
2769
|
+
console.warn("Falling back to browser...");
|
|
2770
|
+
return null;
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
async function openDevWindow(port, options = {}) {
|
|
2774
|
+
const url = `http://localhost:${port}/`;
|
|
2775
|
+
try {
|
|
2776
|
+
const { createApp } = await import("ts-craft");
|
|
2777
|
+
console.log("\u26A1 Opening native window...");
|
|
2778
|
+
const craftPath = getCraftBinaryPath();
|
|
2779
|
+
const useSystemTray = !options.nativeSidebar;
|
|
2780
|
+
const app = createApp({
|
|
2781
|
+
url,
|
|
2782
|
+
craftPath,
|
|
2783
|
+
window: {
|
|
2784
|
+
title: options.title || "stx Development",
|
|
2785
|
+
width: options.width || 1400,
|
|
2786
|
+
height: options.height || 900,
|
|
2787
|
+
resizable: true,
|
|
2788
|
+
systemTray: useSystemTray,
|
|
2789
|
+
darkMode: options.darkMode ?? true,
|
|
2790
|
+
hotReload: options.hotReload ?? true,
|
|
2791
|
+
devTools: true,
|
|
2792
|
+
nativeSidebar: options.nativeSidebar ?? false,
|
|
2793
|
+
sidebarWidth: options.sidebarWidth ?? 260,
|
|
2794
|
+
sidebarConfig: options.sidebarConfig
|
|
2795
|
+
}
|
|
2796
|
+
});
|
|
2797
|
+
const id = `dev-window-${port}`;
|
|
2798
|
+
activeWindows.set(id, { app, url, options });
|
|
2799
|
+
await app.show();
|
|
2800
|
+
console.log(`\u2713 Native window opened at ${url}`);
|
|
2801
|
+
console.log(`\uD83D\uDCCC Look for the "stx Development" icon in your menubar`);
|
|
2802
|
+
return true;
|
|
2803
|
+
} catch (error) {
|
|
2804
|
+
console.warn("\u26A0 Could not open native window:", error.message);
|
|
2805
|
+
if (process2.env.NODE_ENV === "test" || process2.env.BUN_TEST) {
|
|
2806
|
+
console.log("(Skipping browser fallback in test environment)");
|
|
2807
|
+
return false;
|
|
2808
|
+
}
|
|
2809
|
+
console.log("\uD83D\uDCF1 Opening in browser instead...");
|
|
2810
|
+
try {
|
|
2811
|
+
const { spawn } = await import("child_process");
|
|
2812
|
+
const platform = process2.platform;
|
|
2813
|
+
let command;
|
|
2814
|
+
let args;
|
|
2815
|
+
if (platform === "darwin") {
|
|
2816
|
+
command = "open";
|
|
2817
|
+
args = [url];
|
|
2818
|
+
} else if (platform === "win32") {
|
|
2819
|
+
command = "cmd";
|
|
2820
|
+
args = ["/c", "start", url];
|
|
2821
|
+
} else {
|
|
2822
|
+
command = "xdg-open";
|
|
2823
|
+
args = [url];
|
|
2824
|
+
}
|
|
2825
|
+
spawn(command, args, { detached: true, stdio: "ignore" }).unref();
|
|
2826
|
+
console.log(`\u2713 Browser opened at ${url}`);
|
|
2827
|
+
return true;
|
|
2828
|
+
} catch (browserError) {
|
|
2829
|
+
console.error("Could not open browser:", browserError);
|
|
2830
|
+
console.log(`Please open manually: ${url}`);
|
|
2831
|
+
return false;
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
async function createWindowWithHTML(html, options = {}) {
|
|
2836
|
+
const {
|
|
2837
|
+
title = "stx Desktop",
|
|
2838
|
+
width = 1200,
|
|
2839
|
+
height = 800,
|
|
2840
|
+
darkMode = false,
|
|
2841
|
+
hotReload = false,
|
|
2842
|
+
resizable = true,
|
|
2843
|
+
frameless = false,
|
|
2844
|
+
alwaysOnTop = false
|
|
2845
|
+
} = options;
|
|
2846
|
+
try {
|
|
2847
|
+
const { createApp } = await import("ts-craft");
|
|
2848
|
+
const craftPath = getCraftBinaryPath();
|
|
2849
|
+
const app = createApp({
|
|
2850
|
+
html,
|
|
2851
|
+
craftPath,
|
|
2852
|
+
window: {
|
|
2853
|
+
title,
|
|
2854
|
+
width,
|
|
2855
|
+
height,
|
|
2856
|
+
darkMode,
|
|
2857
|
+
hotReload,
|
|
2858
|
+
resizable,
|
|
2859
|
+
frameless,
|
|
2860
|
+
alwaysOnTop
|
|
2861
|
+
}
|
|
2862
|
+
});
|
|
2863
|
+
const id = `craft-window-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
2864
|
+
activeWindows.set(id, { app, url: "html-content", options });
|
|
2865
|
+
await app.show();
|
|
2866
|
+
return createWindowInstance(id, app);
|
|
2867
|
+
} catch (error) {
|
|
2868
|
+
console.error("Failed to create window with HTML:", error);
|
|
2869
|
+
return null;
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
function isWebviewAvailable() {
|
|
2873
|
+
try {
|
|
2874
|
+
__require.resolve("ts-craft");
|
|
2875
|
+
return true;
|
|
2876
|
+
} catch {
|
|
2877
|
+
return false;
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
function getWindowBridgeScript() {
|
|
2881
|
+
return `
|
|
2882
|
+
// STX Desktop Window Bridge
|
|
2883
|
+
// Provides convenient wrappers around window.craft APIs
|
|
2884
|
+
window.stxWindow = {
|
|
2885
|
+
// Window control
|
|
2886
|
+
hide: () => window.craft?.window?.hide(),
|
|
2887
|
+
show: () => window.craft?.window?.show(),
|
|
2888
|
+
toggle: () => window.craft?.window?.toggle(),
|
|
2889
|
+
close: () => window.craft?.window?.close(),
|
|
2890
|
+
minimize: () => window.craft?.window?.minimize(),
|
|
2891
|
+
maximize: () => window.craft?.window?.maximize(),
|
|
2892
|
+
focus: () => window.craft?.window?.focus(),
|
|
2893
|
+
center: () => window.craft?.window?.center(),
|
|
2894
|
+
reload: () => window.craft?.window?.reload(),
|
|
2895
|
+
toggleFullscreen: () => window.craft?.window?.toggleFullscreen(),
|
|
2896
|
+
|
|
2897
|
+
// Window properties
|
|
2898
|
+
setTitle: (title) => window.craft?.window?.setTitle({ title }),
|
|
2899
|
+
setSize: (width, height) => window.craft?.window?.setSize({ width, height }),
|
|
2900
|
+
setPosition: (x, y) => window.craft?.window?.setPosition({ x, y }),
|
|
2901
|
+
setAlwaysOnTop: (alwaysOnTop) => window.craft?.window?.setAlwaysOnTop({ alwaysOnTop }),
|
|
2902
|
+
setResizable: (resizable) => window.craft?.window?.setResizable({ resizable }),
|
|
2903
|
+
setOpacity: (opacity) => window.craft?.window?.setOpacity({ opacity }),
|
|
2904
|
+
|
|
2905
|
+
// macOS-specific
|
|
2906
|
+
setVibrancy: (vibrancy) => window.craft?.window?.setVibrancy({ vibrancy }),
|
|
2907
|
+
|
|
2908
|
+
// App control
|
|
2909
|
+
quit: () => window.craft?.app?.quit(),
|
|
2910
|
+
isDarkMode: () => window.craft?.app?.isDarkMode(),
|
|
2911
|
+
getLocale: () => window.craft?.app?.getLocale(),
|
|
2912
|
+
|
|
2913
|
+
// Notifications
|
|
2914
|
+
notify: (options) => window.craft?.app?.notify(options),
|
|
2915
|
+
|
|
2916
|
+
// Check if running in Craft
|
|
2917
|
+
isCraftAvailable: () => typeof window.craft !== 'undefined',
|
|
2918
|
+
};
|
|
2919
|
+
|
|
2920
|
+
// Expose as global for backwards compatibility
|
|
2921
|
+
window.desktop = window.stxWindow;
|
|
2922
|
+
`;
|
|
2923
|
+
}
|
|
2924
|
+
export {
|
|
2925
|
+
triggerTrayAction,
|
|
2926
|
+
showWarningToast,
|
|
2927
|
+
showWarningModal,
|
|
2928
|
+
showWarningDialog,
|
|
2929
|
+
showToast,
|
|
2930
|
+
showSuccessToast,
|
|
2931
|
+
showSuccessModal,
|
|
2932
|
+
showSaveDialog,
|
|
2933
|
+
showQuestionModal,
|
|
2934
|
+
showOpenDialog,
|
|
2935
|
+
showModal,
|
|
2936
|
+
showMessageBox,
|
|
2937
|
+
showInfoToast,
|
|
2938
|
+
showInfoModal,
|
|
2939
|
+
showErrorToast,
|
|
2940
|
+
showErrorModal,
|
|
2941
|
+
showErrorDialog,
|
|
2942
|
+
showConfirmDialog,
|
|
2943
|
+
showColorPicker,
|
|
2944
|
+
showAlertDialog,
|
|
2945
|
+
showAlert,
|
|
2946
|
+
setDesktopConfig,
|
|
2947
|
+
resetDesktopConfig,
|
|
2948
|
+
requestNotificationPermission,
|
|
2949
|
+
prompt2 as prompt,
|
|
2950
|
+
openDevWindow,
|
|
2951
|
+
notify,
|
|
2952
|
+
isWebviewAvailable,
|
|
2953
|
+
getWindowBridgeScript,
|
|
2954
|
+
getWindow,
|
|
2955
|
+
getTrayInstance,
|
|
2956
|
+
getTrayBridgeScript,
|
|
2957
|
+
getSimulatedTrayHTML,
|
|
2958
|
+
getDialogBridgeScript,
|
|
2959
|
+
getDesktopConfig,
|
|
2960
|
+
getActiveWindowIds,
|
|
2961
|
+
getActiveTrayInstances,
|
|
2962
|
+
getActiveModalCount,
|
|
2963
|
+
getActiveAlertCount,
|
|
2964
|
+
dismissAllAlerts,
|
|
2965
|
+
dismissAlertById,
|
|
2966
|
+
createWindowWithHTML,
|
|
2967
|
+
createWindow,
|
|
2968
|
+
createWebView,
|
|
2969
|
+
createTreeView,
|
|
2970
|
+
createTooltip,
|
|
2971
|
+
createTimePicker,
|
|
2972
|
+
createTextInput,
|
|
2973
|
+
createTabs,
|
|
2974
|
+
createTable,
|
|
2975
|
+
createSystemTray,
|
|
2976
|
+
createStepper,
|
|
2977
|
+
createSplitView,
|
|
2978
|
+
createSlider,
|
|
2979
|
+
createScrollView,
|
|
2980
|
+
createRating,
|
|
2981
|
+
createRadioButton,
|
|
2982
|
+
createProgressBar,
|
|
2983
|
+
createModalComponent,
|
|
2984
|
+
createMenubar,
|
|
2985
|
+
createMediaPlayer,
|
|
2986
|
+
createListView,
|
|
2987
|
+
createLabel,
|
|
2988
|
+
createImageView,
|
|
2989
|
+
createFileExplorer,
|
|
2990
|
+
createDropdown,
|
|
2991
|
+
createDatePicker,
|
|
2992
|
+
createDataGrid,
|
|
2993
|
+
createColorPicker,
|
|
2994
|
+
createCodeEditor,
|
|
2995
|
+
createChip,
|
|
2996
|
+
createCheckbox,
|
|
2997
|
+
createChart,
|
|
2998
|
+
createCard,
|
|
2999
|
+
createButton,
|
|
3000
|
+
createBadge,
|
|
3001
|
+
createAvatar,
|
|
3002
|
+
createAutocomplete,
|
|
3003
|
+
createAccordion,
|
|
3004
|
+
confirm2 as confirm,
|
|
3005
|
+
closeAllWindows,
|
|
3006
|
+
closeAllModals,
|
|
3007
|
+
alert2 as alert,
|
|
3008
|
+
TRAY_MENU_STYLES,
|
|
3009
|
+
TOAST_STYLES,
|
|
3010
|
+
MODAL_STYLES,
|
|
3011
|
+
COMPONENT_STYLES,
|
|
3012
|
+
AVAILABLE_COMPONENTS
|
|
3013
|
+
};
|
|
3014
|
+
|
|
3015
|
+
//# debugId=574CB389B0B4B44F64756E2164756E21
|