@trops/dash-core 0.1.594 → 0.1.595

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.esm.js CHANGED
@@ -60494,6 +60494,37 @@ var DashboardStageInner = function DashboardStageInner(_ref3) {
60494
60494
 
60495
60495
  // Snapshot of the workspace before editing — used to restore on Cancel
60496
60496
  var originalWorkspaceRef = useRef(null);
60497
+
60498
+ // ─── Unsaved-changes guard (Phase 2B) ────────────────────────────
60499
+ // Single source of truth for "is the user mid-edit with un-saved
60500
+ // work?". Edit mode (`previewMode === false`) is necessary; a
60501
+ // populated `currentWorkspaceRef` is sufficient — the layout
60502
+ // builder mutates that ref on every drag/drop/widget change.
60503
+ //
60504
+ // The flag is mirrored to `globalThis.__dashboardIsDirty` so the
60505
+ // Electron main process can poll it synchronously during window
60506
+ // close + app-quit handlers without an IPC round-trip.
60507
+ //
60508
+ // A `pendingNavigation` object captures a navigation request that
60509
+ // arrived while dirty so the user can choose to discard or keep
60510
+ // editing. Shapes:
60511
+ // { kind: "open-workspace", workspace } — sidebar switch
60512
+ // { kind: "cancel-edit" } — Cancel button
60513
+ var _useState57 = useState(null),
60514
+ _useState58 = _slicedToArray(_useState57, 2),
60515
+ pendingNavigation = _useState58[0],
60516
+ setPendingNavigation = _useState58[1];
60517
+ var isDirty = previewMode === false && currentWorkspaceRef.current !== null;
60518
+ useEffect(function () {
60519
+ if (typeof globalThis !== "undefined") {
60520
+ globalThis.__dashboardIsDirty = isDirty;
60521
+ }
60522
+ return function () {
60523
+ if (typeof globalThis !== "undefined") {
60524
+ globalThis.__dashboardIsDirty = false;
60525
+ }
60526
+ };
60527
+ }, [isDirty]);
60497
60528
  useEffect(function () {
60498
60529
  isLoadingWorkspaces === false && loadWorkspaces();
60499
60530
  isLoadingMenuItems === false && loadMenuItems();
@@ -60581,10 +60612,10 @@ var DashboardStageInner = function DashboardStageInner(_ref3) {
60581
60612
  // We record the requested ID and open it once it appears in
60582
60613
  // workspaceConfig — handles the case where the workspace was just
60583
60614
  // created and the config reload is still in flight.
60584
- var _useState57 = useState(null),
60585
- _useState58 = _slicedToArray(_useState57, 2),
60586
- pendingOpenWorkspaceId = _useState58[0],
60587
- setPendingOpenWorkspaceId = _useState58[1];
60615
+ var _useState59 = useState(null),
60616
+ _useState60 = _slicedToArray(_useState59, 2),
60617
+ pendingOpenWorkspaceId = _useState60[0],
60618
+ setPendingOpenWorkspaceId = _useState60[1];
60588
60619
  useEffect(function () {
60589
60620
  var handler = function handler(e) {
60590
60621
  var _e$detail2;
@@ -60841,6 +60872,29 @@ var DashboardStageInner = function DashboardStageInner(_ref3) {
60841
60872
  });
60842
60873
  }
60843
60874
  }
60875
+
60876
+ // Guarded variant of handleOpenTab for user-driven entry points
60877
+ // (sidebar dashboard list, recents). If the user is mid-edit with
60878
+ // unsaved changes AND the request would navigate to a DIFFERENT
60879
+ // workspace than the currently active tab, surface the discard
60880
+ // confirmation modal instead of switching immediately.
60881
+ //
60882
+ // Programmatic callers (post-create auto-open, popout init, the
60883
+ // wizard's create-then-open flow) keep calling `handleOpenTab`
60884
+ // directly — they only fire after an explicit save and have no
60885
+ // dirty state to lose.
60886
+ function handleOpenTabGuarded(workspaceItem) {
60887
+ if (!workspaceItem) return;
60888
+ var switchingAway = activeTabId && activeTabId !== workspaceItem.id;
60889
+ if (isDirty && switchingAway) {
60890
+ setPendingNavigation({
60891
+ kind: "open-workspace",
60892
+ workspace: workspaceItem
60893
+ });
60894
+ return;
60895
+ }
60896
+ handleOpenTab(workspaceItem);
60897
+ }
60844
60898
  function handleCloseTab(tabId) {
60845
60899
  setOpenTabs(function (prev) {
60846
60900
  var remaining = prev.filter(function (tab) {
@@ -61185,10 +61239,10 @@ var DashboardStageInner = function DashboardStageInner(_ref3) {
61185
61239
  }
61186
61240
 
61187
61241
  // ─── Page State ──────────────────────────────────────────────────
61188
- var _useState59 = useState(null),
61189
- _useState60 = _slicedToArray(_useState59, 2),
61190
- activePageId = _useState60[0],
61191
- setActivePageId = _useState60[1];
61242
+ var _useState61 = useState(null),
61243
+ _useState62 = _slicedToArray(_useState61, 2),
61244
+ activePageId = _useState62[0],
61245
+ setActivePageId = _useState62[1];
61192
61246
 
61193
61247
  // Page history stack for goBack() — pushes the previous page id
61194
61248
  // whenever a navigation happens through navigateToPage().
@@ -61574,20 +61628,35 @@ var DashboardStageInner = function DashboardStageInner(_ref3) {
61574
61628
  }
61575
61629
  function handleSaveMenuItemError(e, message) {
61576
61630
  }
61631
+
61632
+ // Internal: the unguarded cancel-edit operation. Called both from
61633
+ // the direct Cancel button (when nothing is dirty) and from the
61634
+ // confirmation modal's Discard action.
61635
+ function performCancelEdit() {
61636
+ if (originalWorkspaceRef.current) {
61637
+ updateTabWorkspace(originalWorkspaceRef.current);
61638
+ }
61639
+ currentWorkspaceRef.current = null;
61640
+ originalWorkspaceRef.current = null;
61641
+ setPreviewMode(true);
61642
+ }
61577
61643
  function handleToggleEditMode() {
61578
61644
  if (previewMode) {
61579
61645
  // Entering edit mode — snapshot the current workspace
61580
61646
  originalWorkspaceRef.current = deepCopy(workspaceSelected);
61581
61647
  setPreviewMode(false);
61582
- } else {
61583
- // Canceling edit mode — restore original workspace
61584
- if (originalWorkspaceRef.current) {
61585
- updateTabWorkspace(originalWorkspaceRef.current);
61586
- }
61587
- currentWorkspaceRef.current = null;
61588
- originalWorkspaceRef.current = null;
61589
- setPreviewMode(true);
61648
+ return;
61590
61649
  }
61650
+ // Cancel path: prompt only if there are unsaved edits. The
61651
+ // `isDirty` value flips true the moment LayoutBuilder writes
61652
+ // anything into `currentWorkspaceRef`.
61653
+ if (isDirty) {
61654
+ setPendingNavigation({
61655
+ kind: "cancel-edit"
61656
+ });
61657
+ return;
61658
+ }
61659
+ performCancelEdit();
61591
61660
  }
61592
61661
  function handleWorkspaceNameChange(name) {
61593
61662
  if (!workspaceSelected) return;
@@ -61938,7 +62007,7 @@ var DashboardStageInner = function DashboardStageInner(_ref3) {
61938
62007
  recentDashboards: recentDashboards,
61939
62008
  authStatus: authStatus,
61940
62009
  authProfile: authProfile,
61941
- onOpenWorkspace: handleOpenTab,
62010
+ onOpenWorkspace: handleOpenTabGuarded,
61942
62011
  onNewDashboard: function onNewDashboard() {
61943
62012
  return setIsLayoutPickerOpen(true);
61944
62013
  },
@@ -62129,7 +62198,7 @@ var DashboardStageInner = function DashboardStageInner(_ref3) {
62129
62198
  onReloadWorkspaces: loadWorkspaces,
62130
62199
  onReloadMenuItems: loadMenuItems,
62131
62200
  onOpenWorkspace: function onOpenWorkspace(ws) {
62132
- handleOpenTab(ws);
62201
+ handleOpenTabGuarded(ws);
62133
62202
  setIsAppSettingsOpen(false);
62134
62203
  },
62135
62204
  onOpenThemeEditor: function onOpenThemeEditor() {
@@ -62171,7 +62240,7 @@ var DashboardStageInner = function DashboardStageInner(_ref3) {
62171
62240
  onSaveMenuItem: handleSaveNewMenuItem,
62172
62241
  appId: credentials === null || credentials === void 0 ? void 0 : credentials.appId,
62173
62242
  onReloadWorkspaces: loadWorkspaces,
62174
- onOpenWorkspace: handleOpenTab,
62243
+ onOpenWorkspace: handleOpenTabGuarded,
62175
62244
  onOpenWizard: function onOpenWizard() {
62176
62245
  return setIsWizardOpen(true);
62177
62246
  }
@@ -62256,6 +62325,36 @@ var DashboardStageInner = function DashboardStageInner(_ref3) {
62256
62325
  onOpenWizard: function onOpenWizard() {
62257
62326
  return setIsWizardOpen(true);
62258
62327
  }
62328
+ }), /*#__PURE__*/jsx(ConfirmationModal, {
62329
+ isOpen: Boolean(pendingNavigation),
62330
+ setIsOpen: function setIsOpen(open) {
62331
+ if (!open) setPendingNavigation(null);
62332
+ },
62333
+ title: "Discard unsaved changes?",
62334
+ message: (pendingNavigation === null || pendingNavigation === void 0 ? void 0 : pendingNavigation.kind) === "cancel-edit" ? "You have edits that haven't been saved. Discard them and exit edit mode?" : "You have edits that haven't been saved. Discard them and switch dashboards?",
62335
+ confirmLabel: "Discard changes",
62336
+ cancelLabel: "Keep editing",
62337
+ variant: "danger",
62338
+ onConfirm: function onConfirm() {
62339
+ var pending = pendingNavigation;
62340
+ // Clear the prompt first so the modal teardown doesn't race
62341
+ // a re-trigger from the same dirty-state read.
62342
+ setPendingNavigation(null);
62343
+ if (!pending) return;
62344
+ if (pending.kind === "cancel-edit") {
62345
+ performCancelEdit();
62346
+ } else if (pending.kind === "open-workspace") {
62347
+ // Clear edit refs before navigating so the new workspace
62348
+ // mount doesn't inherit dirty state.
62349
+ currentWorkspaceRef.current = null;
62350
+ originalWorkspaceRef.current = null;
62351
+ setPreviewMode(true);
62352
+ handleOpenTab(pending.workspace);
62353
+ }
62354
+ },
62355
+ onCancel: function onCancel() {
62356
+ return setPendingNavigation(null);
62357
+ }
62259
62358
  })]
62260
62359
  });
62261
62360
  };