@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.js CHANGED
@@ -1,9 +1,9 @@
1
- import { makeStyles, mergeClasses, Input, tokens, Toolbar, ToolbarButton, ToolbarDivider, Menu, MenuTrigger, MenuPopover, MenuList, MenuItemRadio, Button, createLightTheme, createDarkTheme, makeStaticStyles, MessageBar, MessageBarBody, Dropdown, Option, Spinner, Field, Textarea, Switch, Checkbox, Label, TabList, Tab, Tooltip, Dialog, DialogSurface, DialogBody, DialogTitle, DialogContent, DialogActions, RadioGroup, Radio, useId as useId$1, Text, Overflow, OverflowItem, SearchBox, CounterBadge, Popover, PopoverTrigger, PopoverSurface, MenuItem, Combobox, TagPicker, TagPickerControl, TagPickerGroup, Tag, TagPickerInput, TagPickerList, TagPickerOption, webDarkTheme, webLightTheme, teamsDarkTheme, teamsLightTheme, FluentProvider, Avatar, Divider, OverlayDrawer, DrawerHeader, DrawerHeaderTitle, DrawerBody, useOverflowMenu, useIsOverflowItemVisible, Title3, Table, TableHeader, TableRow, TableHeaderCell, TableBody, TableCell, createTableColumn, DataGrid, DataGridHeader, DataGridRow, DataGridHeaderCell, DataGridBody, DataGridCell, MessageBarTitle, DialogTrigger } from '@fluentui/react-components';
2
- import { MaskMode, useMaskedTextField, useT, usePowerPortalsPro, useRecordContext, useColumnMetadata, useLocale, MainContext, useTableMetadata, useWarnAboutUnsavedChanges, useMainContext, useViewsForTable, useViewMetadata, QueryStatus, useGridData, RecordContext, DialogServiceProvider, useUrlCultureStrategy, useDefaultLocale, useSupportedLocales, setAspNetCultureCookie, buildLocalePath, useAuth, AuthStatus, useOverlayServiceState, OverlayServiceProvider, useOverlayForTarget, useLocalization, useLocalizationLoaded, useValidationContext, useDialogService, useRunWithOverlay, useRecordContextOptional, useOrganizationSettings, useUnsavedChangesGuard, useColumnValidationErrors, isKeyModeEnabled } from '@powerportalspro/react';
1
+ import { makeStyles, mergeClasses, Input, tokens, Toolbar, ToolbarButton, ToolbarDivider, Menu, MenuTrigger, MenuPopover, MenuList, MenuItemRadio, Button, createLightTheme, createDarkTheme, makeStaticStyles, MessageBar, MessageBarBody, Dropdown, Option, Spinner, Field, Textarea, Switch, Checkbox, Label, TabList, Tab, Tooltip, Dialog, DialogSurface, DialogBody, DialogTitle, DialogContent, DialogActions, RadioGroup, Radio, useId as useId$1, Text, Overflow, OverflowItem, SearchBox, CounterBadge, Popover, PopoverTrigger, PopoverSurface, MenuItem, Combobox, TagPicker, TagPickerControl, TagPickerGroup, Tag, TagPickerInput, TagPickerList, TagPickerOption, webDarkTheme, webLightTheme, teamsDarkTheme, teamsLightTheme, FluentProvider, Avatar, Divider, MessageBarTitle, MessageBarActions, OverlayDrawer, DrawerHeader, DrawerHeaderTitle, DrawerBody, useOverflowMenu, useIsOverflowItemVisible, Title3, Table, TableHeader, TableRow, TableHeaderCell, TableBody, TableCell, createTableColumn, DataGrid, DataGridHeader, DataGridRow, DataGridHeaderCell, DataGridBody, DataGridCell, DialogTrigger } from '@fluentui/react-components';
2
+ import { MaskMode, useMaskedTextField, useT, usePowerPortalsPro, useRecordContext, useColumnMetadata, useLocale, useRecordContextOptional, MainContext, useTableMetadata, useWarnAboutUnsavedChanges, useMainContext, useViewsForTable, useViewMetadata, QueryStatus, useGridData, RecordContext, DialogServiceProvider, useUrlCultureStrategy, useDefaultLocale, useSupportedLocales, setAspNetCultureCookie, buildLocalePath, useAuth, AuthStatus, useOverlayServiceState, OverlayServiceProvider, useOverlayForTarget, useLocalization, useLocalizationLoaded, useValidationContext, useDialogService, useRunWithOverlay, useOrganizationSettings, useUnsavedChangesGuard, useColumnValidationErrors, isKeyModeEnabled } from '@powerportalspro/react';
3
3
  import { forwardRef, useRef, useImperativeHandle, useState, useEffect, useCallback, createContext, useMemo, useId, useLayoutEffect, useContext, useTransition, Children, isValidElement } from 'react';
4
4
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
5
- import { ArrowRotateCounterclockwise20Regular, ArrowRotateClockwise20Regular, FlipHorizontal20Regular, FlipVertical20Regular, ZoomIn20Regular, ZoomOut20Regular, ArrowReset20Regular, ChevronDown16Regular, Grid20Regular, Crop20Regular, FullScreenMinimize20Regular, FullScreenMaximize20Regular, ArrowDownload20Regular, Prohibited20Regular, Checkmark20Filled, ArrowDownload24Regular, ArrowClockwise24Regular, ChevronUp12Regular, ChevronDown12Regular, DocumentArrowLeft24Regular, ChevronDown20Regular, ChevronRight20Regular, Folder20Regular, Document20Regular, ErrorCircle20Regular, Eye20Regular, Dismiss20Regular, Edit20Regular, Add16Regular, Edit16Regular, LinkAdd16Regular, LinkDismiss16Regular, Delete16Regular, Settings20Regular, Search20Regular, ArrowClockwise20Regular, ArrowSortDown16Regular, ArrowSortUp16Regular, Dismiss24Regular, LinkAdd20Regular, QuestionCircle24Regular, Info24Regular, DismissCircle24Regular, Warning24Regular, CheckmarkCircle24Regular, ArrowExit20Regular, PersonEdit20Regular, ArrowSwap20Regular, SignOut20Regular, MoreVerticalRegular, ArrowSyncRegular, EyeRegular, ArrowDownloadRegular, SearchRegular, DismissRegular, Save24Regular, Delete24Regular, Add20Regular, Delete20Regular, LinkDismiss20Regular, ArrowUpload32Regular, ArrowUpload24Regular, ArrowSync24Regular, DrawerArrowDownload24Regular, Money20Regular, ErrorCircle12Filled, DocumentText20Regular, FolderZip20Regular, MusicNote220Regular, Video20Regular, Image20Regular, SlideText20Regular, DocumentTable20Regular, DocumentPdf20Regular, MoreHorizontal20Regular, ChevronDoubleLeft20Regular, ChevronLeft20Regular, ChevronDoubleRight20Regular, Info12Regular, Checkmark16Regular, Dismiss16Regular } from '@fluentui/react-icons';
6
- import { RequiredLevel, DateTimeBehavior, getRecordReferenceId, RelationshipFilterMode, SwitchIdentityResult, TableSecurityPermission } from '@powerportalspro/core';
5
+ import { ArrowRotateCounterclockwise20Regular, ArrowRotateClockwise20Regular, FlipHorizontal20Regular, FlipVertical20Regular, ZoomIn20Regular, ZoomOut20Regular, ArrowReset20Regular, ChevronDown16Regular, Grid20Regular, Crop20Regular, FullScreenMinimize20Regular, FullScreenMaximize20Regular, ArrowDownload20Regular, Prohibited20Regular, Checkmark20Filled, ArrowDownload24Regular, ArrowClockwise24Regular, ChevronUp12Regular, ChevronDown12Regular, DocumentArrowLeft24Regular, ChevronDown20Regular, ChevronRight20Regular, Folder20Regular, Document20Regular, ErrorCircle20Regular, Eye20Regular, Dismiss20Regular, Edit20Regular, Add16Regular, Edit16Regular, LinkAdd16Regular, LinkDismiss16Regular, Delete16Regular, Settings20Regular, Search20Regular, ArrowClockwise20Regular, ArrowSortDown16Regular, ArrowSortUp16Regular, Dismiss24Regular, LinkAdd20Regular, QuestionCircle24Regular, Info24Regular, DismissCircle24Regular, Warning24Regular, CheckmarkCircle24Regular, Person20Regular, Warning20Regular, EyeTracking20Regular, ArrowExit20Regular, PersonEdit20Regular, ArrowSwap20Regular, SignOut20Regular, MoreVerticalRegular, ArrowSyncRegular, EyeRegular, ArrowDownloadRegular, SearchRegular, DismissRegular, Save24Regular, Delete24Regular, Add20Regular, Delete20Regular, LinkDismiss20Regular, ArrowUpload32Regular, ArrowUpload24Regular, ArrowSync24Regular, DrawerArrowDownload24Regular, Money20Regular, ErrorCircle12Filled, DocumentText20Regular, FolderZip20Regular, MusicNote220Regular, Video20Regular, Image20Regular, SlideText20Regular, DocumentTable20Regular, DocumentPdf20Regular, MoreHorizontal20Regular, ChevronDoubleLeft20Regular, ChevronLeft20Regular, ChevronDoubleRight20Regular, Info12Regular, Checkmark16Regular, Dismiss16Regular } from '@fluentui/react-icons';
6
+ import { RequiredLevel, DateTimeBehavior, canReadColumn, getRecordReferenceId, RelationshipFilterMode, ImpersonateResult, SwitchIdentityResult, PortalUserType, TableSecurityPermission, canWriteColumn } from '@powerportalspro/core';
7
7
  import { DatePicker } from '@fluentui/react-datepicker-compat';
