@gooddata/sdk-ui-ext 11.44.0 → 11.45.0-alpha.1

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.
@@ -2395,6 +2395,30 @@ export declare const en_US: {
2395
2395
  text: string;
2396
2396
  crowdinContext: string;
2397
2397
  };
2398
+ "objectShare.transfer.alreadyOwner.title": {
2399
+ text: string;
2400
+ crowdinContext: string;
2401
+ };
2402
+ "objectShare.transfer.alreadyOwner.description": {
2403
+ text: string;
2404
+ crowdinContext: string;
2405
+ };
2406
+ "objectShare.transfer.alreadyOwner.keepAccess": {
2407
+ text: string;
2408
+ crowdinContext: string;
2409
+ };
2410
+ "objectShare.transfer.alreadyOwner.removeAccess": {
2411
+ text: string;
2412
+ crowdinContext: string;
2413
+ };
2414
+ "objectShare.transfer.confirmButton": {
2415
+ text: string;
2416
+ crowdinContext: string;
2417
+ };
2418
+ "objectShare.toast.ownershipTransferred": {
2419
+ text: string;
2420
+ crowdinContext: string;
2421
+ };
2398
2422
  "objectShare.toast.granteeAdded": {
2399
2423
  text: string;
2400
2424
  crowdinContext: string;
@@ -1 +1 @@
1
- {"version":3,"file":"en-US.localization-bundle.d.ts","sourceRoot":"","sources":["../../../src/internal/translations/en-US.localization-bundle.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6hFjB,CAAC"}
1
+ {"version":3,"file":"en-US.localization-bundle.d.ts","sourceRoot":"","sources":["../../../src/internal/translations/en-US.localization-bundle.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqjFjB,CAAC"}
@@ -2397,6 +2397,30 @@ export const en_US = {
2397
2397
  "text": "Confirm",
2398
2398
  "crowdinContext": "Primary action label in the general-access confirm dialog."
2399
2399
  },
2400
+ "objectShare.transfer.alreadyOwner.title": {
2401
+ "text": "{name} is already an owner",
2402
+ "crowdinContext": "Title of the confirm dialog shown when the user picked to receive ownership already owns the object. {name} is that user's name."
2403
+ },
2404
+ "objectShare.transfer.alreadyOwner.description": {
2405
+ "text": "There's nothing to transfer. If you want to remove your ownership, choose what happens to your access:",
2406
+ "crowdinContext": "Description in the confirm dialog shown when the picked user already owns the object. Offers to remove the current user's own access instead of transferring."
2407
+ },
2408
+ "objectShare.transfer.alreadyOwner.keepAccess": {
2409
+ "text": "Keep view access",
2410
+ "crowdinContext": "Radio option in the 'already an owner' dialog: the current user keeps view access to the object."
2411
+ },
2412
+ "objectShare.transfer.alreadyOwner.removeAccess": {
2413
+ "text": "Remove my access",
2414
+ "crowdinContext": "Radio option in the 'already an owner' dialog: the current user's access to the object is removed entirely."
2415
+ },
2416
+ "objectShare.transfer.confirmButton": {
2417
+ "text": "Confirm",
2418
+ "crowdinContext": "Primary button in the 'already an owner' dialog. Applies the chosen change to the current user's own access."
2419
+ },
2420
+ "objectShare.toast.ownershipTransferred": {
2421
+ "text": "The ownership has been transferred.",
2422
+ "crowdinContext": "Toast shown after ownership of the shared object was successfully transferred to another user."
2423
+ },
2400
2424
  "objectShare.toast.granteeAdded": {
2401
2425
  "text": "Access added.",
2402
2426
  "crowdinContext": "Toast shown after a new user or group is granted access via the share dialog."
@@ -47,6 +47,7 @@ import { IPivotTableConfig } from '@gooddata/sdk-ui-pivot';
47
47
  import { ISettings } from '@gooddata/sdk-model';
48
48
  import { ITab } from '@gooddata/sdk-ui-kit';
49
49
  import { ITheme } from '@gooddata/sdk-model';
50
+ import type { IUiGranteeAsyncOption } from '@gooddata/sdk-ui-kit';
50
51
  import type { IUiGranteeAsyncOptions } from '@gooddata/sdk-ui-kit';
51
52
  import type { IUiPickedGrantee } from '@gooddata/sdk-ui-kit';
52
53
  import { IUserWorkspaceSettings } from '@gooddata/sdk-backend-spi';
@@ -1454,11 +1455,13 @@ export declare interface IObjectShareControllerActions {
1454
1455
  closeAddGrantee: () => void;
1455
1456
  setPendingGrantees: (next: IUiPickedGrantee[]) => void;
1456
1457
  /**
1457
- * Loader for the add-grantee picker. Wraps `getAvailableAssignees` with
1458
+ * Loader for the grantee pickers. Wraps `getAvailableAssignees` with
1458
1459
  * client-side search + already-picked filtering, returning the picker's
1459
- * `{ groups, users }` shape.
1460
+ * `{ groups, users }` shape. By default excludes already-granted grantees
1461
+ * (add-grantee picker); pass `includeGranted` to keep them (transfer-ownership
1462
+ * picker, which may promote an existing viewer to owner).
1460
1463
  */
1461
- loadOptions: (search: string) => Promise<IUiGranteeAsyncOptions>;
1464
+ loadOptions: (search: string, includeGranted?: boolean) => Promise<IUiGranteeAsyncOptions>;
1462
1465
  /** Commit all pending grantees to the backend. */
1463
1466
  confirmAddGrantees: () => Promise<void>;
1464
1467
  /** Change the permission level for a single grantee. Auto-saves. */
@@ -1482,13 +1485,31 @@ export declare interface IObjectShareControllerActions {
1482
1485
  * already-granted rule.
1483
1486
  */
1484
1487
  changeWorkspaceLevel: (level: "VIEW" | "SHARE") => Promise<void>;
1488
+ /** Open the transfer-ownership subview with an empty picker. */
1489
+ openTransferOwnership: () => void;
1490
+ /** Close the transfer-ownership subview and clear its buffers; returns to main. */
1491
+ closeTransferOwnership: () => void;
1492
+ /**
1493
+ * Pick (or replace) the candidate new owner. Recomputes whether that user
1494
+ * already owns the object (`transferTargetIsOwner`).
1495
+ */
1496
+ setTransferTarget: (owner: IUiGranteeAsyncOption) => void;
1497
+ /** Toggle "Also remove my access" in the transfer dialog. */
1498
+ setTransferAlsoRemoveSelf: (next: boolean) => void;
1499
+ /**
1500
+ * Commit the ownership transfer: grant the picked user EDIT and downgrade the
1501
+ * current user to VIEW (or remove them if "Also remove my access" is set), in
1502
+ * one write. When the picked user already owns the object this transfers
1503
+ * nothing and only applies the self-access choice. Auto-saves.
1504
+ */
1505
+ confirmTransferOwnership: () => Promise<void>;
1485
1506
  }
1486
1507
 
1487
1508
  /**
1488
1509
  * @internal
1489
1510
  */
1490
1511
  export declare interface IObjectShareControllerState {
1491
- subview: "main" | "addGrantee";
1512
+ subview: "main" | "addGrantee" | "transferOwnership";
1492
1513
  status: "idle" | "loading" | "success" | "error" | "saving";
1493
1514
  error?: Error;
1494
1515
  /**
@@ -1537,6 +1558,26 @@ export declare interface IObjectShareControllerState {
1537
1558
  pendingGeneralAccess?: GeneralAccessValue;
1538
1559
  /** Grantees staged in the add-grantee dialog before confirmation. */
1539
1560
  pendingGrantees: IUiPickedGrantee[];
1561
+ /**
1562
+ * The user picked as the new owner in the transfer-ownership subview, or
1563
+ * undefined before one is chosen. Only users can own an object, so this is
1564
+ * never a group.
1565
+ */
1566
+ transferTarget: IUiGranteeAsyncOption | undefined;
1567
+ /**
1568
+ * Whether "Also remove my access" is checked in the transfer dialog. When
1569
+ * true the current user's grant is removed after the transfer; otherwise it
1570
+ * is downgraded to VIEW.
1571
+ */
1572
+ transferAlsoRemoveSelf: boolean;
1573
+ /**
1574
+ * Whether `transferTarget` already owns the object (already holds EDIT). When
1575
+ * true there is nothing to transfer — consumers show the "already an owner"
1576
+ * variant offering to remove the current user's own access instead.
1577
+ */
1578
+ transferTargetIsOwner: boolean;
1579
+ /** Whether the transfer write (or self-removal) is in flight. */
1580
+ transferSaving: boolean;
1540
1581
  }
1541
1582
 
1542
1583
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"ObjectShareDialog.d.ts","sourceRoot":"","sources":["../../src/share/ObjectShareDialog.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAS1E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAC/E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAQpD;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACpC,gDAAgD;IAChD,MAAM,EAAE,wBAAwB,GAAG,SAAS,CAAC;IAC7C,wEAAwE;IACxE,WAAW,EAAE,MAAM,CAAC;IACpB,0DAA0D;IAC1D,MAAM,EAAE,OAAO,CAAC;IAChB,sFAAsF;IACtF,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,4FAA0F;IAC1F,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;;;OAIG;IACH,MAAM,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAC7B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;OAIG;IACH,UAAU,CAAC,EAAE,sBAAsB,CAAC;CACvC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAAC,EAC9B,MAAM,EACN,WAAW,EACX,MAAM,EACN,OAAO,EACP,OAAO,EACP,MAAM,EACN,aAAa,EACb,WAAW,EACX,UAAU,EACb,EAAE,uBAAuB,2CAmKzB"}
1
+ {"version":3,"file":"ObjectShareDialog.d.ts","sourceRoot":"","sources":["../../src/share/ObjectShareDialog.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAW1E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAC/E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAQpD;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACpC,gDAAgD;IAChD,MAAM,EAAE,wBAAwB,GAAG,SAAS,CAAC;IAC7C,wEAAwE;IACxE,WAAW,EAAE,MAAM,CAAC;IACpB,0DAA0D;IAC1D,MAAM,EAAE,OAAO,CAAC;IAChB,sFAAsF;IACtF,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,4FAA0F;IAC1F,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;;;OAIG;IACH,MAAM,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAC7B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;OAIG;IACH,UAAU,CAAC,EAAE,sBAAsB,CAAC;CACvC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAAC,EAC9B,MAAM,EACN,WAAW,EACX,MAAM,EACN,OAAO,EACP,OAAO,EACP,MAAM,EACN,aAAa,EACb,WAAW,EACX,UAAU,EACb,EAAE,uBAAuB,2CAkPzB"}
@@ -2,7 +2,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
2
2
  // (C) 2026 GoodData Corporation
3
3
  import { useCallback } from "react";
4
4
  import { useIntl } from "react-intl";
5
- import { UiAddGranteeDialog, UiConfirmDialog, UiGranteeRowControls, UiObjectShareDialog, } from "@gooddata/sdk-ui-kit";
5
+ import { UiAddGranteeDialog, UiConfirmDialog, UiGranteeRowControls, UiObjectShareDialog, UiRadioRow, UiTransferOwnershipDialog, } from "@gooddata/sdk-ui-kit";
6
6
  import { objectShareMessages } from "./messages.js";
7
7
  import { useObjectShareController } from "./useObjectShareController.js";
8
8
  // The workspace row has no labels menu (see workspaceControls below), so its
@@ -43,8 +43,27 @@ export function ObjectShareDialog({ target, objectTitle, isOpen, onClose, onSave
43
43
  actions.reset();
44
44
  onClose();
45
45
  }, [actions, onClose]);
46
+ // Owner is always an individual user, so the transfer picker drops any groups
47
+ // the shared loader returns. `includeGranted` keeps already-granted users in
48
+ // the list — promoting an existing viewer to owner is a primary case.
49
+ //
50
+ // Depend on the stable `loadOptions` alone, not the whole `actions` object:
51
+ // `actions` changes identity as transfer state ticks (and as the picker's own
52
+ // name-cache writes re-derive grantees), and the autocomplete refetches page 0
53
+ // whenever its loader's identity changes — depending on `actions` here would
54
+ // self-feed into an infinite assignee-fetch loop.
55
+ const { loadOptions } = actions;
56
+ const loadOwnerOptions = useCallback(async (search) => {
57
+ const { users } = await loadOptions(search, true);
58
+ return { groups: [], users };
59
+ }, [loadOptions]);
46
60
  const isAddGranteeOpen = isOpen && state.subview === "addGrantee";
47
- const isShareOpen = isOpen && state.subview !== "addGrantee";
61
+ // The transfer subview splits into two co-mounted dialogs: the transfer card
62
+ // while picking a non-owner, and the "already an owner" confirm once the picked
63
+ // user already owns the object.
64
+ const isTransferOpen = isOpen && state.subview === "transferOwnership" && !state.transferTargetIsOwner;
65
+ const isAlreadyOwnerOpen = isOpen && state.subview === "transferOwnership" && state.transferTargetIsOwner;
66
+ const isShareOpen = isOpen && state.subview === "main";
48
67
  const isConfirmOpen = isOpen && !!state.pendingGeneralAccess;
49
68
  // Don't allow mutations until the access list has loaded: before then the
50
69
  // controller reports empty grantees + RESTRICTED, which is a placeholder, not
@@ -55,8 +74,13 @@ export function ObjectShareDialog({ target, objectTitle, isOpen, onClose, onSave
55
74
  // wrong labels). `labelsResolved` is false while labels are still loading and
56
75
  // when their fetch failed, so a pending/failed label set blocks every
57
76
  // access-changing control here — Add, row controls and general access alike.
77
+ // Also locked while a transfer-ownership write is committing: the transfer
78
+ // dialog can be dismissed (Back/Cancel/close) mid-save, so without this the
79
+ // user could return here and mutate other grantees concurrently with the
80
+ // in-flight ownership write — racing the same object's permissions with no
81
+ // refetch to reconcile. `transferSaving` clears when the transfer settles.
58
82
  const isLoaded = state.status === "success";
59
- const isMutable = isLoaded && state.labelsResolved;
83
+ const isMutable = isLoaded && state.labelsResolved && !state.transferSaving;
60
84
  const isAddDisabled = !isMutable;
61
85
  // Map the controller's labels to the picker's item shape; the primary label
62
86
  // is rendered locked (always selected, can't be unchecked).
@@ -78,13 +102,13 @@ export function ObjectShareDialog({ target, objectTitle, isOpen, onClose, onSave
78
102
  // probe in flight) — removing or re-scoping before then
79
103
  // would diff against the "assume all"/empty placeholder
80
104
  // and silently orphan real per-label grants.
81
- isDisabled: g.pending !== undefined || !state.labelsResolved, onLabelsChange: (selectedIds) => {
105
+ isDisabled: g.pending !== undefined || !state.labelsResolved || state.transferSaving, onLabelsChange: (selectedIds) => {
82
106
  void actions.changeGranteeLabels(g.id, selectedIds);
83
107
  }, onPermissionChange: (level) => {
84
108
  void actions.changePermissionLevel(g.id, level);
85
109
  }, onRemoveAccess: () => {
86
110
  void actions.removeGrantee(g.id);
87
- } })),
111
+ }, onTransferOwnership: actions.openTransferOwnership })),
88
112
  })), onAddClick: actions.openAddGrantee, isAddDisabled: isAddDisabled, generalAccess: state.generalAccess, onGeneralAccessChange: actions.requestGeneralAccessChange,
89
113
  // Permission dropdown on the "All workspace members" row — only while
90
114
  // workspace access is on (the rule must exist to be re-graded). No
@@ -112,6 +136,14 @@ export function ObjectShareDialog({ target, objectTitle, isOpen, onClose, onSave
112
136
  // read as "no one has access".
113
137
  error: state.status === "error" ? intl.formatMessage(objectShareMessages.loadError) : undefined }), _jsx(UiAddGranteeDialog, { isOpen: isAddGranteeOpen, objectTitle: objectTitle, loadOptions: actions.loadOptions, selectedGrantees: state.pendingGrantees, onSelectedGranteesChange: actions.setPendingGrantees, onBack: actions.closeAddGrantee, onClose: handleClose, onCancel: actions.closeAddGrantee, onShare: () => {
114
138
  void actions.confirmAddGrantees();
139
+ } }), _jsx(UiTransferOwnershipDialog, { isOpen: isTransferOpen, objectTitle: objectTitle, loadOptions: loadOwnerOptions, selectedOwner: state.transferTarget, onSelectedOwnerChange: actions.setTransferTarget, alsoRemoveMyAccess: state.transferAlsoRemoveSelf, onAlsoRemoveMyAccessChange: actions.setTransferAlsoRemoveSelf, onBack: actions.closeTransferOwnership, onClose: handleClose, onCancel: actions.closeTransferOwnership, onTransfer: () => {
140
+ void actions.confirmTransferOwnership();
141
+ }, isSaving: state.transferSaving }), _jsx(UiConfirmDialog, { isOpen: isAlreadyOwnerOpen, title: intl.formatMessage(objectShareMessages.transferAlreadyOwnerTitle, {
142
+ name: state.transferTarget?.name ?? "",
143
+ }), description: _jsxs(_Fragment, { children: [
144
+ _jsx("p", { children: intl.formatMessage(objectShareMessages.transferAlreadyOwnerDescription) }), _jsx(UiRadioRow, { name: "transfer-already-owner", checked: !state.transferAlsoRemoveSelf, onChange: () => actions.setTransferAlsoRemoveSelf(false), title: intl.formatMessage(objectShareMessages.transferAlreadyOwnerKeepAccess) }), _jsx(UiRadioRow, { name: "transfer-already-owner", checked: state.transferAlsoRemoveSelf, onChange: () => actions.setTransferAlsoRemoveSelf(true), title: intl.formatMessage(objectShareMessages.transferAlreadyOwnerRemoveAccess) })
145
+ ] }), confirmLabel: intl.formatMessage(objectShareMessages.transferConfirmButton), confirmVariant: "primary", onCancel: actions.closeTransferOwnership, onClose: actions.closeTransferOwnership, onConfirm: () => {
146
+ void actions.confirmTransferOwnership();
115
147
  } }), _jsx(UiConfirmDialog, { isOpen: isConfirmOpen, title: intl.formatMessage(state.pendingGeneralAccess === "RESTRICTED"
116
148
  ? objectShareMessages.confirmRestrictTitle
117
149
  : objectShareMessages.confirmGrantWorkspaceTitle), description: intl.formatMessage(state.pendingGeneralAccess === "RESTRICTED"
@@ -22,6 +22,21 @@ export declare const objectShareMessages: {
22
22
  confirmButton: {
23
23
  id: string;
24
24
  };
25
+ transferAlreadyOwnerTitle: {
26
+ id: string;
27
+ };
28
+ transferAlreadyOwnerDescription: {
29
+ id: string;
30
+ };
31
+ transferAlreadyOwnerKeepAccess: {
32
+ id: string;
33
+ };
34
+ transferAlreadyOwnerRemoveAccess: {
35
+ id: string;
36
+ };
37
+ transferConfirmButton: {
38
+ id: string;
39
+ };
25
40
  toastGranteeAdded: {
26
41
  id: string;
27
42
  };
@@ -31,6 +46,9 @@ export declare const objectShareMessages: {
31
46
  toastGeneralAccessUpdated: {
32
47
  id: string;
33
48
  };
49
+ toastOwnershipTransferred: {
50
+ id: string;
51
+ };
34
52
  toastError: {
35
53
  id: string;
36
54
  };
@@ -1 +1 @@
1
- {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/share/messages.ts"],"names":[],"mappings":"AAIA;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAY9B,CAAC"}
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/share/messages.ts"],"names":[],"mappings":"AAIA;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkB9B,CAAC"}
@@ -14,9 +14,15 @@ export const objectShareMessages = defineMessages({
14
14
  confirmGrantWorkspaceTitle: { id: "objectShare.confirm.grantWorkspace.title" },
15
15
  confirmGrantWorkspaceDescription: { id: "objectShare.confirm.grantWorkspace.description" },
16
16
  confirmButton: { id: "objectShare.confirm.button" },
17
+ transferAlreadyOwnerTitle: { id: "objectShare.transfer.alreadyOwner.title" },
18
+ transferAlreadyOwnerDescription: { id: "objectShare.transfer.alreadyOwner.description" },
19
+ transferAlreadyOwnerKeepAccess: { id: "objectShare.transfer.alreadyOwner.keepAccess" },
20
+ transferAlreadyOwnerRemoveAccess: { id: "objectShare.transfer.alreadyOwner.removeAccess" },
21
+ transferConfirmButton: { id: "objectShare.transfer.confirmButton" },
17
22
  toastGranteeAdded: { id: "objectShare.toast.granteeAdded" },
18
23
  toastAccessUpdated: { id: "objectShare.toast.accessUpdated" },
19
24
  toastGeneralAccessUpdated: { id: "objectShare.toast.generalAccessUpdated" },
25
+ toastOwnershipTransferred: { id: "objectShare.toast.ownershipTransferred" },
20
26
  toastError: { id: "objectShare.toast.error" },
21
27
  toastLabelScopePartial: { id: "objectShare.toast.labelScopePartial" },
22
28
  loadError: { id: "objectShare.loadError" },
@@ -1,5 +1,5 @@
1
1
  import type { AccessGranularPermission, ObjRef } from "@gooddata/sdk-model";
2
- import type { GeneralAccessValue, IUiGranteeAsyncOptions, IUiPickedGrantee } from "@gooddata/sdk-ui-kit";
2
+ import type { GeneralAccessValue, IUiGranteeAsyncOption, IUiGranteeAsyncOptions, IUiPickedGrantee } from "@gooddata/sdk-ui-kit";
3
3
  import type { IObjectAccessSummary, IObjectShareLabel } from "./types.js";
4
4
  /**
5
5
  * Permission level surfaced in the share dialog — the model's granular access
@@ -48,7 +48,7 @@ export interface IObjectShareGrantee {
48
48
  * @internal
49
49
  */
50
50
  export interface IObjectShareControllerState {
51
- subview: "main" | "addGrantee";
51
+ subview: "main" | "addGrantee" | "transferOwnership";
52
52
  status: "idle" | "loading" | "success" | "error" | "saving";
53
53
  error?: Error;
54
54
  /**
@@ -97,6 +97,26 @@ export interface IObjectShareControllerState {
97
97
  pendingGeneralAccess?: GeneralAccessValue;
98
98
  /** Grantees staged in the add-grantee dialog before confirmation. */
99
99
  pendingGrantees: IUiPickedGrantee[];
100
+ /**
101
+ * The user picked as the new owner in the transfer-ownership subview, or
102
+ * undefined before one is chosen. Only users can own an object, so this is
103
+ * never a group.
104
+ */
105
+ transferTarget: IUiGranteeAsyncOption | undefined;
106
+ /**
107
+ * Whether "Also remove my access" is checked in the transfer dialog. When
108
+ * true the current user's grant is removed after the transfer; otherwise it
109
+ * is downgraded to VIEW.
110
+ */
111
+ transferAlsoRemoveSelf: boolean;
112
+ /**
113
+ * Whether `transferTarget` already owns the object (already holds EDIT). When
114
+ * true there is nothing to transfer — consumers show the "already an owner"
115
+ * variant offering to remove the current user's own access instead.
116
+ */
117
+ transferTargetIsOwner: boolean;
118
+ /** Whether the transfer write (or self-removal) is in flight. */
119
+ transferSaving: boolean;
100
120
  }
101
121
  /**
102
122
  * @internal
@@ -108,11 +128,13 @@ export interface IObjectShareControllerActions {
108
128
  closeAddGrantee: () => void;
109
129
  setPendingGrantees: (next: IUiPickedGrantee[]) => void;
110
130
  /**
111
- * Loader for the add-grantee picker. Wraps `getAvailableAssignees` with
131
+ * Loader for the grantee pickers. Wraps `getAvailableAssignees` with
112
132
  * client-side search + already-picked filtering, returning the picker's
113
- * `{ groups, users }` shape.
133
+ * `{ groups, users }` shape. By default excludes already-granted grantees
134
+ * (add-grantee picker); pass `includeGranted` to keep them (transfer-ownership
135
+ * picker, which may promote an existing viewer to owner).
114
136
  */
115
- loadOptions: (search: string) => Promise<IUiGranteeAsyncOptions>;
137
+ loadOptions: (search: string, includeGranted?: boolean) => Promise<IUiGranteeAsyncOptions>;
116
138
  /** Commit all pending grantees to the backend. */
117
139
  confirmAddGrantees: () => Promise<void>;
118
140
  /** Change the permission level for a single grantee. Auto-saves. */
@@ -136,6 +158,24 @@ export interface IObjectShareControllerActions {
136
158
  * already-granted rule.
137
159
  */
138
160
  changeWorkspaceLevel: (level: "VIEW" | "SHARE") => Promise<void>;
161
+ /** Open the transfer-ownership subview with an empty picker. */
162
+ openTransferOwnership: () => void;
163
+ /** Close the transfer-ownership subview and clear its buffers; returns to main. */
164
+ closeTransferOwnership: () => void;
165
+ /**
166
+ * Pick (or replace) the candidate new owner. Recomputes whether that user
167
+ * already owns the object (`transferTargetIsOwner`).
168
+ */
169
+ setTransferTarget: (owner: IUiGranteeAsyncOption) => void;
170
+ /** Toggle "Also remove my access" in the transfer dialog. */
171
+ setTransferAlsoRemoveSelf: (next: boolean) => void;
172
+ /**
173
+ * Commit the ownership transfer: grant the picked user EDIT and downgrade the
174
+ * current user to VIEW (or remove them if "Also remove my access" is set), in
175
+ * one write. When the picked user already owns the object this transfers
176
+ * nothing and only applies the self-access choice. Auto-saves.
177
+ */
178
+ confirmTransferOwnership: () => Promise<void>;
139
179
  }
140
180
  /**
141
181
  * @internal
@@ -1 +1 @@
1
- {"version":3,"file":"objectShareController.types.d.ts","sourceRoot":"","sources":["../../src/share/objectShareController.types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"objectShareController.types.d.ts","sourceRoot":"","sources":["../../src/share/objectShareController.types.ts"],"names":[],"mappings":"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"}
@@ -36,8 +36,20 @@ export interface IAccessList {
36
36
  commit: (mutate: IGranularAccessGrantee[], successMessage: {
37
37
  id: string;
38
38
  }) => Promise<boolean>;
39
- /** Picker loader — available assignees minus already-granted, filtered by query. */
40
- loadOptions: (search: string) => Promise<IUiGranteeAsyncOptions>;
39
+ /**
40
+ * Picker loader available assignees filtered by query. By default excludes
41
+ * already-granted grantees (add-grantee picker); pass `includeGranted` to keep
42
+ * them (transfer-ownership picker, which may promote an existing viewer).
43
+ */
44
+ loadOptions: (search: string, includeGranted?: boolean) => Promise<IUiGranteeAsyncOptions>;
45
+ /**
46
+ * The current user's ref, resolved on demand (and cached). Used by the
47
+ * transfer-ownership flow to write the current user's own grant change. The
48
+ * add-grantee *picker* excludes the current user, but `getAccessList` does
49
+ * not, so a self grant can still appear as a row. Rejects if the profile
50
+ * can't be read.
51
+ */
52
+ getCurrentUserRef: () => Promise<ObjRef>;
41
53
  /** Resolve a grantee id back to the picker's original ObjRef (preserves Uri vs Id ref). */
42
54
  refForId: (id: string) => ObjRef;
43
55
  /** Write through a local grantee-row change (insert / level / remove); rolled back by the caller. */
@@ -1 +1 @@
1
- {"version":3,"file":"useAccessList.d.ts","sourceRoot":"","sources":["../../src/share/useAccessList.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAC1E,OAAO,EAEH,KAAK,sBAAsB,EAC3B,KAAK,MAAM,EAEd,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,KAAK,kBAAkB,EAAE,KAAK,sBAAsB,EAAmB,MAAM,sBAAsB,CAAC;AAW7G,OAAO,KAAK,EAAE,2BAA2B,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACzG,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD;;;;GAIG;AACH,MAAM,WAAW,WAAW;IACxB,iFAAiF;IACjF,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,wFAAwF;IACxF,OAAO,EAAE,OAAO,CAAC;IACjB,iGAA+F;IAC/F,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,uFAAqF;IACrF,aAAa,EAAE,kBAAkB,CAAC;IAClC,6FAA2F;IAC3F,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC;IACjC,iEAAiE;IACjE,OAAO,EAAE,oBAAoB,GAAG,SAAS,CAAC;IAC1C,+DAA+D;IAC/D,MAAM,EAAE,2BAA2B,CAAC,QAAQ,CAAC,CAAC;IAC9C,iDAAiD;IACjD,SAAS,EAAE,KAAK,GAAG,SAAS,CAAC;IAC7B;;;;;OAKG;IACH,iBAAiB,EAAE,OAAO,CAAC;IAE3B,mFAAmF;IACnF,MAAM,EAAE,CAAC,MAAM,EAAE,sBAAsB,EAAE,EAAE,cAAc,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/F,sFAAoF;IACpF,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,sBAAsB,CAAC,CAAC;IACjE,2FAA2F;IAC3F,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;IAEjC,qGAAqG;IACrG,WAAW,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,mBAAmB,EAAE,CAAC,CAAC,CAAC;IACzE,8EAA8E;IAC9E,gBAAgB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,kBAAkB,CAAC,CAAC,CAAC;IAC3E;;;;OAIG;IACH,iBAAiB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAC1E,aAAa,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;CAC/E;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CACzB,MAAM,EAAE,wBAAwB,GAAG,SAAS,EAC5C,OAAO,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,GAClC,WAAW,CA4Qb"}
1
+ {"version":3,"file":"useAccessList.d.ts","sourceRoot":"","sources":["../../src/share/useAccessList.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAC1E,OAAO,EAEH,KAAK,sBAAsB,EAC3B,KAAK,MAAM,EAEd,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,KAAK,kBAAkB,EAAE,KAAK,sBAAsB,EAAmB,MAAM,sBAAsB,CAAC;AAW7G,OAAO,KAAK,EAAE,2BAA2B,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACzG,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD;;;;GAIG;AACH,MAAM,WAAW,WAAW;IACxB,iFAAiF;IACjF,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,wFAAwF;IACxF,OAAO,EAAE,OAAO,CAAC;IACjB,iGAA+F;IAC/F,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,uFAAqF;IACrF,aAAa,EAAE,kBAAkB,CAAC;IAClC,6FAA2F;IAC3F,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC;IACjC,iEAAiE;IACjE,OAAO,EAAE,oBAAoB,GAAG,SAAS,CAAC;IAC1C,+DAA+D;IAC/D,MAAM,EAAE,2BAA2B,CAAC,QAAQ,CAAC,CAAC;IAC9C,iDAAiD;IACjD,SAAS,EAAE,KAAK,GAAG,SAAS,CAAC;IAC7B;;;;;OAKG;IACH,iBAAiB,EAAE,OAAO,CAAC;IAE3B,mFAAmF;IACnF,MAAM,EAAE,CAAC,MAAM,EAAE,sBAAsB,EAAE,EAAE,cAAc,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/F;;;;OAIG;IACH,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC3F;;;;;;OAMG;IACH,iBAAiB,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACzC,2FAA2F;IAC3F,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;IAEjC,qGAAqG;IACrG,WAAW,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,mBAAmB,EAAE,CAAC,CAAC,CAAC;IACzE,8EAA8E;IAC9E,gBAAgB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,kBAAkB,CAAC,CAAC,CAAC;IAC3E;;;;OAIG;IACH,iBAAiB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAC1E,aAAa,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;CAC/E;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CACzB,MAAM,EAAE,wBAAwB,GAAG,SAAS,EAC5C,OAAO,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,GAClC,WAAW,CAoSb"}
@@ -1,5 +1,5 @@
1
1
  // (C) 2026 GoodData Corporation
2
- import { useCallback, useEffect, useMemo, useState } from "react";
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
3
  import { objRefToString, } from "@gooddata/sdk-model";
4
4
  import { useBackendStrict, useCancelablePromise, useWorkspaceStrict } from "@gooddata/sdk-ui";
5
5
  import { useToastMessage } from "@gooddata/sdk-ui-kit";
@@ -41,6 +41,9 @@ export function useAccessList(target, onSaved) {
41
41
  // Original ObjRef per grantee id, learned from the picker (the access-list id
42
42
  // is a serialized `kind:identifier`, which loses Uri-vs-Id). Reused for writes.
43
43
  const [knownRefs, setKnownRefs] = useState({});
44
+ // Memoized current-user-ref fetch — the profile doesn't change while mounted,
45
+ // so resolve it at most once and share the promise across callers.
46
+ const currentUserRefCache = useRef(undefined);
44
47
  const targetKey = target ? objRefToString(target.ref) : undefined;
45
48
  // Learn each assignee's real name + ref into the caches, keyed by the same
46
49
  // grantee id the rows use. Written from both the picker (loadOptions) and the
@@ -190,10 +193,14 @@ export function useAccessList(target, onSaved) {
190
193
  }
191
194
  }, [backend, workspace, target, toast, onSaved]);
192
195
  // Picker loader — fetches available assignees on demand, filters by the typed
193
- // query, excludes anything already granted. Depends only on the granted set
194
- // (grantees), so its identity changes only when that set actually changes. It
195
- // must NOT write state that feeds its own deps.
196
- const loadOptions = useCallback(async (search) => {
196
+ // query, and by default excludes anything already granted. Depends only on the
197
+ // granted set (grantees), so its identity changes only when that set actually
198
+ // changes. It must NOT write state that feeds its own deps.
199
+ //
200
+ // `includeGranted` keeps already-granted grantees in the result. The
201
+ // transfer-ownership picker needs this: promoting an existing viewer to owner
202
+ // is a primary case, and the add-grantee exclusion would otherwise hide them.
203
+ const loadOptions = useCallback(async (search, includeGranted = false) => {
197
204
  if (!target) {
198
205
  return { groups: [], users: [] };
199
206
  }
@@ -210,7 +217,7 @@ export function useAccessList(target, onSaved) {
210
217
  // even when the access-list grant later returns only a raw id. Safe here:
211
218
  // the caches aren't dependencies of loadOptions, so this won't re-trigger it.
212
219
  cacheAssignees(assignees);
213
- const excluded = new Set(excludedIdsKey ? excludedIdsKey.split(",") : []);
220
+ const excluded = new Set(includeGranted || !excludedIdsKey ? [] : excludedIdsKey.split(","));
214
221
  const selectable = withIds
215
222
  .filter(({ id }) => !excluded.has(id)) // hide anyone already granted
216
223
  .filter(({ assignee }) => assigneeMatchesQuery(assignee, query));
@@ -231,6 +238,21 @@ export function useAccessList(target, onSaved) {
231
238
  // Reuse the picker's original ref (preserves UriRef vs IdentifierRef);
232
239
  // fall back to the serialized id only if it wasn't cached.
233
240
  const refForId = useCallback((id) => knownRefs[id] ?? { identifier: id.split(":", 2)[1] }, [knownRefs]);
241
+ const getCurrentUserRef = useCallback(async () => {
242
+ if (!currentUserRefCache.current) {
243
+ currentUserRefCache.current = backend
244
+ .currentUser()
245
+ .getUser()
246
+ .then((user) => user.ref)
247
+ .catch((error) => {
248
+ // Don't cache a rejected promise, or a transient profile-read
249
+ // failure would make every later transfer fail immediately.
250
+ currentUserRefCache.current = undefined;
251
+ throw error;
252
+ });
253
+ }
254
+ return currentUserRefCache.current;
255
+ }, [backend]);
234
256
  return {
235
257
  targetKey,
236
258
  hasList,
@@ -244,6 +266,7 @@ export function useAccessList(target, onSaved) {
244
266
  commit,
245
267
  loadOptions,
246
268
  refForId,
269
+ getCurrentUserRef,
247
270
  setGrantees,
248
271
  setGeneralAccess,
249
272
  setWorkspaceLevel,
@@ -1 +1 @@
1
- {"version":3,"file":"useObjectShareController.d.ts","sourceRoot":"","sources":["../../src/share/useObjectShareController.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAa1E,OAAO,EACH,KAAK,sBAAsB,EAI3B,KAAK,sBAAsB,EAE9B,MAAM,kCAAkC,CAAC;AAI1C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,wBAAwB,CACpC,MAAM,EAAE,wBAAwB,GAAG,SAAS,EAC5C,OAAO,CAAC,EAAE,sBAAsB,GACjC,sBAAsB,CA8jBxB"}
1
+ {"version":3,"file":"useObjectShareController.d.ts","sourceRoot":"","sources":["../../src/share/useObjectShareController.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAkB1E,OAAO,EACH,KAAK,sBAAsB,EAI3B,KAAK,sBAAsB,EAE9B,MAAM,kCAAkC,CAAC;AAI1C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,wBAAwB,CACpC,MAAM,EAAE,wBAAwB,GAAG,SAAS,EAC5C,OAAO,CAAC,EAAE,sBAAsB,GACjC,sBAAsB,CAwyBxB"}
@@ -1,6 +1,7 @@
1
1
  // (C) 2026 GoodData Corporation
2
2
  import { useCallback, useMemo, useRef, useState } from "react";
3
- import { useToastMessage } from "@gooddata/sdk-ui-kit";
3
+ import { areObjRefsEqual } from "@gooddata/sdk-model";
4
+ import { useToastMessage, } from "@gooddata/sdk-ui-kit";
4
5
  import { objectShareMessages } from "./messages.js";
5
6
  import { EMPTY_IDS, NO_LABELS, effectivePermissionAbove, granularGranteeFor, toGranularGrantee, } from "./objectShareController.helpers.js";
6
7
  import { useAccessList } from "./useAccessList.js";
@@ -35,6 +36,10 @@ export function useObjectShareController(target, options) {
35
36
  const [subview, setSubview] = useState("main");
36
37
  const [pendingGrantees, setPendingGrantees] = useState([]);
37
38
  const [pendingGeneralAccess, setPendingGeneralAccess] = useState(undefined);
39
+ // Transfer-ownership buffers. Cleared on close and on target switch.
40
+ const [transferTarget, setTransferTargetState] = useState(undefined);
41
+ const [transferAlsoRemoveSelf, setTransferAlsoRemoveSelf] = useState(false);
42
+ const [transferSaving, setTransferSaving] = useState(false);
38
43
  // True while a workspace-level re-grade is committing. The workspace rule has no
39
44
  // grantee row to carry a `pending` flag (unlike named grantees), so this gates
40
45
  // re-entry and disables the dropdown to keep rapid VIEW↔SHARE toggles from issuing
@@ -42,7 +47,7 @@ export function useObjectShareController(target, options) {
42
47
  const [workspaceLevelSaving, setWorkspaceLevelSaving] = useState(false);
43
48
  // The backend access list, seeded into local state (rows, general access,
44
49
  // summary, the commit/picker primitives) lives in its own hook.
45
- const { targetKey, hasList, grantees, generalAccess, workspaceLevel, summary, status, loadError, accessUnavailable, commit, loadOptions, refForId, setGrantees, setGeneralAccess, setWorkspaceLevel, setKnownNames, } = useAccessList(target, onSaved);
50
+ const { targetKey, hasList, grantees, generalAccess, workspaceLevel, summary, status, loadError, accessUnavailable, commit, loadOptions, refForId, getCurrentUserRef, setGrantees, setGeneralAccess, setWorkspaceLevel, setKnownNames, } = useAccessList(target, onSaved);
46
51
  // Always-current target key, read inside async mutation finalizers that captured
47
52
  // an older one. A write started for object A resolves after the user navigated to
48
53
  // object B; because local state is authoritative (no refetch), applying A's
@@ -69,12 +74,21 @@ export function useObjectShareController(target, options) {
69
74
  setPendingGrantees([]);
70
75
  setPendingGeneralAccess(undefined);
71
76
  setWorkspaceLevelSaving(false);
77
+ setTransferTargetState(undefined);
78
+ setTransferAlsoRemoveSelf(false);
79
+ setTransferSaving(false);
72
80
  }
73
81
  const reset = useCallback(() => {
74
82
  setSubview("main");
75
83
  setPendingGrantees([]);
76
84
  setPendingGeneralAccess(undefined);
77
85
  setWorkspaceLevelSaving(false);
86
+ setTransferTargetState(undefined);
87
+ setTransferAlsoRemoveSelf(false);
88
+ // Deliberately NOT clearing transferSaving here: closing the dialog must not
89
+ // release the transfer lock while the write is still in flight, or a reopened
90
+ // dialog could mutate concurrently with the unfinished transfer. The in-flight
91
+ // confirmTransferOwnership clears it itself once the write settles.
78
92
  }, []);
79
93
  const openAddGrantee = useCallback(() => {
80
94
  setSubview("addGrantee");
@@ -426,6 +440,173 @@ export function useObjectShareController(target, options) {
426
440
  }
427
441
  setWorkspaceLevelSaving(false);
428
442
  }, [generalAccess, workspaceLevel, workspaceLevelSaving, targetKey, commit, setWorkspaceLevel]);
443
+ const openTransferOwnership = useCallback(() => {
444
+ setSubview("transferOwnership");
445
+ setTransferTargetState(undefined);
446
+ setTransferAlsoRemoveSelf(false);
447
+ }, []);
448
+ const closeTransferOwnership = useCallback(() => {
449
+ setSubview("main");
450
+ setTransferTargetState(undefined);
451
+ setTransferAlsoRemoveSelf(false);
452
+ }, []);
453
+ const setTransferTarget = useCallback((owner) => {
454
+ setTransferTargetState(owner);
455
+ }, []);
456
+ // The picked user already owns the object when a grantee row for them holds
457
+ // EDIT — then there's nothing to transfer (the dialog offers to remove the
458
+ // current user's own access instead).
459
+ const transferTargetIsOwner = useMemo(() => transferTarget ? grantees.some((g) => g.id === transferTarget.id && g.level === "EDIT") : false, [transferTarget, grantees]);
460
+ // Insert a brand-new grantee row with its label scope and display name — the
461
+ // shared seed for a freshly-granted principal (used when a transfer promotes a
462
+ // user who wasn't already a grantee; the same row shape the add flow creates).
463
+ const seedNewGranteeRow = useCallback((row, labelIds) => {
464
+ setGrantees((prev) => [...prev, row]);
465
+ setKnownNames((prev) => ({ ...prev, [row.id]: row.name }));
466
+ setSelectedLabelIdsByGrantee((prev) => ({ ...prev, [row.id]: labelIds }));
467
+ }, [setGrantees, setKnownNames, setSelectedLabelIdsByGrantee]);
468
+ const confirmTransferOwnership = useCallback(async () => {
469
+ if (!transferTarget || transferSaving) {
470
+ return;
471
+ }
472
+ const startedFor = targetKey;
473
+ const ownerRef = refForId(transferTarget.id);
474
+ let selfRef;
475
+ try {
476
+ selfRef = await getCurrentUserRef();
477
+ }
478
+ catch {
479
+ toast.addError(objectShareMessages.toastError);
480
+ return;
481
+ }
482
+ if (targetKeyRef.current !== startedFor) {
483
+ return;
484
+ }
485
+ // Guard against transferring to yourself: the picker's loader already
486
+ // excludes the current user, but if a backend ever returned self the batch
487
+ // below would emit conflicting grants for one ref (EDIT + VIEW/none). Bail
488
+ // and just close — there is nothing to transfer to oneself.
489
+ if (areObjRefsEqual(ownerRef, selfRef)) {
490
+ closeTransferOwnership();
491
+ return;
492
+ }
493
+ // Build the write. A real transfer grants the picked user EDIT and changes
494
+ // the current user's own grant: kept as VIEW by default, or removed when
495
+ // "Also remove my access" is set. When the picked user already owns the
496
+ // object there's nothing to transfer — only the self-access choice applies,
497
+ // and "keep" is then a pure no-op (don't downgrade an existing owner-self to
498
+ // VIEW just for confirming).
499
+ const mutations = [];
500
+ if (!transferTargetIsOwner) {
501
+ mutations.push(toGranularGrantee("user", ownerRef, "EDIT"));
502
+ mutations.push(toGranularGrantee("user", selfRef, transferAlsoRemoveSelf ? "none" : "VIEW"));
503
+ }
504
+ else if (transferAlsoRemoveSelf) {
505
+ mutations.push(toGranularGrantee("user", selfRef, "none"));
506
+ }
507
+ if (mutations.length === 0) {
508
+ closeTransferOwnership();
509
+ return;
510
+ }
511
+ // Whether the new owner already has a grantee row (was granted before this
512
+ // transfer). A brand-new owner needs a row seeded and their labels mirrored,
513
+ // exactly like the add-grantee flow; an existing grantee already has both.
514
+ const existingOwnerRow = transferTargetIsOwner
515
+ ? undefined
516
+ : grantees.find((g) => g.id === transferTarget.id);
517
+ const ownerIsNew = !transferTargetIsOwner && !existingOwnerRow;
518
+ const allLabelIds = effectiveLabels.map((l) => l.id);
519
+ setTransferSaving(true);
520
+ // "Ownership transferred" only when ownership actually moves; the
521
+ // already-owner path just changes the current user's own access.
522
+ const successMessage = transferTargetIsOwner
523
+ ? objectShareMessages.toastAccessUpdated
524
+ : objectShareMessages.toastOwnershipTransferred;
525
+ const ok = await commit(mutations, successMessage);
526
+ if (targetKeyRef.current !== startedFor) {
527
+ return;
528
+ }
529
+ if (!ok) {
530
+ setTransferSaving(false);
531
+ return;
532
+ }
533
+ // Mirror per-label grants for the same reason add/remove do: label
534
+ // permissions are independent access-list entries, so the object write
535
+ // alone would leave a new owner without label access, or leave a removed
536
+ // self with orphaned label grants. A failed label write is non-fatal here
537
+ // (the object grant is what matters) — surfaced as a warning, like remove.
538
+ //
539
+ // Do every backend label write first, then apply all local state behind a
540
+ // single target guard. Since no local write happens before that guard, a
541
+ // target switch during a label await can't land a half-applied transfer on
542
+ // the next object's seeded list — unlike the optimistic finalizers, which
543
+ // write before committing and so must re-guard after each await.
544
+ let labelsOk = true;
545
+ if (ownerIsNew) {
546
+ // Grant the brand-new owner every label — the add-grantee default scope.
547
+ const granted = await reconcileLabelScope({ kind: "user", granteeRef: ownerRef }, new Set(allLabelIds), EMPTY_IDS);
548
+ labelsOk = labelsOk && granted;
549
+ }
550
+ if (transferAlsoRemoveSelf) {
551
+ // Self lost object access — revoke their per-label grants too.
552
+ const revoked = await reconcileLabelScope({ kind: "user", granteeRef: selfRef }, EMPTY_IDS, new Set(allLabelIds));
553
+ labelsOk = labelsOk && revoked;
554
+ }
555
+ if (targetKeyRef.current !== startedFor) {
556
+ return;
557
+ }
558
+ // Reflect the object-grant changes locally (authoritative, no refetch).
559
+ if (!transferTargetIsOwner) {
560
+ const nextEffective = effectivePermissionAbove("EDIT", []);
561
+ if (ownerIsNew) {
562
+ seedNewGranteeRow({
563
+ id: transferTarget.id,
564
+ kind: "user",
565
+ granteeRef: ownerRef,
566
+ name: transferTarget.name,
567
+ level: "EDIT",
568
+ effectivePermission: nextEffective,
569
+ }, allLabelIds);
570
+ }
571
+ else {
572
+ // Existing grantee: their label scope is unchanged by an
573
+ // object-level promotion, so only the row's level is raised.
574
+ setGrantees((prev) => prev.map((g) => g.id === transferTarget.id
575
+ ? { ...g, level: "EDIT", effectivePermission: nextEffective }
576
+ : g));
577
+ }
578
+ }
579
+ // The current user may have their own grantee row (the access list keeps
580
+ // every grant, including self) — reflect their object-grant change locally
581
+ // too: dropped when "remove my access", otherwise downgraded to VIEW. Match
582
+ // by ref since self isn't tied to a picker-derived row id.
583
+ setGrantees((prev) => transferAlsoRemoveSelf
584
+ ? prev.filter((g) => !areObjRefsEqual(g.granteeRef, selfRef))
585
+ : prev.map((g) => areObjRefsEqual(g.granteeRef, selfRef)
586
+ ? { ...g, level: "VIEW", effectivePermission: undefined }
587
+ : g));
588
+ if (!labelsOk) {
589
+ toast.addWarning(objectShareMessages.toastLabelScopePartial);
590
+ }
591
+ setTransferSaving(false);
592
+ closeTransferOwnership();
593
+ }, [
594
+ transferTarget,
595
+ transferSaving,
596
+ transferTargetIsOwner,
597
+ transferAlsoRemoveSelf,
598
+ targetKey,
599
+ grantees,
600
+ effectiveLabels,
601
+ refForId,
602
+ getCurrentUserRef,
603
+ commit,
604
+ reconcileLabelScope,
605
+ toast,
606
+ setGrantees,
607
+ seedNewGranteeRow,
608
+ closeTransferOwnership,
609
+ ]);
429
610
  const actions = useMemo(() => ({
430
611
  reset,
431
612
  openAddGrantee,
@@ -440,6 +621,11 @@ export function useObjectShareController(target, options) {
440
621
  cancelGeneralAccessChange: () => setPendingGeneralAccess(undefined),
441
622
  confirmGeneralAccessChange,
442
623
  changeWorkspaceLevel,
624
+ openTransferOwnership,
625
+ closeTransferOwnership,
626
+ setTransferTarget,
627
+ setTransferAlsoRemoveSelf,
628
+ confirmTransferOwnership,
443
629
  }), [
444
630
  reset,
445
631
  openAddGrantee,
@@ -452,6 +638,10 @@ export function useObjectShareController(target, options) {
452
638
  requestGeneralAccessChange,
453
639
  confirmGeneralAccessChange,
454
640
  changeWorkspaceLevel,
641
+ openTransferOwnership,
642
+ closeTransferOwnership,
643
+ setTransferTarget,
644
+ confirmTransferOwnership,
455
645
  ]);
456
646
  const state = useMemo(() => ({
457
647
  subview,
@@ -471,6 +661,10 @@ export function useObjectShareController(target, options) {
471
661
  selectedLabelIdsByGrantee,
472
662
  pendingGeneralAccess,
473
663
  pendingGrantees,
664
+ transferTarget,
665
+ transferAlsoRemoveSelf,
666
+ transferTargetIsOwner,
667
+ transferSaving,
474
668
  }), [
475
669
  subview,
476
670
  status,
@@ -486,6 +680,10 @@ export function useObjectShareController(target, options) {
486
680
  selectedLabelIdsByGrantee,
487
681
  pendingGeneralAccess,
488
682
  pendingGrantees,
683
+ transferTarget,
684
+ transferAlsoRemoveSelf,
685
+ transferTargetIsOwner,
686
+ transferSaving,
489
687
  ]);
490
688
  return useMemo(() => ({ state, actions }), [state, actions]);
491
689
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gooddata/sdk-ui-ext",
3
- "version": "11.44.0",
3
+ "version": "11.45.0-alpha.1",
4
4
  "description": "GoodData.UI SDK - Extensions",
5
5
  "license": "LicenseRef-LICENSE",
6
6
  "author": "GoodData Corporation",
@@ -63,20 +63,20 @@
63
63
  "ts-invariant": "0.10.3",
64
64
  "tslib": "2.8.1",
65
65
  "uuid": "11.1.1",
66
- "@gooddata/sdk-backend-base": "11.44.0",
67
- "@gooddata/sdk-backend-spi": "11.44.0",
68
- "@gooddata/sdk-embedding": "11.44.0",
69
- "@gooddata/sdk-model": "11.44.0",
70
- "@gooddata/sdk-ui": "11.44.0",
71
- "@gooddata/sdk-ui-filters": "11.44.0",
72
- "@gooddata/sdk-ui-charts": "11.44.0",
73
- "@gooddata/sdk-ui-geo": "11.44.0",
74
- "@gooddata/sdk-ui-kit": "11.44.0",
75
- "@gooddata/sdk-ui-pivot": "11.44.0",
76
- "@gooddata/sdk-ui-theme-provider": "11.44.0",
77
- "@gooddata/sdk-ui-semantic-search": "11.44.0",
78
- "@gooddata/sdk-ui-vis-commons": "11.44.0",
79
- "@gooddata/util": "11.44.0"
66
+ "@gooddata/sdk-backend-base": "11.45.0-alpha.1",
67
+ "@gooddata/sdk-backend-spi": "11.45.0-alpha.1",
68
+ "@gooddata/sdk-embedding": "11.45.0-alpha.1",
69
+ "@gooddata/sdk-model": "11.45.0-alpha.1",
70
+ "@gooddata/sdk-ui": "11.45.0-alpha.1",
71
+ "@gooddata/sdk-ui-charts": "11.45.0-alpha.1",
72
+ "@gooddata/sdk-ui-filters": "11.45.0-alpha.1",
73
+ "@gooddata/sdk-ui-geo": "11.45.0-alpha.1",
74
+ "@gooddata/sdk-ui-pivot": "11.45.0-alpha.1",
75
+ "@gooddata/sdk-ui-semantic-search": "11.45.0-alpha.1",
76
+ "@gooddata/sdk-ui-theme-provider": "11.45.0-alpha.1",
77
+ "@gooddata/sdk-ui-kit": "11.45.0-alpha.1",
78
+ "@gooddata/util": "11.45.0-alpha.1",
79
+ "@gooddata/sdk-ui-vis-commons": "11.45.0-alpha.1"
80
80
  },
81
81
  "devDependencies": {
82
82
  "@microsoft/api-documenter": "^7.17.0",
@@ -124,12 +124,12 @@
124
124
  "typescript": "5.9.3",
125
125
  "vitest": "4.1.8",
126
126
  "vitest-dom": "0.1.1",
127
- "@gooddata/i18n-toolkit": "11.44.0",
128
- "@gooddata/oxlint-config": "11.44.0",
129
- "@gooddata/reference-workspace": "11.44.0",
130
- "@gooddata/sdk-backend-mockingbird": "11.44.0",
131
- "@gooddata/eslint-config": "11.44.0",
132
- "@gooddata/stylelint-config": "11.44.0"
127
+ "@gooddata/eslint-config": "11.45.0-alpha.1",
128
+ "@gooddata/oxlint-config": "11.45.0-alpha.1",
129
+ "@gooddata/reference-workspace": "11.45.0-alpha.1",
130
+ "@gooddata/i18n-toolkit": "11.45.0-alpha.1",
131
+ "@gooddata/sdk-backend-mockingbird": "11.45.0-alpha.1",
132
+ "@gooddata/stylelint-config": "11.45.0-alpha.1"
133
133
  },
134
134
  "peerDependencies": {
135
135
  "react": "^18.0.0 || ^19.0.0",