@powerportalspro/react-fluent 6.1.0 → 7.0.0

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.cjs CHANGED
@@ -304,8 +304,12 @@ function isRecordWriteDenied(record) {
304
304
  const perms = record.permissions ?? 0;
305
305
  return (perms & core.TableSecurityPermission.Write) === 0;
306
306
  }
307
- function resolveEditState(metadata, record, explicitReadOnly, explicitDisabled) {
308
- if (isRecordWriteDenied(record)) {
307
+ function isColumnWriteDenied(record, columnName) {
308
+ if (!record || !columnName) return false;
309
+ return !core.canWriteColumn(record, columnName);
310
+ }
311
+ function resolveEditState(metadata, record, explicitReadOnly, explicitDisabled, columnName) {
312
+ if (isRecordWriteDenied(record) || isColumnWriteDenied(record, columnName)) {
309
313
  return { readOnly: true, disabled: true };
310
314
  }
311
315
  return {
@@ -733,7 +737,8 @@ function TextEdit({
733
737
  metadata,
734
738
  record,
735
739
  readOnly,
736
- disabled
740
+ disabled,
741
+ columnName
737
742
  );
738
743
  const setValue = useGuardedSetValue(rawSetValue, resolvedReadOnly, resolvedDisabled);
739
744
  const validationErrors = useColumnValidation(
@@ -827,7 +832,8 @@ function MemoEdit({
827
832
  metadata,
828
833
  record,
829
834
  readOnly,
830
- disabled
835
+ disabled,
836
+ columnName
831
837
  );
832
838
  const setValue = useGuardedSetValue(rawSetValue, resolvedReadOnly, resolvedDisabled);
833
839
  const validationErrors = useColumnValidation(
@@ -941,7 +947,8 @@ function BoolEdit({
941
947
  metadata,
942
948
  record,
943
949
  readOnly,
944
- disabled
950
+ disabled,
951
+ columnName
945
952
  );
946
953
  const setValue = useGuardedSetValue(rawSetValue, resolvedReadOnly, resolvedDisabled);
947
954
  const isInactive = resolvedDisabled || resolvedReadOnly;
@@ -1117,7 +1124,8 @@ function NumberEdit({
1117
1124
  metadata,
1118
1125
  record,
1119
1126
  readOnly,
1120
- disabled
1127
+ disabled,
1128
+ columnName
1121
1129
  );
1122
1130
  const setValue = useGuardedSetValue(rawSetValue, resolvedReadOnly, resolvedDisabled);
1123
1131
  const validationErrors = useColumnValidation(
@@ -1561,7 +1569,8 @@ function MoneyEdit({
1561
1569
  metadata,
1562
1570
  record,
1563
1571
  readOnly,
1564
- disabled
1572
+ disabled,
1573
+ columnName
1565
1574
  );
1566
1575
  const setValue = useGuardedSetValue(rawSetValue, resolvedReadOnly, resolvedDisabled);
1567
1576
  const validationErrors = useColumnValidation(
@@ -1975,7 +1984,8 @@ function DateTimeEdit({
1975
1984
  metadata,
1976
1985
  record,
1977
1986
  readOnly,
1978
- disabled
1987
+ disabled,
1988
+ columnName
1979
1989
  );
1980
1990
  const setValue = useGuardedSetValue(rawSetValue, resolvedReadOnly, resolvedDisabled);
1981
1991
  const resolvedEditorType = editorType ?? resolveEditorTypeFromMetadata(metadata);
@@ -5366,7 +5376,8 @@ function FileEdit({
5366
5376
  metadata,
5367
5377
  record,
5368
5378
  readOnly,
5369
- disabled
5379
+ disabled,
5380
+ columnName
5370
5381
  );
5371
5382
  const setValue = useGuardedSetValue(rawSetValue, resolvedReadOnly, resolvedDisabled);
5372
5383
  const isInteractable = !resolvedReadOnly && !resolvedDisabled;
@@ -5683,7 +5694,8 @@ function ImageEdit({
5683
5694
  metadata,
5684
5695
  record,
5685
5696
  readOnly,
5686
- disabled
5697
+ disabled,
5698
+ columnName
5687
5699
  );
5688
5700
  const setValue = useGuardedSetValue(rawSetValue, resolvedReadOnly, resolvedDisabled);
5689
5701
  const isInteractable = !resolvedReadOnly && !resolvedDisabled;
@@ -6163,7 +6175,8 @@ function ChoiceEdit({
6163
6175
  metadata,
6164
6176
  record,
6165
6177
  readOnly,
6166
- disabled
6178
+ disabled,
6179
+ columnName
6167
6180
  );
6168
6181
  const setValue = useGuardedSetValue(rawSetValue, resolvedReadOnly, resolvedDisabled);
6169
6182
  const choiceMeta = getChoiceMetadata(metadata);
@@ -6383,7 +6396,8 @@ function MultiSelectChoiceEdit({
6383
6396
  metadata,
6384
6397
  record,
6385
6398
  readOnly,
6386
- disabled
6399
+ disabled,
6400
+ columnName
6387
6401
  );
6388
6402
  const setValue = useGuardedSetValue(rawSetValue, resolvedReadOnly, resolvedDisabled);
6389
6403
  const choiceMeta = getChoiceMetadata(metadata);
@@ -6505,30 +6519,30 @@ function foldString(s, caseSensitive) {
6505
6519
  function tokenizeSearch(search, delimiters) {
6506
6520
  if (!delimiters) return [search];
6507
6521
  const delim = new Set(Array.from(delimiters));
6508
- const tokens43 = [];
6522
+ const tokens44 = [];
6509
6523
  let current = "";
6510
6524
  for (const ch of search) {
6511
6525
  if (delim.has(ch)) {
6512
6526
  if (current.length > 0) {
6513
- tokens43.push(current);
6527
+ tokens44.push(current);
6514
6528
  current = "";
6515
6529
  }
6516
6530
  } else {
6517
6531
  current += ch;
6518
6532
  }
6519
6533
  }
6520
- if (current.length > 0) tokens43.push(current);
6521
- return tokens43;
6534
+ if (current.length > 0) tokens44.push(current);
6535
+ return tokens44;
6522
6536
  }
6523
6537
  function splitHighlightSegments(text, search, options) {
6524
6538
  if (!text || !search) return [];
6525
6539
  const caseSensitive = options?.caseSensitive ?? false;
6526
6540
  const delimiters = options?.delimiters ?? "";
6527
- const tokens43 = tokenizeSearch(search, delimiters);
6528
- if (tokens43.length === 0) return [{ text, isMatch: false }];
6541
+ const tokens44 = tokenizeSearch(search, delimiters);
6542
+ if (tokens44.length === 0) return [{ text, isMatch: false }];
6529
6543
  const folded = foldString(text, caseSensitive);
6530
6544
  const ranges = [];
6531
- for (const token of tokens43) {
6545
+ for (const token of tokens44) {
6532
6546
  const tokenFolded = foldString(token, caseSensitive).folded;
6533
6547
  if (tokenFolded.length === 0) continue;
6534
6548
  let cursor2 = 0;
@@ -7299,7 +7313,11 @@ function getRendererTypeForCode(typeCode) {
7299
7313
  }
7300
7314
  function ColumnEdit(props) {
7301
7315
  const metadata = react$1.useColumnMetadata(props.columnName);
7316
+ const record = react$1.useRecordContextOptional()?.record;
7302
7317
  const typeCode = metadata?.$type;
7318
+ if (record && !core.canReadColumn(record, props.columnName)) {
7319
+ return /* @__PURE__ */ jsxRuntime.jsx(ColumnDisplayValue, { ...props });
7320
+ }
7303
7321
  switch (typeCode) {
7304
7322
  case 0:
7305
7323
  return /* @__PURE__ */ jsxRuntime.jsx(BoolEdit, { ...props });
@@ -8412,6 +8430,12 @@ var ViewSort = {
8412
8430
  /** Localized display name, descending. */
8413
8431
  NameDescending: "nameDescending"
8414
8432
  };
8433
+ function resolveViewLabel(view, t) {
8434
+ const key = `tables.${view.tableName}.views.${view.id}.label`;
8435
+ const localized = t(key);
8436
+ if (localized !== key) return localized;
8437
+ return view.displayName?.trim() ? view.displayName : null;
8438
+ }
8415
8439
  function customToViewMetadata(custom) {
8416
8440
  return {
8417
8441
  id: custom.id,
@@ -9088,7 +9112,7 @@ function MainGridImpl({
9088
9112
  const rowRecord = cellCtx.row.original;
9089
9113
  const rowId = rowRecord.id;
9090
9114
  const isPendingCreateRow = !!rowId && rowId.startsWith("__pending-create-");
9091
- if (editable && !isPendingCreateRow && hasEditorForType) {
9115
+ if (editable && !isPendingCreateRow && hasEditorForType && core.canWriteColumn(rowRecord, columnName)) {
9092
9116
  return (
9093
9117
  // Sizing wrapper — overrides Fluent v9's default
9094
9118
  // <Input> / <Combobox> / <Dropdown> min-width
@@ -9778,8 +9802,8 @@ function MainGridImpl({
9778
9802
  };
9779
9803
  const selectedView = views?.find((v) => v.id === resolvedViewId);
9780
9804
  const selectedViewLabelKey = selectedView ? `tables.${selectedView.tableName}.views.${selectedView.id}.label` : "";
9781
- const selectedViewLabel = selectedView ? t(selectedViewLabelKey) : "";
9782
- const singleViewTitle = selectedView && selectedViewLabel !== selectedViewLabelKey ? selectedViewLabel : selectedView?.displayName ?? void 0;
9805
+ const selectedViewLabel = selectedView ? resolveViewLabel(selectedView, t) ?? selectedViewLabelKey : "";
9806
+ const singleViewTitle = selectedView ? resolveViewLabel(selectedView, t) ?? void 0 : void 0;
9783
9807
  return /* @__PURE__ */ jsxRuntime.jsx(GridContextProvider, { value: gridContextValue, children: /* @__PURE__ */ jsxRuntime.jsxs(
9784
9808
  "div",
9785
9809
  {
@@ -9812,7 +9836,7 @@ function MainGridImpl({
9812
9836
  if (data2.optionValue) handleViewChange(data2.optionValue);
9813
9837
  },
9814
9838
  children: views?.map((view) => {
9815
- const label = t(`tables.${view.tableName}.views.${view.id}.label`);
9839
+ const label = resolveViewLabel(view, t) ?? `tables.${view.tableName}.views.${view.id}.label`;
9816
9840
  return /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Option, { value: view.id, text: label, children: label }, view.id);
9817
9841
  })
9818
9842
  }
@@ -10908,7 +10932,8 @@ function LookupEdit({
10908
10932
  metadata,
10909
10933
  record,
10910
10934
  readOnly,
10911
- disabled
10935
+ disabled,
10936
+ columnName
10912
10937
  );
10913
10938
  const setLookupValue = useGuardedSetValue(
10914
10939
  rawSetLookupValue,
@@ -12688,6 +12713,7 @@ function PowerPortalsProThemeProvider({
12688
12713
  defaultMode = ThemeMode.System,
12689
12714
  defaultAccentColor = "default",
12690
12715
  defaultTextDirection = TextDirection.Ltr,
12716
+ lockAccentColor = false,
12691
12717
  customAccents,
12692
12718
  storageKey = DEFAULT_MODE_STORAGE_KEY,
12693
12719
  style,
@@ -12711,9 +12737,11 @@ function PowerPortalsProThemeProvider({
12711
12737
  () => readStoredMode(storageKey) ?? defaultMode
12712
12738
  );
12713
12739
  const [accentColor, setAccentColorState] = react.useState(() => {
12740
+ const configured = defaultAccentColor in accentMap ? defaultAccentColor : "default";
12741
+ if (lockAccentColor) return configured;
12714
12742
  const stored = readStoredString(accentStorageKey);
12715
12743
  if (stored && stored in accentMap) return stored;
12716
- return defaultAccentColor in accentMap ? defaultAccentColor : "default";
12744
+ return configured;
12717
12745
  });
12718
12746
  const [textDirection, setTextDirectionState] = react.useState(
12719
12747
  () => readStoredDirection(directionStorageKey) ?? defaultTextDirection
@@ -12743,6 +12771,12 @@ function PowerPortalsProThemeProvider({
12743
12771
  document.body.classList.toggle("ppp-theme-dark", isDark);
12744
12772
  document.body.classList.toggle("ppp-theme-light", !isDark);
12745
12773
  }, [isDark]);
12774
+ react.useEffect(() => {
12775
+ if (!lockAccentColor) return;
12776
+ const configured = defaultAccentColor in accentMap ? defaultAccentColor : "default";
12777
+ setAccentColorState(configured);
12778
+ writeStored(accentStorageKey, configured);
12779
+ }, [lockAccentColor, defaultAccentColor, accentMap, accentStorageKey]);
12746
12780
  const setMode = react.useCallback(
12747
12781
  (next) => {
12748
12782
  setModeState(next);
@@ -12752,11 +12786,12 @@ function PowerPortalsProThemeProvider({
12752
12786
  );
12753
12787
  const setAccentColor = react.useCallback(
12754
12788
  (next) => {
12789
+ if (lockAccentColor) return;
12755
12790
  if (!(next in accentMap)) return;
12756
12791
  setAccentColorState(next);
12757
12792
  writeStored(accentStorageKey, next);
12758
12793
  },
12759
- [accentMap, accentStorageKey]
12794
+ [lockAccentColor, accentMap, accentStorageKey]
12760
12795
  );
12761
12796
  const setTextDirection = react.useCallback(
12762
12797
  (next) => {
@@ -12781,6 +12816,7 @@ function PowerPortalsProThemeProvider({
12781
12816
  setAccentColor,
12782
12817
  availableAccents,
12783
12818
  accentColors,
12819
+ accentColorLocked: lockAccentColor,
12784
12820
  textDirection,
12785
12821
  setTextDirection
12786
12822
  }),
@@ -12792,6 +12828,7 @@ function PowerPortalsProThemeProvider({
12792
12828
  setAccentColor,
12793
12829
  availableAccents,
12794
12830
  accentColors,
12831
+ lockAccentColor,
12795
12832
  textDirection,
12796
12833
  setTextDirection
12797
12834
  ]
@@ -12872,7 +12909,7 @@ function ThemeColorSelector({
12872
12909
  width,
12873
12910
  accentLabels
12874
12911
  }) {
12875
- const { accentColor, setAccentColor, availableAccents, accentColors } = useTheme();
12912
+ const { accentColor, setAccentColor, availableAccents, accentColors, accentColorLocked } = useTheme();
12876
12913
  const t = react$1.useT();
12877
12914
  const labelId = react.useId();
12878
12915
  const styles = useStyles6();
@@ -12886,6 +12923,7 @@ function ThemeColorSelector({
12886
12923
  },
12887
12924
  [t, accentLabels]
12888
12925
  );
12926
+ if (accentColorLocked) return null;
12889
12927
  const handleOptionSelect = (_event, data) => {
12890
12928
  const next = data.optionValue;
12891
12929
  if (next && availableAccents.includes(next)) {
@@ -13053,7 +13091,304 @@ function LanguageDropdown({
13053
13091
  }
13054
13092
  );
13055
13093
  }
13094
+ var MINIMUM_QUERY_LENGTH = 3;
13095
+ var MAXIMUM_RESULTS = 50;
13096
+ var SEARCH_DEBOUNCE_MS4 = 300;
13097
+ var GUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
13056
13098
  var useStyles7 = reactComponents.makeStyles({
13099
+ surface: {
13100
+ maxWidth: "480px",
13101
+ display: "flex",
13102
+ flexDirection: "column"
13103
+ },
13104
+ titleRow: {
13105
+ display: "flex",
13106
+ flexDirection: "row",
13107
+ alignItems: "flex-start",
13108
+ columnGap: reactComponents.tokens.spacingHorizontalS
13109
+ },
13110
+ titleText: {
13111
+ flexGrow: 1,
13112
+ minWidth: 0
13113
+ },
13114
+ body: {
13115
+ display: "flex",
13116
+ flexDirection: "column",
13117
+ rowGap: reactComponents.tokens.spacingVerticalM,
13118
+ paddingTop: reactComponents.tokens.spacingVerticalM
13119
+ },
13120
+ results: {
13121
+ display: "flex",
13122
+ flexDirection: "column",
13123
+ // Fixed height, not min/max: a list that resizes on every keystroke makes the confirm button
13124
+ // jump around under the pointer while the administrator is still reading.
13125
+ height: "280px",
13126
+ overflowY: "auto",
13127
+ // Full shorthand — griffel types `borderColor` and friends as `never`, so the longhand
13128
+ // properties aren't usable here.
13129
+ border: `1px solid ${reactComponents.tokens.colorNeutralStroke1}`,
13130
+ borderRadius: reactComponents.tokens.borderRadiusMedium,
13131
+ backgroundColor: reactComponents.tokens.colorNeutralBackground1
13132
+ },
13133
+ result: {
13134
+ display: "flex",
13135
+ flexDirection: "row",
13136
+ alignItems: "center",
13137
+ columnGap: reactComponents.tokens.spacingHorizontalS,
13138
+ padding: `${reactComponents.tokens.spacingVerticalS} ${reactComponents.tokens.spacingHorizontalM}`,
13139
+ cursor: "pointer",
13140
+ borderBottom: `1px solid ${reactComponents.tokens.colorNeutralStroke2}`,
13141
+ textAlign: "left",
13142
+ background: "none",
13143
+ // A native <button> does NOT inherit colour or font from its container — the user-agent
13144
+ // stylesheet gives it `color: buttontext` and its own font stack. Without these three the
13145
+ // rows rendered as near-black text on the dark surface in dark mode, which is how this was
13146
+ // first spotted. Set explicitly rather than relying on inheritance.
13147
+ color: reactComponents.tokens.colorNeutralForeground1,
13148
+ fontFamily: "inherit",
13149
+ fontSize: reactComponents.tokens.fontSizeBase300,
13150
+ // The UA also gives buttons a border; `background: none` alone leaves it drawing a box
13151
+ // around every row. Only three sides — borderBottom above is the row separator.
13152
+ borderTop: "none",
13153
+ borderRight: "none",
13154
+ borderLeft: "none",
13155
+ ":hover": {
13156
+ backgroundColor: reactComponents.tokens.colorNeutralBackground1Hover,
13157
+ color: reactComponents.tokens.colorNeutralForeground1Hover
13158
+ },
13159
+ ":focus-visible": {
13160
+ outline: `2px solid ${reactComponents.tokens.colorStrokeFocus2}`,
13161
+ outlineOffset: "-2px"
13162
+ }
13163
+ },
13164
+ resultSelected: {
13165
+ backgroundColor: reactComponents.tokens.colorBrandBackground,
13166
+ color: reactComponents.tokens.colorNeutralForegroundOnBrand,
13167
+ ":hover": {
13168
+ backgroundColor: reactComponents.tokens.colorBrandBackgroundHover,
13169
+ // Repeated because the base row's :hover also sets colour, and whichever griffel emits
13170
+ // last would otherwise win — leaving selected-and-hovered text unreadable on the brand fill.
13171
+ color: reactComponents.tokens.colorNeutralForegroundOnBrand
13172
+ }
13173
+ },
13174
+ resultText: {
13175
+ display: "flex",
13176
+ flexDirection: "column",
13177
+ minWidth: 0
13178
+ },
13179
+ name: {
13180
+ fontSize: reactComponents.tokens.fontSizeBase300,
13181
+ overflow: "hidden",
13182
+ textOverflow: "ellipsis",
13183
+ whiteSpace: "nowrap"
13184
+ },
13185
+ email: {
13186
+ fontSize: reactComponents.tokens.fontSizeBase200,
13187
+ opacity: 0.8,
13188
+ overflow: "hidden",
13189
+ textOverflow: "ellipsis",
13190
+ whiteSpace: "nowrap"
13191
+ },
13192
+ status: {
13193
+ display: "flex",
13194
+ flexDirection: "row",
13195
+ alignItems: "center",
13196
+ columnGap: reactComponents.tokens.spacingHorizontalS,
13197
+ padding: reactComponents.tokens.spacingVerticalXL,
13198
+ color: reactComponents.tokens.colorNeutralForeground3,
13199
+ fontSize: reactComponents.tokens.fontSizeBase200
13200
+ },
13201
+ warning: {
13202
+ display: "flex",
13203
+ flexDirection: "row",
13204
+ alignItems: "flex-start",
13205
+ columnGap: reactComponents.tokens.spacingHorizontalS,
13206
+ padding: `${reactComponents.tokens.spacingVerticalS} ${reactComponents.tokens.spacingHorizontalM}`,
13207
+ borderRadius: reactComponents.tokens.borderRadiusMedium,
13208
+ backgroundColor: reactComponents.tokens.colorNeutralBackground2,
13209
+ borderLeft: `3px solid ${reactComponents.tokens.colorPaletteRedBackground3}`,
13210
+ fontSize: reactComponents.tokens.fontSizeBase200
13211
+ },
13212
+ footer: {
13213
+ display: "flex",
13214
+ flexDirection: "row",
13215
+ justifyContent: "flex-end",
13216
+ columnGap: reactComponents.tokens.spacingHorizontalS,
13217
+ paddingTop: reactComponents.tokens.spacingVerticalM
13218
+ }
13219
+ });
13220
+ function ImpersonationPickerDialog({
13221
+ open,
13222
+ onClose,
13223
+ returnUrl = "/",
13224
+ className
13225
+ }) {
13226
+ const auth = react$1.useAuth();
13227
+ const styles = useStyles7();
13228
+ const t = react$1.useT();
13229
+ const { searchImpersonationTargets, impersonate } = auth;
13230
+ const [query, setQuery] = react.useState("");
13231
+ const [results, setResults] = react.useState([]);
13232
+ const [selected, setSelected] = react.useState(null);
13233
+ const [isSearching, setIsSearching] = react.useState(false);
13234
+ const [hasSearched, setHasSearched] = react.useState(false);
13235
+ const [hasMore, setHasMore] = react.useState(false);
13236
+ const [isStarting, setIsStarting] = react.useState(false);
13237
+ const requestIdRef = react.useRef(0);
13238
+ react.useEffect(() => {
13239
+ if (open) return;
13240
+ setQuery("");
13241
+ setResults([]);
13242
+ setSelected(null);
13243
+ setIsSearching(false);
13244
+ setHasSearched(false);
13245
+ setHasMore(false);
13246
+ setIsStarting(false);
13247
+ }, [open]);
13248
+ const trimmed = query.trim();
13249
+ const isTooShort = trimmed.length > 0 && trimmed.length < MINIMUM_QUERY_LENGTH;
13250
+ react.useEffect(() => {
13251
+ if (!open) return;
13252
+ const requestId = ++requestIdRef.current;
13253
+ if (trimmed.length < MINIMUM_QUERY_LENGTH && !GUID_PATTERN.test(trimmed)) {
13254
+ setResults([]);
13255
+ setHasSearched(false);
13256
+ setHasMore(false);
13257
+ setIsSearching(false);
13258
+ return;
13259
+ }
13260
+ let cancelled = false;
13261
+ const timer = setTimeout(async () => {
13262
+ setIsSearching(true);
13263
+ try {
13264
+ const response = await searchImpersonationTargets(trimmed);
13265
+ if (cancelled || requestId !== requestIdRef.current) return;
13266
+ setResults(response.results ?? []);
13267
+ setHasMore(response.hasMore ?? false);
13268
+ setHasSearched(true);
13269
+ } catch {
13270
+ if (cancelled || requestId !== requestIdRef.current) return;
13271
+ setResults([]);
13272
+ setHasMore(false);
13273
+ setHasSearched(true);
13274
+ } finally {
13275
+ if (!cancelled && requestId === requestIdRef.current) {
13276
+ setIsSearching(false);
13277
+ }
13278
+ }
13279
+ }, SEARCH_DEBOUNCE_MS4);
13280
+ return () => {
13281
+ cancelled = true;
13282
+ clearTimeout(timer);
13283
+ };
13284
+ }, [open, searchImpersonationTargets, trimmed]);
13285
+ const handleQueryChange = react.useCallback((value) => {
13286
+ setQuery(value);
13287
+ setSelected(null);
13288
+ }, []);
13289
+ const handleConfirm = react.useCallback(async () => {
13290
+ if (!selected?.contactId) return;
13291
+ setIsStarting(true);
13292
+ try {
13293
+ const response = await impersonate(selected.contactId);
13294
+ if (response.result === core.ImpersonateResult.Started) {
13295
+ window.location.assign(returnUrl);
13296
+ return;
13297
+ }
13298
+ setIsStarting(false);
13299
+ onClose();
13300
+ } catch {
13301
+ setIsStarting(false);
13302
+ }
13303
+ }, [impersonate, onClose, returnUrl, selected]);
13304
+ return /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Dialog, { open, onOpenChange: (_, data) => {
13305
+ if (!data.open) onClose();
13306
+ }, children: /* @__PURE__ */ jsxRuntime.jsxs(
13307
+ reactComponents.DialogSurface,
13308
+ {
13309
+ className: reactComponents.mergeClasses("ppp-impersonation-picker-dialog", styles.surface, className),
13310
+ children: [
13311
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.titleRow, children: [
13312
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.DialogTitle, { className: styles.titleText, children: t("app.components.impersonation-picker.title") }),
13313
+ /* @__PURE__ */ jsxRuntime.jsx(
13314
+ reactComponents.Button,
13315
+ {
13316
+ appearance: "subtle",
13317
+ icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.Dismiss24Regular, {}),
13318
+ "aria-label": t("app.components.impersonation-picker.cancel"),
13319
+ onClick: onClose
13320
+ }
13321
+ )
13322
+ ] }),
13323
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.body, children: [
13324
+ /* @__PURE__ */ jsxRuntime.jsx(
13325
+ reactComponents.SearchBox,
13326
+ {
13327
+ value: query,
13328
+ onChange: (_, data) => handleQueryChange(data.value),
13329
+ placeholder: t("app.components.impersonation-picker.search-placeholder"),
13330
+ appearance: "outline",
13331
+ style: { width: "100%" }
13332
+ }
13333
+ ),
13334
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles.results, role: "listbox", children: isSearching ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.status, children: [
13335
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Spinner, { size: "tiny" }),
13336
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: t("app.components.impersonation-picker.searching") })
13337
+ ] }) : isTooShort ? (
13338
+ // Distinct from "no matches": the search hasn't run. Conflating the two reads as
13339
+ // "this person doesn't exist" when the user has simply typed too few characters.
13340
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles.status, children: t("app.components.impersonation-picker.hint", [String(MINIMUM_QUERY_LENGTH)]) })
13341
+ ) : hasSearched && results.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles.status, children: t("app.components.impersonation-picker.no-matches") }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
13342
+ results.map((candidate) => {
13343
+ const isSelected = selected?.contactId === candidate.contactId;
13344
+ return /* @__PURE__ */ jsxRuntime.jsxs(
13345
+ "button",
13346
+ {
13347
+ type: "button",
13348
+ role: "option",
13349
+ "aria-selected": isSelected,
13350
+ className: reactComponents.mergeClasses(
13351
+ styles.result,
13352
+ isSelected && styles.resultSelected
13353
+ ),
13354
+ onClick: () => setSelected(candidate),
13355
+ children: [
13356
+ /* @__PURE__ */ jsxRuntime.jsx(reactIcons.Person20Regular, {}),
13357
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: styles.resultText, children: [
13358
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles.name, children: candidate.fullName ?? candidate.email ?? candidate.contactId }),
13359
+ candidate.email && /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles.email, children: candidate.email })
13360
+ ] })
13361
+ ]
13362
+ },
13363
+ candidate.contactId
13364
+ );
13365
+ }),
13366
+ hasMore && /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles.status, children: t("app.components.impersonation-picker.too-many", [String(MAXIMUM_RESULTS)]) })
13367
+ ] }) }),
13368
+ selected && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.warning, children: [
13369
+ /* @__PURE__ */ jsxRuntime.jsx(reactIcons.Warning20Regular, {}),
13370
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: t("app.components.impersonation-picker.warning") })
13371
+ ] })
13372
+ ] }),
13373
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.footer, children: [
13374
+ /* @__PURE__ */ jsxRuntime.jsx(
13375
+ reactComponents.Button,
13376
+ {
13377
+ appearance: "primary",
13378
+ icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.EyeTracking20Regular, {}),
13379
+ disabled: !selected || isStarting,
13380
+ onClick: handleConfirm,
13381
+ children: t("app.components.impersonation-picker.confirm")
13382
+ }
13383
+ ),
13384
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Button, { appearance: "secondary", onClick: onClose, disabled: isStarting, children: t("app.components.impersonation-picker.cancel") })
13385
+ ] })
13386
+ ]
13387
+ }
13388
+ ) });
13389
+ }
13390
+ var IMPERSONATION_ROLE = "SystemAdmin";
13391
+ var useStyles8 = reactComponents.makeStyles({
13057
13392
  trigger: {
13058
13393
  backgroundColor: "transparent",
13059
13394
  border: "none",
@@ -13113,8 +13448,9 @@ function ProfileMainMenu({
13113
13448
  signInUrl = "/login"
13114
13449
  }) {
13115
13450
  const auth = react$1.useAuth();
13116
- const styles = useStyles7();
13451
+ const styles = useStyles8();
13117
13452
  const t = react$1.useT();
13453
+ const [isPickerOpen, setIsPickerOpen] = react.useState(false);
13118
13454
  const handleLogout = react.useCallback(async () => {
13119
13455
  try {
13120
13456
  await auth.logout();
@@ -13131,7 +13467,14 @@ function ProfileMainMenu({
13131
13467
  }
13132
13468
  }, [auth]);
13133
13469
  if (auth.status === react$1.AuthStatus.Loading) {
13134
- return /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Avatar, { className: "ppp-profile-main-menu", size: 28, "aria-label": t("app.messages.record-loading") });
13470
+ return /* @__PURE__ */ jsxRuntime.jsx(
13471
+ reactComponents.Avatar,
13472
+ {
13473
+ className: "ppp-profile-main-menu",
13474
+ size: 28,
13475
+ "aria-label": t("app.messages.record-loading")
13476
+ }
13477
+ );
13135
13478
  }
13136
13479
  if (auth.status === react$1.AuthStatus.Anonymous) {
13137
13480
  if (anonymousContent !== void 0) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: anonymousContent });
@@ -13152,61 +13495,126 @@ function ProfileMainMenu({
13152
13495
  const resolvedAccountLabel = accountLabel ?? t("app.components.profile-main-menu.account");
13153
13496
  const signedInAsLabel = t("app.components.profile-main-menu.signed-in-as");
13154
13497
  const logoutLabel = t("app.buttons.logout.label");
13498
+ const canImpersonate = (auth.user.roles?.includes(IMPERSONATION_ROLE) ?? false) && auth.user.portalUserType === core.PortalUserType.SystemUser;
13155
13499
  const altIdentityTableName = auth.user.altIdentityTableName;
13156
- const switchIdentityLabel = altIdentityTableName ? t(
13157
- "app.components.profile-main-menu.switch-to",
13158
- [
13159
- altIdentityTableName === "systemuser" ? t("app.components.profile-main-menu.alt-kind-systemuser") : t("app.components.profile-main-menu.alt-kind-contact")
13160
- ]
13161
- ) : null;
13162
- return /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.Menu, { children: [
13163
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsxRuntime.jsx(
13164
- "button",
13500
+ const switchIdentityLabel = altIdentityTableName ? t("app.components.profile-main-menu.switch-to", [
13501
+ altIdentityTableName === "systemuser" ? t("app.components.profile-main-menu.alt-kind-systemuser") : t("app.components.profile-main-menu.alt-kind-contact")
13502
+ ]) : null;
13503
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
13504
+ /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.Menu, { children: [
13505
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsxRuntime.jsx(
13506
+ "button",
13507
+ {
13508
+ type: "button",
13509
+ className: reactComponents.mergeClasses("ppp-profile-main-menu", styles.trigger),
13510
+ "aria-label": email || logoutLabel,
13511
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Avatar, { size: 28, initials, name: email })
13512
+ }
13513
+ ) }),
13514
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuPopover, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.popoverBody, children: [
13515
+ headerSlot,
13516
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.identity, children: [
13517
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Avatar, { size: 40, initials, name: email }),
13518
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.identityText, children: [
13519
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles.identityLabel, children: signedInAsLabel }),
13520
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles.identityValue, title: email, children: email })
13521
+ ] })
13522
+ ] }),
13523
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Divider, {}),
13524
+ /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.MenuList, { className: styles.actions, children: [
13525
+ accountUrl && /* @__PURE__ */ jsxRuntime.jsx(
13526
+ reactComponents.MenuItem,
13527
+ {
13528
+ icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.PersonEdit20Regular, {}),
13529
+ onClick: () => {
13530
+ window.location.href = accountUrl;
13531
+ },
13532
+ children: resolvedAccountLabel
13533
+ }
13534
+ ),
13535
+ switchIdentityLabel && /* @__PURE__ */ jsxRuntime.jsx(
13536
+ reactComponents.MenuItem,
13537
+ {
13538
+ icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ArrowSwap20Regular, {}),
13539
+ onClick: handleSwitchIdentity,
13540
+ children: switchIdentityLabel
13541
+ }
13542
+ ),
13543
+ canImpersonate && /* @__PURE__ */ jsxRuntime.jsx(
13544
+ reactComponents.MenuItem,
13545
+ {
13546
+ icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.EyeTracking20Regular, {}),
13547
+ onClick: () => setIsPickerOpen(true),
13548
+ children: t("app.components.profile-main-menu.impersonate")
13549
+ }
13550
+ ),
13551
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuItem, { icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.SignOut20Regular, {}), onClick: handleLogout, children: logoutLabel })
13552
+ ] }),
13553
+ footerSlot && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
13554
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Divider, {}),
13555
+ footerSlot
13556
+ ] })
13557
+ ] }) })
13558
+ ] }),
13559
+ canImpersonate && /* @__PURE__ */ jsxRuntime.jsx(
13560
+ ImpersonationPickerDialog,
13165
13561
  {
13166
- type: "button",
13167
- className: reactComponents.mergeClasses("ppp-profile-main-menu", styles.trigger),
13168
- "aria-label": email || logoutLabel,
13169
- children: /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Avatar, { size: 28, initials, name: email })
13562
+ open: isPickerOpen,
13563
+ onClose: () => setIsPickerOpen(false)
13170
13564
  }
