@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/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">&times;</button>' : ""}
78
+ </div>
79
+ `;
80
+ }
81
+ function escapeHtml(str) {
82
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
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">&times;</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">&times;</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">&times;</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 "&#x2139;";
1733
+ case "warning":
1734
+ return "&#x26A0;";
1735
+ case "error":
1736
+ return "&#x2716;";
1737
+ case "success":
1738
+ return "&#x2714;";
1739
+ case "question":
1740
+ return "&#x2753;";
1741
+ default:
1742
+ return "&#x2139;";
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
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