react-os-shell 0.1.4 → 0.1.6

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.
Files changed (30) hide show
  1. package/dist/{Calculator-QPLQD6L7.js → Calculator-LIRYXAO6.js} +4 -4
  2. package/dist/{Calculator-QPLQD6L7.js.map → Calculator-LIRYXAO6.js.map} +1 -1
  3. package/dist/{Calendar-7USNCCRN.js → Calendar-EPLEHSOJ.js} +3 -3
  4. package/dist/{Calendar-7USNCCRN.js.map → Calendar-EPLEHSOJ.js.map} +1 -1
  5. package/dist/{CurrencyConverter-AFRWG54S.js → CurrencyConverter-DZWLRMAA.js} +4 -4
  6. package/dist/{CurrencyConverter-AFRWG54S.js.map → CurrencyConverter-DZWLRMAA.js.map} +1 -1
  7. package/dist/{Email-ALVDHFXO.js → Email-4PYD3QBK.js} +3 -3
  8. package/dist/{Email-ALVDHFXO.js.map → Email-4PYD3QBK.js.map} +1 -1
  9. package/dist/{Minesweeper-OZHDTQ7B.js → Minesweeper-WD6O5YOD.js} +3 -3
  10. package/dist/{Minesweeper-OZHDTQ7B.js.map → Minesweeper-WD6O5YOD.js.map} +1 -1
  11. package/dist/{Notepad-RCSCNFDX.js → Notepad-F3QYVJ36.js} +3 -3
  12. package/dist/{Notepad-RCSCNFDX.js.map → Notepad-F3QYVJ36.js.map} +1 -1
  13. package/dist/{PomodoroTimer-MMC6YFV4.js → PomodoroTimer-TADWMJCU.js} +4 -4
  14. package/dist/{PomodoroTimer-MMC6YFV4.js.map → PomodoroTimer-TADWMJCU.js.map} +1 -1
  15. package/dist/{Spreadsheet-DX6SUGXJ.js → Spreadsheet-PZGPOSWV.js} +3 -3
  16. package/dist/{Spreadsheet-DX6SUGXJ.js.map → Spreadsheet-PZGPOSWV.js.map} +1 -1
  17. package/dist/{Weather-UPZPYXBA.js → Weather-TOIFJCST.js} +4 -4
  18. package/dist/{Weather-UPZPYXBA.js.map → Weather-TOIFJCST.js.map} +1 -1
  19. package/dist/apps/index.d.ts +1 -1
  20. package/dist/apps/index.js +10 -10
  21. package/dist/apps/index.js.map +1 -1
  22. package/dist/{chunk-WYHVAKJ5.js → chunk-2EBFERVD.js} +207 -16
  23. package/dist/chunk-2EBFERVD.js.map +1 -0
  24. package/dist/{chunk-FDO5OVXE.js → chunk-7JGV4RFC.js} +3 -3
  25. package/dist/{chunk-FDO5OVXE.js.map → chunk-7JGV4RFC.js.map} +1 -1
  26. package/dist/index.d.ts +2 -2
  27. package/dist/index.js +2 -2
  28. package/dist/{types-CFIZ1_xt.d.ts → types-BKoa7nhP.d.ts} +4 -0
  29. package/package.json +1 -1
  30. package/dist/chunk-WYHVAKJ5.js.map +0 -1
@@ -1269,6 +1269,107 @@ function RestoredRegistryModal({ item, onClose, onMinimize }) {
1269
1269
  }
1270
1270
  );
1271
1271
  }