13171
- ) }),
13172
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuPopover, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.popoverBody, children: [
13173
- headerSlot,
13174
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.identity, children: [
13175
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Avatar, { size: 40, initials, name: email }),
13176
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.identityText, children: [
13177
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles.identityLabel, children: signedInAsLabel }),
13178
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles.identityValue, title: email, children: email })
13179
- ] })
13180
- ] }),
13181
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Divider, {}),
13182
- /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.MenuList, { className: styles.actions, children: [
13183
- accountUrl && /* @__PURE__ */ jsxRuntime.jsx(
13184
- reactComponents.MenuItem,
13185
- {
13186
- icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.PersonEdit20Regular, {}),
13187
- onClick: () => {
13188
- window.location.href = accountUrl;
13189
- },
13190
- children: resolvedAccountLabel
13191
- }
13192
- ),
13193
- switchIdentityLabel && /* @__PURE__ */ jsxRuntime.jsx(
13194
- reactComponents.MenuItem,
13195
- {
13196
- icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ArrowSwap20Regular, {}),
13197
- onClick: handleSwitchIdentity,
13198
- children: switchIdentityLabel
13199
- }
13200
- ),
13201
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuItem, { icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.SignOut20Regular, {}), onClick: handleLogout, children: logoutLabel })
13202
- ] }),
13203
- footerSlot && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
13204
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Divider, {}),
13205
- footerSlot
13206
- ] })
13207
- ] }) })
13565
+ )
13208
13566
  ] });