8
8
  import { TimePicker } from '@fluentui/react-timepicker-compat';
9
9
  import { unzipSync } from 'fflate';
@@ -289,8 +289,12 @@ function isRecordWriteDenied(record) {
289
289
  const perms = record.permissions ?? 0;
290
290
  return (perms & TableSecurityPermission.Write) === 0;
291
291
  }
292
- function resolveEditState(metadata, record, explicitReadOnly, explicitDisabled) {
293
- if (isRecordWriteDenied(record)) {
292
+ function isColumnWriteDenied(record, columnName) {
293
+ if (!record || !columnName) return false;
294
+ return !canWriteColumn(record, columnName);
295
+ }
296
+ function resolveEditState(metadata, record, explicitReadOnly, explicitDisabled, columnName) {
297
+ if (isRecordWriteDenied(record) || isColumnWriteDenied(record, columnName)) {
294
298
  return { readOnly: true, disabled: true };
295
299
  }
296
300
  return {
@@ -718,7 +722,8 @@ function TextEdit({
718
722
  metadata,
719
723
  record,
720
724
  readOnly,
721
- disabled
725
+ disabled,
726
+ columnName
722
727
  );
723
728
  const setValue = useGuardedSetValue(rawSetValue, resolvedReadOnly, resolvedDisabled);
724
729
  const validationErrors = useColumnValidation(
@@ -812,7 +817,8 @@ function MemoEdit({
812
817
  metadata,
813
818
  record,
814
819
  readOnly,
815
- disabled
820
+ disabled,
821
+ columnName
816
822
  );
817
823
  const setValue = useGuardedSetValue(rawSetValue, resolvedReadOnly, resolvedDisabled);
818
824
  const validationErrors = useColumnValidation(
@@ -926,7 +932,8 @@ function BoolEdit({
926
932
  metadata,
927
933
  record,
928
934
  readOnly,
929
- disabled
935
+ disabled,
936
+ columnName
930
937
  );
931
938
  const setValue = useGuardedSetValue(rawSetValue, resolvedReadOnly, resolvedDisabled);
932
939
  const isInactive = resolvedDisabled || resolvedReadOnly;
@@ -1102,7 +1109,8 @@ function NumberEdit({
1102
1109
  metadata,
1103
1110
  record,
1104
1111
  readOnly,
1105
- disabled
1112
+ disabled,
1113
+ columnName
1106
1114
  );
1107
1115
  const setValue = useGuardedSetValue(rawSetValue, resolvedReadOnly, resolvedDisabled);
1108
1116
  const validationErrors = useColumnValidation(
@@ -1546,7 +1554,8 @@ function MoneyEdit({
1546
1554
  metadata,
1547
1555
  record,
1548
1556
  readOnly,
1549
- disabled
1557
+ disabled,
1558
+ columnName
1550
1559
  );
1551
1560
  const setValue = useGuardedSetValue(rawSetValue, resolvedReadOnly, resolvedDisabled);
1552
1561
  const validationErrors = useColumnValidation(
@@ -1960,7 +1969,8 @@ function DateTimeEdit({
1960
1969
  metadata,
1961
1970
  record,
1962
1971
  readOnly,
1963
- disabled
1972
+ disabled,
1973
+ columnName
1964
1974
  );
1965
1975
  const setValue = useGuardedSetValue(rawSetValue, resolvedReadOnly, resolvedDisabled);
1966
1976
  const resolvedEditorType = editorType ?? resolveEditorTypeFromMetadata(metadata);
@@ -5351,7 +5361,8 @@ function FileEdit({
5351
5361
  metadata,
5352
5362
  record,
5353
5363
  readOnly,
5354
- disabled
5364
+ disabled,
5365
+ columnName
5355
5366
  );
5356
5367
  const setValue = useGuardedSetValue(rawSetValue, resolvedReadOnly, resolvedDisabled);
5357
5368
  const isInteractable = !resolvedReadOnly && !resolvedDisabled;
@@ -5668,7 +5679,8 @@ function ImageEdit({
5668
5679
  metadata,
5669
5680
  record,
5670
5681
  readOnly,
5671
- disabled
5682
+ disabled,
5683
+ columnName
5672
5684
  );
5673
5685
  const setValue = useGuardedSetValue(rawSetValue, resolvedReadOnly, resolvedDisabled);
5674
5686
  const isInteractable = !resolvedReadOnly && !resolvedDisabled;
@@ -6148,7 +6160,8 @@ function ChoiceEdit({
6148
6160
  metadata,
6149
6161
  record,
6150
6162
  readOnly,
6151
- disabled
6163
+ disabled,
6164
+ columnName
6152
6165
  );
6153
6166
  const setValue = useGuardedSetValue(rawSetValue, resolvedReadOnly, resolvedDisabled);
6154
6167
  const choiceMeta = getChoiceMetadata(metadata);
@@ -6368,7 +6381,8 @@ function MultiSelectChoiceEdit({
6368
6381
  metadata,
6369
6382
  record,
6370
6383
  readOnly,
6371
- disabled
6384
+ disabled,
6385
+ columnName
6372
6386
  );
6373
6387
  const setValue = useGuardedSetValue(rawSetValue, resolvedReadOnly, resolvedDisabled);
6374
6388
  const choiceMeta = getChoiceMetadata(metadata);
@@ -6490,30 +6504,30 @@ function foldString(s, caseSensitive) {
6490
6504
  function tokenizeSearch(search, delimiters) {
6491
6505
  if (!delimiters) return [search];
6492
6506
  const delim = new Set(Array.from(delimiters));
6493
- const tokens43 = [];
6507
+ const tokens44 = [];
6494
6508
  let current = "";
6495
6509
  for (const ch of search) {
6496
6510
  if (delim.has(ch)) {
6497
6511
  if (current.length > 0) {
6498
- tokens43.push(current);
6512
+ tokens44.push(current);
6499
6513
  current = "";
6500
6514
  }
6501
6515
  } else {
6502
6516
  current += ch;
6503
6517
  }
6504
6518
  }
6505
- if (current.length > 0) tokens43.push(current);
6506
- return tokens43;
6519
+ if (current.length > 0) tokens44.push(current);
6520
+ return tokens44;
6507
6521
  }
6508
6522
  function splitHighlightSegments(text, search, options) {
6509
6523
  if (!text || !search) return [];
6510
6524
  const caseSensitive = options?.caseSensitive ?? false;
6511
6525
  const delimiters = options?.delimiters ?? "";
6512
- const tokens43 = tokenizeSearch(search, delimiters);
6513
- if (tokens43.length === 0) return [{ text, isMatch: false }];
6526
+ const tokens44 = tokenizeSearch(search, delimiters);
6527
+ if (tokens44.length === 0) return [{ text, isMatch: false }];
6514
6528
  const folded = foldString(text, caseSensitive);
6515
6529
  const ranges = [];
6516
- for (const token of tokens43) {
6530
+ for (const token of tokens44) {
6517
6531
  const tokenFolded = foldString(token, caseSensitive).folded;
6518
6532
  if (tokenFolded.length === 0) continue;
6519
6533
  let cursor2 = 0;
@@ -7284,7 +7298,11 @@ function getRendererTypeForCode(typeCode) {
7284
7298
  }
7285
7299
  function ColumnEdit(props) {
7286
7300
  const metadata = useColumnMetadata(props.columnName);
7301
+ const record = useRecordContextOptional()?.record;
7287
7302
  const typeCode = metadata?.$type;
7303
+ if (record && !canReadColumn(record, props.columnName)) {
7304
+ return /* @__PURE__ */ jsx(ColumnDisplayValue, { ...props });
7305
+ }
7288
7306
  switch (typeCode) {
7289
7307
  case 0:
7290
7308
  return /* @__PURE__ */ jsx(BoolEdit, { ...props });
@@ -8397,6 +8415,12 @@ var ViewSort = {
8397
8415
  /** Localized display name, descending. */
8398
8416
  NameDescending: "nameDescending"
8399
8417
  };
8418
+ function resolveViewLabel(view, t) {
8419
+ const key = `tables.${view.tableName}.views.${view.id}.label`;
8420
+ const localized = t(key);
8421
+ if (localized !== key) return localized;
8422
+ return view.displayName?.trim() ? view.displayName : null;
8423
+ }
8400
8424
  function customToViewMetadata(custom) {
8401
8425
  return {
8402
8426
  id: custom.id,
@@ -9073,7 +9097,7 @@ function MainGridImpl({
9073
9097
  const rowRecord = cellCtx.row.original;
9074
9098
  const rowId = rowRecord.id;
9075
9099
  const isPendingCreateRow = !!rowId && rowId.startsWith("__pending-create-");
9076
- if (editable && !isPendingCreateRow && hasEditorForType) {
9100
+ if (editable && !isPendingCreateRow && hasEditorForType && canWriteColumn(rowRecord, columnName)) {
9077
9101
  return (
9078
9102
  // Sizing wrapper — overrides Fluent v9's default
9079
9103
  // <Input> / <Combobox> / <Dropdown> min-width
@@ -9763,8 +9787,8 @@ function MainGridImpl({
9763
9787
  };
9764
9788
  const selectedView = views?.find((v) => v.id === resolvedViewId);
9765
9789
  const selectedViewLabelKey = selectedView ? `tables.${selectedView.tableName}.views.${selectedView.id}.label` : "";
9766
- const selectedViewLabel = selectedView ? t(selectedViewLabelKey) : "";
9767
- const singleViewTitle = selectedView && selectedViewLabel !== selectedViewLabelKey ? selectedViewLabel : selectedView?.displayName ?? void 0;
9790
+ const selectedViewLabel = selectedView ? resolveViewLabel(selectedView, t) ?? selectedViewLabelKey : "";
9791
+ const singleViewTitle = selectedView ? resolveViewLabel(selectedView, t) ?? void 0 : void 0;
9768
9792
  return /* @__PURE__ */ jsx(GridContextProvider, { value: gridContextValue, children: /* @__PURE__ */ jsxs(
9769
9793
  "div",
9770
9794
  {
@@ -9797,7 +9821,7 @@ function MainGridImpl({
9797
9821
  if (data2.optionValue) handleViewChange(data2.optionValue);
9798
9822
  },
9799
9823
  children: views?.map((view) => {
9800
- const label = t(`tables.${view.tableName}.views.${view.id}.label`);
9824
+ const label = resolveViewLabel(view, t) ?? `tables.${view.tableName}.views.${view.id}.label`;
9801
9825
  return /* @__PURE__ */ jsx(Option, { value: view.id, text: label, children: label }, view.id);
9802
9826
  })
9803
9827
  }
@@ -10893,7 +10917,8 @@ function LookupEdit({
10893
10917
  metadata,
10894
10918
  record,
10895
10919
  readOnly,
10896
- disabled
10920
+ disabled,
10921
+ columnName
10897
10922
  );
10898
10923
  const setLookupValue = useGuardedSetValue(
10899
10924
  rawSetLookupValue,
@@ -12673,6 +12698,7 @@ function PowerPortalsProThemeProvider({
12673
12698
  defaultMode = ThemeMode.System,
12674
12699
  defaultAccentColor = "default",
12675
12700
  defaultTextDirection = TextDirection.Ltr,
12701
+ lockAccentColor = false,
12676
12702
  customAccents,
12677
12703
  storageKey = DEFAULT_MODE_STORAGE_KEY,
12678
12704
  style,
@@ -12696,9 +12722,11 @@ function PowerPortalsProThemeProvider({
12696
12722
  () => readStoredMode(storageKey) ?? defaultMode
12697
12723
  );
12698
12724
  const [accentColor, setAccentColorState] = useState(() => {
12725
+ const configured = defaultAccentColor in accentMap ? defaultAccentColor : "default";
12726
+ if (lockAccentColor) return configured;
12699
12727
  const stored = readStoredString(accentStorageKey);
12700
12728
  if (stored && stored in accentMap) return stored;
12701
- return defaultAccentColor in accentMap ? defaultAccentColor : "default";
12729
+ return configured;
12702
12730
  });
12703
12731
  const [textDirection, setTextDirectionState] = useState(
12704
12732
  () => readStoredDirection(directionStorageKey) ?? defaultTextDirection
@@ -12728,6 +12756,12 @@ function PowerPortalsProThemeProvider({
12728
12756
  document.body.classList.toggle("ppp-theme-dark", isDark);
12729
12757
  document.body.classList.toggle("ppp-theme-light", !isDark);
12730
12758
  }, [isDark]);
12759
+ useEffect(() => {
12760
+ if (!lockAccentColor) return;
12761
+ const configured = defaultAccentColor in accentMap ? defaultAccentColor : "default";
12762
+ setAccentColorState(configured);
12763
+ writeStored(accentStorageKey, configured);
12764
+ }, [lockAccentColor, defaultAccentColor, accentMap, accentStorageKey]);
12731
12765
  const setMode = useCallback(
12732
12766
  (next) => {
12733
12767
  setModeState(next);
@@ -12737,11 +12771,12 @@ function PowerPortalsProThemeProvider({
12737
12771
  );
12738
12772
  const setAccentColor = useCallback(
12739
12773
  (next) => {
12774
+ if (lockAccentColor) return;
12740
12775
  if (!(next in accentMap)) return;
12741
12776
  setAccentColorState(next);
12742
12777
  writeStored(accentStorageKey, next);
12743
12778
  },
12744
- [accentMap, accentStorageKey]
12779
+ [lockAccentColor, accentMap, accentStorageKey]
12745
12780
  );
12746
12781
  const setTextDirection = useCallback(
12747
12782
  (next) => {
@@ -12766,6 +12801,7 @@ function PowerPortalsProThemeProvider({
12766
12801
  setAccentColor,
12767
12802
  availableAccents,
12768
12803
  accentColors,
12804
+ accentColorLocked: lockAccentColor,
12769
12805
  textDirection,
12770
12806
  setTextDirection
12771
12807
  }),
@@ -12777,6 +12813,7 @@ function PowerPortalsProThemeProvider({
12777
12813
  setAccentColor,
12778
12814
  availableAccents,
12779
12815
  accentColors,
12816
+ lockAccentColor,
12780
12817
  textDirection,
12781
12818
  setTextDirection
12782
12819
  ]
@@ -12857,7 +12894,7 @@ function ThemeColorSelector({
12857
12894
  width,
12858
12895
  accentLabels
12859
12896
  }) {
12860
- const { accentColor, setAccentColor, availableAccents, accentColors } = useTheme();
12897
+ const { accentColor, setAccentColor, availableAccents, accentColors, accentColorLocked } = useTheme();
12861
12898
  const t = useT();
12862
12899
  const labelId = useId();
12863
12900
  const styles = useStyles6();
@@ -12871,6 +12908,7 @@ function ThemeColorSelector({
12871
12908
  },
12872
12909
  [t, accentLabels]
12873
12910
  );
12911
+ if (accentColorLocked) return null;
12874
12912
  const handleOptionSelect = (_event, data) => {
12875
12913
  const next = data.optionValue;
12876
12914
  if (next && availableAccents.includes(next)) {
@@ -13038,7 +13076,304 @@ function LanguageDropdown({
13038
13076
  }
13039
13077
  );
13040
13078
  }
13079
+ var MINIMUM_QUERY_LENGTH = 3;
13080
+ var MAXIMUM_RESULTS = 50;
13081
+ var SEARCH_DEBOUNCE_MS4 = 300;
13082
+ var GUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
13041
13083
  var useStyles7 = makeStyles({
13084
+ surface: {
13085
+ maxWidth: "480px",
13086
+ display: "flex",
13087
+ flexDirection: "column"
13088
+ },
13089
+ titleRow: {
13090
+ display: "flex",
13091
+ flexDirection: "row",
13092
+ alignItems: "flex-start",
13093
+ columnGap: tokens.spacingHorizontalS
13094
+ },
13095
+ titleText: {
13096
+ flexGrow: 1,
13097
+ minWidth: 0
13098
+ },
13099
+ body: {
13100
+ display: "flex",
13101
+ flexDirection: "column",
13102
+ rowGap: tokens.spacingVerticalM,
13103
+ paddingTop: tokens.spacingVerticalM
13104
+ },
13105
+ results: {
13106
+ display: "flex",
13107
+ flexDirection: "column",
13108
+ // Fixed height, not min/max: a list that resizes on every keystroke makes the confirm button
13109
+ // jump around under the pointer while the administrator is still reading.
13110
+ height: "280px",
13111
+ overflowY: "auto",
13112
+ // Full shorthand — griffel types `borderColor` and friends as `never`, so the longhand
13113
+ // properties aren't usable here.
13114
+ border: `1px solid ${tokens.colorNeutralStroke1}`,
13115
+ borderRadius: tokens.borderRadiusMedium,
13116
+ backgroundColor: tokens.colorNeutralBackground1
13117
+ },
13118
+ result: {
13119
+ display: "flex",
13120
+ flexDirection: "row",
13121
+ alignItems: "center",
13122
+ columnGap: tokens.spacingHorizontalS,
13123
+ padding: `${tokens.spacingVerticalS} ${tokens.spacingHorizontalM}`,
13124
+ cursor: "pointer",
13125
+ borderBottom: `1px solid ${tokens.colorNeutralStroke2}`,
13126
+ textAlign: "left",
13127
+ background: "none",
13128
+ // A native <button> does NOT inherit colour or font from its container — the user-agent
13129
+ // stylesheet gives it `color: buttontext` and its own font stack. Without these three the
13130
+ // rows rendered as near-black text on the dark surface in dark mode, which is how this was
13131
+ // first spotted. Set explicitly rather than relying on inheritance.
13132
+ color: tokens.colorNeutralForeground1,
13133
+ fontFamily: "inherit",
13134
+ fontSize: tokens.fontSizeBase300,
13135
+ // The UA also gives buttons a border; `background: none` alone leaves it drawing a box
13136
+ // around every row. Only three sides — borderBottom above is the row separator.
13137
+ borderTop: "none",
13138
+ borderRight: "none",
13139
+ borderLeft: "none",
13140
+ ":hover": {
13141
+ backgroundColor: tokens.colorNeutralBackground1Hover,
13142
+ color: tokens.colorNeutralForeground1Hover
13143
+ },
13144
+ ":focus-visible": {
13145
+ outline: `2px solid ${tokens.colorStrokeFocus2}`,
13146
+ outlineOffset: "-2px"
13147
+ }
13148
+ },
13149
+ resultSelected: {
13150
+ backgroundColor: tokens.colorBrandBackground,
13151
+ color: tokens.colorNeutralForegroundOnBrand,
13152
+ ":hover": {
13153
+ backgroundColor: tokens.colorBrandBackgroundHover,
13154
+ // Repeated because the base row's :hover also sets colour, and whichever griffel emits
13155
+ // last would otherwise win — leaving selected-and-hovered text unreadable on the brand fill.
13156
+ color: tokens.colorNeutralForegroundOnBrand
13157
+ }
13158
+ },
13159
+ resultText: {
13160
+ display: "flex",
13161
+ flexDirection: "column",
13162
+ minWidth: 0
13163
+ },
13164
+ name: {
13165
+ fontSize: tokens.fontSizeBase300,
13166
+ overflow: "hidden",
13167
+ textOverflow: "ellipsis",
13168
+ whiteSpace: "nowrap"
13169
+ },
13170
+ email: {
13171
+ fontSize: tokens.fontSizeBase200,
13172
+ opacity: 0.8,
13173
+ overflow: "hidden",
13174
+ textOverflow: "ellipsis",
13175
+ whiteSpace: "nowrap"
13176
+ },
13177
+ status: {
13178
+ display: "flex",
13179
+ flexDirection: "row",
13180
+ alignItems: "center",
13181
+ columnGap: tokens.spacingHorizontalS,
13182
+ padding: tokens.spacingVerticalXL,
13183
+ color: tokens.colorNeutralForeground3,
13184
+ fontSize: tokens.fontSizeBase200
13185
+ },
13186
+ warning: {
13187
+ display: "flex",
13188
+ flexDirection: "row",
13189
+ alignItems: "flex-start",
13190
+ columnGap: tokens.spacingHorizontalS,
13191
+ padding: `${tokens.spacingVerticalS} ${tokens.spacingHorizontalM}`,
13192
+ borderRadius: tokens.borderRadiusMedium,
13193
+ backgroundColor: tokens.colorNeutralBackground2,
13194
+ borderLeft: `3px solid ${tokens.colorPaletteRedBackground3}`,
13195
+ fontSize: tokens.fontSizeBase200
13196
+ },
13197
+ footer: {
13198
+ display: "flex",
13199
+ flexDirection: "row",
13200
+ justifyContent: "flex-end",
13201
+ columnGap: tokens.spacingHorizontalS,
13202
+ paddingTop: tokens.spacingVerticalM
13203
+ }
13204
+ });
13205
+ function ImpersonationPickerDialog({
13206
+ open,
13207
+ onClose,
13208
+ returnUrl = "/",
13209
+ className
13210
+ }) {
13211
+ const auth = useAuth();
13212
+ const styles = useStyles7();
13213
+ const t = useT();
13214
+ const { searchImpersonationTargets, impersonate } = auth;
13215
+ const [query, setQuery] = useState("");
13216
+ const [results, setResults] = useState([]);
13217
+ const [selected, setSelected] = useState(null);
13218
+ const [isSearching, setIsSearching] = useState(false);
13219
+ const [hasSearched, setHasSearched] = useState(false);
13220
+ const [hasMore, setHasMore] = useState(false);
13221
+ const [isStarting, setIsStarting] = useState(false);
13222
+ const requestIdRef = useRef(0);
13223
+ useEffect(() => {
13224
+ if (open) return;
13225
+ setQuery("");
13226
+ setResults([]);
13227
+ setSelected(null);
13228
+ setIsSearching(false);
13229
+ setHasSearched(false);
13230
+ setHasMore(false);
13231
+ setIsStarting(false);
13232
+ }, [open]);
13233
+ const trimmed = query.trim();
13234
+ const isTooShort = trimmed.length > 0 && trimmed.length < MINIMUM_QUERY_LENGTH;
13235
+ useEffect(() => {
13236
+ if (!open) return;
13237
+ const requestId = ++requestIdRef.current;
13238
+ if (trimmed.length < MINIMUM_QUERY_LENGTH && !GUID_PATTERN.test(trimmed)) {
13239
+ setResults([]);
13240
+ setHasSearched(false);
13241
+ setHasMore(false);
13242
+ setIsSearching(false);
13243
+ return;
13244
+ }
13245
+ let cancelled = false;
13246
+ const timer = setTimeout(async () => {
13247
+ setIsSearching(true);
13248
+ try {
13249
+ const response = await searchImpersonationTargets(trimmed);
13250
+ if (cancelled || requestId !== requestIdRef.current) return;
13251
+ setResults(response.results ?? []);
13252
+ setHasMore(response.hasMore ?? false);
13253
+ setHasSearched(true);
13254
+ } catch {
13255
+ if (cancelled || requestId !== requestIdRef.current) return;
13256
+ setResults([]);
13257
+ setHasMore(false);
13258
+ setHasSearched(true);
13259
+ } finally {
13260
+ if (!cancelled && requestId === requestIdRef.current) {
13261
+ setIsSearching(false);
13262
+ }
13263
+ }
13264
+ }, SEARCH_DEBOUNCE_MS4);
13265
+ return () => {
13266
+ cancelled = true;
13267
+ clearTimeout(timer);
13268
+ };
13269
+ }, [open, searchImpersonationTargets, trimmed]);
13270
+ const handleQueryChange = useCallback((value) => {
13271
+ setQuery(value);
13272
+ setSelected(null);
13273
+ }, []);
13274
+ const handleConfirm = useCallback(async () => {
13275
+ if (!selected?.contactId) return;
13276
+ setIsStarting(true);
13277
+ try {
13278
+ const response = await impersonate(selected.contactId);
13279
+ if (response.result === ImpersonateResult.Started) {
13280
+ window.location.assign(returnUrl);
13281
+ return;
13282
+ }
13283
+ setIsStarting(false);
13284
+ onClose();
13285
+ } catch {
13286
+ setIsStarting(false);
13287
+ }
13288
+ }, [impersonate, onClose, returnUrl, selected]);
13289
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: (_, data) => {
13290
+ if (!data.open) onClose();
13291
+ }, children: /* @__PURE__ */ jsxs(
13292
+ DialogSurface,
13293
+ {
13294
+ className: mergeClasses("ppp-impersonation-picker-dialog", styles.surface, className),
13295
+ children: [
13296
+ /* @__PURE__ */ jsxs("div", { className: styles.titleRow, children: [
13297
+ /* @__PURE__ */ jsx(DialogTitle, { className: styles.titleText, children: t("app.components.impersonation-picker.title") }),
13298
+ /* @__PURE__ */ jsx(
13299
+ Button,
13300
+ {
13301
+ appearance: "subtle",
13302
+ icon: /* @__PURE__ */ jsx(Dismiss24Regular, {}),
13303
+ "aria-label": t("app.components.impersonation-picker.cancel"),
13304
+ onClick: onClose
13305
+ }
13306
+ )
13307
+ ] }),
13308
+ /* @__PURE__ */ jsxs("div", { className: styles.body, children: [
13309
+ /* @__PURE__ */ jsx(
13310
+ SearchBox,
13311
+ {
13312
+ value: query,
13313
+ onChange: (_, data) => handleQueryChange(data.value),
13314
+ placeholder: t("app.components.impersonation-picker.search-placeholder"),
13315
+ appearance: "outline",
13316
+ style: { width: "100%" }
13317
+ }
13318
+ ),
13319
+ /* @__PURE__ */ jsx("div", { className: styles.results, role: "listbox", children: isSearching ? /* @__PURE__ */ jsxs("div", { className: styles.status, children: [
13320
+ /* @__PURE__ */ jsx(Spinner, { size: "tiny" }),
13321
+ /* @__PURE__ */ jsx("span", { children: t("app.components.impersonation-picker.searching") })
13322
+ ] }) : isTooShort ? (
13323
+ // Distinct from "no matches": the search hasn't run. Conflating the two reads as
13324
+ // "this person doesn't exist" when the user has simply typed too few characters.
13325
+ /* @__PURE__ */ jsx("div", { className: styles.status, children: t("app.components.impersonation-picker.hint", [String(MINIMUM_QUERY_LENGTH)]) })
13326
+ ) : hasSearched && results.length === 0 ? /* @__PURE__ */ jsx("div", { className: styles.status, children: t("app.components.impersonation-picker.no-matches") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
13327
+ results.map((candidate) => {
13328
+ const isSelected = selected?.contactId === candidate.contactId;
13329
+ return /* @__PURE__ */ jsxs(
13330
+ "button",
13331
+ {
13332
+ type: "button",
13333
+ role: "option",
13334
+ "aria-selected": isSelected,
13335
+ className: mergeClasses(
13336
+ styles.result,
13337
+ isSelected && styles.resultSelected
13338
+ ),
13339
+ onClick: () => setSelected(candidate),
13340
+ children: [
13341
+ /* @__PURE__ */ jsx(Person20Regular, {}),
13342
+ /* @__PURE__ */ jsxs("span", { className: styles.resultText, children: [
13343
+ /* @__PURE__ */ jsx("span", { className: styles.name, children: candidate.fullName ?? candidate.email ?? candidate.contactId }),
13344
+ candidate.email && /* @__PURE__ */ jsx("span", { className: styles.email, children: candidate.email })
13345
+ ] })
13346
+ ]
13347
+ },
13348
+ candidate.contactId
13349
+ );
13350
+ }),
13351
+ hasMore && /* @__PURE__ */ jsx("div", { className: styles.status, children: t("app.components.impersonation-picker.too-many", [String(MAXIMUM_RESULTS)]) })
13352
+ ] }) }),
13353
+ selected && /* @__PURE__ */ jsxs("div", { className: styles.warning, children: [
13354
+ /* @__PURE__ */ jsx(Warning20Regular, {}),
13355
+ /* @__PURE__ */ jsx("span", { children: t("app.components.impersonation-picker.warning") })
13356
+ ] })
13357
+ ] }),
13358
+ /* @__PURE__ */ jsxs("div", { className: styles.footer, children: [
13359
+ /* @__PURE__ */ jsx(
13360
+ Button,
13361
+ {
13362
+ appearance: "primary",
13363
+ icon: /* @__PURE__ */ jsx(EyeTracking20Regular, {}),
13364
+ disabled: !selected || isStarting,
13365
+ onClick: handleConfirm,
13366
+ children: t("app.components.impersonation-picker.confirm")
13367
+ }
13368
+ ),
13369
+ /* @__PURE__ */ jsx(Button, { appearance: "secondary", onClick: onClose, disabled: isStarting, children: t("app.components.impersonation-picker.cancel") })
13370
+ ] })
13371
+ ]
13372
+ }
13373
+ ) });
13374
+ }
13375
+ var IMPERSONATION_ROLE = "SystemAdmin";
13376
+ var useStyles8 = makeStyles({
13042
13377
  trigger: {
13043
13378
  backgroundColor: "transparent",
13044
13379
  border: "none",
@@ -13098,8 +13433,9 @@ function ProfileMainMenu({
13098
13433
  signInUrl = "/login"
13099
13434
  }) {
13100
13435
  const auth = useAuth();
13101
- const styles = useStyles7();
13436
+ const styles = useStyles8();
13102
13437
  const t = useT();
13438
+ const [isPickerOpen, setIsPickerOpen] = useState(false);
13103
13439
  const handleLogout = useCallback(async () => {
13104
13440
  try {
13105
13441
  await auth.logout();
@@ -13116,7 +13452,14 @@ function ProfileMainMenu({
13116
13452
  }
13117
13453
  }, [auth]);
13118
13454
  if (auth.status === AuthStatus.Loading) {
13119
- return /* @__PURE__ */ jsx(Avatar, { className: "ppp-profile-main-menu", size: 28, "aria-label": t("app.messages.record-loading") });
13455
+ return /* @__PURE__ */ jsx(
13456
+ Avatar,
13457
+ {
13458
+ className: "ppp-profile-main-menu",
13459
+ size: 28,
13460
+ "aria-label": t("app.messages.record-loading")
13461
+ }
13462
+ );
13120
13463
  }
13121
13464
  if (auth.status === AuthStatus.Anonymous) {
13122
13465
  if (anonymousContent !== void 0) return /* @__PURE__ */ jsx(Fragment, { children: anonymousContent });
@@ -13137,61 +13480,126 @@ function ProfileMainMenu({
13137
13480
  const resolvedAccountLabel = accountLabel ?? t("app.components.profile-main-menu.account");
13138
13481
  const signedInAsLabel = t("app.components.profile-main-menu.signed-in-as");
13139
13482
  const logoutLabel = t("app.buttons.logout.label");
13483
+ const canImpersonate = (auth.user.roles?.includes(IMPERSONATION_ROLE) ?? false) && auth.user.portalUserType === PortalUserType.SystemUser;
13140
13484
  const altIdentityTableName = auth.user.altIdentityTableName;
13141
- const switchIdentityLabel = altIdentityTableName ? t(
13142
- "app.components.profile-main-menu.switch-to",
13143
- [
13144
- altIdentityTableName === "systemuser" ? t("app.components.profile-main-menu.alt-kind-systemuser") : t("app.components.profile-main-menu.alt-kind-contact")
13145
- ]
13146
- ) : null;
13147
- return /* @__PURE__ */ jsxs(Menu, { children: [
13148
- /* @__PURE__ */ jsx(MenuTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsx(
13149
- "button",
13485
+ const switchIdentityLabel = altIdentityTableName ? t("app.components.profile-main-menu.switch-to", [
13486
+ altIdentityTableName === "systemuser" ? t("app.components.profile-main-menu.alt-kind-systemuser") : t("app.components.profile-main-menu.alt-kind-contact")
13487
+ ]) : null;
13488
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
13489
+ /* @__PURE__ */ jsxs(Menu, { children: [
13490
+ /* @__PURE__ */ jsx(MenuTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsx(
13491
+ "button",
13492
+ {
13493
+ type: "button",
13494
+ className: mergeClasses("ppp-profile-main-menu", styles.trigger),
13495
+ "aria-label": email || logoutLabel,
13496
+ children: /* @__PURE__ */ jsx(Avatar, { size: 28, initials, name: email })
13497
+ }
13498
+ ) }),
13499
+ /* @__PURE__ */ jsx(MenuPopover, { children: /* @__PURE__ */ jsxs("div", { className: styles.popoverBody, children: [
13500
+ headerSlot,
13501
+ /* @__PURE__ */ jsxs("div", { className: styles.identity, children: [
13502
+ /* @__PURE__ */ jsx(Avatar, { size: 40, initials, name: email }),
13503
+ /* @__PURE__ */ jsxs("div", { className: styles.identityText, children: [
13504
+ /* @__PURE__ */ jsx("span", { className: styles.identityLabel, children: signedInAsLabel }),
13505
+ /* @__PURE__ */ jsx("span", { className: styles.identityValue, title: email, children: email })
13506
+ ] })
13507
+ ] }),
13508
+ /* @__PURE__ */ jsx(Divider, {}),
13509
+ /* @__PURE__ */ jsxs(MenuList, { className: styles.actions, children: [
13510
+ accountUrl && /* @__PURE__ */ jsx(
13511
+ MenuItem,
13512
+ {
13513
+ icon: /* @__PURE__ */ jsx(PersonEdit20Regular, {}),
13514
+ onClick: () => {
13515
+ window.location.href = accountUrl;
13516
+ },
13517
+ children: resolvedAccountLabel
13518
+ }
13519
+ ),
13520
+ switchIdentityLabel && /* @__PURE__ */ jsx(
13521
+ MenuItem,
13522
+ {
13523
+ icon: /* @__PURE__ */ jsx(ArrowSwap20Regular, {}),
13524
+ onClick: handleSwitchIdentity,
13525
+ children: switchIdentityLabel
13526
+ }
13527
+ ),
13528
+ canImpersonate && /* @__PURE__ */ jsx(
13529
+ MenuItem,
13530
+ {
13531
+ icon: /* @__PURE__ */ jsx(EyeTracking20Regular, {}),
13532
+ onClick: () => setIsPickerOpen(true),
13533
+ children: t("app.components.profile-main-menu.impersonate")
13534
+ }
13535
+ ),
13536
+ /* @__PURE__ */ jsx(MenuItem, { icon: /* @__PURE__ */ jsx(SignOut20Regular, {}), onClick: handleLogout, children: logoutLabel })
13537
+ ] }),
13538
+ footerSlot && /* @__PURE__ */ jsxs(Fragment, { children: [
13539
+ /* @__PURE__ */ jsx(Divider, {}),
13540
+ footerSlot
13541
+ ] })
13542
+ ] }) })
13543
+ ] }),
13544
+ canImpersonate && /* @__PURE__ */ jsx(
13545
+ ImpersonationPickerDialog,
13150
13546
  {
13151
- type: "button",
13152
- className: mergeClasses("ppp-profile-main-menu", styles.trigger),
13153
- "aria-label": email || logoutLabel,
13154
- children: /* @__PURE__ */ jsx(Avatar, { size: 28, initials, name: email })
13547
+ open: isPickerOpen,
13548
+ onClose: () => setIsPickerOpen(false)
13155
13549
  }
13156
- ) }),
13157
- /* @__PURE__ */ jsx(MenuPopover, { children: /* @__PURE__ */ jsxs("div", { className: styles.popoverBody, children: [
13158
- headerSlot,
13159
- /* @__PURE__ */ jsxs("div", { className: styles.identity, children: [
13160
- /* @__PURE__ */ jsx(Avatar, { size: 40, initials, name: email }),
13161
- /* @__PURE__ */ jsxs("div", { className: styles.identityText, children: [
13162
- /* @__PURE__ */ jsx("span", { className: styles.identityLabel, children: signedInAsLabel }),
13163
- /* @__PURE__ */ jsx("span", { className: styles.identityValue, title: email, children: email })
13164
- ] })
13165
- ] }),
13166
- /* @__PURE__ */ jsx(Divider, {}),
13167
- /* @__PURE__ */ jsxs(MenuList, { className: styles.actions, children: [
13168
- accountUrl && /* @__PURE__ */ jsx(
13169
- MenuItem,
13170
- {
13171
- icon: /* @__PURE__ */ jsx(PersonEdit20Regular, {}),
13172
- onClick: () => {
13173
- window.location.href = accountUrl;
13174
- },
13175
- children: resolvedAccountLabel
13176
- }
13177
- ),
13178
- switchIdentityLabel && /* @__PURE__ */ jsx(
13179
- MenuItem,
13180
- {
13181
- icon: /* @__PURE__ */ jsx(ArrowSwap20Regular, {}),
13182
- onClick: handleSwitchIdentity,
13183
- children: switchIdentityLabel
13184
- }
13185
- ),
13186
- /* @__PURE__ */ jsx(MenuItem, { icon: /* @__PURE__ */ jsx(SignOut20Regular, {}), onClick: handleLogout, children: logoutLabel })
13187
- ] }),
13188
- footerSlot && /* @__PURE__ */ jsxs(Fragment, { children: [
13189
- /* @__PURE__ */ jsx(Divider, {}),
13190
- footerSlot
13191
- ] })
13192
- ] }) })
13550
+ )
13193
13551
  ] });
13194
13552
  }
13553
+ function ImpersonationBanner({
13554
+ returnUrl = "/",
13555
+ className
13556
+ }) {
13557
+ const auth = useAuth();
13558
+ const t = useT();
13559
+ const [isStopping, setIsStopping] = useState(false);
13560
+ const { stopImpersonation } = auth;
13561
+ const handleStop = useCallback(async () => {
13562
+ setIsStopping(true);
13563
+ try {
13564
+ await stopImpersonation();
13565
+ window.location.assign(returnUrl);
13566
+ } catch {
13567
+ setIsStopping(false);
13568
+ }
13569
+ }, [returnUrl, stopImpersonation]);
13570
+ if (auth.status !== AuthStatus.Authenticated || !auth.user.isImpersonating) {
13571
+ return null;
13572
+ }
13573
+ const viewingAs = auth.user.email ?? auth.user.userName ?? "";
13574
+ const impersonator = auth.user.impersonatorName;
13575
+ return (
13576
+ // The class is placement only — PageLayout targets it to assign the grid row. It carries no
13577
+ // appearance of its own; Fluent owns all of that.
13578
+ /* @__PURE__ */ jsxs(
13579
+ MessageBar,
13580
+ {
13581
+ className: mergeClasses("ppp-impersonation-banner", className),
13582
+ intent: "error",
13583
+ children: [
13584
+ /* @__PURE__ */ jsxs(MessageBarBody, { children: [
13585
+ /* @__PURE__ */ jsx(MessageBarTitle, { children: t("app.components.impersonation-banner.viewing-as", [viewingAs]) }),
13586
+ impersonator ? ` ${t("app.components.impersonation-banner.you-are", [impersonator])}` : null
13587
+ ] }),
13588
+ /* @__PURE__ */ jsx(MessageBarActions, { children: /* @__PURE__ */ jsx(
13589
+ Button,
13590
+ {
13591
+ icon: /* @__PURE__ */ jsx(ArrowExit20Regular, {}),
13592
+ iconPosition: "after",
13593
+ disabled: isStopping,
13594
+ onClick: handleStop,
13595
+ children: t("app.components.impersonation-banner.stop")
13596
+ }
13597
+ ) })
13598
+ ]
13599
+ }
13600
+ )
13601
+ );
13602
+ }
13195
13603
  var usePanelStyles = makeStyles({
13196
13604
  // Vertical stack of setting sections with consistent gap. Matches the
13197
13605
  // panel body the SiteSettingsButton's drawer renders, but extracted
@@ -13240,7 +13648,7 @@ function SiteSettingsPanel({
13240
13648
  }
13241
13649
  );
13242
13650
  }
13243
- var useStyles8 = makeStyles({
13651
+ var useStyles9 = makeStyles({
13244
13652
  // Wraps the standalone panel inside the drawer body to add the small
13245
13653
  // top inset that the popover's title visually expects. The panel
13246
13654
  // itself stays inset-free so it doesn't carry extra padding when
@@ -13260,7 +13668,7 @@ function SiteSettingsButton({
13260
13668
  accentLabels
13261
13669
  }) {
13262
13670
  const t = useT();
13263
- const styles = useStyles8();
13671
+ const styles = useStyles9();
13264
13672
  const [open, setOpen] = useState(false);
13265
13673
  const close = useCallback(() => setOpen(false), []);
13266
13674
  const label = t("app.site-settings-label");
@@ -13738,7 +14146,7 @@ function bestRouteMatch(routes, pathname) {
13738
14146
  }
13739
14147
  return best;
13740
14148
  }
13741
- var useStyles9 = makeStyles({
14149
+ var useStyles10 = makeStyles({
13742
14150
  // Shared scrim look. Both global (full-screen) and scoped variants reuse
13743
14151
  // these so the visual feel is identical — only the positioning differs.
13744
14152
  // Theme-aware translucent backdrop drawn via a `::before` pseudo-element
@@ -13800,7 +14208,7 @@ function FluentOverlayProvider({ children }) {
13800
14208
  ] });
13801
14209
  }
13802
14210
  function GlobalOverlayLayer() {
13803
- const styles = useStyles9();
14211
+ const styles = useStyles10();
13804
14212
  const overlay = useOverlayForTarget(void 0);
13805
14213
  if (!overlay) return null;
13806
14214
  return /* @__PURE__ */ jsx(ScrimContent, { overlay, className: mergeClasses(styles.scrim, styles.global) });
@@ -13811,7 +14219,7 @@ function FluentOverlayTarget({
13811
14219
  className,
13812
14220
  style
13813
14221
  }) {
13814
- const styles = useStyles9();
14222
+ const styles = useStyles10();
13815
14223
  const overlay = useOverlayForTarget(targetId);
13816
14224
  return /* @__PURE__ */ jsxs("div", { className: mergeClasses("ppp-overlay-target", styles.scopedHost, className), style, children: [
13817
14225
  children,
@@ -13819,14 +14227,14 @@ function FluentOverlayTarget({
13819
14227
  ] });
13820
14228
  }
13821
14229
  function ScrimContent({ overlay, className }) {
13822
- const styles = useStyles9();
14230
+ const styles = useStyles10();
13823
14231
  const showProgress = overlay.showProgress !== false;
13824
14232
  return /* @__PURE__ */ jsxs("div", { className, role: "status", "aria-live": "polite", children: [
13825
14233
  showProgress && /* @__PURE__ */ jsx(Spinner, { size: "large" }),
13826
14234
  overlay.description && /* @__PURE__ */ jsx("span", { className: styles.description, children: overlay.description })
13827
14235
  ] });
13828
14236
  }
13829
- var useStyles10 = makeStyles({
14237
+ var useStyles11 = makeStyles({
13830
14238
  // Centered placeholder used while prefixes are being fetched. Same
13831
14239
  // shape as the spinner blocks in `<NewRecordGridButton>` /
13832
14240
  // `<OpenRecordGridButton>` dialogs (~160px min height + vertical
@@ -13846,7 +14254,7 @@ function LocalizationBoundary({
13846
14254
  fallback,
13847
14255
  children
13848
14256
  }) {
13849
- const styles = useStyles10();
14257
+ const styles = useStyles11();
13850
14258
  const resolvedPrefixes = prefixes ?? EMPTY_PREFIXES;
13851
14259
  const { locale } = useLocale();
13852
14260
  useLocalization(resolvedPrefixes);
@@ -13868,7 +14276,7 @@ function LocalizationBoundary({
13868
14276
  return /* @__PURE__ */ jsx("div", { className: mergeClasses("ppp-localization-boundary", styles.fallback), role: "status", "aria-live": "polite", children: /* @__PURE__ */ jsx(Spinner, { size: "medium" }) });
13869
14277
  }
13870
14278
  var EMPTY_PREFIXES = Object.freeze([]);
13871
- var useStyles11 = makeStyles({
14279
+ var useStyles12 = makeStyles({
13872
14280
  root: {
13873
14281
  display: "flex",
13874
14282
  flexDirection: "column",
@@ -13926,7 +14334,7 @@ function isSameLanguage(a, b) {
13926
14334
  return primaryA.length > 0 && primaryA === primary(b);
13927
14335
  }
13928
14336
  function LocalizationTranslator({ className }) {
13929
- const styles = useStyles11();
14337
+ const styles = useStyles12();
13930
14338
  const t = useT();
13931
14339
  const { ppp } = usePowerPortalsPro();
13932
14340
  const [availability, setAvailability] = useState(null);
@@ -14414,7 +14822,7 @@ function SectionColumnRenderer({
14414
14822
  }
14415
14823
  );
14416
14824
  }
14417
- var useStyles12 = makeStyles({
14825
+ var useStyles13 = makeStyles({
14418
14826
  root: {
14419
14827
  display: "flex",
14420
14828
  flexDirection: "column",
@@ -14537,7 +14945,7 @@ function cultureLabel(culture) {
14537
14945
  }
14538
14946
  }
14539
14947
  function LocalizationAdmin({ className }) {
14540
- const styles = useStyles12();
14948
+ const styles = useStyles13();
14541
14949
  const t = useT();
14542
14950
  const { ppp } = usePowerPortalsPro();
14543
14951
  const [overview, setOverview] = useState(null);
@@ -15007,7 +15415,7 @@ function MergedSourcesDialog({
15007
15415
  ] })
15008
15416
  ] }) }) });
15009
15417
  }
15010
- var useStyles13 = makeStyles({
15418
+ var useStyles14 = makeStyles({
15011
15419
  root: {
15012
15420
  display: "flex",
15013
15421
  flexDirection: "column",
@@ -15030,7 +15438,7 @@ var useStyles13 = makeStyles({
15030
15438
  });
15031
15439
  var toMs = (value) => Number(value);
15032
15440
  function CacheAdmin({ className }) {
15033
- const styles = useStyles13();
15441
+ const styles = useStyles14();
15034
15442
  const t = useT();
15035
15443
  const { ppp } = usePowerPortalsPro();
15036
15444
  const [cacheNames, setCacheNames] = useState([]);
@@ -15165,7 +15573,7 @@ function CacheAdmin({ className }) {
15165
15573
  ] }) : null
15166
15574
  ] });
15167
15575
  }
15168
- var useStyles14 = makeStyles({
15576
+ var useStyles15 = makeStyles({
15169
15577
  list: {
15170
15578
  margin: 0,
15171
15579
  paddingLeft: tokens.spacingHorizontalXL,
@@ -15180,7 +15588,7 @@ function ValidationSummary({
15180
15588
  className,
15181
15589
  style
15182
15590
  }) {
15183
- const styles = useStyles14();
15591
+ const styles = useStyles15();
15184
15592
  const t = useT();
15185
15593
  const validation = useValidationContext();
15186
15594
  const resolvedTitle = title === void 0 ? t("app.errors.form-validation-errors") : title;
@@ -16057,7 +16465,7 @@ function isWizardRecordPageElement(node) {
16057
16465
  const type = node.type;
16058
16466
  return !!type && type[PAGE_MARKER] === true;
16059
16467
  }
16060
- var useStyles15 = makeStyles({
16468
+ var useStyles16 = makeStyles({
16061
16469
  shell: {
16062
16470
  display: "flex",
16063
16471
  flexDirection: "column",
@@ -16199,7 +16607,7 @@ function WizardRecordFormInner({
16199
16607
  stepperVisibility,
16200
16608
  isCreate
16201
16609
  }) {
16202
- const styles = useStyles15();
16610
+ const styles = useStyles16();
16203
16611
  return /* @__PURE__ */ jsx("div", { className: mergeClasses("ppp-wizard-record-form", styles.shell), children: /* @__PURE__ */ jsx(
16204
16612
  Wizard,
16205
16613
  {
@@ -16233,7 +16641,7 @@ function WizardFooter({
16233
16641
  onCancel,
16234
16642
  cancelLabel
16235
16643
  }) {
16236
- const styles = useStyles15();
16644
+ const styles = useStyles16();
16237
16645
  const t = useT();
16238
16646
  const recordContext = useRecordContext();
16239
16647
  const wizard = useWizard();
@@ -17715,13 +18123,17 @@ var usePageLayoutStyles = makeStyles({
17715
18123
  position: "relative",
17716
18124
  display: "grid",
17717
18125
  gridTemplateColumns: "auto minmax(0, 1fr)",
17718
- gridTemplateRows: "auto 1fr auto",
18126
+ // Four rows, not three: the first carries the impersonation banner. It's an `auto` track and
18127
+ // the banner renders nothing for an ordinary session, so the row collapses to zero height and
18128
+ // the layout is unchanged whenever nobody is impersonating.
18129
+ gridTemplateRows: "auto auto 1fr auto",
17719
18130
  // Lets the PageLayout shrink when it's nested inside another
17720
18131
  // PageLayout's `<main>` (the outer main is a grid item; without
17721
18132
  // `min-width: 0` on this inner root the outer's `minmax(0, 1fr)`
17722
18133
  // track wouldn't be honored once content tries to grow wider).
17723
18134
  minWidth: 0,
17724
18135
  gridTemplateAreas: `
18136
+ 'impersonation impersonation'
17725
18137
  'header header'
17726
18138
  'navigation main'
17727
18139
  'footer footer'
@@ -17831,6 +18243,11 @@ var usePageLayoutStyles = makeStyles({
17831
18243
  childHeader: {
17832
18244
  borderBottomWidth: "1px"
17833
18245
  },
18246
+ impersonationBanner: {
18247
+ // Placement only. The banner is a Fluent MessageBar and Fluent owns everything about how it
18248
+ // looks; this grid assignment is the extent of PageLayout's involvement.
18249
+ gridArea: "impersonation"
18250
+ },
17834
18251
  navigation: {
17835
18252
  gridArea: "navigation",
17836
18253
  overflowY: "auto",
@@ -17957,6 +18374,7 @@ function PageLayout({
17957
18374
  ...resizable ? { "--ppp-nav-width": `${navWidth}px` } : {}
17958
18375
  };
17959
18376
  return /* @__PURE__ */ jsx(PageLayoutNestingContext.Provider, { value: { isNested: true }, children: /* @__PURE__ */ jsxs("div", { className: rootClass, style: rootStyle, children: [
18377
+ !isNested && /* @__PURE__ */ jsx(ImpersonationBanner, { className: styles.impersonationBanner }),
17960
18378
  header !== void 0 && headerVisible && /* @__PURE__ */ jsx(Header, { className: headerClass, ...headerStyle && { style: headerStyle }, children: header }),
17961
18379
  navigation !== void 0 && navigationVisible && /* @__PURE__ */ jsx(
17962
18380
  "nav",
@@ -18185,7 +18603,7 @@ var RADIAL_CHART_TYPES = /* @__PURE__ */ new Set([
18185
18603
  ChartType.Radar,
18186
18604
  ChartType.Funnel
18187
18605
  ]);
18188
- var useStyles16 = makeStyles({
18606
+ var useStyles17 = makeStyles({
18189
18607
  // The wrapper uses Fluent design tokens directly so the rounded bordered
18190
18608
  // look picks up theme changes. `colorNeutralBackground1` (white in light,
18191
18609
  // grey[16] in dark) matches the Blazor FluentUIChart's
@@ -18263,7 +18681,7 @@ function FluentUIChartImpl(props, ref) {
18263
18681
  showBorder = true,
18264
18682
  onElementClick
18265
18683
  } = props;
18266
- const styles = useStyles16();
18684
+ const styles = useStyles17();
18267
18685
  const containerRef = useRef(null);
18268
18686
  const palette = useFluentChartPalette(containerRef);
18269
18687
  const themedDatasets = useMemo(() => {
@@ -18344,7 +18762,7 @@ function assembleTheme(palette, yAxisPrefix, yAxisSuffix, xAxisPrefix, xAxisSuff
18344
18762
  if (orientation !== void 0) theme.indexAxis = toIndexAxis(orientation);
18345
18763
  return Object.keys(theme).length > 0 ? theme : void 0;
18346
18764
  }
18347
- var useStyles17 = makeStyles({
18765
+ var useStyles18 = makeStyles({
18348
18766
  // Outer container — flex column so the view selector, message bar, and
18349
18767
  // chart fill share the caller's height budget.
18350
18768
  root: {
@@ -18422,7 +18840,7 @@ var DataverseChart = forwardRef(
18422
18840
  } = props;
18423
18841
  const chartSource = dataSource instanceof DataverseChartDataSource ? dataSource : void 0;
18424
18842
  const viewDs = dataSource instanceof DataverseChartDataSource ? void 0 : dataSource;
18425
- const styles = useStyles17();
18843
+ const styles = useStyles18();
18426
18844
  const t = useT();
18427
18845
  const { ppp, viewMetadataCache } = usePowerPortalsPro();
18428
18846
  const [data, setData] = useState({ labels: [], datasets: [] });
@@ -18656,6 +19074,6 @@ function applyViewIdToSource(source, viewId) {
18656
19074
  // src/index.ts
18657
19075
  var VERSION = "0.1.0";
18658
19076
 
18659
- export { Body, BoolEdit, BoolEditorType, CacheAdmin, ChoiceEdit, ChoiceEditorType, ChoiceInvalidValueBehavior, ChoiceOrientation, ChoiceValueSort, ColumnDisplayValue, ColumnEdit, DataverseChart, DateTimeEdit, DateTimeEditorType, DeleteContextButton, DeleteRecordGridButton, DescriptionLocation, FileEdit, FileGrid, FileViewer, FluentDialogProvider, FluentOverlayProvider, FluentOverlayTarget, FluentUIChart, Footer, GridButton, GridButtonBehavior, GridButtonRole, GridButtons, GridColumn, GridColumns, GridTemplateColumn, Header, Highlighter, ImageEdit, ImageViewer, LabelPosition, LanguageDropdown, LinkExistingRecordGridButton, LocalizationAdmin, LocalizationBoundary, LocalizationTranslator, LogoutButton, LookupDialog, LookupEdit, LookupEditorType, LookupForm, MainGrid, ManyToManyLookupEdit, ManyToManyLookupEditorType, MaskedTextField, MemoEdit, MenuBar, MoneyEdit, MultiSelectChoiceEdit, NavGroup, NavItem, NavMenu, NavigateNewRecordGridButton, NavigateOpenRecordGridButton, NavigateRecordGridButton, NewRecordGridButton, NumberEdit, OFFICE_ACCENT_DEFINITIONS, OFFICE_ACCENT_HEX_TO_NAME, OFFICE_ACCENT_THEMES, OFFICE_COLOR_NAME_TO_ACCENT, OpenRecordGridButton, PageLayout, PagingMode, PowerPortalsProThemeProvider, ProfileMainMenu, RefreshContextButton, RefreshGridButton, RouterUnsavedChangesGuard, SaveContextButton, Section, SectionColumn, SectionColumnOrientation, SiteSettingsButton, SiteSettingsPanel, SubGrid, TextDirection, TextEdit, ThemeColorSelector, ThemeMode, ThemeSelector, ThemeTextDirection, UnlinkExistingRecordGridButton, VERSION, ValidationSummary, ViewSort, WizardRecordForm, WizardRecordPage, evaluateGridButtonBehavior, findFirstButtonOfRole, getCellRenderer, getChartPalette, getChoiceMetadata, getDisplayDescription, getDisplayLabel, getEditComponentForMetadata, getEditComponentForType, getGridButtonRoleFromElement, getLookupMetadata, getNumberLimits, getRendererTypeForMetadata, getStringMaxLength, isColumnSortable, isMetadataReadOnly, isMetadataRequired, isNewRecord, officeColorValueToAccentName, resolveColumnDisplayValue, setGridButtonRole, splitHighlightSegments, toTranslucent, useBoolColumn, useChoiceColumn, useDateColumn, useFileColumn, useFluentChartPalette, useGridContext, useLookupColumn, useMoneyColumn, useMultiSelectChoiceColumn, useNumberColumn, useStringColumn, useTheme };
19077
+ export { Body, BoolEdit, BoolEditorType, CacheAdmin, ChoiceEdit, ChoiceEditorType, ChoiceInvalidValueBehavior, ChoiceOrientation, ChoiceValueSort, ColumnDisplayValue, ColumnEdit, DataverseChart, DateTimeEdit, DateTimeEditorType, DeleteContextButton, DeleteRecordGridButton, DescriptionLocation, FileEdit, FileGrid, FileViewer, FluentDialogProvider, FluentOverlayProvider, FluentOverlayTarget, FluentUIChart, Footer, GridButton, GridButtonBehavior, GridButtonRole, GridButtons, GridColumn, GridColumns, GridTemplateColumn, Header, Highlighter, ImageEdit, ImageViewer, ImpersonationBanner, ImpersonationPickerDialog, LabelPosition, LanguageDropdown, LinkExistingRecordGridButton, LocalizationAdmin, LocalizationBoundary, LocalizationTranslator, LogoutButton, LookupDialog, LookupEdit, LookupEditorType, LookupForm, MainGrid, ManyToManyLookupEdit, ManyToManyLookupEditorType, MaskedTextField, MemoEdit, MenuBar, MoneyEdit, MultiSelectChoiceEdit, NavGroup, NavItem, NavMenu, NavigateNewRecordGridButton, NavigateOpenRecordGridButton, NavigateRecordGridButton, NewRecordGridButton, NumberEdit, OFFICE_ACCENT_DEFINITIONS, OFFICE_ACCENT_HEX_TO_NAME, OFFICE_ACCENT_THEMES, OFFICE_COLOR_NAME_TO_ACCENT, OpenRecordGridButton, PageLayout, PagingMode, PowerPortalsProThemeProvider, ProfileMainMenu, RefreshContextButton, RefreshGridButton, RouterUnsavedChangesGuard, SaveContextButton, Section, SectionColumn, SectionColumnOrientation, SiteSettingsButton, SiteSettingsPanel, SubGrid, TextDirection, TextEdit, ThemeColorSelector, ThemeMode, ThemeSelector, ThemeTextDirection, UnlinkExistingRecordGridButton, VERSION, ValidationSummary, ViewSort, WizardRecordForm, WizardRecordPage, evaluateGridButtonBehavior, findFirstButtonOfRole, getCellRenderer, getChartPalette, getChoiceMetadata, getDisplayDescription, getDisplayLabel, getEditComponentForMetadata, getEditComponentForType, getGridButtonRoleFromElement, getLookupMetadata, getNumberLimits, getRendererTypeForMetadata, getStringMaxLength, isColumnSortable, isMetadataReadOnly, isMetadataRequired, isNewRecord, officeColorValueToAccentName, resolveColumnDisplayValue, setGridButtonRole, splitHighlightSegments, toTranslucent, useBoolColumn, useChoiceColumn, useDateColumn, useFileColumn, useFluentChartPalette, useGridContext, useLookupColumn, useMoneyColumn, useMultiSelectChoiceColumn, useNumberColumn, useStringColumn, useTheme };
18660
19078
  //# sourceMappingURL=index.js.map
18661
19079
  //# sourceMappingURL=index.js.map