1272
+ function findPanelByLabel(label) {
1273
+ const panels = document.querySelectorAll("[data-modal-panel]");
1274
+ for (const p of Array.from(panels)) {
1275
+ const t = p.querySelector(".text-lg, .text-sm.font-medium");
1276
+ if (t?.textContent?.includes(label)) return p;
1277
+ }
1278
+ return null;
1279
+ }
1280
+ function ThumbCard({ label, width, height, onClick, onClose }) {
1281
+ const previewRef = useRef(null);
1282
+ useEffect(() => {
1283
+ const inner = previewRef.current;
1284
+ if (!inner) return;
1285
+ const target = findPanelByLabel(label);
1286
+ if (!target) return;
1287
+ const rect = target.getBoundingClientRect();
1288
+ if (rect.width === 0 || rect.height === 0) return;
1289
+ const scale = Math.min(width / rect.width, height / rect.height);
1290
+ const clone = target.cloneNode(true);
1291
+ clone.style.position = "absolute";
1292
+ clone.style.top = "0";
1293
+ clone.style.left = "0";
1294
+ clone.style.right = "auto";
1295
+ clone.style.bottom = "auto";
1296
+ clone.style.width = rect.width + "px";
1297
+ clone.style.height = rect.height + "px";
1298
+ clone.style.transform = `scale(${scale})`;
1299
+ clone.style.transformOrigin = "top left";
1300
+ clone.style.pointerEvents = "none";
1301
+ clone.style.animation = "none";
1302
+ clone.removeAttribute("data-modal-panel");
1303
+ clone.removeAttribute("data-modal-id");
1304
+ clone.querySelectorAll('[role="dialog"], [data-portal]').forEach((el) => {
1305
+ el.style.display = "none";
1306
+ });
1307
+ inner.innerHTML = "";
1308
+ inner.appendChild(clone);
1309
+ return () => {
1310
+ inner.innerHTML = "";
1311
+ };
1312
+ }, [label, width, height]);
1313
+ return /* @__PURE__ */ jsxs(
1314
+ "div",
1315
+ {
1316
+ style: { width, height },
1317
+ className: "relative rounded-md overflow-hidden bg-white/95 border border-gray-300 shadow-md cursor-pointer hover:ring-2 hover:ring-blue-400 transition",
1318
+ onClick,
1319
+ children: [
1320
+ /* @__PURE__ */ jsx("div", { ref: previewRef, className: "absolute inset-0 overflow-hidden" }),
1321
+ /* @__PURE__ */ jsx("div", { className: "absolute bottom-0 left-0 right-0 px-2 py-1 text-[10px] font-medium text-white bg-gradient-to-t from-black/80 to-transparent truncate pointer-events-none", children: label }),
1322
+ onClose && /* @__PURE__ */ jsx(
1323
+ "button",
1324
+ {
1325
+ onClick: (e) => {
1326
+ e.stopPropagation();
1327
+ onClose();
1328
+ },
1329
+ className: "absolute top-1 right-1 h-4 w-4 rounded-full bg-black/40 hover:bg-red-500/90 text-white flex items-center justify-center",
1330
+ title: "Close window",
1331
+ children: /* @__PURE__ */ jsx("svg", { className: "h-2.5 w-2.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 3, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" }) })
1332
+ }
1333
+ )
1334
+ ]
1335
+ }
1336
+ );
1337
+ }
1338
+ function TaskbarTabPreview({ items, anchorEl, onActivate, onClose, onMouseEnter, onMouseLeave }) {
1339
+ const PREVIEW_W = 240;
1340
+ const PREVIEW_H = 150;
1341
+ const isGroup = items.length > 1;
1342
+ const totalWidth = isGroup ? Math.min(items.length * (PREVIEW_W + 8) + 8, window.innerWidth - 16) : PREVIEW_W;
1343
+ const totalHeight = isGroup ? PREVIEW_H + 16 : PREVIEW_H;
1344
+ const rect = anchorEl.getBoundingClientRect();
1345
+ const taskbarPos = getComputedStyle(document.documentElement).getPropertyValue("--taskbar-position")?.trim() || "bottom";
1346
+ const left = Math.max(8, Math.min(rect.left + rect.width / 2 - totalWidth / 2, window.innerWidth - totalWidth - 8));
1347
+ const top = taskbarPos === "top" ? rect.bottom + 8 : taskbarPos === "bottom" ? rect.top - totalHeight - 8 : rect.top + rect.height / 2 - totalHeight / 2;
1348
+ const adjustedLeft = taskbarPos === "left" ? rect.right + 8 : taskbarPos === "right" ? rect.left - totalWidth - 8 : left;
1349
+ return createPortal(
1350
+ /* @__PURE__ */ jsx(
1351
+ "div",
1352
+ {
1353
+ style: { position: "fixed", left: adjustedLeft, top, zIndex: 9999 },
1354
+ className: isGroup ? "flex gap-2 p-2 rounded-lg bg-white/40 backdrop-blur-sm border border-white/30 shadow-2xl" : "",
1355
+ onMouseEnter,
1356
+ onMouseLeave,
1357
+ children: items.map((it) => /* @__PURE__ */ jsx(
1358
+ ThumbCard,
1359
+ {
1360
+ label: it.label,
1361
+ width: PREVIEW_W,
1362
+ height: PREVIEW_H,
1363
+ onClick: () => onActivate(it.label),
1364
+ onClose: () => onClose(it.id)
1365
+ },
1366
+ it.id
1367
+ ))
1368
+ }
1369
+ ),
1370
+ document.body
1371
+ );
1372
+ }
1272
1373
  function TaskbarWindows({ openWindows, onRemove, onCloseAll, onSplitView, onActivate }) {
1273
1374
  const [target, setTarget] = useState(null);
1274
1375
  useEffect(() => {
@@ -1286,45 +1387,105 @@ function TaskbarWindows({ openWindows, onRemove, onCloseAll, onSplitView, onActi
1286
1387
  }
1287
1388
  }, []);
1288
1389
  const activeModalId = useSyncExternalStore(subscribeActive, getActiveModalId);
1390
+ const [, forceTick] = useState(0);
1391
+ useEffect(() => {
1392
+ const onTitle = () => forceTick((t) => t + 1);
1393
+ window.addEventListener("window-title-update", onTitle);
1394
+ return () => window.removeEventListener("window-title-update", onTitle);
1395
+ }, []);
1396
+ const liveTitle = (label) => {
1397
+ const panel = findPanelByLabel(label);
1398
+ const titleEl = panel?.querySelector(".text-lg, .text-sm.font-medium");
1399
+ return titleEl?.textContent?.trim() || label;
1400
+ };
1401
+ const [hoveredItems, setHoveredItems] = useState(null);
1402
+ const [hoveredAnchor, setHoveredAnchor] = useState(null);
1403
+ const hoverTimerRef = useRef(null);
1404
+ const handleEnter = (items, el) => {
1405
+ if (hoverTimerRef.current) {
1406
+ clearTimeout(hoverTimerRef.current);
1407
+ hoverTimerRef.current = null;
1408
+ }
1409
+ hoverTimerRef.current = setTimeout(() => {
1410
+ setHoveredItems(items);
1411
+ setHoveredAnchor(el);
1412
+ }, 350);
1413
+ };
1414
+ const handleLeave = () => {
1415
+ if (hoverTimerRef.current) {
1416
+ clearTimeout(hoverTimerRef.current);
1417
+ hoverTimerRef.current = null;
1418
+ }
1419
+ hoverTimerRef.current = setTimeout(() => {
1420
+ setHoveredItems(null);
1421
+ setHoveredAnchor(null);
1422
+ }, 150);
1423
+ };
1424
+ const cancelLeave = () => {
1425
+ if (hoverTimerRef.current) {
1426
+ clearTimeout(hoverTimerRef.current);
1427
+ hoverTimerRef.current = null;
1428
+ }
1429
+ };
1289
1430
  const tabWindows = openWindows.filter((item) => !item.route || !WINDOW_REGISTRY[item.route]?.utility);
1290
1431
  if (!target || tabWindows.length === 0) return null;
1432
+ const groups = [];
1433
+ const idx = /* @__PURE__ */ new Map();
1434
+ for (const item of tabWindows) {
1435
+ const key = item.route ?? `entity:${item.id}`;
1436
+ const i = idx.get(key);
1437
+ if (i !== void 0) {
1438
+ groups[i].items.push(item);
1439
+ } else {
1440
+ idx.set(key, groups.length);
1441
+ const registryLabel = item.route ? WINDOW_REGISTRY[item.route]?.label : void 0;
1442
+ groups.push({ key, route: item.route, label: registryLabel ?? item.label, items: [item] });
1443
+ }
1444
+ }
1291
1445
  return createPortal(
1292
1446
  /* @__PURE__ */ jsxs(Fragment, { children: [
1293
- tabWindows.map((item) => {
1294
- const icon = item.route ? navIcons[item.route] : null;
1447
+ groups.map((group) => {
1448
+ const icon = group.route ? navIcons[group.route] : null;
1449
+ const primary = group.items[group.items.length - 1];
1295
1450
  let isActive = false;
1296
1451
  if (activeModalId) {
1297
1452
  const panel = document.querySelector(`[data-modal-id="${activeModalId}"]`);
1298
1453
  if (panel) {
1299
1454
  const titleEl = panel.querySelector(".text-lg, .text-sm.font-medium");
1300
- if (titleEl?.textContent?.includes(item.label)) isActive = true;
1455
+ const titleText = titleEl?.textContent ?? "";
1456
+ isActive = group.items.some((it) => titleText.includes(it.label));
1301
1457
  }
1302
1458
  }
1459
+ const isGrouped = group.items.length > 1;
1303
1460
  return /* @__PURE__ */ jsxs(
1304
1461
  "button",
1305
1462
  {
1306
- onClick: () => onActivate(item.label),
1463
+ onClick: () => onActivate(primary.label),
1464
+ onMouseEnter: (e) => handleEnter(group.items, e.currentTarget),
1465
+ onMouseLeave: handleLeave,
1307
1466
  onDoubleClick: (e) => {
1308
1467
  e.stopPropagation();
1309
- window.dispatchEvent(new CustomEvent("modal-center", { detail: { label: item.label } }));
1468
+ window.dispatchEvent(new CustomEvent("modal-center", { detail: { label: primary.label } }));
1310
1469
  },
1311
1470
  onContextMenu: (e) => {
1312
1471
  e.preventDefault();
1313
1472
  e.stopPropagation();
1314
- window.dispatchEvent(new CustomEvent("modal-context-menu", { detail: { label: item.label, x: e.clientX, y: e.clientY } }));
1473
+ window.dispatchEvent(new CustomEvent("modal-context-menu", { detail: { label: primary.label, x: e.clientX, y: e.clientY } }));
1315
1474
  },
1316
1475
  style: { width: "var(--window-tab-width, 200px)", fontSize: "var(--window-tab-font-size, 12px)" },
1317
- className: `group flex items-center gap-1.5 rounded-lg px-3 py-2 font-medium transition-all min-w-0 shrink ${isActive ? "bg-blue-100/60 border border-blue-400/60 text-blue-700" : "bg-gray-50/40 border border-gray-200/40 text-gray-700 hover:bg-gray-200/40"}`,
1476
+ "data-tab-group": group.key,
1477
+ className: `group relative flex items-center gap-1.5 rounded-lg px-3 py-2 font-medium transition-all min-w-0 shrink ${isActive ? "bg-blue-100/60 border border-blue-400/60 text-blue-700" : "bg-gray-50/40 border border-gray-200/40 text-gray-700 hover:bg-gray-200/40"}`,
1318
1478
  children: [
1319
1479
  icon && isValidElement(icon) ? cloneElement(icon, { className: `h-3.5 w-3.5 shrink-0 ${isActive ? "text-blue-600" : "text-gray-400"}` }) : /* @__PURE__ */ jsx("svg", { className: `h-3.5 w-3.5 shrink-0 ${isActive ? "text-blue-600" : "text-gray-400"}`, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" }) }),
1320
- /* @__PURE__ */ jsx("span", { className: "truncate flex-1", children: item.label }),
1321
- /* @__PURE__ */ jsx("span", { role: "button", onClick: (e) => {
1480
+ /* @__PURE__ */ jsx("span", { className: "truncate flex-1", children: isGrouped ? group.label : liveTitle(primary.label) }),
1481
+ isGrouped && /* @__PURE__ */ jsx("span", { className: "shrink-0 px-1.5 py-0.5 rounded-full bg-blue-500/80 text-white text-[10px] font-bold leading-none", children: group.items.length }),
1482
+ !isGrouped && /* @__PURE__ */ jsx("span", { role: "button", onClick: (e) => {
1322
1483
  e.stopPropagation();
1323
- onRemove(item.id);
1484
+ onRemove(primary.id);
1324
1485
  }, className: "ml-auto text-gray-400 hover:text-red-500 shrink-0 opacity-0 group-hover:opacity-100 transition-opacity", children: /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" }) }) })
1325
1486
  ]
1326
1487
  },
1327
- item.id
1488
+ group.key
1328
1489
  );
1329
1490
  }),
1330
1491
  /* @__PURE__ */ jsx("div", { className: "flex-1" }),
@@ -1338,6 +1499,25 @@ function TaskbarWindows({ openWindows, onRemove, onCloseAll, onSplitView, onActi
1338
1499
  "Split"
1339
1500
  ]
1340
1501
  }
1502
+ ),
1503
+ hoveredItems && hoveredAnchor && /* @__PURE__ */ jsx(
1504
+ TaskbarTabPreview,
1505
+ {
1506
+ items: hoveredItems,
1507
+ anchorEl: hoveredAnchor,
1508
+ onActivate: (label) => {
1509
+ onActivate(label);
1510
+ setHoveredItems(null);
1511
+ setHoveredAnchor(null);
1512
+ },
1513
+ onClose: (id) => {
1514
+ onRemove(id);
1515
+ setHoveredItems(null);
1516
+ setHoveredAnchor(null);
1517
+ },
1518
+ onMouseEnter: cancelLeave,
1519
+ onMouseLeave: handleLeave
1520
+ }
1341
1521
  )
1342
1522
  ] }),
1343
1523
  target
@@ -1387,7 +1567,7 @@ function WindowManagerProvider({ children }) {
1387
1567
  setTimeout(() => {
1388
1568
  const panels = document.querySelectorAll("[data-modal-panel]");
1389
1569
  panels.forEach((p) => {
1390
- const titleEl = p.querySelector(".text-lg");
1570
+ const titleEl = p.querySelector(".text-lg, .text-sm.font-medium");
1391
1571
  if (titleEl?.textContent?.includes(existing.label)) {
1392
1572
  const mid = p.getAttribute("data-modal-id");
1393
1573
  if (mid) activateModal(mid);
@@ -1411,6 +1591,17 @@ function WindowManagerProvider({ children }) {
1411
1591
  if (!WINDOW_REGISTRY[path] || !isPageEntry(WINDOW_REGISTRY[path])) return;
1412
1592
  const entry = WINDOW_REGISTRY[path];
1413
1593
  setOpenWindows((prev) => {
1594
+ if (entry.multiInstance) {
1595
+ const instanceCount = prev.filter((m) => m.type === "page" && m.route === path).length;
1596
+ const nextNum = instanceCount + 1;
1597
+ const id = `page:${path}:${Math.random().toString(36).slice(2, 8)}`;
1598
+ return [...prev, {
1599
+ id,
1600
+ type: "page",
1601
+ label: instanceCount === 0 ? entry.label : `${entry.label} ${nextNum}`,
1602
+ route: path
1603
+ }];
1604
+ }
1414
1605
  const existing = prev.find((m) => m.type === "page" && m.route === path);
1415
1606
  if (existing) {
1416
1607
  if (entry.widget) {
@@ -1419,7 +1610,7 @@ function WindowManagerProvider({ children }) {
1419
1610
  setTimeout(() => {
1420
1611
  const panels = document.querySelectorAll("[data-modal-panel]");
1421
1612
  panels.forEach((p) => {
1422
- const titleEl = p.querySelector(".text-lg");
1613
+ const titleEl = p.querySelector(".text-lg, .text-sm.font-medium");
1423
1614
  if (titleEl?.textContent?.includes(existing.label)) {
1424
1615
  const mid = p.getAttribute("data-modal-id");
1425
1616
  if (mid) activateModal(mid);
@@ -1454,7 +1645,7 @@ function WindowManagerProvider({ children }) {
1454
1645
  onActivate: (label) => {
1455
1646
  const panels = document.querySelectorAll("[data-modal-panel]");
1456
1647
  panels.forEach((p) => {
1457
- const titleEl = p.querySelector(".text-lg");
1648
+ const titleEl = p.querySelector(".text-lg, .text-sm.font-medium");
1458
1649
  if (titleEl?.textContent?.includes(label)) {
1459
1650
  const mid = p.getAttribute("data-modal-id");
1460
1651
  if (mid) activateModal(mid);
@@ -1477,5 +1668,5 @@ function WindowManagerProvider({ children }) {
1477
1668
  }
1478
1669
 
1479
1670
  export { CancelButton, CopyButton, DocFavStar, GLASS_DIVIDER, GLASS_INPUT_BG, Modal, ModalActions, PopupMenu, PopupMenuDivider, PopupMenuItem, PopupMenuLabel, WindowManagerProvider, WindowTitle, client_default, glassStyle, isEntityEntry, isPageEntry, isSection, navIcons, navSections, sectionIcons, setShellApiClient, setShellNavIcons, setShellWindowRegistry, startMenuCategories, useModalActive, useWidgetSettings, useWindowManager, useWindowMenuItem, useWindowTitle };
1480
- //# sourceMappingURL=chunk-WYHVAKJ5.js.map
1481
- //# sourceMappingURL=chunk-WYHVAKJ5.js.map
1671
+ //# sourceMappingURL=chunk-2EBFERVD.js.map
1672
+ //# sourceMappingURL=chunk-2EBFERVD.js.map