13209
13567
  }
13568
+ function ImpersonationBanner({
13569
+ returnUrl = "/",
13570
+ className
13571
+ }) {
13572
+ const auth = react$1.useAuth();
13573
+ const t = react$1.useT();
13574
+ const [isStopping, setIsStopping] = react.useState(false);
13575
+ const { stopImpersonation } = auth;
13576
+ const handleStop = react.useCallback(async () => {
13577
+ setIsStopping(true);
13578
+ try {
13579
+ await stopImpersonation();
13580
+ window.location.assign(returnUrl);
13581
+ } catch {
13582
+ setIsStopping(false);
13583
+ }
13584
+ }, [returnUrl, stopImpersonation]);
13585
+ if (auth.status !== react$1.AuthStatus.Authenticated || !auth.user.isImpersonating) {
13586
+ return null;
13587
+ }
13588
+ const viewingAs = auth.user.email ?? auth.user.userName ?? "";
13589
+ const impersonator = auth.user.impersonatorName;
13590
+ return (
13591
+ // The class is placement only — PageLayout targets it to assign the grid row. It carries no
13592
+ // appearance of its own; Fluent owns all of that.
13593
+ /* @__PURE__ */ jsxRuntime.jsxs(
13594
+ reactComponents.MessageBar,
13595
+ {
13596
+ className: reactComponents.mergeClasses("ppp-impersonation-banner", className),
13597
+ intent: "error",
13598
+ children: [
13599
+ /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.MessageBarBody, { children: [
13600
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MessageBarTitle, { children: t("app.components.impersonation-banner.viewing-as", [viewingAs]) }),
13601
+ impersonator ? ` ${t("app.components.impersonation-banner.you-are", [impersonator])}` : null
13602
+ ] }),
13603
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MessageBarActions, { children: /* @__PURE__ */ jsxRuntime.jsx(
13604
+ reactComponents.Button,
13605
+ {
13606
+ icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ArrowExit20Regular, {}),
13607
+ iconPosition: "after",
13608
+ disabled: isStopping,
13609
+ onClick: handleStop,
13610
+ children: t("app.components.impersonation-banner.stop")
13611
+ }
13612
+ ) })
13613
+ ]
13614
+ }
13615
+ )
13616
+ );
13617
+ }
13210
13618
  var usePanelStyles = reactComponents.makeStyles({
13211
13619
  // Vertical stack of setting sections with consistent gap. Matches the
13212
13620
  // panel body the SiteSettingsButton's drawer renders, but extracted
@@ -13255,7 +13663,7 @@ function SiteSettingsPanel({
13255
13663
  }
13256
13664
  );
13257
13665
  }
13258
- var useStyles8 = reactComponents.makeStyles({
13666
+ var useStyles9 = reactComponents.makeStyles({
13259
13667
  // Wraps the standalone panel inside the drawer body to add the small
13260
13668
  // top inset that the popover's title visually expects. The panel
13261
13669
  // itself stays inset-free so it doesn't carry extra padding when
@@ -13275,7 +13683,7 @@ function SiteSettingsButton({
13275
13683
  accentLabels
13276
13684
  }) {
13277
13685
  const t = react$1.useT();
13278
- const styles = useStyles8();
13686
+ const styles = useStyles9();
13279
13687
  const [open, setOpen] = react.useState(false);
13280
13688
  const close = react.useCallback(() => setOpen(false), []);
13281
13689
  const label = t("app.site-settings-label");
@@ -13753,7 +14161,7 @@ function bestRouteMatch(routes, pathname) {
13753
14161
  }
13754
14162
  return best;
13755
14163
  }
13756
- var useStyles9 = reactComponents.makeStyles({
14164
+ var useStyles10 = reactComponents.makeStyles({
13757
14165
  // Shared scrim look. Both global (full-screen) and scoped variants reuse
13758
14166
  // these so the visual feel is identical — only the positioning differs.
13759
14167
  // Theme-aware translucent backdrop drawn via a `::before` pseudo-element
@@ -13815,7 +14223,7 @@ function FluentOverlayProvider({ children }) {
13815
14223
  ] });
13816
14224
  }
13817
14225
  function GlobalOverlayLayer() {
13818
- const styles = useStyles9();
14226
+ const styles = useStyles10();
13819
14227
  const overlay = react$1.useOverlayForTarget(void 0);
13820
14228
  if (!overlay) return null;
13821
14229
  return /* @__PURE__ */ jsxRuntime.jsx(ScrimContent, { overlay, className: reactComponents.mergeClasses(styles.scrim, styles.global) });
@@ -13826,7 +14234,7 @@ function FluentOverlayTarget({
13826
14234
  className,
13827
14235
  style
13828
14236
  }) {
13829
- const styles = useStyles9();
14237
+ const styles = useStyles10();
13830
14238
  const overlay = react$1.useOverlayForTarget(targetId);
13831
14239
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: reactComponents.mergeClasses("ppp-overlay-target", styles.scopedHost, className), style, children: [
13832
14240
  children,
@@ -13834,14 +14242,14 @@ function FluentOverlayTarget({
13834
14242
  ] });
13835
14243
  }
13836
14244
  function ScrimContent({ overlay, className }) {
13837
- const styles = useStyles9();
14245
+ const styles = useStyles10();
13838
14246
  const showProgress = overlay.showProgress !== false;
13839
14247
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, role: "status", "aria-live": "polite", children: [
13840
14248
  showProgress && /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Spinner, { size: "large" }),
13841
14249
  overlay.description && /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles.description, children: overlay.description })
13842
14250
  ] });
13843
14251
  }
13844
- var useStyles10 = reactComponents.makeStyles({
14252
+ var useStyles11 = reactComponents.makeStyles({
13845
14253
  // Centered placeholder used while prefixes are being fetched. Same
13846
14254
  // shape as the spinner blocks in `<NewRecordGridButton>` /
13847
14255
  // `<OpenRecordGridButton>` dialogs (~160px min height + vertical
@@ -13861,7 +14269,7 @@ function LocalizationBoundary({
13861
14269
  fallback,
13862
14270
  children
13863
14271
  }) {
13864
- const styles = useStyles10();
14272
+ const styles = useStyles11();
13865
14273
  const resolvedPrefixes = prefixes ?? EMPTY_PREFIXES;
13866
14274
  const { locale } = react$1.useLocale();
13867
14275
  react$1.useLocalization(resolvedPrefixes);
@@ -13883,7 +14291,7 @@ function LocalizationBoundary({
13883
14291
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: reactComponents.mergeClasses("ppp-localization-boundary", styles.fallback), role: "status", "aria-live": "polite", children: /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Spinner, { size: "medium" }) });
13884
14292
  }
13885
14293
  var EMPTY_PREFIXES = Object.freeze([]);
13886
- var useStyles11 = reactComponents.makeStyles({
14294
+ var useStyles12 = reactComponents.makeStyles({
13887
14295
  root: {
13888
14296
  display: "flex",
13889
14297
  flexDirection: "column",
@@ -13941,7 +14349,7 @@ function isSameLanguage(a, b) {
13941
14349
  return primaryA.length > 0 && primaryA === primary(b);
13942
14350
  }
13943
14351
  function LocalizationTranslator({ className }) {
13944
- const styles = useStyles11();
14352
+ const styles = useStyles12();
13945
14353
  const t = react$1.useT();
13946
14354
  const { ppp } = react$1.usePowerPortalsPro();
13947
14355
  const [availability, setAvailability] = react.useState(null);
@@ -14429,7 +14837,7 @@ function SectionColumnRenderer({
14429
14837
  }
14430
14838
  );
14431
14839
  }
14432
- var useStyles12 = reactComponents.makeStyles({
14840
+ var useStyles13 = reactComponents.makeStyles({
14433
14841
  root: {
14434
14842
  display: "flex",
14435
14843
  flexDirection: "column",
@@ -14552,7 +14960,7 @@ function cultureLabel(culture) {
14552
14960
  }
14553
14961
  }
14554
14962
  function LocalizationAdmin({ className }) {
14555
- const styles = useStyles12();
14963
+ const styles = useStyles13();
14556
14964
  const t = react$1.useT();
14557
14965
  const { ppp } = react$1.usePowerPortalsPro();
14558
14966
  const [overview, setOverview] = react.useState(null);
@@ -15022,7 +15430,7 @@ function MergedSourcesDialog({
15022
15430
  ] })
15023
15431
  ] }) }) });
15024
15432
  }
15025
- var useStyles13 = reactComponents.makeStyles({
15433
+ var useStyles14 = reactComponents.makeStyles({
15026
15434
  root: {
15027
15435
  display: "flex",
15028
15436
  flexDirection: "column",
@@ -15045,7 +15453,7 @@ var useStyles13 = reactComponents.makeStyles({
15045
15453
  });
15046
15454
  var toMs = (value) => Number(value);
15047
15455
  function CacheAdmin({ className }) {
15048
- const styles = useStyles13();
15456
+ const styles = useStyles14();
15049
15457
  const t = react$1.useT();
15050
15458
  const { ppp } = react$1.usePowerPortalsPro();
15051
15459
  const [cacheNames, setCacheNames] = react.useState([]);
@@ -15180,7 +15588,7 @@ function CacheAdmin({ className }) {
15180
15588
  ] }) : null
15181
15589
  ] });
15182
15590
  }
15183
- var useStyles14 = reactComponents.makeStyles({
15591
+ var useStyles15 = reactComponents.makeStyles({
15184
15592
  list: {
15185
15593
  margin: 0,
15186
15594
  paddingLeft: reactComponents.tokens.spacingHorizontalXL,
@@ -15195,7 +15603,7 @@ function ValidationSummary({
15195
15603
  className,
15196
15604
  style
15197
15605
  }) {
15198
- const styles = useStyles14();
15606
+ const styles = useStyles15();
15199
15607
  const t = react$1.useT();
15200
15608
  const validation = react$1.useValidationContext();
15201
15609
  const resolvedTitle = title === void 0 ? t("app.errors.form-validation-errors") : title;
@@ -16072,7 +16480,7 @@ function isWizardRecordPageElement(node) {
16072
16480
  const type = node.type;
16073
16481
  return !!type && type[PAGE_MARKER] === true;
16074
16482
  }
16075
- var useStyles15 = reactComponents.makeStyles({
16483
+ var useStyles16 = reactComponents.makeStyles({
16076
16484
  shell: {
16077
16485
  display: "flex",
16078
16486
  flexDirection: "column",
@@ -16214,7 +16622,7 @@ function WizardRecordFormInner({
16214
16622
  stepperVisibility,
16215
16623
  isCreate
16216
16624
  }) {
16217
- const styles = useStyles15();
16625
+ const styles = useStyles16();
16218
16626
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: reactComponents.mergeClasses("ppp-wizard-record-form", styles.shell), children: /* @__PURE__ */ jsxRuntime.jsx(
16219
16627
  reactUseWizard.Wizard,
16220
16628
  {
@@ -16248,7 +16656,7 @@ function WizardFooter({
16248
16656
  onCancel,
16249
16657
  cancelLabel
16250
16658
  }) {
16251
- const styles = useStyles15();
16659
+ const styles = useStyles16();
16252
16660
  const t = react$1.useT();
16253
16661
  const recordContext = react$1.useRecordContext();
16254
16662
  const wizard = reactUseWizard.useWizard();
@@ -17730,13 +18138,17 @@ var usePageLayoutStyles = reactComponents.makeStyles({
17730
18138
  position: "relative",
17731
18139
  display: "grid",
17732
18140
  gridTemplateColumns: "auto minmax(0, 1fr)",
17733
- gridTemplateRows: "auto 1fr auto",
18141
+ // Four rows, not three: the first carries the impersonation banner. It's an `auto` track and
18142
+ // the banner renders nothing for an ordinary session, so the row collapses to zero height and
18143
+ // the layout is unchanged whenever nobody is impersonating.
18144
+ gridTemplateRows: "auto auto 1fr auto",
17734
18145
  // Lets the PageLayout shrink when it's nested inside another
17735
18146
  // PageLayout's `<main>` (the outer main is a grid item; without
17736
18147
  // `min-width: 0` on this inner root the outer's `minmax(0, 1fr)`
17737
18148
  // track wouldn't be honored once content tries to grow wider).
17738
18149
  minWidth: 0,
17739
18150
  gridTemplateAreas: `
18151
+ 'impersonation impersonation'
17740
18152
  'header header'
17741
18153
  'navigation main'
17742
18154
  'footer footer'
@@ -17846,6 +18258,11 @@ var usePageLayoutStyles = reactComponents.makeStyles({
17846
18258
  childHeader: {
17847
18259
  borderBottomWidth: "1px"
17848
18260
  },
18261
+ impersonationBanner: {
18262
+ // Placement only. The banner is a Fluent MessageBar and Fluent owns everything about how it
18263
+ // looks; this grid assignment is the extent of PageLayout's involvement.
18264
+ gridArea: "impersonation"
18265
+ },
17849
18266
  navigation: {
17850
18267
  gridArea: "navigation",
17851
18268
  overflowY: "auto",
@@ -17972,6 +18389,7 @@ function PageLayout({
17972
18389
  ...resizable ? { "--ppp-nav-width": `${navWidth}px` } : {}
17973
18390
  };
17974
18391
  return /* @__PURE__ */ jsxRuntime.jsx(PageLayoutNestingContext.Provider, { value: { isNested: true }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: rootClass, style: rootStyle, children: [
18392
+ !isNested && /* @__PURE__ */ jsxRuntime.jsx(ImpersonationBanner, { className: styles.impersonationBanner }),
17975
18393
  header !== void 0 && headerVisible && /* @__PURE__ */ jsxRuntime.jsx(Header, { className: headerClass, ...headerStyle && { style: headerStyle }, children: header }),
17976
18394
  navigation !== void 0 && navigationVisible && /* @__PURE__ */ jsxRuntime.jsx(
17977
18395
  "nav",
@@ -18200,7 +18618,7 @@ var RADIAL_CHART_TYPES = /* @__PURE__ */ new Set([
18200
18618
  reactCharts.ChartType.Radar,
18201
18619
  reactCharts.ChartType.Funnel
18202
18620
  ]);
18203
- var useStyles16 = reactComponents.makeStyles({
18621
+ var useStyles17 = reactComponents.makeStyles({
18204
18622
  // The wrapper uses Fluent design tokens directly so the rounded bordered
18205
18623
  // look picks up theme changes. `colorNeutralBackground1` (white in light,
18206
18624
  // grey[16] in dark) matches the Blazor FluentUIChart's
@@ -18278,7 +18696,7 @@ function FluentUIChartImpl(props, ref) {
18278
18696
  showBorder = true,
18279
18697
  onElementClick
18280
18698
  } = props;
18281
- const styles = useStyles16();
18699
+ const styles = useStyles17();
18282
18700
  const containerRef = react.useRef(null);
18283
18701
  const palette = useFluentChartPalette(containerRef);
18284
18702
  const themedDatasets = react.useMemo(() => {
@@ -18359,7 +18777,7 @@ function assembleTheme(palette, yAxisPrefix, yAxisSuffix, xAxisPrefix, xAxisSuff
18359
18777
  if (orientation !== void 0) theme.indexAxis = reactCharts.toIndexAxis(orientation);
18360
18778
  return Object.keys(theme).length > 0 ? theme : void 0;
18361
18779
  }
18362
- var useStyles17 = reactComponents.makeStyles({
18780
+ var useStyles18 = reactComponents.makeStyles({
18363
18781
  // Outer container — flex column so the view selector, message bar, and
18364
18782
  // chart fill share the caller's height budget.
18365
18783
  root: {
@@ -18437,7 +18855,7 @@ var DataverseChart = react.forwardRef(
18437
18855
  } = props;
18438
18856
  const chartSource = dataSource instanceof reactCharts.DataverseChartDataSource ? dataSource : void 0;
18439
18857
  const viewDs = dataSource instanceof reactCharts.DataverseChartDataSource ? void 0 : dataSource;
18440
- const styles = useStyles17();
18858
+ const styles = useStyles18();
18441
18859
  const t = react$1.useT();
18442
18860
  const { ppp, viewMetadataCache } = react$1.usePowerPortalsPro();
18443
18861
  const [data, setData] = react.useState({ labels: [], datasets: [] });
@@ -18707,6 +19125,8 @@ exports.Header = Header;
18707
19125
  exports.Highlighter = Highlighter;
18708
19126
  exports.ImageEdit = ImageEdit;
18709
19127
  exports.ImageViewer = ImageViewer;
19128
+ exports.ImpersonationBanner = ImpersonationBanner;
19129
+ exports.ImpersonationPickerDialog = ImpersonationPickerDialog;
18710
19130
  exports.LabelPosition = LabelPosition;
18711
19131
  exports.LanguageDropdown = LanguageDropdown;
18712
19132
  exports.LinkExistingRecordGridButton = LinkExistingRecordGridButton;