efficient-shell 0.0.0 → 4.126.2
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/LICENSE +21 -0
- package/README.md +752 -4
- package/dist/Browser-DBRSIQ4J.js +478 -0
- package/dist/Browser-DBRSIQ4J.js.map +1 -0
- package/dist/Calculator-HCKFTSJR.js +188 -0
- package/dist/Calculator-HCKFTSJR.js.map +1 -0
- package/dist/ConfirmDialog-LJXMB45A.js +3 -0
- package/dist/ConfirmDialog-LJXMB45A.js.map +1 -0
- package/dist/CurrencyConverter-BKKORN6K.js +232 -0
- package/dist/CurrencyConverter-BKKORN6K.js.map +1 -0
- package/dist/Documents-5MD5SKPB.js +437 -0
- package/dist/Documents-5MD5SKPB.js.map +1 -0
- package/dist/Files-KY35DPWB.js +801 -0
- package/dist/Files-KY35DPWB.js.map +1 -0
- package/dist/Notepad-2BO5VEUG.js +406 -0
- package/dist/Notepad-2BO5VEUG.js.map +1 -0
- package/dist/PdfViewer-LSP3YNFC.js +4 -0
- package/dist/PdfViewer-LSP3YNFC.js.map +1 -0
- package/dist/PomodoroTimer-SVJ62RXM.js +629 -0
- package/dist/PomodoroTimer-SVJ62RXM.js.map +1 -0
- package/dist/Preview-N4UYYXG6.js +2955 -0
- package/dist/Preview-N4UYYXG6.js.map +1 -0
- package/dist/Sidebar-J76PV4QF.js +290 -0
- package/dist/Sidebar-J76PV4QF.js.map +1 -0
- package/dist/Spreadsheet-5EWZGMXN.js +515 -0
- package/dist/Spreadsheet-5EWZGMXN.js.map +1 -0
- package/dist/Stock-CDUS4VCZ.js +154 -0
- package/dist/Stock-CDUS4VCZ.js.map +1 -0
- package/dist/Weather-EEDBWBPQ.js +427 -0
- package/dist/Weather-EEDBWBPQ.js.map +1 -0
- package/dist/WorldClock-FFFULVTJ.js +198 -0
- package/dist/WorldClock-FFFULVTJ.js.map +1 -0
- package/dist/apps/index.d.ts +267 -0
- package/dist/apps/index.js +147 -0
- package/dist/apps/index.js.map +1 -0
- package/dist/brand.css +2 -0
- package/dist/chunk-2VM2FRZJ.js +215 -0
- package/dist/chunk-2VM2FRZJ.js.map +1 -0
- package/dist/chunk-32W6VY2J.js +549 -0
- package/dist/chunk-32W6VY2J.js.map +1 -0
- package/dist/chunk-3CUSBZWG.js +59 -0
- package/dist/chunk-3CUSBZWG.js.map +1 -0
- package/dist/chunk-3L463T5H.js +26 -0
- package/dist/chunk-3L463T5H.js.map +1 -0
- package/dist/chunk-4TVV2BIU.js +13334 -0
- package/dist/chunk-4TVV2BIU.js.map +1 -0
- package/dist/chunk-6XQHH7D2.js +105 -0
- package/dist/chunk-6XQHH7D2.js.map +1 -0
- package/dist/chunk-7CCHEEYC.js +94 -0
- package/dist/chunk-7CCHEEYC.js.map +1 -0
- package/dist/chunk-7KIOEKHS.js +617 -0
- package/dist/chunk-7KIOEKHS.js.map +1 -0
- package/dist/chunk-AXWC3332.js +1103 -0
- package/dist/chunk-AXWC3332.js.map +1 -0
- package/dist/chunk-BFBWLCO6.js +16 -0
- package/dist/chunk-BFBWLCO6.js.map +1 -0
- package/dist/chunk-C3NELUTX.js +79 -0
- package/dist/chunk-C3NELUTX.js.map +1 -0
- package/dist/chunk-CT3HVZRZ.js +26 -0
- package/dist/chunk-CT3HVZRZ.js.map +1 -0
- package/dist/chunk-D7PYW2QS.js +265 -0
- package/dist/chunk-D7PYW2QS.js.map +1 -0
- package/dist/chunk-DMCFOWOJ.js +153 -0
- package/dist/chunk-DMCFOWOJ.js.map +1 -0
- package/dist/chunk-FQB24B42.js +416 -0
- package/dist/chunk-FQB24B42.js.map +1 -0
- package/dist/chunk-GA35NEND.js +56 -0
- package/dist/chunk-GA35NEND.js.map +1 -0
- package/dist/chunk-HNH7FOYB.js +69 -0
- package/dist/chunk-HNH7FOYB.js.map +1 -0
- package/dist/chunk-HYZ5QMVO.js +462 -0
- package/dist/chunk-HYZ5QMVO.js.map +1 -0
- package/dist/chunk-I464GYJJ.js +17 -0
- package/dist/chunk-I464GYJJ.js.map +1 -0
- package/dist/chunk-JFQGPY7W.js +819 -0
- package/dist/chunk-JFQGPY7W.js.map +1 -0
- package/dist/chunk-MGGY74RY.js +14 -0
- package/dist/chunk-MGGY74RY.js.map +1 -0
- package/dist/chunk-N3CG5HG5.js +144 -0
- package/dist/chunk-N3CG5HG5.js.map +1 -0
- package/dist/chunk-W6XTG23K.js +3708 -0
- package/dist/chunk-W6XTG23K.js.map +1 -0
- package/dist/chunk-YG45DAUT.js +96 -0
- package/dist/chunk-YG45DAUT.js.map +1 -0
- package/dist/chunk-YMGXZABY.js +7 -0
- package/dist/chunk-YMGXZABY.js.map +1 -0
- package/dist/chunk-YNGPTOL6.js +46 -0
- package/dist/chunk-YNGPTOL6.js.map +1 -0
- package/dist/chunk-YNHLQ24V.js +400 -0
- package/dist/chunk-YNHLQ24V.js.map +1 -0
- package/dist/chunk-YNKK7NE3.js +171 -0
- package/dist/chunk-YNKK7NE3.js.map +1 -0
- package/dist/chunk-YPO6RDMX.js +20 -0
- package/dist/chunk-YPO6RDMX.js.map +1 -0
- package/dist/file-intake/index.d.ts +87 -0
- package/dist/file-intake/index.js +3 -0
- package/dist/file-intake/index.js.map +1 -0
- package/dist/index-dQia6NpB.d.ts +5904 -0
- package/dist/index.d.ts +2495 -0
- package/dist/index.js +7460 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown/index.d.ts +118 -0
- package/dist/markdown/index.js +184 -0
- package/dist/markdown/index.js.map +1 -0
- package/dist/markup/index.d.ts +235 -0
- package/dist/markup/index.js +135 -0
- package/dist/markup/index.js.map +1 -0
- package/dist/page-tabs/index.d.ts +154 -0
- package/dist/page-tabs/index.js +706 -0
- package/dist/page-tabs/index.js.map +1 -0
- package/dist/shell.css +105 -0
- package/dist/sounds-NT4DEZGD.js +3 -0
- package/dist/sounds-NT4DEZGD.js.map +1 -0
- package/dist/styles.css +22 -0
- package/dist/themes.css +249 -0
- package/dist/types-BOinJXUf.d.ts +118 -0
- package/dist/ui/index.d.ts +4 -0
- package/dist/ui/index.js +15 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui.css +1518 -0
- package/package.json +172 -3
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,2495 @@
|
|
|
1
|
+
import { S as SearchConfig, C as ColumnDef, a as SortState, P as PaginatedResponse } from './index-dQia6NpB.js';
|
|
2
|
+
export { A as ALT, b as ALT_SHIFT_D, c as ALT_SHIFT_E, d as ALT_SHIFT_N, e as APP_UPDATE_POLL_MS, f as Accordion, g as AccordionItem, h as AccordionProps, i as Align, j as AngleScale, k as AppUpdateBanner, l as AppUpdateBannerProps, m as AttachButton, n as AttachButtonProps, o as AttachmentDropZone, p as AttachmentDropZoneProps, q as AttachmentList, r as AttachmentListProps, s as AuthScreen, t as AuthScreenProps, u as Avatar, v as AvatarGroup, w as AvatarGroupProps, x as AvatarProps, y as AvatarSize, z as AvatarStatus, B as BackgroundVariant, D as BandScale, E as Banner, F as BannerEmphasis, G as BannerProps, H as BannerTone, I as BarChart, J as BarChartProps, K as BoxPlotBox, L as BoxPlotChart, M as BoxPlotChartProps, N as BrandAssetEditor, O as BrandAssetEditorProps, Q as BrandAssetPreview, R as BrandMark, T as BrandMarkProps, U as BrandMarkSlot, V as BrandMarkSurface, W as BrandMarkTreatment, X as BreadcrumbItem, Y as Breadcrumbs, Z as BreadcrumbsProps, _ as BudgetBar, $ as BudgetBarProps, a0 as BudgetState, a1 as Button, a2 as ButtonProps, a3 as ButtonSize, a4 as ButtonVariant, a5 as CHART_INK, a6 as CMD_A, a7 as CMD_DOT, a8 as CMD_ENTER, a9 as CMD_K, aa as CMD_S, ab as Calendar, ac as CalendarMode, ad as CalendarProps, ae as Candle, af as CandlestickChart, ag as CandlestickChartProps, ah as Card, ai as CardPadding, aj as CardProps, ak as CartesianPlot, al as CartesianPlotProps, am as CellStyle, an as ChartBackgroundVariant, ao as ChartBrush, ap as ChartBrushProps, aq as ChartCurve, ar as ChartDefs, as as ChartDefsProps, at as ChartDot, au as ChartDotProps, av as ChartDotVariant, aw as ChartFillVariant, ax as ChartFrame, ay as ChartFrameProps, az as ChartHighlightProvider, aA as ChartLegendEntry, aB as ChartLegendSwatch, aC as ChartRange, aD as ChartReferenceLine, aE as ChartRevealDirection, aF as ChartSkeleton, aG as ChartSkeletonProps, aH as ChartStatusTone, aI as ChartStrokeVariant, aJ as ChartTooltip, aK as ChartTooltipProps, aL as ChartTooltipRow, aM as ChatTemplate, aN as Checkbox, aO as CheckboxProps, aP as CheckoutTemplate, aQ as ChordChart, aR as ChordChartProps, aS as ColoredBadge, aT as ColoredBadgeProps, aU as ColumnChart, aV as ColumnChartProps, aW as ColumnSeries, aX as ComposerAttachments, aY as ComposerAttachmentsProps, aZ as ConfirmProvider, a_ as ContainerFillChart, a$ as ContainerFillChartProps, b0 as ContainerFillItem, b1 as CountBadge, b2 as CountBadgeProps, b3 as CountBadgeTone, b4 as Curve, b5 as DAY_MS, b6 as DEFAULT_SHELL_STRINGS, b7 as DashboardTemplate, b8 as DataTable, b9 as DataTableColumn, ba as DataTableColumnGroup, bb as DataTableHeader, bc as DataTablePage, bd as DataTableProps, be as DatePicker, bf as DatePickerProps, bg as DateRangePicker, bh as DateRangePickerProps, bi as DateTimePicker, bj as DateTimePickerProps, bk as DescriptionColumns, bl as DescriptionItem, bm as DescriptionList, bn as DescriptionListProps, bo as Dialog, bp as DialogProps, bq as DialogSize, br as Divider, bs as DividerProps, bt as DividerSpacing, bu as DonutChart, bv as DonutChartProps, bw as DonutSegment, bx as Drawer, by as DrawerProps, bz as DrawerSide, bA as DrawerSize, bB as DropdownMenu, bC as DropdownMenuAlign, bD as DropdownMenuItem, bE as DropdownMenuProps, bF as DropdownMenuSide, bG as DropdownPositionOptions, bH as ENTER, bI as EditableGrid, bJ as EditableGridProps, bK as EmailTemplate, bL as EmptyState, bM as EmptyStateProps, bN as ErrorBoundary, bO as ErrorBoundaryProps, bP as ErrorPage, bQ as ErrorPageProps, bR as FileIntake, bS as FilePicker, bT as FilePickerProps, bU as FillVariant, bV as FormError, bW as FormErrorSummary, bX as FormErrorSummaryProps, bY as FormField, bZ as FormFieldProps, b_ as FormLayoutPage, b$ as FunnelChart, c0 as FunnelChartProps, c1 as FunnelStage, c2 as GLASS_DIVIDER, c3 as GLASS_INPUT_BG, c4 as GROUP_COLORS, c5 as GROUP_COLORS_SOLID, c6 as GalleryTemplate, c7 as Gap, c8 as GlobalSearch, c9 as GlobalSearchProps, ca as Grid, cb as GridCols, cc as GridColumn, cd as GridProps, ce as HeatmapChart, cf as HeatmapChartProps, cg as HelpCenter, ch as HelpCenterDoc, ci as HelpCenterProps, cj as HistogramBin, ck as HistogramChart, cl as HistogramChartProps, cm as INPUT_BASE, cn as INPUT_SIZES, co as IconButton, cp as IconButtonProps, cq as Inline, cr as InlineProps, cs as Input, ct as InputNumber, cu as InputNumberProps, cv as InputProps, cw as InputSize, cx as Justify, cy as Kanban, cz as KanbanColumn, cA as KanbanProps, cB as Kbd, cC as KbdProps, cD as KbdSize, cE as Label, cF as LabelProps, cG as LadderScale, cH as LineChart, cI as LineChartProps, cJ as LineChartSeries, cK as LinearScale, cL as ListFooter, cM as ListLoadError, cN as ListLoadErrorProps, cO as LoadingSpinner, cP as LoadingSpinnerProps, cQ as MAX_FRACTION_DIGITS, cR as MENU_MAX_HEIGHT, cS as MOD, cT as MOTION, cU as Markdown, cU as MarkdownLite, cV as MarkdownLiteProps, cV as MarkdownProps, cW as MediaUploadField, cX as MediaUploadFieldProps, cY as MediaUploadGrid, cZ as MediaUploadGridItem, c_ as MediaUploadGridProps, c$ as MenuPos, d0 as Meter, d1 as MeterProps, d2 as MetricBar, d3 as MetricBarProps, d4 as Milestone, d5 as MilestoneKind, d6 as MilestoneTimeline, d7 as MilestoneTimelineProps, d8 as NEXT_CHUNK_PATH_PREFIX, d9 as NativeSelect, da as NumericKeypad, db as NumericKeypadProps, dc as OVERLAY_OPEN_EVENT, dd as POPUP_MAX_WIDTH, de as POST_PRODUCTION_STATUSES, df as PageHeader, dg as PageHeaderProps, dh as Pagination, di as PaginationProps, dj as PaperclipGlyph, dk as Paragraph, dl as ParagraphProps, dm as PieChart, dn as PieChartProps, dp as PieSegment, dq as PlotGeometry, dr as Point, ds as PopupMenu, dt as PopupMenuDivider, du as PopupMenuItem, dv as PopupMenuItemProps, dw as PopupMenuLabel, dx as PopupMenuProps, dy as PopupSubmenu, dz as PopupSubmenuProps, dA as PortalBrandingContextValue, dB as PortalBrandingProvider, dC as PortalBrandingProviderProps, dD as ProductionTimeline, dE as ProductionTimelineProps, dF as ProductionTimelineSnapshot, dG as ProgressItem, dH as PublicPortal, dI as PublicPortalBranding, dJ as RADAR_SERIES_CAP, dK as RadarAxis, dL as RadarChart, dM as RadarChartProps, dN as RadarSeries, dO as RadialBarChart, dP as RadialBarChartProps, dQ as RadialBarRow, dR as Radio, dS as RadioGroup, dT as RadioGroupOption, dU as RadioGroupOrientation, dV as RadioGroupProps, dW as RadioProps, dX as Range, dY as RangeChart, dZ as RangeChartProps, d_ as RangeRow, d$ as RankedBars, e0 as RankedBarsProps, e1 as RankedBarsRow, e2 as ResolvedPortalBrandAsset, e3 as Result, e4 as ResultProps, e5 as ResultStatus, e6 as RevealDirection, e7 as SCATTER_SERIES_CAP, e8 as SERIES_SLOT_COUNT, e9 as SERIES_VARS, ea as SHIFT, eb as STAGES, ec as STALE_CHUNK_RELOAD_COOLDOWN_MS, ed as STALE_CHUNK_RELOAD_KEY, ee as STATUS_VARS, ef as SankeyChart, eg as SankeyChartProps, eh as SankeyLink, ei as SankeyNode, ej as ScatterChart, ek as ScatterChartProps, el as ScatterPoint, em as ScatterSeries, en as SearchProvider, eo as SearchResult, ep as SearchableOption, eq as SearchableSelect, er as SearchableSelectProps, es as Segmented, et as SegmentedOption, eu as SegmentedProps, ev as SegmentedSize, ew as Select, ex as SelectOption, ey as SelectProps, ez as SemanticGroup, eA as SettingRow, eB as SettingRowProps, eC as SeverityTone, eD as ShellPrefsAdapter, eE as ShellPrefsProvider, eF as ShellStrings, eG as ShellStringsOverride, eH as ShellStringsProvider, eI as SidebarActionButton, eJ as SidebarActionButtonProps, eK as SidebarGroupLabel, eL as SidebarLayout, eM as SidebarLayoutProps, eN as SidebarNavItem, eO as SidebarNavItemProps, eP as Skeleton, eQ as SkeletonProps, eR as SkeletonVariant, eS as Snippet, eT as SnippetProps, eU as SnippetSize, eV as SnippetVariant, eW as Sparkline, eX as SparklineProps, eY as Stack, eZ as StackProps, e_ as StaleChunkReloadOptions, e$ as StatCard, f0 as StatCardProps, f1 as StatTile, f2 as StatTileProps, f3 as Statistic, f4 as StatisticProps, f5 as StatisticSize, f6 as StatisticTone, f7 as StatusBadge, f8 as StatusBadgeProvider, f9 as StatusEmphasis, fa as StepItem, fb as Stepper, fc as StepperProps, fd as StrokeVariant, fe as SunburstChart, ff as SunburstChartProps, fg as SunburstNode, fh as Switch, fi as SwitchProps, fj as SwitchSize, fk as TabItem, fl as Tabs, fm as TabsProps, fn as TagInput, fo as TagInputOption, fp as TagInputProps, fq as Text, fr as TextProps, fs as TextSize, ft as TextTone, fu as TextWeight, fv as Textarea, fw as TextareaProps, fx as Theme, fy as TileButton, fz as TileButtonProps, fA as TileSize, fB as TimePicker, fC as TimePickerProps, fD as TimeSeriesChart, fE as TimeSeriesChartProps, fF as TimeSeriesSeries, fG as TimelineMarker, fH as TimelineMarkerKind, fI as TimelineReport, fJ as TimelineScrubProgress, fK as TimelineTrack, fL as TimelineTrackItem, fM as TimelineTrackKind, fN as TimelineTrackPending, fO as TimelineTrackPhase, fP as TimelineTrackPlayback, fQ as TimelineTrackProps, fR as TimelineTrackThumb, fS as Title, fT as TitleLevel, fU as TitleProps, fV as ToastOptions, fW as ToastPlacement, fX as Tooltip, fY as TooltipProps, fZ as TopNav, f_ as TopNavItem, f$ as TopNavProps, g0 as TreemapChart, g1 as TreemapChartProps, g2 as TreemapItem, g3 as TreemapTile, g4 as UseAppUpdateOptions, g5 as UseProductionTimelineOpts, g6 as VERSION, g7 as VITE_PRELOAD_ERROR_EVENT, g8 as WaterfallChart, g9 as WaterfallChartProps, ga as WaterfallStep, gb as ZLayer, gc as Z_LAYERS, gd as angleScale, ge as appendKey, gf as applyThemePrefs, gg as arcPath, gh as areaBetween, gi as areaFrom, gj as autoHighlightIndex, gk as backspace, gl as bandScale, gm as binValues, gn as budgetState, go as bumpPath, gp as calcOverall, gq as calcReportOverall, gr as confirm, gs as confirmDestructive, gt as curvePath, gu as dashFor, gv as dismissPopups, gw as fillFor, gx as fillId, gy as fmtSliderDate, gz as formatDate, gA as fromDateKey, gB as glassStyle, gC as glowId, gD as groundId, gE as groupColors, gF as highlightOpacity, gG as inputClasses, gH as installStaleChunkReload, gI as isMac, gJ as isSeverityTone, gK as isStaleChunkError, gL as ladderScale, gM as linearPath, gN as linearScale, gO as logScale, gP as maskId, gQ as mediaFileName, gR as monotonePath, gS as niceMax, gT as polygonPoints, gU as popupBounds, gV as prompt, gW as radiusScale, gX as resolveBrandMarkTreatment, gY as resolvePortalBrandAsset, gZ as resolveSeriesColor, g_ as resolveTheme, g$ as seriesColor, h0 as severityOf, h1 as splinePath, h2 as squarify, h3 as stagger, h4 as stepPath, h5 as tabButtonId, h6 as tabPanelId, h7 as toDateKey, h8 as toDayMs, h9 as toISODate, ha as toast, hb as useAppUpdate, hc as useClickOutside, hd as useConfirm, he as useDropdownPosition, hf as useFocusTrap, hg as useHighlight, hh as useIsMobile, hi as useLocalStoragePrefs, hj as usePlotWidth, hk as usePortalBranding, hl as useProductionTimeline, hm as useScrollLock, hn as useShellPrefs, ho as useShellStrings, hp as useTheme } from './index-dQia6NpB.js';
|
|
3
|
+
export { FileIntakeAlert, FileIntakeLimits, FileIntakeOptions, FileRejection, FileRejectionReason, acceptsFile, useFileIntake } from './file-intake/index.js';
|
|
4
|
+
import { W as WindowRegistry } from './types-BOinJXUf.js';
|
|
5
|
+
export { M as ModalRegistryEntry, P as PageRegistryEntry, a as WindowRegistryEntry, i as isEntityEntry, b as isPageEntry, s as setShellWindowRegistry } from './types-BOinJXUf.js';
|
|
6
|
+
import * as react from 'react';
|
|
7
|
+
import react__default, { ReactNode, Component, ErrorInfo, Dispatch, SetStateAction, RefObject } from 'react';
|
|
8
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
9
|
+
import * as _tanstack_query_core from '@tanstack/query-core';
|
|
10
|
+
import { AxiosInstance } from 'axios';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* createWindowRegistry — composes a window registry from one or more partial
|
|
14
|
+
* registry maps. Used at the consumer's App layer to merge package-bundled
|
|
15
|
+
* apps with the consumer's own entity windows.
|
|
16
|
+
*
|
|
17
|
+
* const windows = createWindowRegistry(
|
|
18
|
+
* bundledApps, // utility + document + web apps from the package
|
|
19
|
+
* entityWindows, // consumer-defined entity windows (e.g. Sales Order, Invoice)
|
|
20
|
+
* );
|
|
21
|
+
*
|
|
22
|
+
* Later partials override earlier ones on the same key — handy when a
|
|
23
|
+
* consumer wants to swap a built-in app for their own variant.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
declare function createWindowRegistry(...partials: WindowRegistry[]): WindowRegistry;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Nav data types used by <Layout> + <StartMenu>. The actual sections, icons,
|
|
30
|
+
* and categories are consumer-supplied via Layout props — the package never
|
|
31
|
+
* ships nav DATA, only the shape it expects.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
interface NavItem {
|
|
35
|
+
to: string;
|
|
36
|
+
label: string;
|
|
37
|
+
/** Optional compact wording used only inside navigation menus. Keep `label`
|
|
38
|
+
* as the full semantic name for windows, search and assistive technology. */
|
|
39
|
+
menuLabel?: string;
|
|
40
|
+
perms?: string[];
|
|
41
|
+
/** Permissions the user must hold EVERY one of, unlike `perms` which needs
|
|
42
|
+
* only one. For an entry whose page is gated on a narrower permission than
|
|
43
|
+
* the one that puts it in the menu: listing both in `perms` would widen the
|
|
44
|
+
* audience (it's an OR), so the extra one goes here instead. */
|
|
45
|
+
allPerms?: string[];
|
|
46
|
+
dividerAfter?: boolean;
|
|
47
|
+
/** An action instead of a destination. When set, choosing the row calls this
|
|
48
|
+
* and opens no window — `to` is then only the row's key (and its `navIcons`
|
|
49
|
+
* lookup), so give it one no page uses. The menu closes as it would after
|
|
50
|
+
* opening a page. For rows whose target is not a window: signing in to
|
|
51
|
+
* another tenant, say, which Sign Out is the built-in precedent for. */
|
|
52
|
+
onSelect?: () => void;
|
|
53
|
+
/** Optional sub-items, to any depth — a child may have children of its own.
|
|
54
|
+
* Hovering the parent in <StartMenu> opens a nested flyout; in <Sidebar>
|
|
55
|
+
* the parent becomes an inline sub-accordion. */
|
|
56
|
+
children?: NavItem[];
|
|
57
|
+
}
|
|
58
|
+
interface NavSection {
|
|
59
|
+
label: string;
|
|
60
|
+
/** Optional compact wording used only inside navigation menus. */
|
|
61
|
+
menuLabel?: string;
|
|
62
|
+
items: NavItem[];
|
|
63
|
+
perms?: string[];
|
|
64
|
+
/** Same all-of rule as `NavItem.allPerms`, for a whole section. */
|
|
65
|
+
allPerms?: string[];
|
|
66
|
+
/** Optional landing route for clicks on the section title itself
|
|
67
|
+
* (e.g. R&D's `/rd` dashboard). */
|
|
68
|
+
to?: string;
|
|
69
|
+
/** Draw a divider under this section's row, so a consumer can split one
|
|
70
|
+
* flat group into visual bands (e.g. an operator console pinned above the
|
|
71
|
+
* ERP sections). Same knob as `NavItem.dividerAfter`, and drawn INSIDE the
|
|
72
|
+
* section's wrapper so it stays below the row in the `flex-col-reverse`
|
|
73
|
+
* Start menu a side/top taskbar renders. */
|
|
74
|
+
dividerAfter?: boolean;
|
|
75
|
+
}
|
|
76
|
+
interface VirtualSection {
|
|
77
|
+
label: string;
|
|
78
|
+
/** Optional compact wording used only inside navigation menus. */
|
|
79
|
+
menuLabel?: string;
|
|
80
|
+
items: NavItem[];
|
|
81
|
+
icon?: ReactNode;
|
|
82
|
+
}
|
|
83
|
+
interface StartMenuCategories {
|
|
84
|
+
/** Section labels rendered in the "ERP" group (bold, with section icon). */
|
|
85
|
+
erp: string[];
|
|
86
|
+
/** Section labels rendered in the "system" group. */
|
|
87
|
+
system: string[];
|
|
88
|
+
/** Optional virtual flyouts (e.g. a "Utilities" tray). */
|
|
89
|
+
virtual?: VirtualSection[];
|
|
90
|
+
/** Optional section labels pinned to the bottom of the menu, next to the user
|
|
91
|
+
* profile, separated from the ERP group by a divider. */
|
|
92
|
+
footer?: string[];
|
|
93
|
+
/** Optional flat top-level items pinned to the bottom of the menu, next to
|
|
94
|
+
* the user profile, separated from the ERP group by a divider. Unlike
|
|
95
|
+
* `footer` (section labels rendered as flyouts), these render as direct
|
|
96
|
+
* clickable rows — use for standalone destinations like System Preferences
|
|
97
|
+
* or a bug-report link. */
|
|
98
|
+
footerItems?: NavItem[];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* INTERNAL stub — package-side compatibility for files that legacy-imported
|
|
103
|
+
* default nav data from `shell-config/nav`. The package never ships nav
|
|
104
|
+
* DATA; consumers always supply `navSections` / `navIcons` / `sectionIcons` /
|
|
105
|
+
* `categories` as Layout props.
|
|
106
|
+
*
|
|
107
|
+
* Empty defaults exported here so the copied files compile during the
|
|
108
|
+
* extraction transition. Each consumer-facing field is also re-exported as
|
|
109
|
+
* a TYPE alias for code that only needed the shape.
|
|
110
|
+
*/
|
|
111
|
+
|
|
112
|
+
declare function setShellNavIcons(icons: Record<string, ReactNode>): void;
|
|
113
|
+
|
|
114
|
+
/** Register an Escape interceptor; returns an unregister function. */
|
|
115
|
+
declare function registerModalEscapeInterceptor(fn: (e: KeyboardEvent) => boolean): () => void;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* What Cmd/Ctrl+Enter clicks when a window has no `<form>` to `requestSubmit()`.
|
|
119
|
+
*
|
|
120
|
+
* Two kinds of entry, in descending order of how much we trust them:
|
|
121
|
+
*
|
|
122
|
+
* - **Explicit** — `type="submit"`, the bare `data-submit` attribute, and the
|
|
123
|
+
* `.btn-submit` utility class. These are how a consumer *declares* "this is
|
|
124
|
+
* the submit", so they are the ones to reach for in new markup.
|
|
125
|
+
* - **Legacy colour heuristic** — `.bg-green-600` / `.bg-blue-600`. From before
|
|
126
|
+
* the explicit opt-ins existed, when the submit button was identified by
|
|
127
|
+
* being the blue (or green, for approve/post) one. Kept because consumers
|
|
128
|
+
* still ship plenty of it; do not add more colours here.
|
|
129
|
+
*
|
|
130
|
+
* `.btn-submit` was missing from this list until 4.76.0, which made converting a
|
|
131
|
+
* button from a hardcoded `bg-blue-600` fill to the themable `.btn-submit`
|
|
132
|
+
* utility silently drop it out of the selector — Cmd+Enter stopped working while
|
|
133
|
+
* the button's own ⌘⏎ badge went on advertising it. The admin portal hit this
|
|
134
|
+
* across ~26 buttons and worked around it by also adding `data-submit`.
|
|
135
|
+
*
|
|
136
|
+
* NOTE: a comma-separated `querySelector` returns the first match in **document
|
|
137
|
+
* order**, not the first *selector* that matches — so the order of the entries
|
|
138
|
+
* below is documentation, not precedence. When a panel holds several candidates,
|
|
139
|
+
* the earliest one in the DOM wins.
|
|
140
|
+
*/
|
|
141
|
+
declare const MODAL_SUBMIT_SELECTOR = "button[type=\"submit\"], button[data-submit], button.btn-submit, button.bg-green-600, button.bg-blue-600";
|
|
142
|
+
/** Hook for widget components to register a settings handler for the right-click menu. */
|
|
143
|
+
declare function useWidgetSettings(handler: () => void): void;
|
|
144
|
+
/** Hook to add custom items to the window title menu from inside a modal */
|
|
145
|
+
declare function useWindowMenuItem(label: string, onClick: () => void, icon?: React.ReactNode): void;
|
|
146
|
+
/** Component that updates the surrounding window's title bar. Render anywhere
|
|
147
|
+
* inside a Modal-rendered page. Cross-bundle safe (uses DOM traversal rather
|
|
148
|
+
* than React Context, which has separate instances in each bundle). */
|
|
149
|
+
declare function WindowTitle({ title }: {
|
|
150
|
+
title: string;
|
|
151
|
+
}): react_jsx_runtime.JSX.Element;
|
|
152
|
+
/** @deprecated Use <WindowTitle> instead — the hook variant doesn't work
|
|
153
|
+
* across bundle boundaries (apps barrel has its own ModalIdContext). */
|
|
154
|
+
declare function useWindowTitle(_title: string): void;
|
|
155
|
+
/**
|
|
156
|
+
* Portal component — renders children into the nearest Modal's footer actions area.
|
|
157
|
+
* position="left" for destructive actions (Delete), default is right for primary actions.
|
|
158
|
+
*/
|
|
159
|
+
declare function CopyButton({ text }: {
|
|
160
|
+
text: string;
|
|
161
|
+
}): react_jsx_runtime.JSX.Element;
|
|
162
|
+
/**
|
|
163
|
+
* The id of the `<Modal>` this component is rendered inside, or `''` outside one.
|
|
164
|
+
*
|
|
165
|
+
* This is the PRIVATE per-mount id (`modal-xxxxxx`) that `activationOrder` and
|
|
166
|
+
* the window-scoped CustomEvents carry — not the caller-supplied `windowKey`.
|
|
167
|
+
* Same context `useWindowMenuItem` and `useWidgetSettings` already read, and it
|
|
168
|
+
* reaches portal window content the same way theirs does.
|
|
169
|
+
*
|
|
170
|
+
* Use it to answer "was this event meant for MY window?", which
|
|
171
|
+
* {@link useModalActive} cannot: that asks "is the frontmost modal mine", and
|
|
172
|
+
* every nested dialog a form opens pushes itself onto `activationOrder`, so a
|
|
173
|
+
* form with a child dialog open reads as inactive.
|
|
174
|
+
*/
|
|
175
|
+
declare function useEnclosingModalId(): string;
|
|
176
|
+
/** Hook to check if the current modal is active (frontmost) */
|
|
177
|
+
declare function useModalActive(): boolean;
|
|
178
|
+
declare function ModalActions({ children, position, quiet }: {
|
|
179
|
+
children: React.ReactNode;
|
|
180
|
+
position?: 'left' | 'right';
|
|
181
|
+
/** @internal Content that joins a footer but is no reason to show one — the
|
|
182
|
+
* shell's own Undo/Redo pair. It does not tell the Modal the footer has
|
|
183
|
+
* content. */
|
|
184
|
+
quiet?: boolean;
|
|
185
|
+
}): react.ReactPortal | null;
|
|
186
|
+
/**
|
|
187
|
+
* Cancel button that checks for unsaved changes before calling onClick.
|
|
188
|
+
* Automatically detects dirty state from the nearest Modal context.
|
|
189
|
+
*/
|
|
190
|
+
declare function CancelButton({ onClick, children, className }: {
|
|
191
|
+
onClick: () => void;
|
|
192
|
+
children?: React.ReactNode;
|
|
193
|
+
className?: string;
|
|
194
|
+
}): react_jsx_runtime.JSX.Element;
|
|
195
|
+
/**
|
|
196
|
+
* Seed an initial position for a window — applied only when no saved
|
|
197
|
+
* position already exists for the given key. Use this from the consumer
|
|
198
|
+
* to lay out the first-run desktop without overwriting whatever the user
|
|
199
|
+
* has subsequently dragged the windows to.
|
|
200
|
+
*
|
|
201
|
+
* The key matches the window's `windowKey` — for `openPage('/weather')`
|
|
202
|
+
* that's `'page:/weather'`.
|
|
203
|
+
*/
|
|
204
|
+
declare function setWindowDefaultPosition(key: string, pos: {
|
|
205
|
+
x: number;
|
|
206
|
+
y: number;
|
|
207
|
+
w: number;
|
|
208
|
+
h: number;
|
|
209
|
+
}): void;
|
|
210
|
+
/** Read the saved box for a window key (viewport-relative px), or null. */
|
|
211
|
+
declare function getWindowPosition(key: string): {
|
|
212
|
+
x: number;
|
|
213
|
+
y: number;
|
|
214
|
+
w: number;
|
|
215
|
+
h: number;
|
|
216
|
+
} | null;
|
|
217
|
+
/**
|
|
218
|
+
* Force a window's saved position — unlike `setWindowDefaultPosition` this
|
|
219
|
+
* overwrites any existing entry. Use when the consumer wants to deliberately
|
|
220
|
+
* (re)place a window, e.g. the Widget Manager dropping a freshly-added widget
|
|
221
|
+
* into a tidy top-left slot regardless of where it last sat. For `autoHeight`
|
|
222
|
+
* windows the `h` here is only a first-paint placeholder — the window
|
|
223
|
+
* re-measures its content height on open.
|
|
224
|
+
*/
|
|
225
|
+
declare function setWindowPosition(key: string, pos: {
|
|
226
|
+
x: number;
|
|
227
|
+
y: number;
|
|
228
|
+
w: number;
|
|
229
|
+
h: number;
|
|
230
|
+
}): void;
|
|
231
|
+
interface ModalProps {
|
|
232
|
+
open: boolean;
|
|
233
|
+
onClose: () => void;
|
|
234
|
+
title: React.ReactNode;
|
|
235
|
+
/** Window icon — shown before title, clicking it opens window menu */
|
|
236
|
+
icon?: React.ReactNode;
|
|
237
|
+
/** Text to copy when the copy icon in the header is clicked (e.g. entity number) */
|
|
238
|
+
copyText?: string;
|
|
239
|
+
size?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl';
|
|
240
|
+
dirty?: boolean | 'auto';
|
|
241
|
+
onNext?: () => void;
|
|
242
|
+
onPrev?: () => void;
|
|
243
|
+
footer?: React.ReactNode;
|
|
244
|
+
/** When true, body uses overflow-hidden — child content manages its own scrolling */
|
|
245
|
+
bodyScroll?: false;
|
|
246
|
+
/** Custom minimize handler — called instead of the default global minimize */
|
|
247
|
+
onMinimize?: (savedBox: {
|
|
248
|
+
x: number;
|
|
249
|
+
y: number;
|
|
250
|
+
w: number;
|
|
251
|
+
h: number;
|
|
252
|
+
maximized: boolean;
|
|
253
|
+
}) => void;
|
|
254
|
+
/** Restore to a previously saved position/size */
|
|
255
|
+
initialBox?: {
|
|
256
|
+
x: number;
|
|
257
|
+
y: number;
|
|
258
|
+
w: number;
|
|
259
|
+
h: number;
|
|
260
|
+
maximized: boolean;
|
|
261
|
+
};
|
|
262
|
+
/** Footer actions (right side) — alternative to ModalActions portal */
|
|
263
|
+
actions?: React.ReactNode;
|
|
264
|
+
/** Footer actions (left side) — alternative to ModalActions portal */
|
|
265
|
+
actionsLeft?: React.ReactNode;
|
|
266
|
+
/** The window is in an editing state — a draft, a duplicate, or Edit mode
|
|
267
|
+
* on a detail — which is when the footer shows Undo/Redo for the form's
|
|
268
|
+
* stack of its own accord. `WindowManager` sets it; a window that edits in
|
|
269
|
+
* place says `useUndoCanEdit(true)` instead. */
|
|
270
|
+
editing?: boolean;
|
|
271
|
+
/** Allow the window to be pinned on top of all others */
|
|
272
|
+
allowPinOnTop?: boolean;
|
|
273
|
+
/** Initial position hint */
|
|
274
|
+
initialPosition?: 'top-right' | 'top-left';
|
|
275
|
+
/** Widget mode: no title/footer, drag anywhere, right-click context menu */
|
|
276
|
+
widget?: boolean;
|
|
277
|
+
/** Compact title bar: smaller header with title + close only, no minimize/maximize */
|
|
278
|
+
compact?: boolean;
|
|
279
|
+
/** App-style window: full title bar but body uses no padding so the app's own
|
|
280
|
+
* chrome (toolbars, menus) sits flush against the window frame. For
|
|
281
|
+
* self-chromed apps like Preview, Files, Browser. Implies `bodyScroll: false`
|
|
282
|
+
* — apps manage their own scrolling internally. */
|
|
283
|
+
appStyle?: boolean;
|
|
284
|
+
/** Flush body: keeps the STANDARD full title bar and footer, but drops the
|
|
285
|
+
* body padding so the content sits flush to the window edges (e.g. a
|
|
286
|
+
* `<SidebarLayout>`). Implies `bodyScroll: false`. */
|
|
287
|
+
flushBody?: boolean;
|
|
288
|
+
/** Auto-size height based on content. Window's height adapts to whatever the
|
|
289
|
+
* body renders; floored by `autoMinHeight` and capped to the available
|
|
290
|
+
* viewport so nothing overflows the screen. Naturally-flowing content (a
|
|
291
|
+
* form, a table) shrinks the window to hug it; a fill-height root (`h-full` /
|
|
292
|
+
* `flex-1` between a fixed header and footer) keeps the normal size-ladder
|
|
293
|
+
* height and scrolls internally rather than collapsing. */
|
|
294
|
+
autoHeight?: boolean;
|
|
295
|
+
/** Minimum height (px) when `autoHeight` is on. Defaults to 240. */
|
|
296
|
+
autoMinHeight?: number;
|
|
297
|
+
/** Fit the width to the content when the window opens: a body that would
|
|
298
|
+
* scroll sideways grows the window until it doesn't, up to the work area.
|
|
299
|
+
* Grow-only, and only for the first few seconds — never after the user
|
|
300
|
+
* moves, resizes or maximizes it. On by default; `false` keeps the
|
|
301
|
+
* size-ladder / `dimensions` width exactly. Widgets, `appStyle` windows and
|
|
302
|
+
* mobile never fit. */
|
|
303
|
+
autoWidth?: boolean;
|
|
304
|
+
/** Custom menu items for widget right-click context menu */
|
|
305
|
+
widgetMenu?: React.ReactNode;
|
|
306
|
+
/** Custom window dimensions [width, height] in pixels */
|
|
307
|
+
dimensions?: [number, number];
|
|
308
|
+
/** Stable key for persisting window position to sessionStorage */
|
|
309
|
+
windowKey?: string;
|
|
310
|
+
/** windowKey of whichever window was active when this one opened. On
|
|
311
|
+
* mobile, swipe-to-back broadcasts this value so the parent window can
|
|
312
|
+
* un-hide itself underneath the sliding panel. */
|
|
313
|
+
openedFromKey?: string;
|
|
314
|
+
/** Per-section window accent (SG#00372): an `R G B` triple (e.g.
|
|
315
|
+
* '91 141 190'). When set, the panel publishes it as the
|
|
316
|
+
* `--window-accent-rgb` CSS custom property, and — if the user has turned
|
|
317
|
+
* the `window_accent_stripe` preference on — renders a thin accent stripe
|
|
318
|
+
* between the title bar and the body, so overlapping windows from
|
|
319
|
+
* different app sections are distinguishable at a glance. The header
|
|
320
|
+
* itself stays theme-neutral — themes and the user's custom header
|
|
321
|
+
* colour are untouched. Absent = exactly the previous rendering.
|
|
322
|
+
*
|
|
323
|
+
* The CSS custom property is published whenever `accentRgb` is passed,
|
|
324
|
+
* independent of the preference, so consumer CSS can key off the section
|
|
325
|
+
* accent even with the stripe switched off. */
|
|
326
|
+
accentRgb?: string;
|
|
327
|
+
children: React.ReactNode;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Bring the window with this stable key to the front — now if it is mounted,
|
|
331
|
+
* or the moment it mounts if it is not.
|
|
332
|
+
*
|
|
333
|
+
* This is the shell's answer to "the user asked for this window", and it is
|
|
334
|
+
* deliberately the ONLY way a window rises without the user touching the
|
|
335
|
+
* window itself. Nothing about rendering reaches this function: a background
|
|
336
|
+
* window that refetches, re-renders, or receives a push cannot call it, so it
|
|
337
|
+
* cannot jump in front of whoever is typing. Somebody has to ASK.
|
|
338
|
+
*
|
|
339
|
+
* The deferred half is not an edge case, it is the common one. A window that
|
|
340
|
+
* is closed has no modal to raise, so the very first "open this" arrives
|
|
341
|
+
* before React has rendered anything — the request waits here and `mountModal`
|
|
342
|
+
* claims it below. That makes first-open and re-open one code path instead of
|
|
343
|
+
* two, which is the whole point: the second one is what used to be missing.
|
|
344
|
+
*
|
|
345
|
+
* An outstanding request is SPENT by the mount that honours it — see
|
|
346
|
+
* {@link _frontRequest} for why that is safe for a StrictMode remount. It also
|
|
347
|
+
* expires unhonoured when the user puts a different window in front (see
|
|
348
|
+
* `activateModal`), or when the ask is withdrawn.
|
|
349
|
+
*/
|
|
350
|
+
declare function requestWindowFront(windowKey: string): void;
|
|
351
|
+
/**
|
|
352
|
+
* Is the window identified by `windowKey` the frontmost one?
|
|
353
|
+
*
|
|
354
|
+
* For anything that binds a global shortcut from *outside* the `<Modal>` it
|
|
355
|
+
* belongs to — `UndoProvider` is mounted a level above `PageWindow`, so it has
|
|
356
|
+
* no `ModalActionsContext` to read and cannot use {@link useModalActive}, whose
|
|
357
|
+
* fallback path is made of module globals and therefore answers identically for
|
|
358
|
+
* every caller. That fallback is fine for "is any window active"; it is useless
|
|
359
|
+
* for "is *mine* active", and a per-window listener that asks the wrong
|
|
360
|
+
* question fires in every window at once.
|
|
361
|
+
*
|
|
362
|
+
* `windowKey` is the stable key a window passes to `<Modal windowKey=...>` —
|
|
363
|
+
* `WindowManager`'s `item.id` — not the private per-mount `modalId` that
|
|
364
|
+
* `activationOrder` actually holds. The two are mapped by `mountModal`, and
|
|
365
|
+
* resolving through that map is the whole point: `useIsActiveModal(item.id)`
|
|
366
|
+
* would compare a window key against a `modal-xxxxxx` id and be false for every
|
|
367
|
+
* window rather than true for every window.
|
|
368
|
+
*
|
|
369
|
+
* Omit `windowKey` — or pass one no mounted modal has claimed, which is the
|
|
370
|
+
* case for a `rendersOwnModal` window whose inner `<Modal>` sets no
|
|
371
|
+
* `windowKey` — and this degrades to {@link useModalActive}: the enclosing
|
|
372
|
+
* `<ModalActions>` context if there is one, the global test if there is not.
|
|
373
|
+
*/
|
|
374
|
+
declare function useIsActiveWindow(windowKey?: string | null): boolean;
|
|
375
|
+
declare function toggleExposeMode(): void;
|
|
376
|
+
declare function exitExposeMode(): void;
|
|
377
|
+
declare function subscribeExposeHighlight(fn: () => void): () => boolean;
|
|
378
|
+
declare function getExposeHighlight(): string | null;
|
|
379
|
+
declare function setExposeHighlight(id: string | null): void;
|
|
380
|
+
/**
|
|
381
|
+
* Commit the current keyboard highlight: focus the highlighted window
|
|
382
|
+
* and exit exposé with the standard "picked tile glides back" animation.
|
|
383
|
+
* No-op when no highlight is set.
|
|
384
|
+
*/
|
|
385
|
+
declare function commitExposeHighlight(): void;
|
|
386
|
+
|
|
387
|
+
declare function Modal({ open, onClose, title, icon, copyText, size, dirty, onNext, onPrev, footer, bodyScroll, onMinimize, initialBox, actions, actionsLeft, editing, allowPinOnTop, initialPosition, widget, compact, appStyle, flushBody, autoHeight, autoMinHeight, autoWidth, widgetMenu, dimensions, windowKey, openedFromKey, accentRgb, children }: ModalProps): react.ReactPortal | null;
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* A target as the window receives it: what was asked for, plus the stamp that
|
|
391
|
+
* makes THIS staging distinct from the last one.
|
|
392
|
+
*
|
|
393
|
+
* The stamp is not decoration. Without it, staging is only observable when the
|
|
394
|
+
* target's contents change — so asking for the window that is already showing
|
|
395
|
+
* exactly what you asked for would be a no-op, and "New form" on top of an open
|
|
396
|
+
* blank form would neither reset it nor (before this module) raise it. Callers
|
|
397
|
+
* never set `nav`; staging does.
|
|
398
|
+
*/
|
|
399
|
+
type StagedTarget<T> = T & {
|
|
400
|
+
readonly nav: number;
|
|
401
|
+
};
|
|
402
|
+
interface WindowTargetOptions {
|
|
403
|
+
/**
|
|
404
|
+
* Whether staging this target should also bring the window forward.
|
|
405
|
+
* Defaults to `true`, because staging a target is how the product says
|
|
406
|
+
* "the user asked to see this".
|
|
407
|
+
*
|
|
408
|
+
* Pass `false` for the rare staging that is not a request to be looked at —
|
|
409
|
+
* restoring a session's window state on load, say, where several windows are
|
|
410
|
+
* re-staged at once and none of them was just asked for.
|
|
411
|
+
*/
|
|
412
|
+
raise?: boolean;
|
|
413
|
+
}
|
|
414
|
+
interface WindowTarget<T> {
|
|
415
|
+
/** The stable key the window's `Modal` must be given, so the shell can find
|
|
416
|
+
* it to raise. Read it straight off the channel:
|
|
417
|
+
* `<Modal windowKey={myTarget.windowKey}>`. */
|
|
418
|
+
readonly windowKey: string;
|
|
419
|
+
/** Point the window at something — or `null` to close it. A non-null target
|
|
420
|
+
* also brings the window forward, whether it was closed, open and buried, or
|
|
421
|
+
* already showing this very thing. */
|
|
422
|
+
set(target: T | null, options?: WindowTargetOptions): void;
|
|
423
|
+
/** What is staged right now (`null` if nothing) — for readers outside React. */
|
|
424
|
+
get(): StagedTarget<T> | null;
|
|
425
|
+
/** Watch for stagings. Returns its own unsubscribe. */
|
|
426
|
+
subscribe(listener: () => void): () => void;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Create the staging channel for one app-mounted window.
|
|
430
|
+
*
|
|
431
|
+
* `windowKey` identifies the window to the shell and must match the `windowKey`
|
|
432
|
+
* given to that window's `Modal`. Use a stable, namespaced string — it also
|
|
433
|
+
* becomes the key the window's position and stacking order are remembered
|
|
434
|
+
* under, so it outlives a page refresh.
|
|
435
|
+
*/
|
|
436
|
+
declare function createWindowTarget<T extends object>(windowKey: string): WindowTarget<T>;
|
|
437
|
+
/**
|
|
438
|
+
* Read the staged target inside the window's host component, re-rendering
|
|
439
|
+
* whenever a new one is staged — including a fresh staging of an identical
|
|
440
|
+
* target, which `nav` makes a distinct value.
|
|
441
|
+
*
|
|
442
|
+
* Replaces the `useState` + `useEffect(subscribe)` pair every hand-written
|
|
443
|
+
* version of this channel used to carry, and closes that pair's own gap: it
|
|
444
|
+
* reads the currently-staged target during render rather than after mount, so a
|
|
445
|
+
* host mounted while something is already staged shows it on its first frame.
|
|
446
|
+
*/
|
|
447
|
+
declare function useWindowTarget<T extends object>(target: WindowTarget<T>): StagedTarget<T> | null;
|
|
448
|
+
|
|
449
|
+
/** Inline crash state rendered in place of a window's content. Centers itself
|
|
450
|
+
* in whatever space the body gives it; `onReload` resets the owning boundary
|
|
451
|
+
* so the content remounts from scratch.
|
|
452
|
+
*
|
|
453
|
+
* One crash a remount can never fix: a lazy chunk the server no longer has,
|
|
454
|
+
* because a deploy replaced the build under this tab (see `staleChunk.ts`).
|
|
455
|
+
* Remounting re-imports the same missing file. For that error the button is
|
|
456
|
+
* "Reload page" and reloads the document, which is the only recovery. */
|
|
457
|
+
declare function WindowCrashedFallback({ error, onReload }: {
|
|
458
|
+
error: Error;
|
|
459
|
+
onReload: () => void;
|
|
460
|
+
}): react_jsx_runtime.JSX.Element;
|
|
461
|
+
interface WindowErrorBoundaryProps {
|
|
462
|
+
children: ReactNode;
|
|
463
|
+
/** Replaces the default inline {@link WindowCrashedFallback}. `reset`
|
|
464
|
+
* clears the caught error so the children remount. */
|
|
465
|
+
fallback?: (error: Error, reset: () => void) => ReactNode;
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Error boundary around a window's content. Without it, a page/entity
|
|
469
|
+
* component that throws during render propagates to the root and unmounts the
|
|
470
|
+
* entire desktop. With it, only the crashed window's body is replaced — the
|
|
471
|
+
* window chrome (close/minimize), the taskbar and every other window keep
|
|
472
|
+
* running. Modal wraps its body in one; WindowManager wraps each whole window
|
|
473
|
+
* in another as a last resort for crashes outside the body (e.g. a registry
|
|
474
|
+
* `title()` throwing on malformed data).
|
|
475
|
+
*/
|
|
476
|
+
declare class WindowErrorBoundary extends Component<WindowErrorBoundaryProps, {
|
|
477
|
+
error: Error | null;
|
|
478
|
+
}> {
|
|
479
|
+
state: {
|
|
480
|
+
error: Error | null;
|
|
481
|
+
};
|
|
482
|
+
static getDerivedStateFromError(error: unknown): {
|
|
483
|
+
error: Error;
|
|
484
|
+
};
|
|
485
|
+
componentDidCatch(error: Error, info: ErrorInfo): void;
|
|
486
|
+
reset: () => void;
|
|
487
|
+
render(): string | number | boolean | Iterable<ReactNode> | react_jsx_runtime.JSX.Element | null | undefined;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
declare function ShortcutHelp(): react_jsx_runtime.JSX.Element;
|
|
491
|
+
|
|
492
|
+
interface ShellContextMenuProps {
|
|
493
|
+
/** Turn this instance off — every right-click falls through to the browser.
|
|
494
|
+
* Under `Layout`, pass `contextMenu={false}` to `Layout` instead: it mounts
|
|
495
|
+
* its own instance. */
|
|
496
|
+
disabled?: boolean;
|
|
497
|
+
}
|
|
498
|
+
declare function ShellContextMenu({ disabled }?: ShellContextMenuProps): react_jsx_runtime.JSX.Element | null;
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* What the shell's global context menu should offer for a given right-click.
|
|
502
|
+
*
|
|
503
|
+
* Kept free of React (and of the DOM beyond the element it is handed) so the
|
|
504
|
+
* two rules that matter can be tested directly: which targets keep the
|
|
505
|
+
* browser's own menu, and which items a click produces.
|
|
506
|
+
*/
|
|
507
|
+
/** Opt-out attribute: anything inside `[data-native-context-menu]` keeps the
|
|
508
|
+
* browser's menu. For surfaces the shell has no business re-skinning — an
|
|
509
|
+
* embedded viewer, a third-party widget. */
|
|
510
|
+
declare const NATIVE_MENU_ATTR = "data-native-context-menu";
|
|
511
|
+
/**
|
|
512
|
+
* Does this target keep the browser's native menu?
|
|
513
|
+
*
|
|
514
|
+
* Text inputs, textareas and contenteditable do, deliberately: the browser's
|
|
515
|
+
* spellcheck suggestions and "Add to dictionary" cannot be rebuilt by a web
|
|
516
|
+
* page, and a Paste item of our own would need a clipboard permission prompt.
|
|
517
|
+
* Images, canvases and media do for the same reason — Save / Copy image and
|
|
518
|
+
* the playback controls are the browser's. The shell menu is for everywhere
|
|
519
|
+
* else.
|
|
520
|
+
*/
|
|
521
|
+
declare function keepsNativeMenu(target: EventTarget | null): boolean;
|
|
522
|
+
type ShellContextKind = 'selection' | 'link' | 'default';
|
|
523
|
+
interface ShellContextTarget {
|
|
524
|
+
/** The most specific thing under the pointer — what the menu leads with. */
|
|
525
|
+
kind: ShellContextKind;
|
|
526
|
+
/** Trimmed selection text, when something is selected. */
|
|
527
|
+
selectionText?: string;
|
|
528
|
+
/** Absolute href of the nearest enclosing link. */
|
|
529
|
+
linkUrl?: string;
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Describe a right-click: what was under the pointer, and what is selected.
|
|
533
|
+
*
|
|
534
|
+
* A selection inside a link reports both, so the menu can offer both sections
|
|
535
|
+
* the way a browser's does; `kind` names whichever the menu should lead with.
|
|
536
|
+
*/
|
|
537
|
+
declare function describeContextTarget(target: EventTarget | null, selectionText?: string | null, baseUrl?: string): ShellContextTarget;
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Copy, then say so: `"<label> copied"`, or an error toast when the browser
|
|
541
|
+
* refused. `label` names what was copied ("Text", "Link address", "3 rows",
|
|
542
|
+
* a column heading) and is shown as written — the failure message leaves it
|
|
543
|
+
* out, so a heading like "PO #" is never lower-cased into a sentence.
|
|
544
|
+
*/
|
|
545
|
+
declare function copyToClipboard(label: string, text: string, html?: string): Promise<boolean>;
|
|
546
|
+
|
|
547
|
+
/** Generic notification shape consumed by the shell. Consumer-specific
|
|
548
|
+
* fields live on `extra` (or just on additional properties — TS structural
|
|
549
|
+
* typing is permissive). */
|
|
550
|
+
interface ShellNotification {
|
|
551
|
+
id: string;
|
|
552
|
+
title: string;
|
|
553
|
+
message?: string;
|
|
554
|
+
entity_type?: string;
|
|
555
|
+
entity_id?: string;
|
|
556
|
+
entity_label?: string;
|
|
557
|
+
is_read: boolean;
|
|
558
|
+
actor_name?: string;
|
|
559
|
+
created_at: string;
|
|
560
|
+
}
|
|
561
|
+
/** Config bundle for the notification system. Supplied by the consumer;
|
|
562
|
+
* the shell never calls a hardcoded URL. */
|
|
563
|
+
interface NotificationsConfig {
|
|
564
|
+
/** Hook called inside <NotificationBell> on every render. Return the
|
|
565
|
+
* current unread count for the signed-in user. */
|
|
566
|
+
useUnreadCount: () => number;
|
|
567
|
+
/** Fetcher for the dropdown list (most recent first). */
|
|
568
|
+
list: (params?: {
|
|
569
|
+
page_size?: number;
|
|
570
|
+
}) => Promise<{
|
|
571
|
+
results: ShellNotification[];
|
|
572
|
+
}>;
|
|
573
|
+
/** Mark a single notification as read. */
|
|
574
|
+
markRead: (id: string) => Promise<unknown>;
|
|
575
|
+
/** Mark every unread notification as read. */
|
|
576
|
+
markAllRead: () => Promise<unknown>;
|
|
577
|
+
/** Click handler for a notification item. */
|
|
578
|
+
onItemClick: (notif: ShellNotification) => void;
|
|
579
|
+
/** Click handler for "View all notifications". When omitted, the link is hidden. */
|
|
580
|
+
onViewAll?: () => void;
|
|
581
|
+
}
|
|
582
|
+
interface NotificationBellProps extends NotificationsConfig {
|
|
583
|
+
popDirection?: 'left' | 'right';
|
|
584
|
+
}
|
|
585
|
+
declare function NotificationBell({ useUnreadCount, list, markRead, markAllRead, onItemClick, onViewAll, popDirection, }: NotificationBellProps): react_jsx_runtime.JSX.Element;
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* System-tray badge shown only when the app is served from a developer's own
|
|
589
|
+
* machine (localhost / 127.0.0.1). Drop it into the host's `taskbarTrayLeft`
|
|
590
|
+
* slot — it renders nothing off-localhost, so it's safe to leave wired on every
|
|
591
|
+
* build. Visibility is gated on the runtime hostname; see utils/env.ts.
|
|
592
|
+
*/
|
|
593
|
+
declare function DevIndicator(): react_jsx_runtime.JSX.Element | null;
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Dev-environment detection + browser chrome.
|
|
597
|
+
*
|
|
598
|
+
* A deployed bundle is byte-identical across hosts (CI builds once and promotes
|
|
599
|
+
* the same image between environments), so build-time signals like
|
|
600
|
+
* `import.meta.env.MODE` can't tell a developer's machine from a deployed site —
|
|
601
|
+
* only the runtime hostname can. `isDevEnv()` is therefore gated on
|
|
602
|
+
* `localhost`/`127.0.0.1`, and everything dev-only (the <DevIndicator/> tray
|
|
603
|
+
* badge, the `[DEV]` tab-title prefix) keys off it so it never leaks onto a
|
|
604
|
+
* deployed host.
|
|
605
|
+
*/
|
|
606
|
+
/** Tooltip/banner copy for the dev indicator. */
|
|
607
|
+
declare const DEV_BANNER_TEXT = "LOCAL DEV ENVIRONMENT \u2014 running against your local backend; not connected to the live system";
|
|
608
|
+
/**
|
|
609
|
+
* True only when served from a developer's own machine.
|
|
610
|
+
*
|
|
611
|
+
* Gated on `localhost`/`127.0.0.1` only (not `*.local` mDNS names), so a
|
|
612
|
+
* teammate reaching a dev server over the LAN doesn't get the dev chrome.
|
|
613
|
+
*/
|
|
614
|
+
declare function isDevEnv(): boolean;
|
|
615
|
+
/**
|
|
616
|
+
* Apply local-dev browser chrome: prefix the tab title with `[DEV]` and,
|
|
617
|
+
* optionally, swap the favicon. No-op outside localhost. Idempotent — safe to
|
|
618
|
+
* call at startup and again whenever a route overwrites `document.title`.
|
|
619
|
+
*
|
|
620
|
+
* Call before React mounts (e.g. in `main.tsx`) so the title is correct on the
|
|
621
|
+
* first paint. Pass `faviconHref` only if the host actually ships that asset.
|
|
622
|
+
*/
|
|
623
|
+
declare function applyDevTitle(opts?: {
|
|
624
|
+
faviconHref?: string;
|
|
625
|
+
}): void;
|
|
626
|
+
|
|
627
|
+
interface SavedBox {
|
|
628
|
+
x: number;
|
|
629
|
+
y: number;
|
|
630
|
+
w: number;
|
|
631
|
+
h: number;
|
|
632
|
+
maximized: boolean;
|
|
633
|
+
}
|
|
634
|
+
interface MinimizedItem {
|
|
635
|
+
id: string;
|
|
636
|
+
type: 'part_number' | 'modal' | 'page';
|
|
637
|
+
label: string;
|
|
638
|
+
/** Route path for icon lookup (e.g. '/orders', '/invoices') */
|
|
639
|
+
route?: string;
|
|
640
|
+
savedBox?: SavedBox;
|
|
641
|
+
/** Entity registry key (e.g. 'order', 'manufacturer') for restore */
|
|
642
|
+
entityType?: string;
|
|
643
|
+
/** Entity UUID for API fetch on cross-page restore */
|
|
644
|
+
entityId?: string;
|
|
645
|
+
/** Cached entity data for instant rendering while refetch happens */
|
|
646
|
+
entitySnapshot?: any;
|
|
647
|
+
/** windowKey of the window that was active when this opened — used by the
|
|
648
|
+
* mobile swipe-to-back gesture to reveal the parent (e.g. the list a
|
|
649
|
+
* detail entity was opened from) instead of the home wallpaper. Undefined
|
|
650
|
+
* when this window opened directly from home / the start menu. */
|
|
651
|
+
openedFrom?: string;
|
|
652
|
+
}
|
|
653
|
+
interface MinimizedContextType {
|
|
654
|
+
openWindows: MinimizedItem[];
|
|
655
|
+
openEntity: (entityType: string, entityId: string, snapshot?: any, label?: string, route?: string) => void;
|
|
656
|
+
openPage: (path: string) => void;
|
|
657
|
+
closeEntity: (id: string) => void;
|
|
658
|
+
/** @deprecated kept for backward compat — aliases openEntity/closeEntity */
|
|
659
|
+
minimize: (item: MinimizedItem) => void;
|
|
660
|
+
items: MinimizedItem[];
|
|
661
|
+
restore: (item: MinimizedItem) => void;
|
|
662
|
+
remove: (id: string) => void;
|
|
663
|
+
restoreIfMinimized: (label: string) => boolean;
|
|
664
|
+
openItems: MinimizedItem[];
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Registers controlled dirty state with the enclosing shell window, so closing
|
|
668
|
+
* it asks before discarding. Works in both kinds of window the shell owns the
|
|
669
|
+
* Modal for: page windows and entity/detail windows.
|
|
670
|
+
*
|
|
671
|
+
* While dirty it also holds the page itself: a reload, Back out of the app or
|
|
672
|
+
* closing the tab gets the browser's "Leave site?" prompt first.
|
|
673
|
+
*
|
|
674
|
+
* Ignored — deliberately, and silently — in the one place the shell has no
|
|
675
|
+
* Modal to guard: a registry entry with `rendersOwnModal`, where the consumer
|
|
676
|
+
* renders its own Modal and should pass that Modal `dirty` directly. Also a
|
|
677
|
+
* no-op outside a WindowManager entirely.
|
|
678
|
+
*/
|
|
679
|
+
declare function useWindowDirty(dirty: boolean): void;
|
|
680
|
+
declare function useWindowManager(): MinimizedContextType;
|
|
681
|
+
/** Star button to favorite a document — saves to preferences.favorite_documents */
|
|
682
|
+
declare function DocFavStar({ entityType, entityId, label }: {
|
|
683
|
+
entityType: string;
|
|
684
|
+
entityId: string;
|
|
685
|
+
label: string;
|
|
686
|
+
}): react_jsx_runtime.JSX.Element;
|
|
687
|
+
/** Public, framework-free helper: route ('/orders', '/qc-reports', …)
|
|
688
|
+
* of the window the user is currently focused on. Returns undefined
|
|
689
|
+
* when no app window is active (start menu / dashboard with nothing
|
|
690
|
+
* open). Useful for code paths that fire outside React render — the
|
|
691
|
+
* shell consumer can stamp metadata on a payload (e.g. "which module
|
|
692
|
+
* is this bug report against?") without needing to lift state. */
|
|
693
|
+
declare function getActiveWindowRoute(): string | undefined;
|
|
694
|
+
declare function WindowManagerProvider({ children, windowAccentForRoute }: {
|
|
695
|
+
children: ReactNode;
|
|
696
|
+
/** SG#00372: optional per-section window accent. Called with each open
|
|
697
|
+
* window's route ('/sales-orders', '/payments', …); return an `R G B`
|
|
698
|
+
* triple (e.g. '91 141 190') to give that window a thin accent stripe
|
|
699
|
+
* between its title bar and body (see Modal's `accentRgb`), or undefined
|
|
700
|
+
* for no accent. The stripe is drawn only for users who have switched it
|
|
701
|
+
* on in Customization → Windows; the mapping is still worth wiring
|
|
702
|
+
* regardless, since it also publishes `--window-accent-rgb` per window.
|
|
703
|
+
* The consumer owns the route→section→colour mapping; omitting the prop
|
|
704
|
+
* keeps every window exactly as before. */
|
|
705
|
+
windowAccentForRoute?: (route: string) => string | undefined;
|
|
706
|
+
}): react_jsx_runtime.JSX.Element;
|
|
707
|
+
|
|
708
|
+
interface EntityWindowStateProps<T> extends EntityWindowFallbackProps {
|
|
709
|
+
entity: T | null | undefined;
|
|
710
|
+
children: (entity: T) => ReactNode;
|
|
711
|
+
}
|
|
712
|
+
interface EntityWindowFallbackProps {
|
|
713
|
+
isPending: boolean;
|
|
714
|
+
isFetching: boolean;
|
|
715
|
+
/**
|
|
716
|
+
* Whether the query is switched on. FALSE for a window whose detail fetch is
|
|
717
|
+
* deliberately skipped — a `new-` draft, a duplicate, or a host that never
|
|
718
|
+
* called `setShellApiClient`.
|
|
719
|
+
*
|
|
720
|
+
* This has to be passed, because a DISABLED TanStack query is
|
|
721
|
+
* `status: 'pending'` / `fetchStatus: 'idle'` forever, so `isPending` alone
|
|
722
|
+
* reads as "still loading" for a request that will never be made — and the
|
|
723
|
+
* window sits on a spinner it can never leave. `isLoading` (`isPending &&
|
|
724
|
+
* isFetching`) is the pair that already accounts for this, which is why the
|
|
725
|
+
* old inline check did not have the bug.
|
|
726
|
+
*/
|
|
727
|
+
enabled?: boolean;
|
|
728
|
+
error: unknown;
|
|
729
|
+
onRetry: () => void;
|
|
730
|
+
}
|
|
731
|
+
declare function EntityWindowLoading(): react_jsx_runtime.JSX.Element;
|
|
732
|
+
/**
|
|
733
|
+
* The no-data half of an entity window: what to show when there is no entity
|
|
734
|
+
* to render yet, or never will be. Split out from `EntityWindowState` because
|
|
735
|
+
* a `rendersOwnModal` entry supplies its own Modal only once it HAS an entity,
|
|
736
|
+
* so these states need a modal of their own to live inside.
|
|
737
|
+
*/
|
|
738
|
+
declare function EntityWindowFallback({ isPending, isFetching, enabled, error, onRetry, }: EntityWindowFallbackProps): react_jsx_runtime.JSX.Element;
|
|
739
|
+
/**
|
|
740
|
+
* Owns the no-data states for an entity window. Existing data always wins so
|
|
741
|
+
* background refreshes never replace a usable detail with transient chrome.
|
|
742
|
+
*/
|
|
743
|
+
declare function EntityWindowState<T>({ entity, children, ...fallback }: EntityWindowStateProps<T>): react_jsx_runtime.JSX.Element;
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Frame-timing maths + bottleneck attribution for the desktop perf HUD.
|
|
747
|
+
*
|
|
748
|
+
* Split out from `PerfStats.tsx` because the interesting part is the
|
|
749
|
+
* *attribution*, and attribution is pure: given a frame rate and how much of
|
|
750
|
+
* the wall clock the main thread spent blocked, decide whether a janky UI is
|
|
751
|
+
* the GPU's fault or JavaScript's. That decision is what the HUD exists for —
|
|
752
|
+
* "the UI feels laggy" is not actionable, "you are GPU-bound" is — so it lives
|
|
753
|
+
* where a test can pin it.
|
|
754
|
+
*
|
|
755
|
+
* The discriminator: frames are landing late, but the main thread is idle.
|
|
756
|
+
* If JS were the problem the main thread would be busy, so late frames plus an
|
|
757
|
+
* idle thread means the cost is downstream of script — compositing, paint,
|
|
758
|
+
* backdrop-filter. The shell leans hard on frosted glass (`utils/glass.ts`
|
|
759
|
+
* blurs at a 40px radius), which is exactly the kind of work that shows up
|
|
760
|
+
* here and nowhere in a JS profile.
|
|
761
|
+
*/
|
|
762
|
+
/** Frame rate at or above which we call the UI smooth. Below 60 because
|
|
763
|
+
* rAF sampling is noisy and a display may be capped at 60Hz anyway — a
|
|
764
|
+
* steady 55 is not a complaint anyone would file. */
|
|
765
|
+
declare const SMOOTH_FPS = 50;
|
|
766
|
+
/** Share of wall-clock time in long tasks that makes JS the prime suspect.
|
|
767
|
+
* `longtask` entries are >50ms by definition, so even 20% means the thread
|
|
768
|
+
* is stalling several times a second — visible as jank on its own. */
|
|
769
|
+
declare const BLOCKED_PCT_CPU = 20;
|
|
770
|
+
type BottleneckKind = 'smooth' | 'gpu' | 'cpu' | 'unknown';
|
|
771
|
+
interface PerfReading {
|
|
772
|
+
/** Frames per second across the sample window. */
|
|
773
|
+
fps: number;
|
|
774
|
+
/** Mean frame interval, ms. */
|
|
775
|
+
frameMs: number;
|
|
776
|
+
/** Slowest single frame in the window, ms — where the jank actually lives. */
|
|
777
|
+
worstMs: number;
|
|
778
|
+
/** Percentage of wall-clock time the main thread spent inside long tasks
|
|
779
|
+
* (0–100), or null when the browser exposes no `longtask` observer.
|
|
780
|
+
* Null is load-bearing: without it we cannot separate GPU from CPU, and
|
|
781
|
+
* guessing would defeat the point of the HUD. */
|
|
782
|
+
blockedPct: number | null;
|
|
783
|
+
}
|
|
784
|
+
interface Verdict {
|
|
785
|
+
kind: BottleneckKind;
|
|
786
|
+
/** Headline, for the HUD's verdict row. */
|
|
787
|
+
label: string;
|
|
788
|
+
/** One line of what to do about it. */
|
|
789
|
+
detail: string;
|
|
790
|
+
}
|
|
791
|
+
/** Turn a window of rAF timestamps into a frame-rate reading.
|
|
792
|
+
* Needs two timestamps to describe one interval; anything less is not yet a
|
|
793
|
+
* measurement and reports zeroes rather than a fabricated 0 fps. */
|
|
794
|
+
declare function summariseFrames(timestamps: number[]): Pick<PerfReading, 'fps' | 'frameMs' | 'worstMs'>;
|
|
795
|
+
/**
|
|
796
|
+
* Attribute a reading to a bottleneck.
|
|
797
|
+
*
|
|
798
|
+
* Order matters: smooth wins outright (nobody cares what a healthy frame rate
|
|
799
|
+
* is *not* bound by), and an unknown block share can only ever downgrade to
|
|
800
|
+
* `unknown` — never to `gpu`. Blaming the GPU because the browser withheld
|
|
801
|
+
* long-task timing would be a confident wrong answer, which is worse than no
|
|
802
|
+
* answer when someone is about to go change settings on the strength of it.
|
|
803
|
+
*/
|
|
804
|
+
declare function classify(reading: PerfReading): Verdict;
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* Session log for the desktop perf HUD — and the analysis that turns it into
|
|
808
|
+
* a conclusion.
|
|
809
|
+
*
|
|
810
|
+
* A live frame rate tells you the UI is slow *now*. It does not tell you what
|
|
811
|
+
* made it slow, and the person watching it is usually the last person able to
|
|
812
|
+
* say. So while the HUD is on, every reading is stamped with what was
|
|
813
|
+
* happening around it: how many windows were open, which one was on top,
|
|
814
|
+
* whether the user was clicking, typing, scrolling or dragging. The log is
|
|
815
|
+
* exportable, so a laggy machine somewhere else can produce evidence rather
|
|
816
|
+
* than an adjective.
|
|
817
|
+
*
|
|
818
|
+
* The maths here is deliberately non-clever. Median rather than mean, because
|
|
819
|
+
* one 400ms stall would drag a mean somewhere no frame ever was. Buckets
|
|
820
|
+
* rather than a fitted curve, because "6 windows open halves the frame rate"
|
|
821
|
+
* is a sentence someone can act on and a correlation coefficient is not.
|
|
822
|
+
*/
|
|
823
|
+
|
|
824
|
+
/** One flush interval's worth of measurement plus its context. Keys are short
|
|
825
|
+
* because thousands of these get JSON-serialised into localStorage and into
|
|
826
|
+
* whatever the user sends afterwards. */
|
|
827
|
+
interface PerfLogRecord {
|
|
828
|
+
/** Milliseconds since logging began. */
|
|
829
|
+
t: number;
|
|
830
|
+
fps: number;
|
|
831
|
+
frameMs: number;
|
|
832
|
+
worstMs: number;
|
|
833
|
+
blockedPct: number | null;
|
|
834
|
+
heapMB: number | null;
|
|
835
|
+
verdict: BottleneckKind;
|
|
836
|
+
/** Open shell windows at the moment of the reading. */
|
|
837
|
+
windows: number;
|
|
838
|
+
/** Identity of the topmost window, when there is one — so a summary can name
|
|
839
|
+
* the screen that was slow rather than just the count. */
|
|
840
|
+
active: string | null;
|
|
841
|
+
clicks: number;
|
|
842
|
+
keys: number;
|
|
843
|
+
scrolls: number;
|
|
844
|
+
/** Milliseconds spent with the pointer down and moving. Dragging is the
|
|
845
|
+
* single most compositing-heavy thing a user does in a window shell, so it
|
|
846
|
+
* gets its own axis rather than being lumped in with clicks. Covers any
|
|
847
|
+
* drag — a window gesture, a desktop icon, a text selection; `moveMs` and
|
|
848
|
+
* `resizeMs` below are the window subset of it. */
|
|
849
|
+
dragMs: number;
|
|
850
|
+
/** Start-menu opens (`perfEvents`). */
|
|
851
|
+
menus: number;
|
|
852
|
+
/** Flyout opens — 2nd- and 3rd-level menus. Hover-opened flyouts fire no
|
|
853
|
+
* click and no keypress, so before this axis existed the frame a submenu
|
|
854
|
+
* painted on was filed as *idle*, which is where the jank people actually
|
|
855
|
+
* report was going missing. */
|
|
856
|
+
submenus: number;
|
|
857
|
+
/** Last menu or flyout opened in the interval, so one can be named. */
|
|
858
|
+
menuKey: string | null;
|
|
859
|
+
/** Milliseconds spent dragging a window by its title bar. */
|
|
860
|
+
moveMs: number;
|
|
861
|
+
/** Milliseconds spent dragging a window's resize handle. */
|
|
862
|
+
resizeMs: number;
|
|
863
|
+
}
|
|
864
|
+
interface FpsGroup {
|
|
865
|
+
samples: number;
|
|
866
|
+
/** Median across the group's *measurable* samples — see `summariseLog`. Zero
|
|
867
|
+
* when every sample in the group stalled. */
|
|
868
|
+
medianFps: number;
|
|
869
|
+
/** Slowest single frame anywhere in the group. For a group of brief
|
|
870
|
+
* interactions this is the number that matters: a flyout that costs one
|
|
871
|
+
* 300ms frame barely moves a median but is exactly what the user saw. */
|
|
872
|
+
worstMs: number;
|
|
873
|
+
/** Samples too blocked to report a frame rate at all. Kept beside the median
|
|
874
|
+
* rather than folded into it, so a group that is entirely stalls reads as
|
|
875
|
+
* the emergency it is instead of as a 0 fps reading. */
|
|
876
|
+
stalls: number;
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* What the user was doing during a sample, reduced to one label.
|
|
880
|
+
*
|
|
881
|
+
* A single interval routinely carries several axes at once — opening a flyout
|
|
882
|
+
* involves a click, a pointer move and a menu mark — so the ranking below
|
|
883
|
+
* decides which one the sample is filed under. It runs most-specific first:
|
|
884
|
+
* the deliberate, expensive gesture beats the incidental click that came with
|
|
885
|
+
* it, because filing a janky submenu open under "click" is how you end up
|
|
886
|
+
* optimising the wrong thing.
|
|
887
|
+
*/
|
|
888
|
+
type ActivityKind = 'submenu' | 'menu' | 'resize' | 'move' | 'scroll' | 'type' | 'click' | 'idle';
|
|
889
|
+
/** File a sample under the one thing most likely to have cost it. */
|
|
890
|
+
declare function classifyActivity(r: PerfLogRecord): ActivityKind;
|
|
891
|
+
interface LogSummary {
|
|
892
|
+
samples: number;
|
|
893
|
+
durationMs: number;
|
|
894
|
+
medianFps: number;
|
|
895
|
+
worstFrameMs: number;
|
|
896
|
+
/** Fraction of samples in each verdict, 0–1. */
|
|
897
|
+
verdictShare: Record<BottleneckKind, number>;
|
|
898
|
+
/** Split by whether the user was doing anything. The gap between these two
|
|
899
|
+
* is the headline: a desktop that is smooth at rest and janky in use has a
|
|
900
|
+
* rendering cost that only shows up under interaction. */
|
|
901
|
+
interacting: FpsGroup | null;
|
|
902
|
+
idle: FpsGroup | null;
|
|
903
|
+
/** Frame rate per kind of interaction, worst-first — the answer to "which
|
|
904
|
+
* gesture is slow", which is the question a report is usually filed to
|
|
905
|
+
* ask. */
|
|
906
|
+
byActivity: (FpsGroup & {
|
|
907
|
+
kind: ActivityKind;
|
|
908
|
+
})[];
|
|
909
|
+
/** Frame rate against how many windows were open. */
|
|
910
|
+
byWindowCount: (FpsGroup & {
|
|
911
|
+
windows: number;
|
|
912
|
+
})[];
|
|
913
|
+
/** Windows ranked worst-first, so the slowest screen names itself. */
|
|
914
|
+
worstWindows: (FpsGroup & {
|
|
915
|
+
key: string;
|
|
916
|
+
})[];
|
|
917
|
+
/** Menus and flyouts ranked worst-first, same idea one layer up. */
|
|
918
|
+
worstMenus: (FpsGroup & {
|
|
919
|
+
key: string;
|
|
920
|
+
})[];
|
|
921
|
+
}
|
|
922
|
+
/** Records kept in memory before the oldest are dropped. At one record per
|
|
923
|
+
* 500ms this is about 20 minutes — long enough to catch an intermittent
|
|
924
|
+
* stall, small enough to serialise without thinking about it. */
|
|
925
|
+
declare const LOG_CAP = 2400;
|
|
926
|
+
/** Minimum samples before a group is reported. One unlucky reading is not a
|
|
927
|
+
* finding, and a summary that names a window off a single sample invites
|
|
928
|
+
* someone to go optimise the wrong screen. */
|
|
929
|
+
declare const MIN_GROUP_SAMPLES = 4;
|
|
930
|
+
/** The floor for menu and gesture groups. Lower because these events are rare
|
|
931
|
+
* by construction — a flyout opens inside a single 500ms interval, not across
|
|
932
|
+
* twenty of them, so holding them to the window floor would suppress exactly
|
|
933
|
+
* the findings the axes were added for. Two still rules out a one-off, and
|
|
934
|
+
* every group carries its own `samples` for whoever reads it. */
|
|
935
|
+
declare const MIN_EVENT_SAMPLES = 2;
|
|
936
|
+
/** Append with a cap, oldest dropped first. Returns a new array — callers hold
|
|
937
|
+
* this in React state, where mutation would not re-render. */
|
|
938
|
+
declare function appendRecord(log: PerfLogRecord[], record: PerfLogRecord, cap?: number): PerfLogRecord[];
|
|
939
|
+
/** True when the user was doing something during the interval. */
|
|
940
|
+
declare function isInteracting(r: PerfLogRecord): boolean;
|
|
941
|
+
/** Reduce a log to the handful of statements worth acting on. */
|
|
942
|
+
declare function summariseLog(log: PerfLogRecord[]): LogSummary;
|
|
943
|
+
/** Flat CSV, for opening in a spreadsheet without writing any code. */
|
|
944
|
+
declare function toCsv(log: PerfLogRecord[]): string;
|
|
945
|
+
|
|
946
|
+
/**
|
|
947
|
+
* What machine the readings came off.
|
|
948
|
+
*
|
|
949
|
+
* A frame rate without a machine attached is half a report. "GPU-bound
|
|
950
|
+
* (compositing)" means something very different on an Intel UHD 620 driving a
|
|
951
|
+
* 4K panel than on an M3 Max, and the first question anyone triaging a
|
|
952
|
+
* slowdown asks is which one they are looking at — a question the reporter is
|
|
953
|
+
* usually the worst-placed person to answer, and often simply cannot.
|
|
954
|
+
*
|
|
955
|
+
* The user agent alone does not settle it. Chromium freezes its UA string, so
|
|
956
|
+
* Windows 11 reports itself as Windows 10 and an ARM machine looks like an x86
|
|
957
|
+
* one; and no UA string has ever named the GPU, which is the part that matters
|
|
958
|
+
* most here. So this reads the specifics directly, from four sources:
|
|
959
|
+
*
|
|
960
|
+
* - **Synchronous navigator and screen fields** — cores, memory, panel,
|
|
961
|
+
* network, display settings. Cheap, and occasionally withheld.
|
|
962
|
+
* - **A WebGL context**, purely to ask the driver its own name. Created and
|
|
963
|
+
* destroyed inside one call, at report time and never during measurement,
|
|
964
|
+
* because a live context is exactly the sort of thing that would show up in
|
|
965
|
+
* the numbers it is meant to explain.
|
|
966
|
+
* - **Client hints and the battery**, both async, both unchanging, so both
|
|
967
|
+
* are resolved once when the HUD mounts rather than while a report is being
|
|
968
|
+
* assembled.
|
|
969
|
+
* - **Media queries**, for the settings that change how much compositing the
|
|
970
|
+
* browser is being asked to do in the first place.
|
|
971
|
+
*
|
|
972
|
+
* Everything is best-effort and every field degrades to null. Firefox and
|
|
973
|
+
* Safari withhold `deviceMemory`; Safari has no client hints; a blocklisted
|
|
974
|
+
* driver refuses WebGL. A report missing one line is a far smaller loss than a
|
|
975
|
+
* report that failed to send, so nothing here is allowed to throw.
|
|
976
|
+
*/
|
|
977
|
+
/** Async platform detail from `navigator.userAgentData`. */
|
|
978
|
+
interface PlatformHints {
|
|
979
|
+
/** 'macOS', 'Windows', 'Linux', … */
|
|
980
|
+
platform: string | null;
|
|
981
|
+
/** The real OS version, which the frozen UA string no longer carries. */
|
|
982
|
+
platformVersion: string | null;
|
|
983
|
+
/** 'arm' | 'x86' — an Intel Mac and an Apple Silicon Mac are not the same
|
|
984
|
+
* machine and do not have the same compositing story. */
|
|
985
|
+
architecture: string | null;
|
|
986
|
+
/** Device model, on mobile. */
|
|
987
|
+
model: string | null;
|
|
988
|
+
/** Browser name and full version, e.g. 'Chrome 141.0.7390.55'. */
|
|
989
|
+
browser: string | null;
|
|
990
|
+
}
|
|
991
|
+
/** Power state. A laptop in Low Power Mode is capped at 30 fps by the OS, which
|
|
992
|
+
* is the single most common cause of "it halved this afternoon". */
|
|
993
|
+
interface BatteryInfo {
|
|
994
|
+
charging: boolean;
|
|
995
|
+
/** 0–1. */
|
|
996
|
+
level: number;
|
|
997
|
+
}
|
|
998
|
+
/** The parts that cannot be read synchronously, resolved once on mount. */
|
|
999
|
+
interface AsyncEnvironment {
|
|
1000
|
+
hints: PlatformHints | null;
|
|
1001
|
+
battery: BatteryInfo | null;
|
|
1002
|
+
}
|
|
1003
|
+
interface PerfEnvironment {
|
|
1004
|
+
userAgent: string;
|
|
1005
|
+
/** Browser name and version from client hints; null outside Chromium, where
|
|
1006
|
+
* `userAgent` above is the fallback. */
|
|
1007
|
+
browser: string | null;
|
|
1008
|
+
platform: string | null;
|
|
1009
|
+
platformVersion: string | null;
|
|
1010
|
+
architecture: string | null;
|
|
1011
|
+
model: string | null;
|
|
1012
|
+
/** Logical cores. The shell is single-threaded, so this mostly says how much
|
|
1013
|
+
* else the machine can be doing at once. */
|
|
1014
|
+
cpuCores: number | null;
|
|
1015
|
+
/** Approximate RAM in GB. Deliberately coarse — the spec has browsers round
|
|
1016
|
+
* to a power of two and permits clamping to 8 to blunt fingerprinting, so
|
|
1017
|
+
* treat it as a floor ("this much or more"), not a measurement. Chromium
|
|
1018
|
+
* only; Firefox and Safari publish nothing. */
|
|
1019
|
+
deviceMemoryGB: number | null;
|
|
1020
|
+
/** The driver's own name for itself, e.g. 'Apple M3 Max' or
|
|
1021
|
+
* 'ANGLE (Intel, Intel(R) UHD Graphics 620 …)'. */
|
|
1022
|
+
gpu: string | null;
|
|
1023
|
+
gpuVendor: string | null;
|
|
1024
|
+
/** False when the browser fell back to software rendering — which explains a
|
|
1025
|
+
* GPU-bound verdict outright. */
|
|
1026
|
+
webglAvailable: boolean;
|
|
1027
|
+
screen: {
|
|
1028
|
+
width: number;
|
|
1029
|
+
height: number;
|
|
1030
|
+
dpr: number;
|
|
1031
|
+
colorDepth: number | null;
|
|
1032
|
+
};
|
|
1033
|
+
/** The window, which is what actually gets composited — a maximised window on
|
|
1034
|
+
* a 4K panel and a small one are different amounts of work. */
|
|
1035
|
+
viewport: {
|
|
1036
|
+
width: number;
|
|
1037
|
+
height: number;
|
|
1038
|
+
};
|
|
1039
|
+
/** Heap ceiling this tab was given, MB. Chromium only. */
|
|
1040
|
+
heapLimitMB: number | null;
|
|
1041
|
+
/** Effective connection class, downlink Mb/s and round-trip ms. Separates a
|
|
1042
|
+
* UI that is slow from a UI that is waiting. */
|
|
1043
|
+
network: {
|
|
1044
|
+
effectiveType: string | null;
|
|
1045
|
+
downlinkMbps: number | null;
|
|
1046
|
+
rttMs: number | null;
|
|
1047
|
+
saveData: boolean;
|
|
1048
|
+
} | null;
|
|
1049
|
+
battery: BatteryInfo | null;
|
|
1050
|
+
/** Settings that change how much work the browser is being asked to do. */
|
|
1051
|
+
prefersReducedMotion: boolean;
|
|
1052
|
+
forcedColors: boolean;
|
|
1053
|
+
/** The shell's own "reduce transparency" — the first thing to suggest, and
|
|
1054
|
+
* the first thing to check has not already been tried. */
|
|
1055
|
+
reducedTransparency: boolean;
|
|
1056
|
+
/** Screens can be scaled; a report from a 4K panel at 200% is not the same as
|
|
1057
|
+
* one at 100%. */
|
|
1058
|
+
touchPoints: number | null;
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Resolve everything that needs a promise. Call once, keep the result — the
|
|
1062
|
+
* values cannot change for the life of the page, and the prompt some browsers
|
|
1063
|
+
* attach to high-entropy hints must not fire while someone is filing a report.
|
|
1064
|
+
*
|
|
1065
|
+
* Never rejects. A browser with no client hints and no battery API is the
|
|
1066
|
+
* normal case outside Chromium, not an error worth handling at the call site.
|
|
1067
|
+
*/
|
|
1068
|
+
declare function requestAsyncEnvironment(): Promise<AsyncEnvironment>;
|
|
1069
|
+
/** Snapshot the machine. Synchronous — pass what `requestAsyncEnvironment`
|
|
1070
|
+
* resolved earlier, or null if it never arrived. */
|
|
1071
|
+
declare function readEnvironment(async_: AsyncEnvironment | null): PerfEnvironment;
|
|
1072
|
+
/**
|
|
1073
|
+
* One line naming the machine, for a report title or a bug-tracker
|
|
1074
|
+
* description — the place where a reader decides whether this report is about
|
|
1075
|
+
* a slow machine or a slow application. The full record is in the attachment;
|
|
1076
|
+
* this is the digest.
|
|
1077
|
+
*
|
|
1078
|
+
* Omits what it does not know rather than printing "unknown": a line of
|
|
1079
|
+
* absences reads as a broken probe, when in truth Firefox simply does not
|
|
1080
|
+
* publish RAM.
|
|
1081
|
+
*/
|
|
1082
|
+
declare function describeMachine(env: PerfEnvironment): string;
|
|
1083
|
+
|
|
1084
|
+
/** A finished report, handed to the host to file. The JSON is pre-serialised
|
|
1085
|
+
* because the host's job is to attach it, not to re-derive it; `summary` and
|
|
1086
|
+
* `verdict` come along so a host can title or route the report without
|
|
1087
|
+
* parsing the attachment back apart. */
|
|
1088
|
+
interface PerfReport {
|
|
1089
|
+
/** What the user typed about what they were doing. May be empty. */
|
|
1090
|
+
message: string;
|
|
1091
|
+
/** Suggested attachment filename, stamped with the local time. */
|
|
1092
|
+
filename: string;
|
|
1093
|
+
/** The whole report — environment, summary and every raw record. */
|
|
1094
|
+
json: string;
|
|
1095
|
+
summary: LogSummary;
|
|
1096
|
+
/** The HUD's current headline, e.g. "GPU-bound (compositing)". */
|
|
1097
|
+
verdict: string;
|
|
1098
|
+
/** The machine the readings came off — GPU, cores, memory, OS, screen. A
|
|
1099
|
+
* verdict of "GPU-bound" is not actionable until you know which GPU. */
|
|
1100
|
+
environment: PerfEnvironment;
|
|
1101
|
+
}
|
|
1102
|
+
interface PerfStatsProps {
|
|
1103
|
+
/** Dismiss handler — the shell wires this to turn the pref back off, so the
|
|
1104
|
+
* HUD can be closed from itself rather than only from Preferences. */
|
|
1105
|
+
onClose?: () => void;
|
|
1106
|
+
/** File the report through the host's own feedback channel — the whole point
|
|
1107
|
+
* of the HUD, since a log that reaches nobody fixes nothing. Rejecting
|
|
1108
|
+
* surfaces the error and keeps the composer open with the text intact.
|
|
1109
|
+
* Without it the button downloads the same JSON instead, so the shell stays
|
|
1110
|
+
* usable standalone. */
|
|
1111
|
+
onSubmit?: (report: PerfReport) => void | Promise<void>;
|
|
1112
|
+
className?: string;
|
|
1113
|
+
}
|
|
1114
|
+
declare function PerfStats({ onClose, onSubmit, className }: PerfStatsProps): react_jsx_runtime.JSX.Element;
|
|
1115
|
+
|
|
1116
|
+
/**
|
|
1117
|
+
* INTERNAL stub — Desktop's What's New modal references the consumer-side
|
|
1118
|
+
* changelog. The package ships no built-in changelog; the consumer wires
|
|
1119
|
+
* their own through `DesktopHostConfig.productChangelog` (the What's New
|
|
1120
|
+
* dialog reads it first, and this empty default keeps the import compiling
|
|
1121
|
+
* when they don't). Layout's `branding` prop carries the rest of the visual
|
|
1122
|
+
* identity — name, logo, tagline.
|
|
1123
|
+
*/
|
|
1124
|
+
interface ChangelogEntry {
|
|
1125
|
+
version: string;
|
|
1126
|
+
date: string;
|
|
1127
|
+
changes: string[];
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
type PreviewFileKind = 'pdf' | 'dxf' | '3d' | 'image' | 'csv';
|
|
1131
|
+
|
|
1132
|
+
interface DesktopItem {
|
|
1133
|
+
entityType: string;
|
|
1134
|
+
entityId: string;
|
|
1135
|
+
label: string;
|
|
1136
|
+
x?: number;
|
|
1137
|
+
y?: number;
|
|
1138
|
+
folderId?: string;
|
|
1139
|
+
folderX?: number;
|
|
1140
|
+
folderY?: number;
|
|
1141
|
+
filePath?: string;
|
|
1142
|
+
fileKind?: PreviewFileKind;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
interface DesktopFolder {
|
|
1146
|
+
id: string;
|
|
1147
|
+
name: string;
|
|
1148
|
+
x?: number;
|
|
1149
|
+
y?: number;
|
|
1150
|
+
}
|
|
1151
|
+
interface StickyEntityRef {
|
|
1152
|
+
/** Window-registry key used by `openEntity`. */
|
|
1153
|
+
entityType: string;
|
|
1154
|
+
entityId: string;
|
|
1155
|
+
/** Optional human-readable label (defaults to `${prefix}#${number}`). */
|
|
1156
|
+
label?: string;
|
|
1157
|
+
/** Optional pre-fetched entity payload passed to openEntity as snapshot. */
|
|
1158
|
+
snapshot?: unknown;
|
|
1159
|
+
}
|
|
1160
|
+
type StickyResolver = (prefix: string, number: string) => Promise<StickyEntityRef | null>;
|
|
1161
|
+
/** Optional items in the desktop right-click menu that a consumer can hide via
|
|
1162
|
+
* {@link DesktopHostConfig.hiddenContextMenuItems} — e.g. when they're surfaced
|
|
1163
|
+
* under Preferences instead. `'customization'` is the Preferences/Customization
|
|
1164
|
+
* shortcut, `'favorites'` the Favorites shortcut, `'about'` the About item,
|
|
1165
|
+
* `'perf-stats'` the performance-overlay toggle. */
|
|
1166
|
+
type DesktopContextMenuItem = 'customization' | 'favorites' | 'about' | 'perf-stats';
|
|
1167
|
+
interface DesktopHostConfig {
|
|
1168
|
+
/** Product name shown in the About dialog and desktop context menu. */
|
|
1169
|
+
productName?: string;
|
|
1170
|
+
/** Tagline shown under the product name in the About dialog. */
|
|
1171
|
+
productTagline?: string;
|
|
1172
|
+
/** Icon URL shown in the About dialog. Defaults to `/favicon.svg`. */
|
|
1173
|
+
productIcon?: string;
|
|
1174
|
+
/** Version string shown on the desktop watermark and About dialog. Falls
|
|
1175
|
+
* back to the react-os-shell package version if omitted. */
|
|
1176
|
+
productVersion?: string;
|
|
1177
|
+
/** Changelog rendered in the "What's New" dialog. Hidden when omitted. */
|
|
1178
|
+
productChangelog?: ChangelogEntry[];
|
|
1179
|
+
/** Copyright line in the About dialog footer. Hidden when omitted. */
|
|
1180
|
+
productCopyright?: string;
|
|
1181
|
+
/** Website URL in the About dialog footer. Hidden when omitted. */
|
|
1182
|
+
productWebsite?: string;
|
|
1183
|
+
/** Wallpaper picker options for the Customization settings page. */
|
|
1184
|
+
wallpapers?: {
|
|
1185
|
+
src: string;
|
|
1186
|
+
label: string;
|
|
1187
|
+
}[];
|
|
1188
|
+
/** Resolves sticky-note refs (e.g. "SO#27150") to window-registry coords. */
|
|
1189
|
+
stickyResolver?: StickyResolver;
|
|
1190
|
+
/** Persists the user's desktop shortcut list. */
|
|
1191
|
+
saveShortcuts?: (items: DesktopItem[]) => void | Promise<void>;
|
|
1192
|
+
/** Persists the user's folder list. */
|
|
1193
|
+
saveFolders?: (folders: DesktopFolder[]) => void | Promise<void>;
|
|
1194
|
+
/** Persists the snap-to-grid preference. */
|
|
1195
|
+
saveSnap?: (snap: boolean) => void | Promise<void>;
|
|
1196
|
+
/** Persists the user's notepad / sticky-note content. */
|
|
1197
|
+
saveNotes?: (notes: unknown[]) => void | Promise<void>;
|
|
1198
|
+
/** Desktop right-click menu items to hide (all shown by default). Pass keys
|
|
1199
|
+
* here when the consumer surfaces them elsewhere — e.g. under Preferences. */
|
|
1200
|
+
hiddenContextMenuItems?: DesktopContextMenuItem[];
|
|
1201
|
+
/** Open the consumer's own "report a bug / suggestion" UI. When set, the
|
|
1202
|
+
* desktop and taskbar right-click menus show a "Suggestion or Bug" item that
|
|
1203
|
+
* calls this. Lets a consumer that files feedback natively (the shell itself
|
|
1204
|
+
* dropped bug reporting in v3.0.0) surface the familiar right-click entry. */
|
|
1205
|
+
onReportBug?: () => void;
|
|
1206
|
+
/** File a performance report from the desktop perf HUD through the same
|
|
1207
|
+
* feedback channel, with the session log attached. Unset, the HUD's button
|
|
1208
|
+
* downloads the report instead — which is a strictly worse outcome, because
|
|
1209
|
+
* a file on someone's Downloads folder is not a bug report. Rejecting shows
|
|
1210
|
+
* the error in the HUD and keeps what the user typed. */
|
|
1211
|
+
onSubmitPerfReport?: (report: PerfReport) => void | Promise<void>;
|
|
1212
|
+
}
|
|
1213
|
+
declare function DesktopHostProvider({ value, children }: {
|
|
1214
|
+
value: DesktopHostConfig;
|
|
1215
|
+
children: ReactNode;
|
|
1216
|
+
}): react_jsx_runtime.JSX.Element;
|
|
1217
|
+
declare function useDesktopHost(): DesktopHostConfig;
|
|
1218
|
+
declare function Desktop({ profile }: {
|
|
1219
|
+
profile: any;
|
|
1220
|
+
}): react_jsx_runtime.JSX.Element;
|
|
1221
|
+
|
|
1222
|
+
interface MobileAppConfig {
|
|
1223
|
+
/** Link the primary button opens — a native deep link, an App Store /
|
|
1224
|
+
* Play Store page, or a mobile-optimised web app. When omitted the screen
|
|
1225
|
+
* still renders (branding + message) but without a call-to-action. */
|
|
1226
|
+
url?: string;
|
|
1227
|
+
/** Primary button label. Defaults to `Open the app`. */
|
|
1228
|
+
ctaLabel?: string;
|
|
1229
|
+
/** Headline. Defaults to a message built from the product name. */
|
|
1230
|
+
heading?: string;
|
|
1231
|
+
/** Supporting copy under the headline. */
|
|
1232
|
+
description?: string;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
/**
|
|
1236
|
+
* The product's visual identity, in one object: the start-menu button, the
|
|
1237
|
+
* startup splash, the logout cover and the mobile landing all read from it.
|
|
1238
|
+
* The About dialog and What's New changelog belong to `DesktopHostConfig`
|
|
1239
|
+
* (`productName`, `productIcon`, `productChangelog`, …), which configures the
|
|
1240
|
+
* desktop; this configures the chrome around it.
|
|
1241
|
+
*/
|
|
1242
|
+
interface ShellBranding {
|
|
1243
|
+
/** Product name — start-menu button, startup splash, mobile landing. */
|
|
1244
|
+
productName?: string;
|
|
1245
|
+
/** Logo URL — start-menu button, splash and logout covers. Defaults to `/favicon.svg`. */
|
|
1246
|
+
logo?: string;
|
|
1247
|
+
/** Apply the shared contrast treatment to arbitrary tenant artwork. */
|
|
1248
|
+
adaptiveLogo?: boolean;
|
|
1249
|
+
/** Server-detected transparency hint for `logo`. */
|
|
1250
|
+
logoHasAlpha?: boolean | null;
|
|
1251
|
+
/** Server-detected tone hint for `logo`. */
|
|
1252
|
+
logoIsLight?: boolean | null;
|
|
1253
|
+
/** Line under the product name on the splash and logout covers —
|
|
1254
|
+
* typically the company name. */
|
|
1255
|
+
tagline?: string;
|
|
1256
|
+
}
|
|
1257
|
+
interface LayoutProps {
|
|
1258
|
+
/** One-object form of the brand fields below; its fields win over them, so
|
|
1259
|
+
* a consumer can move over field by field. */
|
|
1260
|
+
branding?: ShellBranding;
|
|
1261
|
+
/** Brand label rendered on the start-menu button. */
|
|
1262
|
+
productName?: string;
|
|
1263
|
+
/** Icon URL rendered next to the brand label. Defaults to `/favicon.svg`. */
|
|
1264
|
+
productIcon?: string;
|
|
1265
|
+
/** Wallpaper image URLs for the desktop background. When omitted, falls back
|
|
1266
|
+
* to the wallpapers registered on the `DesktopHostProvider` (the same pool the
|
|
1267
|
+
* Customization picker shows); with neither, the desktop renders no image. */
|
|
1268
|
+
wallpapers?: string[];
|
|
1269
|
+
/** Override the default nav sections shown in the start menu. */
|
|
1270
|
+
navSections?: (NavSection | NavItem)[];
|
|
1271
|
+
/** Override the per-route icon map used by start menu and favorites. */
|
|
1272
|
+
navIcons?: Record<string, ReactNode>;
|
|
1273
|
+
/** Override the per-section header icon map used by start menu. */
|
|
1274
|
+
sectionIcons?: Record<string, ReactNode>;
|
|
1275
|
+
/** Override the start menu's section grouping + virtual flyouts. */
|
|
1276
|
+
categories?: StartMenuCategories;
|
|
1277
|
+
/** Notification system config — when omitted, the bell isn't rendered. */
|
|
1278
|
+
notifications?: NotificationsConfig;
|
|
1279
|
+
/** Universal search config — when omitted, Cmd-K opens the picker but no
|
|
1280
|
+
* results ever come back. */
|
|
1281
|
+
search?: SearchConfig;
|
|
1282
|
+
/** Custom node rendered in the system tray to the LEFT of the
|
|
1283
|
+
* notification bell (or above it on a vertical taskbar). Lets a host
|
|
1284
|
+
* app drop in icons like a server-status indicator without forking
|
|
1285
|
+
* the shell. The shell renders the node as-is — keep it small (a
|
|
1286
|
+
* single icon-sized button) so it fits the existing tray rhythm. */
|
|
1287
|
+
taskbarTrayLeft?: ReactNode;
|
|
1288
|
+
/** Taskbar clock popover config — lets the host surface per-day content
|
|
1289
|
+
* (e.g. tasks due that day) inside the mini month calendar. When omitted
|
|
1290
|
+
* the popover stays a plain date grid. */
|
|
1291
|
+
clockCalendar?: ClockCalendarConfig;
|
|
1292
|
+
/** Mobile-app link shown on phone/tablet-portrait viewports in place of the
|
|
1293
|
+
* desktop chrome. The shell has no built-in touch UI — wire a link to your
|
|
1294
|
+
* dedicated mobile app here (see MobileAppConfig). When omitted, small
|
|
1295
|
+
* screens get a plain "best viewed on desktop" notice. */
|
|
1296
|
+
mobileApp?: MobileAppConfig;
|
|
1297
|
+
/** The shell-wide right-click menu (`ShellContextMenu`). On by default;
|
|
1298
|
+
* `false` hands every right-click back to the browser — for a consumer
|
|
1299
|
+
* that is mid-migration, or that draws a menu of its own everywhere. */
|
|
1300
|
+
contextMenu?: boolean;
|
|
1301
|
+
}
|
|
1302
|
+
interface ClockCalendarConfig {
|
|
1303
|
+
/** Local dates (YYYY-MM-DD) to mark with a dot in the mini month grid —
|
|
1304
|
+
* typically days that have items on them. */
|
|
1305
|
+
markedDates?: string[];
|
|
1306
|
+
/** Render content for the selected day below the grid. The popover
|
|
1307
|
+
* defaults the selection to today when it opens, so clicking the clock
|
|
1308
|
+
* immediately shows today's items; clicking any day in the grid
|
|
1309
|
+
* re-renders the panel for that day (days outside the shown month also
|
|
1310
|
+
* flip the grid to their month). `close` dismisses the popover — call
|
|
1311
|
+
* it when a row click opens a window so the popup doesn't linger over
|
|
1312
|
+
* it. */
|
|
1313
|
+
renderDay?: (dateISO: string, api: {
|
|
1314
|
+
close: () => void;
|
|
1315
|
+
}) => ReactNode;
|
|
1316
|
+
}
|
|
1317
|
+
declare function Layout({ branding, productName, productIcon, wallpapers, navSections, navIcons, sectionIcons, categories, notifications, search, taskbarTrayLeft, clockCalendar, mobileApp, contextMenu, }?: LayoutProps): react_jsx_runtime.JSX.Element;
|
|
1318
|
+
|
|
1319
|
+
interface StartMenuProps {
|
|
1320
|
+
open: boolean;
|
|
1321
|
+
onClose: () => void;
|
|
1322
|
+
openPage: (path: string) => void;
|
|
1323
|
+
openWindows: {
|
|
1324
|
+
route?: string;
|
|
1325
|
+
label: string;
|
|
1326
|
+
}[];
|
|
1327
|
+
profile: any;
|
|
1328
|
+
user: any;
|
|
1329
|
+
onLogout: () => void;
|
|
1330
|
+
onNavigate: (path: string) => void;
|
|
1331
|
+
taskbarPosition: 'top' | 'bottom' | 'left' | 'right';
|
|
1332
|
+
taskbarH: number;
|
|
1333
|
+
taskbarW?: number;
|
|
1334
|
+
/** Gap (px) between the menu and the taskbar edge; set per taskbar size by Layout. */
|
|
1335
|
+
taskbarGap?: number;
|
|
1336
|
+
size?: 'small' | 'medium' | 'large';
|
|
1337
|
+
/** Override the default nav sections (sections + top-level items). */
|
|
1338
|
+
navSections?: (NavSection | NavItem)[];
|
|
1339
|
+
/** Per-route icon map for items in the menu. */
|
|
1340
|
+
navIcons?: Record<string, ReactNode>;
|
|
1341
|
+
/** Per-section header icon map. */
|
|
1342
|
+
sectionIcons?: Record<string, ReactNode>;
|
|
1343
|
+
/** Section grouping + virtual flyouts (Utilities-style trays). */
|
|
1344
|
+
categories?: StartMenuCategories;
|
|
1345
|
+
}
|
|
1346
|
+
declare function StartMenu({ open, onClose, openPage, profile, user, onLogout, taskbarPosition, taskbarH, taskbarW, taskbarGap, size, navSections, navIcons, sectionIcons, categories, }: StartMenuProps): react_jsx_runtime.JSX.Element | null;
|
|
1347
|
+
|
|
1348
|
+
/**
|
|
1349
|
+
* Widget gallery — the "manage widgets on the desktop" surface.
|
|
1350
|
+
*
|
|
1351
|
+
* Enumerates every widget-flagged page in the live window registry (so it
|
|
1352
|
+
* automatically lists whatever the consumer registered, not just the bundled
|
|
1353
|
+
* five) and cross-references the window manager's `openWindows` to know which
|
|
1354
|
+
* are currently sitting on the desktop. Adding a widget calls `openPage`
|
|
1355
|
+
* (which drops the chrome-less panel onto the desktop and persists it via the
|
|
1356
|
+
* normal open-windows session store); removing one calls `closeEntity` on the
|
|
1357
|
+
* live window. No new persistence layer — it drives the same plumbing the
|
|
1358
|
+
* Start Menu already uses.
|
|
1359
|
+
*
|
|
1360
|
+
* Surfaced from the desktop right-click menu ("Manage Widgets…") and exported
|
|
1361
|
+
* so a consumer can also register it as a window or wire it to a tray button.
|
|
1362
|
+
*/
|
|
1363
|
+
declare function WidgetManager({ open, onClose }: {
|
|
1364
|
+
open: boolean;
|
|
1365
|
+
onClose: () => void;
|
|
1366
|
+
}): react_jsx_runtime.JSX.Element | null;
|
|
1367
|
+
|
|
1368
|
+
/** Sections that can be omitted from the inline Customization page when the
|
|
1369
|
+
* consumer renders them elsewhere (e.g. as separate entries in SystemPreferences). */
|
|
1370
|
+
type CustomizationOmitSection = 'behavior' | 'desktop';
|
|
1371
|
+
/** A logical group of the Customization page. Pass `section` to render only one
|
|
1372
|
+
* group, so the page can be split across separate SystemPreferences entries
|
|
1373
|
+
* (Appearance / Layout / Behavior). Omit `section` to render the whole page. */
|
|
1374
|
+
type CustomizationSection = 'appearance' | 'layout' | 'behavior';
|
|
1375
|
+
interface CustomizationProps {
|
|
1376
|
+
omit?: readonly CustomizationOmitSection[];
|
|
1377
|
+
/** Render only one logical group of settings. Omit to render everything. */
|
|
1378
|
+
section?: CustomizationSection;
|
|
1379
|
+
}
|
|
1380
|
+
declare function Customization({ omit, section }?: CustomizationProps): react_jsx_runtime.JSX.Element;
|
|
1381
|
+
|
|
1382
|
+
/**
|
|
1383
|
+
* Standalone Behavior settings panel — window position, double-click,
|
|
1384
|
+
* default window size, plus the two desktop-related toggles
|
|
1385
|
+
* (Show version on desktop, Enter full screen mode automatically).
|
|
1386
|
+
*
|
|
1387
|
+
* Reads/writes shell prefs via `useShellPrefs()`. Suitable for use as a
|
|
1388
|
+
* section in `SystemPreferences` or rendered standalone.
|
|
1389
|
+
*/
|
|
1390
|
+
declare function BehaviorPanel(): react_jsx_runtime.JSX.Element;
|
|
1391
|
+
|
|
1392
|
+
/**
|
|
1393
|
+
* Standalone Sounds settings panel — sound effects toggle + per-event
|
|
1394
|
+
* sound-pack picker. Reads/writes the shell's `erp_sounds` and
|
|
1395
|
+
* `erp_sound_config` localStorage keys. Plays a preview when a pack is
|
|
1396
|
+
* picked. Suitable as a section in `SystemPreferences` or rendered
|
|
1397
|
+
* standalone.
|
|
1398
|
+
*/
|
|
1399
|
+
declare function SoundsPanel(): react_jsx_runtime.JSX.Element;
|
|
1400
|
+
|
|
1401
|
+
/** One section in the SystemPreferences sidebar. */
|
|
1402
|
+
interface SystemPreferencesSection {
|
|
1403
|
+
/** Unique key, e.g. `'notifications'`, `'customization'`. */
|
|
1404
|
+
key: string;
|
|
1405
|
+
/** Sidebar label, e.g. "Notifications". */
|
|
1406
|
+
label: string;
|
|
1407
|
+
/** Short description shown under the label in the sidebar. */
|
|
1408
|
+
description?: string;
|
|
1409
|
+
/** Sidebar icon (any ReactNode — typically a 4×4 svg). */
|
|
1410
|
+
icon?: ReactNode;
|
|
1411
|
+
/** Body renderer. Receives no args; access shell prefs via your own hook. */
|
|
1412
|
+
render: () => ReactNode;
|
|
1413
|
+
}
|
|
1414
|
+
interface SystemPreferencesProps {
|
|
1415
|
+
/** Ordered sections — left sidebar lists these top-to-bottom, the matching
|
|
1416
|
+
* body renders on the right. The first section is selected by default
|
|
1417
|
+
* unless `defaultSelected` is supplied. */
|
|
1418
|
+
sections: readonly SystemPreferencesSection[];
|
|
1419
|
+
/** Key of the section to highlight on first render. Falls back to the first
|
|
1420
|
+
* section in `sections`. */
|
|
1421
|
+
defaultSelected?: string;
|
|
1422
|
+
/** Optional className applied to the outer flex container. */
|
|
1423
|
+
className?: string;
|
|
1424
|
+
}
|
|
1425
|
+
/**
|
|
1426
|
+
* Two-pane settings window: a fixed sidebar of sections on the left, the
|
|
1427
|
+
* active section's body on the right. Consumers compose by passing
|
|
1428
|
+
* `sections` — each entry has a `key`, `label`, optional `icon` and
|
|
1429
|
+
* `description`, and a `render()` callback that returns the body JSX.
|
|
1430
|
+
*
|
|
1431
|
+
* The shell exposes ready-made panels (`Customization`, `BehaviorPanel`,
|
|
1432
|
+
* `SoundsPanel`) that can be used as section bodies directly; portals
|
|
1433
|
+
* add their own sections (notification subscriptions, delivery defaults,
|
|
1434
|
+
* personal formatting prefs, etc.) alongside.
|
|
1435
|
+
*
|
|
1436
|
+
* Example:
|
|
1437
|
+
* ```tsx
|
|
1438
|
+
* <SystemPreferences sections={[
|
|
1439
|
+
* { key: 'notifications', label: 'Notifications', icon: <BellIcon />, render: () => <MyNotifications /> },
|
|
1440
|
+
* { key: 'customization', label: 'Customization', icon: <PaintBrushIcon />, render: () => <Customization omit={['behavior','desktop']} /> },
|
|
1441
|
+
* { key: 'behavior', label: 'Behavior', icon: <Cog6ToothIcon />, render: () => <BehaviorPanel /> },
|
|
1442
|
+
* { key: 'sounds', label: 'Sounds', icon: <SpeakerWaveIcon />, render: () => <SoundsPanel /> },
|
|
1443
|
+
* ]} />
|
|
1444
|
+
* ```
|
|
1445
|
+
*/
|
|
1446
|
+
declare function SystemPreferences({ sections, defaultSelected, className, }: SystemPreferencesProps): react_jsx_runtime.JSX.Element;
|
|
1447
|
+
|
|
1448
|
+
interface ImageAnnotatorHandle {
|
|
1449
|
+
copy: () => Promise<void>;
|
|
1450
|
+
save: () => Promise<void>;
|
|
1451
|
+
}
|
|
1452
|
+
interface ImageAnnotatorProps {
|
|
1453
|
+
src: string;
|
|
1454
|
+
filename: string;
|
|
1455
|
+
/** Standalone-mode: when provided, an Apply pill is rendered in the
|
|
1456
|
+
* annotator's toolbar that composites the current canvas + SVG into a PNG
|
|
1457
|
+
* blob and hands it back. Used by callers like the bug-report dialog that
|
|
1458
|
+
* embed the annotator outside the Preview app and want the result. */
|
|
1459
|
+
onApply?: (blob: Blob) => void;
|
|
1460
|
+
/** Standalone-mode companion to `onApply` — Cancel pill that lets the
|
|
1461
|
+
* consumer dismiss the annotator without applying. */
|
|
1462
|
+
onCancel?: () => void;
|
|
1463
|
+
}
|
|
1464
|
+
declare const ImageAnnotator: react.ForwardRefExoticComponent<ImageAnnotatorProps & react.RefAttributes<ImageAnnotatorHandle>>;
|
|
1465
|
+
|
|
1466
|
+
interface EntityListColumn {
|
|
1467
|
+
key: string;
|
|
1468
|
+
label: string;
|
|
1469
|
+
defaultWidth?: number;
|
|
1470
|
+
minWidth?: number;
|
|
1471
|
+
defaultHidden?: boolean;
|
|
1472
|
+
sortField?: string;
|
|
1473
|
+
headerNode?: ReactNode;
|
|
1474
|
+
}
|
|
1475
|
+
/** A page-supplied entry in the right-click bulk menu (e.g. invoice Post /
|
|
1476
|
+
* Cancel). `onClick` receives the currently-selected rows. Set `divider` to
|
|
1477
|
+
* render a separator above the item, `danger` for a destructive tint. */
|
|
1478
|
+
interface EntityListContextAction<T = unknown> {
|
|
1479
|
+
label: string;
|
|
1480
|
+
onClick: (items: T[]) => void;
|
|
1481
|
+
danger?: boolean;
|
|
1482
|
+
disabled?: boolean;
|
|
1483
|
+
divider?: boolean;
|
|
1484
|
+
}
|
|
1485
|
+
interface EntityListProps<T> {
|
|
1486
|
+
items: T[];
|
|
1487
|
+
isLoading: boolean;
|
|
1488
|
+
emptyState: ReactNode;
|
|
1489
|
+
totalCount?: number;
|
|
1490
|
+
/** When true and no rows have loaded yet, render {@link ListLoadError}
|
|
1491
|
+
* (with a retry) instead of `emptyState` — so a failed fetch reads as an
|
|
1492
|
+
* error, not "nothing here". Wire `useInfiniteScroll`'s `isError`. A
|
|
1493
|
+
* mid-scroll next-page failure (rows already loaded) keeps the list.
|
|
1494
|
+
* Defaults to unset → behaviour unchanged for existing callers. */
|
|
1495
|
+
isError?: boolean;
|
|
1496
|
+
/** Retry handler for the error state — wire the data source's `refetch`.
|
|
1497
|
+
* It also backs the row menu's Refresh item. */
|
|
1498
|
+
onRetry?: () => void;
|
|
1499
|
+
/** Heading for the error state, e.g. "Couldn't load candidates". Omit for
|
|
1500
|
+
* {@link ListLoadError}'s generic "Couldn't load this list" — name the
|
|
1501
|
+
* entity where the list has one, so an outage reads as *this* list failing. */
|
|
1502
|
+
errorTitle?: string;
|
|
1503
|
+
/** Explanatory line under {@link EntityListProps.errorTitle}. Omit for
|
|
1504
|
+
* ListLoadError's generic connection/retry message. */
|
|
1505
|
+
errorMessage?: string;
|
|
1506
|
+
tableId: string;
|
|
1507
|
+
columns: EntityListColumn[];
|
|
1508
|
+
renderCell: (item: T, colKey: string) => ReactNode;
|
|
1509
|
+
getRowId?: (item: T) => string | number;
|
|
1510
|
+
sort?: {
|
|
1511
|
+
field: string;
|
|
1512
|
+
direction: 'asc' | 'desc';
|
|
1513
|
+
};
|
|
1514
|
+
onSort?: (field: string) => void;
|
|
1515
|
+
selected: Set<string | number>;
|
|
1516
|
+
setSelected: Dispatch<SetStateAction<Set<string | number>>>;
|
|
1517
|
+
onRowClick: (item: T) => void;
|
|
1518
|
+
onRowHover?: (item: T) => void;
|
|
1519
|
+
getRowClassName?: (item: T) => string;
|
|
1520
|
+
footerLabel: string;
|
|
1521
|
+
footerExtra?: ReactNode;
|
|
1522
|
+
sentinelRef?: RefObject<any>;
|
|
1523
|
+
isFetchingNextPage?: boolean;
|
|
1524
|
+
/** Perm codenames that gate "Save as default for all users" in the column
|
|
1525
|
+
* picker. Defaults to `['change_numberingconfig']` (admin-portal's proxy
|
|
1526
|
+
* for admin access). Pass `[]` to hide the link entirely. */
|
|
1527
|
+
saveDefaultPerms?: string[];
|
|
1528
|
+
/** Right-click row menu. Every list has one, with nothing to wire: Open (the
|
|
1529
|
+
* row right-clicked), Copy for text selected under the pointer, Copy
|
|
1530
|
+
* `<first column>` and Copy rows (the ticked rows as a table of the visible
|
|
1531
|
+
* columns in their on-screen order, which pastes into a spreadsheet as
|
|
1532
|
+
* cells), Select all, Clear selection, and Refresh when `onRetry` is wired.
|
|
1533
|
+
* Provide `exportEndpoint` (a list's `<base>/export_csv/` path, relative to
|
|
1534
|
+
* the api base) to add "Export selected to CSV", which downloads just the
|
|
1535
|
+
* ticked rows (`?ids=`, honouring the visible/ordered columns). Provide
|
|
1536
|
+
* `contextActions` for domain actions (e.g. invoice Post / Cancel); they sit
|
|
1537
|
+
* between the copy items and the selection items. */
|
|
1538
|
+
exportEndpoint?: string;
|
|
1539
|
+
exportFilename?: string;
|
|
1540
|
+
contextActions?: (items: T[]) => EntityListContextAction<T>[];
|
|
1541
|
+
}
|
|
1542
|
+
/**
|
|
1543
|
+
* The canonical pageless data grid for both portals. Wraps `<ResizableTable>`
|
|
1544
|
+
* with selection-checkbox logic, keyboard navigation, the standardized list
|
|
1545
|
+
* footer, an infinite-scroll sentinel hook-up, and a right-click row menu.
|
|
1546
|
+
*
|
|
1547
|
+
* Usage:
|
|
1548
|
+
*
|
|
1549
|
+
* const { sort, onSort } = useSort('mid', 'asc', 'suppliers');
|
|
1550
|
+
* const { items, totalCount, isLoading, isFetchingNextPage, sentinelRef } =
|
|
1551
|
+
* useInfiniteScroll<Supplier>({ queryKey: ['suppliers', filters],
|
|
1552
|
+
* fetchFn: (p) => getSuppliers(p) });
|
|
1553
|
+
*
|
|
1554
|
+
* <EntityList
|
|
1555
|
+
* items={items}
|
|
1556
|
+
* totalCount={totalCount}
|
|
1557
|
+
* isLoading={isLoading}
|
|
1558
|
+
* sentinelRef={sentinelRef}
|
|
1559
|
+
* isFetchingNextPage={isFetchingNextPage}
|
|
1560
|
+
* tableId="suppliers"
|
|
1561
|
+
* columns={SUPPLIER_COLUMNS}
|
|
1562
|
+
* renderCell={(s, k) => …}
|
|
1563
|
+
* sort={sort} onSort={onSort}
|
|
1564
|
+
* selected={selected} setSelected={setSelected}
|
|
1565
|
+
* onRowClick={(s) => openEntity('supplier', s.id, s, s.mid, '/suppliers')}
|
|
1566
|
+
* exportEndpoint="/suppliers/export_csv/" exportFilename="Suppliers.csv"
|
|
1567
|
+
* footerLabel="suppliers"
|
|
1568
|
+
* emptyState={<EmptyState message="No suppliers yet." />}
|
|
1569
|
+
* />
|
|
1570
|
+
*/
|
|
1571
|
+
declare function EntityList<T>(props: EntityListProps<T>): react_jsx_runtime.JSX.Element;
|
|
1572
|
+
|
|
1573
|
+
interface ResizableTableProps {
|
|
1574
|
+
tableId: string;
|
|
1575
|
+
columns: ColumnDef[];
|
|
1576
|
+
sort?: SortState;
|
|
1577
|
+
onSort?: (field: string) => void;
|
|
1578
|
+
footer?: React.ReactNode;
|
|
1579
|
+
afterBody?: React.ReactNode;
|
|
1580
|
+
/** Permission codename(s) that gate the "Save as default for all users" link
|
|
1581
|
+
* in the column picker. The shell's `useShellAuth().hasAnyPerm` is queried
|
|
1582
|
+
* against this list. Defaults to a single placeholder permission; consumers
|
|
1583
|
+
* with no admin gate can pass `[]` to hide the link entirely. */
|
|
1584
|
+
saveDefaultPerms?: string[];
|
|
1585
|
+
children: (orderedColumns: (ColumnDef & {
|
|
1586
|
+
width: number;
|
|
1587
|
+
})[]) => React.ReactNode;
|
|
1588
|
+
}
|
|
1589
|
+
/**
|
|
1590
|
+
* Sticky-header, scrollable-body table primitive with:
|
|
1591
|
+
* - drag-to-reorder columns (with a blue drop-indicator line)
|
|
1592
|
+
* - resize columns by the right-edge handle
|
|
1593
|
+
* - hide/show columns via a glass popover picker
|
|
1594
|
+
* - admin-only "Save as default for all users" link in the picker
|
|
1595
|
+
* - per-user persistence (via the shell-registered apiClient)
|
|
1596
|
+
* - mobile-aware selection mode toggle
|
|
1597
|
+
*/
|
|
1598
|
+
declare function ResizableTable({ tableId, columns, sort, onSort, footer, afterBody, saveDefaultPerms, children, }: ResizableTableProps): react_jsx_runtime.JSX.Element;
|
|
1599
|
+
|
|
1600
|
+
/**
|
|
1601
|
+
* Keyboard + shift-click navigation for table rows.
|
|
1602
|
+
*
|
|
1603
|
+
* - J/K or Arrow keys to move focus
|
|
1604
|
+
* - Enter to open the focused row (calls `onSelect`)
|
|
1605
|
+
* - Space to toggle the focused row's checkbox (calls `onToggle`)
|
|
1606
|
+
* - Shift+J/K to extend selection while moving
|
|
1607
|
+
* - Shift+click to range-select (calls `onSelectRange`, which must ADD the range
|
|
1608
|
+
* rather than toggle it — the clicked checkbox is ticked on that assumption)
|
|
1609
|
+
* - Cmd/Ctrl+A to select/deselect all (calls `onSelectAll`)
|
|
1610
|
+
*
|
|
1611
|
+
* Each list instance gates its document-level listeners on `useModalActive`
|
|
1612
|
+
* so two open lists don't both react to the same shift-click.
|
|
1613
|
+
*/
|
|
1614
|
+
declare function useTableNav<T>(items: T[], onSelect: (item: T) => void, onToggle?: (item: T) => void, onSelectAll?: () => void, onSelectRange?: (from: number, to: number) => void): number;
|
|
1615
|
+
|
|
1616
|
+
/**
|
|
1617
|
+
* Resizable + reorderable + hideable column state for `<ResizableTable>`.
|
|
1618
|
+
*
|
|
1619
|
+
* Reads and persists per-user through the `<ShellPrefsProvider>` adapter
|
|
1620
|
+
* (`prefs.columns_{tableId}`), with a 1-second debounce on the save, and reads
|
|
1621
|
+
* per-viewport admin defaults through the shared
|
|
1622
|
+
* `GET /auth/default-columns/{tableId}?viewport=…` probe.
|
|
1623
|
+
*
|
|
1624
|
+
* The prefs half used to be a raw `GET /auth/me/` fired from a `[tableId]`
|
|
1625
|
+
* effect, outside react-query — so every list mount re-fetched the whole user
|
|
1626
|
+
* profile (~1.5 MB on prod), on a client with no cache adapter and no in-flight
|
|
1627
|
+
* dedupe, and a screen that also renders a column-aware CSV export paid for it
|
|
1628
|
+
* twice. The adapter already holds that profile in one cached query, which is
|
|
1629
|
+
* the same object the raw GET was reading `preferences` off (SG#00590).
|
|
1630
|
+
*
|
|
1631
|
+
* Three consequences worth knowing, because this is NOT behaviour-preserving:
|
|
1632
|
+
*
|
|
1633
|
+
* - **A `<ShellPrefsProvider>` is now required for server-side persistence.**
|
|
1634
|
+
* Without one, `useShellPrefs()` reads empty and drops saves, so column
|
|
1635
|
+
* config degrades to localStorage-only. All three consumers that render
|
|
1636
|
+
* `<EntityList>`/`<ResizableTable>` mount the provider.
|
|
1637
|
+
* - **Prefs and admin defaults resolve independently**, where the raw pair was
|
|
1638
|
+
* a single `Promise.all`. If the admin default lands while prefs are still
|
|
1639
|
+
* loading it applies first and the user's own config replaces it a beat
|
|
1640
|
+
* later, rather than never showing. `useSort` has always behaved this way;
|
|
1641
|
+
* the two hooks now agree. The precedence that used to be one `if/else if`
|
|
1642
|
+
* is now the `userSavedRef` guard below plus the order the two effects fire
|
|
1643
|
+
* in — `tests/columnConfigDefaultsPrecedence.test.tsx` pins both directions.
|
|
1644
|
+
* - **A failed save is silent here.** The old `useMutation` let a rejected
|
|
1645
|
+
* `PATCH` reach a consumer's global `MutationCache({ onError })` — the admin
|
|
1646
|
+
* portal toasted it. The adapter's `save` owns its own failures now, so a
|
|
1647
|
+
* consumer that wants that toast raises it inside the adapter.
|
|
1648
|
+
*
|
|
1649
|
+
* What it does NOT require is a `<QueryClientProvider>` — it used to, for the
|
|
1650
|
+
* `useMutation`. `useDefaultColumnConfig` falls back to a client the package
|
|
1651
|
+
* owns when no provider is mounted.
|
|
1652
|
+
*/
|
|
1653
|
+
declare function useColumnConfig(tableId: string, defaultColumns: ColumnDef[]): {
|
|
1654
|
+
orderedColumns: {
|
|
1655
|
+
width: number;
|
|
1656
|
+
key: string;
|
|
1657
|
+
label: string;
|
|
1658
|
+
defaultWidth?: number;
|
|
1659
|
+
minWidth?: number;
|
|
1660
|
+
defaultHidden?: boolean;
|
|
1661
|
+
headerNode?: react__default.ReactNode;
|
|
1662
|
+
sortField?: string;
|
|
1663
|
+
}[];
|
|
1664
|
+
allColumns: {
|
|
1665
|
+
key: string;
|
|
1666
|
+
label: string;
|
|
1667
|
+
hidden: boolean;
|
|
1668
|
+
}[];
|
|
1669
|
+
onResizeStart: (idx: number, e: react__default.MouseEvent) => void;
|
|
1670
|
+
onDragStart: (idx: number) => void;
|
|
1671
|
+
onDragOver: (visIdx: number, e: react__default.DragEvent) => void;
|
|
1672
|
+
onDrop: (_visIdx: number, e: react__default.DragEvent) => void;
|
|
1673
|
+
onDragEnd: () => void;
|
|
1674
|
+
toggleColumn: (key: string) => void;
|
|
1675
|
+
resetColumns: () => void;
|
|
1676
|
+
draggedIdx: number | null;
|
|
1677
|
+
dropGap: number | null;
|
|
1678
|
+
};
|
|
1679
|
+
|
|
1680
|
+
interface UseInfiniteScrollOptions<T> {
|
|
1681
|
+
/** queryKey segments — arbitrary cache shapes (filters often include booleans,
|
|
1682
|
+
* dates, undefined). Callers pass their natural filter shape. */
|
|
1683
|
+
queryKey: (string | Record<string, unknown>)[];
|
|
1684
|
+
fetchFn: (params: Record<string, string>) => Promise<PaginatedResponse<T>>;
|
|
1685
|
+
/** Extra query-string params. Undefined values are dropped so axios doesn't
|
|
1686
|
+
* serialise them as "?key=undefined". */
|
|
1687
|
+
extraParams?: Record<string, string | undefined>;
|
|
1688
|
+
pageSize?: number;
|
|
1689
|
+
}
|
|
1690
|
+
/**
|
|
1691
|
+
* Wraps TanStack `useInfiniteQuery` with an IntersectionObserver-driven
|
|
1692
|
+
* sentinel so attaching `sentinelRef` to a div near the bottom of a scrollable
|
|
1693
|
+
* container auto-fires `fetchNextPage` as the user scrolls.
|
|
1694
|
+
*
|
|
1695
|
+
* Works inside modal windows too — the observer roots itself on the nearest
|
|
1696
|
+
* scrollable ancestor rather than the viewport.
|
|
1697
|
+
*/
|
|
1698
|
+
declare function useInfiniteScroll<T>({ queryKey, fetchFn, extraParams, pageSize: _pageSize, }: UseInfiniteScrollOptions<T>): {
|
|
1699
|
+
items: T[];
|
|
1700
|
+
totalCount: number;
|
|
1701
|
+
isLoading: boolean;
|
|
1702
|
+
isError: boolean;
|
|
1703
|
+
error: Error | null;
|
|
1704
|
+
isFetchingNextPage: boolean;
|
|
1705
|
+
hasNextPage: boolean;
|
|
1706
|
+
sentinelRef: react.RefObject<HTMLDivElement>;
|
|
1707
|
+
refetch: (options?: _tanstack_query_core.RefetchOptions) => Promise<_tanstack_query_core.QueryObserverResult<_tanstack_query_core.InfiniteData<PaginatedResponse<T>, unknown>, Error>>;
|
|
1708
|
+
};
|
|
1709
|
+
|
|
1710
|
+
/** The part of a column `useSort` reads — `ColumnDef` and `EntityList`'s own
|
|
1711
|
+
* column both fit.
|
|
1712
|
+
*
|
|
1713
|
+
* Not `DataTable`: its columns nest inside groups and it offers a sort on
|
|
1714
|
+
* `sortable`, not on `sortField ?? key`. `DataTableColumn` satisfies this
|
|
1715
|
+
* interface structurally, so passing one typechecks and then drops sorts the
|
|
1716
|
+
* table does offer. `DataTable` owns its own sort state through
|
|
1717
|
+
* `onSortChange`. */
|
|
1718
|
+
interface SortableColumn {
|
|
1719
|
+
key: string;
|
|
1720
|
+
sortField?: string;
|
|
1721
|
+
}
|
|
1722
|
+
/**
|
|
1723
|
+
* Sort-state hook used by list pages. Returns:
|
|
1724
|
+
* - `sort` — current `{ field, direction }`
|
|
1725
|
+
* - `onSort(field)` — toggle direction if same field, else activate asc
|
|
1726
|
+
* - `ordering` — DRF-style string (`'-mid'` for desc, `'mid'` for asc) ready
|
|
1727
|
+
* to drop into a query-string param.
|
|
1728
|
+
*
|
|
1729
|
+
* Pass `tableId` (the same id the list gives `<ResizableTable>`/`<EntityList>`)
|
|
1730
|
+
* to persist the user's choice alongside their column config. Restore order:
|
|
1731
|
+
* per-user pref (`prefs.sort_{tableId}` via the ShellPrefs adapter) → the
|
|
1732
|
+
* admin-saved default on `/auth/default-columns/{tableId}/` → the page's
|
|
1733
|
+
* `defaultField`/`defaultDir`. localStorage mirrors the last-applied value so
|
|
1734
|
+
* the first render (before async prefs arrive) already uses the last-known
|
|
1735
|
+
* sort instead of flashing the default and refetching. Without `tableId` the
|
|
1736
|
+
* hook is pure in-memory state, exactly as before.
|
|
1737
|
+
*
|
|
1738
|
+
* The admin default is read through `useDefaultColumnConfig`, shared with
|
|
1739
|
+
* `useColumnConfig` so a list page fetches that row once rather than twice
|
|
1740
|
+
* (SG#00590). That probe is a react-query query, but this hook still needs no
|
|
1741
|
+
* `<QueryClientProvider>` in either form: with no provider above it the probe
|
|
1742
|
+
* falls back to a client the package owns (see `useDefaultColumnConfig`), and
|
|
1743
|
+
* the `tableId`-less form issues no request at all, as before.
|
|
1744
|
+
*
|
|
1745
|
+
* Pass `options.columns` (the same array the table gets) and a sort the
|
|
1746
|
+
* columns do not offer is replaced by the page default in `sort` and
|
|
1747
|
+
* `ordering` — a save on a column since given `sortField: ''`, or on a key
|
|
1748
|
+
* whose column now sorts on another `sortField`. Without it that stale save
|
|
1749
|
+
* goes out as `?ordering=` on every open; DRF drops a term it cannot order by,
|
|
1750
|
+
* so the list comes back unsorted for good. Without `columns` — or while the
|
|
1751
|
+
* array is still empty, which is a list page whose columns are not built yet
|
|
1752
|
+
* rather than a table that offers no sort — nothing is checked, as before.
|
|
1753
|
+
*/
|
|
1754
|
+
declare function useSort(defaultField: string, defaultDir?: 'asc' | 'desc', tableId?: string, options?: {
|
|
1755
|
+
columns?: ReadonlyArray<SortableColumn>;
|
|
1756
|
+
}): {
|
|
1757
|
+
sort: SortState;
|
|
1758
|
+
onSort: (field: string) => void;
|
|
1759
|
+
ordering: string;
|
|
1760
|
+
};
|
|
1761
|
+
|
|
1762
|
+
interface FilterOption {
|
|
1763
|
+
label: string;
|
|
1764
|
+
field: string;
|
|
1765
|
+
options: {
|
|
1766
|
+
value: string;
|
|
1767
|
+
label: string;
|
|
1768
|
+
}[];
|
|
1769
|
+
searchable?: boolean;
|
|
1770
|
+
}
|
|
1771
|
+
interface FilterBarProps {
|
|
1772
|
+
filters: FilterOption[];
|
|
1773
|
+
values: Record<string, string>;
|
|
1774
|
+
onChange: (field: string, value: string) => void;
|
|
1775
|
+
onClear: () => void;
|
|
1776
|
+
children?: ReactNode;
|
|
1777
|
+
}
|
|
1778
|
+
/**
|
|
1779
|
+
* FilterBar — a horizontal row of filter controls. Each filter renders a custom
|
|
1780
|
+
* listbox dropdown or, for long lists, a glass-styled searchable dropdown; a
|
|
1781
|
+
* "Clear filters" button appears when any filter is active. Pair with
|
|
1782
|
+
* `useFilters`.
|
|
1783
|
+
*
|
|
1784
|
+
* The short-list path used to render a native `<select>`, but a native select's
|
|
1785
|
+
* OS popup swallows every key event while open — so list-page hotkeys died
|
|
1786
|
+
* until it closed (BG#00421). PlainFilter is a custom listbox that keeps key
|
|
1787
|
+
* events flowing to the page.
|
|
1788
|
+
*/
|
|
1789
|
+
declare function FilterBar({ filters, values, onChange, onClear, children }: FilterBarProps): react_jsx_runtime.JSX.Element;
|
|
1790
|
+
/** Local filter-state helper: tracks active filter values + derived params. */
|
|
1791
|
+
declare function useFilters(): {
|
|
1792
|
+
filterValues: Record<string, string>;
|
|
1793
|
+
setFilter: (field: string, value: string) => void;
|
|
1794
|
+
clearFilters: () => void;
|
|
1795
|
+
filterParams: Record<string, string>;
|
|
1796
|
+
};
|
|
1797
|
+
|
|
1798
|
+
/**
|
|
1799
|
+
* Shell interaction marks, folded into every perf-log record.
|
|
1800
|
+
*
|
|
1801
|
+
* The HUD can see that frames were dropped and it can see that the pointer
|
|
1802
|
+
* moved, but "the pointer moved" covers wandering across the desktop and
|
|
1803
|
+
* opening a third-level flyout, and only one of those repaints a frosted
|
|
1804
|
+
* surface. The first version of the log conflated them, so the readings that
|
|
1805
|
+
* mattered most — the frame the start menu opened on, the frame a window was
|
|
1806
|
+
* dragged across — arrived indistinguishable from an idle mouse. A report full
|
|
1807
|
+
* of anonymous samples points at nothing.
|
|
1808
|
+
*
|
|
1809
|
+
* So the places that do the expensive things say so. Menus mark themselves when
|
|
1810
|
+
* they open; window drags and resizes mark themselves for as long as they run.
|
|
1811
|
+
* Everything here is a plain counter drained once per flush interval — no
|
|
1812
|
+
* subscriptions, no allocation per event, nothing that would make marking an
|
|
1813
|
+
* event cost more than the event.
|
|
1814
|
+
*
|
|
1815
|
+
* Marks accumulate only while the HUD is mounted (`setPerfCollecting`). That is
|
|
1816
|
+
* not really about cost — incrementing a number is free — but about honesty: a
|
|
1817
|
+
* counter that had been climbing since page load would attribute a morning's
|
|
1818
|
+
* worth of menu opens to whichever 500ms interval happened to drain it first.
|
|
1819
|
+
*/
|
|
1820
|
+
/** Which menu layer opened. The distinction is the point: the root menu is one
|
|
1821
|
+
* surface appearing, a flyout is a second one appearing *over* it, and the
|
|
1822
|
+
* cost of the second is what people report. */
|
|
1823
|
+
type MenuLayer = 'menu' | 'submenu';
|
|
1824
|
+
/** Record that a menu surface opened. `key` names it (a section label, a route)
|
|
1825
|
+
* so the summary can rank flyouts against each other. */
|
|
1826
|
+
declare function markMenuOpen(layer: MenuLayer, key: string): void;
|
|
1827
|
+
/**
|
|
1828
|
+
* Record a window drag or resize for as long as it runs. Returns the end
|
|
1829
|
+
* function; calling it twice adds the span once.
|
|
1830
|
+
*
|
|
1831
|
+
* Duration rather than a count because these are the gestures whose *cost is
|
|
1832
|
+
* their length* — a drag that stutters for four seconds and a drag that lasted
|
|
1833
|
+
* one frame are the same event and very different reports. The span is credited
|
|
1834
|
+
* to the interval it ends in, which can straddle a flush boundary; that is
|
|
1835
|
+
* accepted rather than apportioned, because a drag long enough to straddle two
|
|
1836
|
+
* intervals is already the thing being investigated.
|
|
1837
|
+
*/
|
|
1838
|
+
declare function beginWindowGesture(kind: 'move' | 'resize'): () => void;
|
|
1839
|
+
|
|
1840
|
+
/** The description body for a filed performance report. */
|
|
1841
|
+
declare function describePerfReport(report: PerfReport): string;
|
|
1842
|
+
/** The attachment. A `File` rather than a `Blob` so the multipart part carries
|
|
1843
|
+
* the stamped filename the receiving end stores. */
|
|
1844
|
+
declare function perfReportFile(report: PerfReport): File;
|
|
1845
|
+
|
|
1846
|
+
/**
|
|
1847
|
+
* Semantic role of a column, used to auto-map CSV columns to fields and to
|
|
1848
|
+
* format the per-column totals. The first column is always the merge "key"
|
|
1849
|
+
* regardless of its declared kind.
|
|
1850
|
+
* - `key` — the identifier rows are de-duplicated/merged on (text-like).
|
|
1851
|
+
* - `price` — a money column (matched by `$`/decimals; totalled with 2 dp).
|
|
1852
|
+
* - `qty` — a whole-number quantity column.
|
|
1853
|
+
* - `text` — anything else; never auto-mapped, never totalled.
|
|
1854
|
+
*/
|
|
1855
|
+
type BulkColumnKind = 'key' | 'price' | 'qty' | 'text';
|
|
1856
|
+
interface BulkColumn {
|
|
1857
|
+
key: string;
|
|
1858
|
+
title: string;
|
|
1859
|
+
width?: number;
|
|
1860
|
+
required?: boolean;
|
|
1861
|
+
/**
|
|
1862
|
+
* Hint for CSV auto-mapping and totals. Defaults: the first column is `key`,
|
|
1863
|
+
* every other column is `text`. Set `price`/`qty` to opt a column into
|
|
1864
|
+
* auto-detection and the totals strip.
|
|
1865
|
+
*/
|
|
1866
|
+
kind?: BulkColumnKind;
|
|
1867
|
+
}
|
|
1868
|
+
interface BulkImportGridProps {
|
|
1869
|
+
/** Column definitions. The FIRST column is always the merge key. */
|
|
1870
|
+
columns: BulkColumn[];
|
|
1871
|
+
/** Called with the resolved, de-duplicated rows (one object per row, keyed by column key). */
|
|
1872
|
+
onImport: (rows: Record<string, string>[]) => Promise<void>;
|
|
1873
|
+
/** Called when the user cancels an in-progress mapping/duplicate review. */
|
|
1874
|
+
onCancel: () => void;
|
|
1875
|
+
/** Optional override for the help text shown above the grid. */
|
|
1876
|
+
description?: string;
|
|
1877
|
+
/**
|
|
1878
|
+
* When set, duplicate key values are merged into a single row with their
|
|
1879
|
+
* numeric columns summed, instead of offering keep-first / keep-last / skip.
|
|
1880
|
+
* Use for quantity-based imports where two rows of the same key mean "both" —
|
|
1881
|
+
* never for price imports, where summing prices would be nonsensical.
|
|
1882
|
+
*/
|
|
1883
|
+
mergeDuplicates?: boolean;
|
|
1884
|
+
}
|
|
1885
|
+
/**
|
|
1886
|
+
* Spreadsheet-style bulk entry surface: type, paste, or upload a CSV/TSV, with
|
|
1887
|
+
* automatic column auto-mapping (with a manual mapping fallback) and duplicate
|
|
1888
|
+
* de-duplication (keep-first/last/all/skip, or summed merge in `mergeDuplicates`
|
|
1889
|
+
* mode). Purely presentational — owns local grid/CSV state and reports the
|
|
1890
|
+
* resolved rows via `onImport`; it does no fetching, auth, or persistence.
|
|
1891
|
+
*
|
|
1892
|
+
* Rows are de-duplicated and merged on the FIRST column (the "key"). Declare a
|
|
1893
|
+
* column's `kind` (`price`/`qty`) to opt it into CSV auto-detection and the
|
|
1894
|
+
* totals strip.
|
|
1895
|
+
*/
|
|
1896
|
+
declare function BulkImportGrid({ columns, onImport, description, mergeDuplicates }: BulkImportGridProps): react_jsx_runtime.JSX.Element;
|
|
1897
|
+
|
|
1898
|
+
/**
|
|
1899
|
+
* Pure helpers for reconciling a bulk-import grid (rows of string cells) against
|
|
1900
|
+
* an existing items array, keyed by a designated "key column" (e.g. a part
|
|
1901
|
+
* number, SKU, or code). No app/framework dependencies — usable by any consumer
|
|
1902
|
+
* of {@link BulkImportGrid}.
|
|
1903
|
+
*
|
|
1904
|
+
* Merge rule: a blank cell means "don't touch the original" — only an explicit
|
|
1905
|
+
* value (including "0") replaces what's already there. Existing rows the user
|
|
1906
|
+
* didn't mention in the bulk grid are left untouched. Unknown keys are appended
|
|
1907
|
+
* via the caller-supplied `newItem` factory.
|
|
1908
|
+
*/
|
|
1909
|
+
type BulkRow = Record<string, string | undefined>;
|
|
1910
|
+
interface DuplicateGroup {
|
|
1911
|
+
/** Key value as it appeared in the first occurrence (trimmed, original casing). */
|
|
1912
|
+
key: string;
|
|
1913
|
+
/** Indices into the rows array where this key appears. Length is always >= 2. */
|
|
1914
|
+
rowIndices: number[];
|
|
1915
|
+
}
|
|
1916
|
+
/**
|
|
1917
|
+
* Find key values that appear more than once within a single bulk-import file.
|
|
1918
|
+
* Match is case-insensitive (consistent with {@link mergeBulkItems}). Blank key
|
|
1919
|
+
* cells are ignored. The caller is expected to surface these to the user before
|
|
1920
|
+
* merge so they can pick a per-group resolution.
|
|
1921
|
+
*/
|
|
1922
|
+
declare function findDuplicateKeys(rows: BulkRow[], keyRowKey: string): DuplicateGroup[];
|
|
1923
|
+
interface BaseItem {
|
|
1924
|
+
[key: string]: unknown;
|
|
1925
|
+
}
|
|
1926
|
+
interface MergeBulkResult<T> {
|
|
1927
|
+
merged: T[];
|
|
1928
|
+
importedCount: number;
|
|
1929
|
+
}
|
|
1930
|
+
interface MergeBulkOptions<T extends BaseItem> {
|
|
1931
|
+
/** Raw rows from BulkImportGrid. */
|
|
1932
|
+
rows: BulkRow[];
|
|
1933
|
+
/** Existing items array to merge into. */
|
|
1934
|
+
existing: T[];
|
|
1935
|
+
/**
|
|
1936
|
+
* Mapping from row column key → item field key.
|
|
1937
|
+
* MUST include the key-column mapping (e.g. `{ pn: 'part_number' }`).
|
|
1938
|
+
* Example: `{ pn: 'part_number', qty: 'quantity', price: 'unit_price' }`.
|
|
1939
|
+
*/
|
|
1940
|
+
fieldMap: Record<string, string>;
|
|
1941
|
+
/**
|
|
1942
|
+
* The item field that holds the merge key — the value `fieldMap` maps the
|
|
1943
|
+
* grid's key column onto. Existing items are matched against this field
|
|
1944
|
+
* (case-insensitive). Defaults to `'part_number'`.
|
|
1945
|
+
*/
|
|
1946
|
+
keyField?: string;
|
|
1947
|
+
/**
|
|
1948
|
+
* Build a brand-new item for a row whose key isn't in `existing`.
|
|
1949
|
+
* `filled` contains only the fields the user actually entered (mapped to
|
|
1950
|
+
* item-field keys via `fieldMap`); supply defaults for anything required
|
|
1951
|
+
* by your item shape.
|
|
1952
|
+
*/
|
|
1953
|
+
newItem: (filled: Partial<T>) => T;
|
|
1954
|
+
}
|
|
1955
|
+
declare function mergeBulkItems<T extends BaseItem>(options: MergeBulkOptions<T>): MergeBulkResult<T>;
|
|
1956
|
+
|
|
1957
|
+
interface UndoControlsProps {
|
|
1958
|
+
/** Extra classes for the wrapping row. */
|
|
1959
|
+
className?: string;
|
|
1960
|
+
/**
|
|
1961
|
+
* Set by the shell on the pair it mounts itself — the window footer's, or a
|
|
1962
|
+
* nested provider's. A form's own `<UndoControls />` leaves it unset and
|
|
1963
|
+
* claims the stack, and the shell then mounts nothing for it — so a form
|
|
1964
|
+
* written before the shell did this keeps its pair where it put it, and
|
|
1965
|
+
* shows one rather than two.
|
|
1966
|
+
*/
|
|
1967
|
+
auto?: boolean;
|
|
1968
|
+
}
|
|
1969
|
+
/**
|
|
1970
|
+
* Undo/Redo for the enclosing form, reading the stack from {@link UndoProvider}.
|
|
1971
|
+
*
|
|
1972
|
+
* The shell mounts this pair itself, in the window footer's left slot, for any
|
|
1973
|
+
* window whose form has registered state and whose user may edit it — a form
|
|
1974
|
+
* has nothing to add for the buttons to appear. Mount it by hand only for a
|
|
1975
|
+
* place the footer is not: a section header, a toolbar of a page with no
|
|
1976
|
+
* window footer. The keys work without it; this is for discoverability and for
|
|
1977
|
+
* the mouse. Both buttons stay rendered and go disabled, so the row does not
|
|
1978
|
+
* reflow the moment there is something to undo.
|
|
1979
|
+
*
|
|
1980
|
+
* Renders nothing for a user who may not edit the record — dead buttons on a
|
|
1981
|
+
* read-only form read as something broken rather than something withheld —
|
|
1982
|
+
* and nothing while the shell's own pair is showing for the same stack.
|
|
1983
|
+
*/
|
|
1984
|
+
declare function UndoControls({ className, auto }: UndoControlsProps): react_jsx_runtime.JSX.Element | null;
|
|
1985
|
+
|
|
1986
|
+
/** Result of a single health poll. `latencyMs` is optional; when omitted on
|
|
1987
|
+
* an ok result the indicator simply shows no latency for that poll. */
|
|
1988
|
+
interface HealthCheckResult {
|
|
1989
|
+
ok: boolean;
|
|
1990
|
+
latencyMs?: number;
|
|
1991
|
+
}
|
|
1992
|
+
/** Display fields for the signed-in user shown in the popover's Session
|
|
1993
|
+
* section. All optional — the indicator derives a sensible name and hides
|
|
1994
|
+
* the Role row when `role` is empty. Pass `null`/`undefined` for "not
|
|
1995
|
+
* signed in". */
|
|
1996
|
+
interface ServerStatusUser {
|
|
1997
|
+
name?: string;
|
|
1998
|
+
email?: string;
|
|
1999
|
+
/** Secondary label for the Role row (e.g. group names, portal, company).
|
|
2000
|
+
* Hidden when empty. */
|
|
2001
|
+
role?: string;
|
|
2002
|
+
}
|
|
2003
|
+
interface ServerStatusIndicatorProps {
|
|
2004
|
+
/**
|
|
2005
|
+
* Performs one health poll and resolves with the outcome. The shell calls
|
|
2006
|
+
* this on mount and on every interval tick. A custom implementation owns
|
|
2007
|
+
* its own timeout/abort. When omitted, the indicator polls `healthUrl`
|
|
2008
|
+
* (default `/api/health/`) with a `requestTimeoutMs` AbortController.
|
|
2009
|
+
*/
|
|
2010
|
+
healthCheck?: () => Promise<HealthCheckResult>;
|
|
2011
|
+
/** URL used by the built-in fetcher when no `healthCheck` is supplied. */
|
|
2012
|
+
healthUrl?: string;
|
|
2013
|
+
/** Poll interval in ms. Default 15 000. */
|
|
2014
|
+
pollMs?: number;
|
|
2015
|
+
/** Per-request timeout for the built-in fetcher, in ms. Default 5 000. */
|
|
2016
|
+
requestTimeoutMs?: number;
|
|
2017
|
+
/** Signed-in user's display fields for the Session section. */
|
|
2018
|
+
user?: ServerStatusUser | null;
|
|
2019
|
+
/** Build/version label for the Build section. Defaults to the shell's own
|
|
2020
|
+
* package version. Pass the consuming app's version here. */
|
|
2021
|
+
version?: string;
|
|
2022
|
+
}
|
|
2023
|
+
/**
|
|
2024
|
+
* System-tray globe that polls a health check and exposes connection details
|
|
2025
|
+
* in a click-to-open popover. Drop into a taskbar tray alongside the
|
|
2026
|
+
* notification bell; supply `healthCheck` (or `healthUrl`), `user`, and
|
|
2027
|
+
* `version` via a thin per-app wrapper.
|
|
2028
|
+
*/
|
|
2029
|
+
declare function ServerStatusIndicator({ healthCheck, healthUrl, pollMs, requestTimeoutMs, user, version, }: ServerStatusIndicatorProps): react_jsx_runtime.JSX.Element;
|
|
2030
|
+
|
|
2031
|
+
interface ChangePasswordFormProps {
|
|
2032
|
+
/**
|
|
2033
|
+
* Perform the password change. Resolve to indicate success; reject with an
|
|
2034
|
+
* `Error` whose `message` becomes the inline error shown to the user.
|
|
2035
|
+
*/
|
|
2036
|
+
onSubmit: (oldPassword: string, newPassword: string) => Promise<void>;
|
|
2037
|
+
/** Called after a successful change — on Done click, or immediately when
|
|
2038
|
+
* `confirmOnSuccess` is false. */
|
|
2039
|
+
onSuccess?: () => void;
|
|
2040
|
+
/**
|
|
2041
|
+
* Show a success confirmation screen with a Done button after a successful
|
|
2042
|
+
* change. When false, `onSuccess` fires immediately on success. Default true.
|
|
2043
|
+
*/
|
|
2044
|
+
confirmOnSuccess?: boolean;
|
|
2045
|
+
/** Minimum length required for the new password. Default 8. */
|
|
2046
|
+
minLength?: number;
|
|
2047
|
+
/** Label for the submit button. Default 'Change Password'. */
|
|
2048
|
+
submitLabel?: string;
|
|
2049
|
+
/** Label for the Done button on the success screen. Default 'Done'. */
|
|
2050
|
+
doneLabel?: string;
|
|
2051
|
+
}
|
|
2052
|
+
/**
|
|
2053
|
+
* Product-agnostic change-password form. The host supplies `onSubmit` to do the
|
|
2054
|
+
* actual change (e.g. call an API then re-authenticate).
|
|
2055
|
+
*/
|
|
2056
|
+
declare function ChangePasswordForm({ onSubmit, onSuccess, confirmOnSuccess, minLength, submitLabel, doneLabel, }: ChangePasswordFormProps): react_jsx_runtime.JSX.Element;
|
|
2057
|
+
|
|
2058
|
+
interface PdfActionButtonProps {
|
|
2059
|
+
/**
|
|
2060
|
+
* Resolves the PDF bytes. The consumer owns the transport (axios/fetch/etc.)
|
|
2061
|
+
* and any error reporting; returning `null` aborts the action without the
|
|
2062
|
+
* shell surfacing a toast (the consumer is expected to have explained the
|
|
2063
|
+
* failure already).
|
|
2064
|
+
*/
|
|
2065
|
+
fetchPdf: () => Promise<Blob | null>;
|
|
2066
|
+
/** Filename used for download and shown in the Preview window (e.g. "Invoice_CI#1234.pdf"). */
|
|
2067
|
+
filename: string;
|
|
2068
|
+
/** Button label. */
|
|
2069
|
+
label?: string;
|
|
2070
|
+
/** Override the default button styling. */
|
|
2071
|
+
className?: string;
|
|
2072
|
+
/** Disabled state. */
|
|
2073
|
+
disabled?: boolean;
|
|
2074
|
+
/**
|
|
2075
|
+
* Optional email handler. When supplied, a "Send by Email" item (with a
|
|
2076
|
+
* divider above it) is added to the menu and an Email button is wired into
|
|
2077
|
+
* the Preview window — both invoked with the already-fetched blob so the
|
|
2078
|
+
* consumer can hand it to its own composer. Omit it to hide email entirely.
|
|
2079
|
+
*/
|
|
2080
|
+
onEmail?: (blob: Blob) => void;
|
|
2081
|
+
/**
|
|
2082
|
+
* Optional notification fired after a Preview window has been opened with the
|
|
2083
|
+
* resolved PDF (e.g. to drop a Recent Documents shortcut). Receives the
|
|
2084
|
+
* filename so the consumer can label the entry.
|
|
2085
|
+
*/
|
|
2086
|
+
onPreviewOpened?: (filename: string) => void;
|
|
2087
|
+
/**
|
|
2088
|
+
* How to show the Preview. Defaults to the window manager's
|
|
2089
|
+
* `openPage('/preview')`; a routed app without windows (no
|
|
2090
|
+
* `WindowManagerProvider`, so that `openPage` does nothing) passes its own —
|
|
2091
|
+
* a tab, a route or an overlay that mounts the shell's `Preview`.
|
|
2092
|
+
*
|
|
2093
|
+
* It is called where `openPage` would be: after the "LOADING PDF"
|
|
2094
|
+
* placeholder is staged and before the fetch, so the viewer opens on the
|
|
2095
|
+
* placeholder and swaps the document in itself. `onPreviewOpened` is too
|
|
2096
|
+
* late for this: it fires once the document has arrived, so nothing would
|
|
2097
|
+
* show while it loads.
|
|
2098
|
+
*/
|
|
2099
|
+
openPreview?: () => void;
|
|
2100
|
+
/** Optional leading icon override for the button (defaults to a document glyph). */
|
|
2101
|
+
icon?: ReactNode;
|
|
2102
|
+
/**
|
|
2103
|
+
* How long to wait for `fetchPdf()` before giving up, in milliseconds.
|
|
2104
|
+
* Defaults to 30 seconds. Pass `0` (or `Infinity`) to wait for ever.
|
|
2105
|
+
*
|
|
2106
|
+
* This exists because a promise that never settles used to leave the Preview
|
|
2107
|
+
* window on its "LOADING PDF" placeholder indefinitely, so a slow document
|
|
2108
|
+
* and a dead one were indistinguishable to the person waiting. Timing out
|
|
2109
|
+
* turns that silence into a stated failure. The request itself is not
|
|
2110
|
+
* cancelled — the consumer owns the transport (see `withTimeout`).
|
|
2111
|
+
*/
|
|
2112
|
+
timeoutMs?: number;
|
|
2113
|
+
}
|
|
2114
|
+
/**
|
|
2115
|
+
* A dropdown PDF button that can Preview the document in the shell's Preview
|
|
2116
|
+
* window, Download it, or (when `onEmail` is supplied) hand the bytes to a
|
|
2117
|
+
* consumer-provided email composer.
|
|
2118
|
+
*
|
|
2119
|
+
* The shell is transport-agnostic: it never fetches the PDF itself. The
|
|
2120
|
+
* consumer injects a `fetchPdf()` resolver (typically wrapping its own HTTP
|
|
2121
|
+
* client) and the shell handles object-URL lifecycle, download, the loading
|
|
2122
|
+
* placeholder in Preview, and success toasts. App concerns (email composer,
|
|
2123
|
+
* recent-documents logging, where the Preview opens) are lifted to the
|
|
2124
|
+
* optional `onEmail` / `onPreviewOpened` / `openPreview` callbacks.
|
|
2125
|
+
*/
|
|
2126
|
+
declare function PdfActionButton({ fetchPdf, filename, label, className, disabled, onEmail, onPreviewOpened, openPreview, icon, timeoutMs, }: PdfActionButtonProps): react_jsx_runtime.JSX.Element;
|
|
2127
|
+
|
|
2128
|
+
interface ShellAuth {
|
|
2129
|
+
/** Returns true if the current user has any of the listed permission codes. */
|
|
2130
|
+
hasAnyPerm: (perms: string[]) => boolean;
|
|
2131
|
+
}
|
|
2132
|
+
declare function ShellAuthProvider({ value, children }: {
|
|
2133
|
+
value: ShellAuth;
|
|
2134
|
+
children: ReactNode;
|
|
2135
|
+
}): react_jsx_runtime.JSX.Element;
|
|
2136
|
+
declare function useShellAuth(): ShellAuth;
|
|
2137
|
+
|
|
2138
|
+
type EntityFetcher = (endpoint: string, id: string) => Promise<any>;
|
|
2139
|
+
declare function ShellEntityFetcherProvider({ value, children, }: {
|
|
2140
|
+
value: EntityFetcher;
|
|
2141
|
+
children: ReactNode;
|
|
2142
|
+
}): react_jsx_runtime.JSX.Element;
|
|
2143
|
+
/** Returns the consumer-supplied entity fetcher, or a stub that throws
|
|
2144
|
+
* on call. Components that fetch entities call this hook eagerly so any
|
|
2145
|
+
* misconfiguration surfaces synchronously rather than at click-time. */
|
|
2146
|
+
declare function useShellEntityFetcher(): EntityFetcher;
|
|
2147
|
+
|
|
2148
|
+
/**
|
|
2149
|
+
* INTERNAL stub — bridges legacy `apiClient.get/patch('/auth/me/', ...)` calls
|
|
2150
|
+
* inside the shell to the new <ShellPrefsProvider> contract. This keeps the
|
|
2151
|
+
* package's copied source compiling without rewriting every call site at
|
|
2152
|
+
* once; consumer-supplied prefs flow through `<ShellPrefsProvider>`.
|
|
2153
|
+
*
|
|
2154
|
+
* Outside calls (anything not hitting `/auth/me/`) need the consumer to mount
|
|
2155
|
+
* <ShellEntityFetcherProvider> and wire `entityFetcher` on <WindowManager>.
|
|
2156
|
+
*
|
|
2157
|
+
* When `setShellApiClient` has not been called, HTTP methods resolve with an
|
|
2158
|
+
* empty payload instead of throwing — so consumers without a backend (the
|
|
2159
|
+
* Pages demo, for instance) can still mount the shell. A one-time console
|
|
2160
|
+
* warning surfaces the missing wiring without crashing the app.
|
|
2161
|
+
*
|
|
2162
|
+
* Long-term this file is removed when each call site migrates to
|
|
2163
|
+
* `useShellPrefs()` directly.
|
|
2164
|
+
*/
|
|
2165
|
+
|
|
2166
|
+
/** Consumer registers their own axios instance — typically the same one
|
|
2167
|
+
* they use elsewhere. The package only delegates HTTP to this. */
|
|
2168
|
+
declare function setShellApiClient(instance: AxiosInstance): void;
|
|
2169
|
+
|
|
2170
|
+
/**
|
|
2171
|
+
* Shared task data model — used by the TodoList app, the Pomodoro widget,
|
|
2172
|
+
* and the Calendar app. Stored in `shellPrefs.todo_tasks` (a single array)
|
|
2173
|
+
* so all three views read/write the same source of truth.
|
|
2174
|
+
*/
|
|
2175
|
+
interface TodoTask {
|
|
2176
|
+
/** Local UUID — stable across renames, used by Pomodoro for active-task pinning. */
|
|
2177
|
+
id: string;
|
|
2178
|
+
name: string;
|
|
2179
|
+
done: boolean;
|
|
2180
|
+
/** Optional deadline, formatted YYYY-MM-DD so it sorts as a string. Drives the Calendar badge. */
|
|
2181
|
+
dueDate?: string;
|
|
2182
|
+
/** Pomodoro estimate (count of focus blocks the user expects to spend). */
|
|
2183
|
+
estimated?: number;
|
|
2184
|
+
/** Pomodoro count completed against this task. */
|
|
2185
|
+
completed?: number;
|
|
2186
|
+
/** Free-text notes — surfaced in the Todo edit drawer. */
|
|
2187
|
+
notes?: string;
|
|
2188
|
+
/** Origin of the task — e.g. 'crm' for tasks backed by a CRM deal/contact. Set by a TodoProvider. */
|
|
2189
|
+
source?: string;
|
|
2190
|
+
/** Short context label shown as a badge (e.g. "Deal: Acme", "Contact: Jane"). Set by a TodoProvider. */
|
|
2191
|
+
contextLabel?: string;
|
|
2192
|
+
/** ISO timestamp — useful for "recently added" sorting. */
|
|
2193
|
+
createdAt: string;
|
|
2194
|
+
/** ISO timestamp — bumped on every local mutation. */
|
|
2195
|
+
updatedAt: string;
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
/**
|
|
2199
|
+
* A consumer-supplied task backend. Registering one (opt-in) routes every
|
|
2200
|
+
* shell task surface through it instead of the prefs blob. Methods map to/from
|
|
2201
|
+
* the shell's `TodoTask` shape, including the optional `source`/`contextLabel`.
|
|
2202
|
+
*/
|
|
2203
|
+
interface TodoProvider {
|
|
2204
|
+
list(): Promise<TodoTask[]>;
|
|
2205
|
+
create(input: Partial<Omit<TodoTask, 'id' | 'createdAt' | 'updatedAt'>> & {
|
|
2206
|
+
name: string;
|
|
2207
|
+
}): Promise<TodoTask>;
|
|
2208
|
+
update(id: string, patch: Partial<TodoTask>): Promise<TodoTask>;
|
|
2209
|
+
remove(id: string): Promise<void>;
|
|
2210
|
+
}
|
|
2211
|
+
/** Register (or clear with `null`) the backend task provider. Opt-in. */
|
|
2212
|
+
declare function setShellTodoProvider(next: TodoProvider | null): void;
|
|
2213
|
+
|
|
2214
|
+
interface ShellUser {
|
|
2215
|
+
email?: string;
|
|
2216
|
+
first_name?: string;
|
|
2217
|
+
last_name?: string;
|
|
2218
|
+
avatar_url?: string;
|
|
2219
|
+
}
|
|
2220
|
+
/** Consumer registers user-identity + logout handler once at app startup. */
|
|
2221
|
+
declare function setShellAuthBridge(bridge: {
|
|
2222
|
+
user?: ShellUser;
|
|
2223
|
+
logout?: () => void;
|
|
2224
|
+
}): void;
|
|
2225
|
+
|
|
2226
|
+
/**
|
|
2227
|
+
* Registers Alt+Shift+N (Windows) / Opt+Shift+N (Mac) hotkey to trigger a "New" action.
|
|
2228
|
+
* Uses e.code (physical key) to avoid locale/modifier character issues.
|
|
2229
|
+
* Only fires for the active (frontmost) window, mirroring useEditHotkey — without
|
|
2230
|
+
* the guard every mounted window's hook answered one keypress at once, so with
|
|
2231
|
+
* two or more list windows open Alt+Shift+N fired "New" in all of them.
|
|
2232
|
+
*/
|
|
2233
|
+
declare function useNewHotkey(callback: () => void): void;
|
|
2234
|
+
|
|
2235
|
+
/**
|
|
2236
|
+
* Registers Alt+Shift+E (Windows) / Opt+Shift+E (Mac) hotkey to trigger an "Edit" action.
|
|
2237
|
+
* Uses e.code (physical key) to avoid locale/modifier character issues.
|
|
2238
|
+
* Only fires for the active (frontmost) modal.
|
|
2239
|
+
*/
|
|
2240
|
+
declare function useEditHotkey(callback: (() => void) | null): void;
|
|
2241
|
+
|
|
2242
|
+
/**
|
|
2243
|
+
* Registers a handler for the Cmd+S "save and stay" event dispatched by Modal.
|
|
2244
|
+
* The callback should save the form data WITHOUT closing the modal.
|
|
2245
|
+
*
|
|
2246
|
+
* Fires ONLY for the window the keystroke was aimed at. Modal restricts the
|
|
2247
|
+
* dispatch to the topmost modal, but the event goes to `document`, so without
|
|
2248
|
+
* this match every mounted window's hook answered one keypress — and with two
|
|
2249
|
+
* forms open, a single Cmd+S saved both. Depending on each form's mode that
|
|
2250
|
+
* either PATCHed a background record or CREATEd a brand new one, each with its
|
|
2251
|
+
* own success toast, so the user saw the write and misread which window it came
|
|
2252
|
+
* from.
|
|
2253
|
+
*
|
|
2254
|
+
* The match is on the originating modal's id rather than on
|
|
2255
|
+
* {@link useModalActive}, which asks a different question: "is the frontmost
|
|
2256
|
+
* modal mine". Every nested dialog a form opens pushes itself onto the
|
|
2257
|
+
* activation order, so a form with a child dialog open would read as inactive
|
|
2258
|
+
* and Cmd+S would silently do nothing where it used to save.
|
|
2259
|
+
*
|
|
2260
|
+
* An event with no `detail.modalId` is treated as "for me", so a consumer on a
|
|
2261
|
+
* newer package than the shell that dispatched it keeps working exactly as
|
|
2262
|
+
* before rather than losing Cmd+S entirely.
|
|
2263
|
+
*/
|
|
2264
|
+
declare function useModalSave(callback: (() => void) | null): void;
|
|
2265
|
+
|
|
2266
|
+
/**
|
|
2267
|
+
* Registers a handler for the Alt+Shift+D "save as new / duplicate" event
|
|
2268
|
+
* dispatched by Modal. The callback should save the form data as a NEW record
|
|
2269
|
+
* (ignoring the current ID).
|
|
2270
|
+
*
|
|
2271
|
+
* Window-scoped for the same reason as {@link useModalSave}, and the stakes are
|
|
2272
|
+
* higher here: an unscoped duplicate created a second record from every open
|
|
2273
|
+
* form at once, and one consumer closes its window on success, so a background
|
|
2274
|
+
* window vanished as well.
|
|
2275
|
+
*/
|
|
2276
|
+
declare function useModalDuplicate(callback: (() => void) | null): void;
|
|
2277
|
+
|
|
2278
|
+
/**
|
|
2279
|
+
* Undo/redo for one open form — the pure half.
|
|
2280
|
+
*
|
|
2281
|
+
* A form window owns a single stack covering everything in it: its fields, its
|
|
2282
|
+
* line-items table, a bulk import. One ⌘Z steps back the last thing the user
|
|
2283
|
+
* did, whatever part of the form it happened in.
|
|
2284
|
+
*
|
|
2285
|
+
* The stack holds snapshots, not diffs. A step stores the value of every
|
|
2286
|
+
* registered slice **as it was before the change**, so undoing restores the
|
|
2287
|
+
* whole form to a coherent moment rather than replaying one slice out of step
|
|
2288
|
+
* with the others. Forms are small — a few dozen fields and some line items —
|
|
2289
|
+
* so whole-form snapshots stay cheap and the logic stays honest.
|
|
2290
|
+
*
|
|
2291
|
+
* Scope is the unsaved edit. History lives with the open window and dies with
|
|
2292
|
+
* it; nothing here reverses a saved record.
|
|
2293
|
+
*/
|
|
2294
|
+
/** Registered slice id → that slice's value at the time of the snapshot. */
|
|
2295
|
+
type UndoSnapshot = Record<string, unknown>;
|
|
2296
|
+
interface UndoStep {
|
|
2297
|
+
/** The form as it stood before this change. Undo restores exactly this. */
|
|
2298
|
+
values: UndoSnapshot;
|
|
2299
|
+
/** What the step was, for a button title: `"qty"`, `"import of 5 lines"`. */
|
|
2300
|
+
label: string;
|
|
2301
|
+
/**
|
|
2302
|
+
* Consecutive changes sharing a non-null key fold into one step — typing in
|
|
2303
|
+
* a field is one Undo, not one per keystroke. Null never folds.
|
|
2304
|
+
*/
|
|
2305
|
+
coalesceKey: string | null;
|
|
2306
|
+
}
|
|
2307
|
+
interface UndoState {
|
|
2308
|
+
past: UndoStep[];
|
|
2309
|
+
future: UndoStep[];
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
interface UndoProviderProps {
|
|
2313
|
+
children: React.ReactNode;
|
|
2314
|
+
/**
|
|
2315
|
+
* Whether this user may edit the record. Undo is offered to everyone who
|
|
2316
|
+
* can — it is not gated on role or seniority — and withheld from a reader
|
|
2317
|
+
* only because they have nothing to take back. Defaults to true.
|
|
2318
|
+
*/
|
|
2319
|
+
canEdit?: boolean;
|
|
2320
|
+
/**
|
|
2321
|
+
* Permission codes that count as "may edit", checked through
|
|
2322
|
+
* `ShellAuthProvider`. Combined with `canEdit`, so a form that already knows
|
|
2323
|
+
* it is read-only stays read-only whatever the codes say. Omit to rely on
|
|
2324
|
+
* `canEdit` alone.
|
|
2325
|
+
*/
|
|
2326
|
+
perms?: string[];
|
|
2327
|
+
/**
|
|
2328
|
+
* The window this stack belongs to — the same stable key its `<Modal>` gets
|
|
2329
|
+
* as `windowKey`, which for a `WindowManager` window is `item.id`.
|
|
2330
|
+
*
|
|
2331
|
+
* This is what keeps ⌘Z inside the window the user is looking at. The
|
|
2332
|
+
* provider binds its hotkey on `window`, so every open window's provider sees
|
|
2333
|
+
* every keypress; the id is how one of them recognises the press as its own.
|
|
2334
|
+
* Without it there is no per-window identity to test and the best available
|
|
2335
|
+
* answer is "is any window active", which is true in all of them at once.
|
|
2336
|
+
*
|
|
2337
|
+
* Omit it for a provider nested inside a `<Modal>` — there the enclosing
|
|
2338
|
+
* modal's own context answers the question — or where there are no windows
|
|
2339
|
+
* at all.
|
|
2340
|
+
*/
|
|
2341
|
+
windowId?: string;
|
|
2342
|
+
}
|
|
2343
|
+
/**
|
|
2344
|
+
* Undo/redo for everything in one open form.
|
|
2345
|
+
*
|
|
2346
|
+
* Wrap a form window in it, register each piece of its state with
|
|
2347
|
+
* {@link useUndoable}, and the whole form shares one stack: a field edit, a
|
|
2348
|
+
* line added, a bulk import are all steps in the same history, undone newest
|
|
2349
|
+
* first. Two open windows have two independent stacks, and passing
|
|
2350
|
+
* {@link UndoProviderProps.windowId} is what keeps them independent in
|
|
2351
|
+
* practice — see that prop.
|
|
2352
|
+
*
|
|
2353
|
+
* History is the unsaved edit only. It lives with the mounted provider and
|
|
2354
|
+
* dies with it. Both ends of the edit are the caller's to mark: `baseline()`
|
|
2355
|
+
* once the record has arrived, so the load is the starting point rather than
|
|
2356
|
+
* the first thing ⌘Z takes back, and `clear()` at a save, past which "earlier"
|
|
2357
|
+
* is on the server and not something a form can reach.
|
|
2358
|
+
*
|
|
2359
|
+
* Anyone who may edit the record gets it — it is not a privileged feature, and
|
|
2360
|
+
* the user most helped by an undo is the one least sure of what they just did.
|
|
2361
|
+
* A reader is the only one it is withheld from, and only because there is
|
|
2362
|
+
* nothing for them to take back. Note that `canEdit` is the caller's claim, and
|
|
2363
|
+
* the shell-level provider `WindowManager` mounts around every window has no
|
|
2364
|
+
* way to make it: it knows nothing about the record inside. That one defaults
|
|
2365
|
+
* to enabled, and a read-only form states the fact by nesting its own
|
|
2366
|
+
* `<UndoProvider canEdit={false}>`, which shadows it.
|
|
2367
|
+
*/
|
|
2368
|
+
declare function UndoProvider({ children, canEdit, perms, windowId }: UndoProviderProps): react_jsx_runtime.JSX.Element;
|
|
2369
|
+
interface UndoControlsApi {
|
|
2370
|
+
undo: () => void;
|
|
2371
|
+
redo: () => void;
|
|
2372
|
+
canUndo: boolean;
|
|
2373
|
+
canRedo: boolean;
|
|
2374
|
+
/** What Undo/Redo would act on, for a button title. Null when unavailable. */
|
|
2375
|
+
undoLabel: string | null;
|
|
2376
|
+
redoLabel: string | null;
|
|
2377
|
+
/** End the history — call after a successful save. */
|
|
2378
|
+
clear: () => void;
|
|
2379
|
+
/**
|
|
2380
|
+
* Make the form as it stands the starting point, discarding the history.
|
|
2381
|
+
*
|
|
2382
|
+
* Call it once the record has arrived from the server. State filled from a
|
|
2383
|
+
* fetch is a change like any other as far as the slices are concerned, so
|
|
2384
|
+
* without this the load itself is the oldest step and the user's first ⌘Z
|
|
2385
|
+
* empties the form they were just given:
|
|
2386
|
+
*
|
|
2387
|
+
* const { data } = useQuery(...);
|
|
2388
|
+
* const { baseline } = useUndo();
|
|
2389
|
+
* useEffect(() => { if (data) baseline(); }, [data, baseline]);
|
|
2390
|
+
*
|
|
2391
|
+
* Worth calling on every arrival, not just the first: a window kept open
|
|
2392
|
+
* across a refetch, or one whose entity changes underneath it, wants the
|
|
2393
|
+
* same treatment, and the call is idempotent for a form nobody has touched.
|
|
2394
|
+
*
|
|
2395
|
+
* A history is only ever dropped when a record actually lands (a slice takes
|
|
2396
|
+
* a value while the baseline settles); a call on a refetch edge where the
|
|
2397
|
+
* form's guard re-seeds nothing leaves the user's edits undoable. Name the
|
|
2398
|
+
* record — `baseline(id)` — and a switch to another record drops the history
|
|
2399
|
+
* even when the new record's values happen to equal the old, so an undo can
|
|
2400
|
+
* never land on the wrong record.
|
|
2401
|
+
*
|
|
2402
|
+
* Name it from the first load on. The first named call has no earlier name
|
|
2403
|
+
* to compare with, so it never drops the history by itself: a switch away
|
|
2404
|
+
* from a record that was baselined bare keeps its history. And a record with
|
|
2405
|
+
* no id yet passes `null`, not `undefined` — `baseline(undefined)` is a bare
|
|
2406
|
+
* call, so `baseline(record?.id)` loses the switch guard on a create route;
|
|
2407
|
+
* write `baseline(record?.id ?? null)`.
|
|
2408
|
+
*/
|
|
2409
|
+
baseline: (key?: string | number | null) => void;
|
|
2410
|
+
/** False when the user may not edit this record, so custom UI can hide
|
|
2411
|
+
* itself the way `UndoControls` does. */
|
|
2412
|
+
enabled: boolean;
|
|
2413
|
+
/** True once the form has registered any state — the window has something
|
|
2414
|
+
* an Undo could act on, and the shell shows the controls for it. */
|
|
2415
|
+
hasState: boolean;
|
|
2416
|
+
}
|
|
2417
|
+
/**
|
|
2418
|
+
* The enclosing form's undo stack, for custom UI or to `clear()` on save.
|
|
2419
|
+
* Everything is inert outside an {@link UndoProvider}.
|
|
2420
|
+
*/
|
|
2421
|
+
declare function useUndo(): UndoControlsApi;
|
|
2422
|
+
/**
|
|
2423
|
+
* Tell the enclosing stack whether this record may be edited right now.
|
|
2424
|
+
*
|
|
2425
|
+
* `enabled` is otherwise the provider's own claim (`canEdit`, `perms`), and
|
|
2426
|
+
* the provider `WindowManager` mounts around every window makes none — it
|
|
2427
|
+
* knows nothing about the record inside. A form that does know says so here:
|
|
2428
|
+
*
|
|
2429
|
+
* useUndoCanEdit(!isLocked);
|
|
2430
|
+
*
|
|
2431
|
+
* and while the value is false the stack records nothing, ⌘Z is left to the
|
|
2432
|
+
* browser, and the footer shows no controls. A nested
|
|
2433
|
+
* `<UndoProvider canEdit={false}>` in the form's JSX says the same thing: it
|
|
2434
|
+
* shadows the stack for its children and forwards the read-only claim to the
|
|
2435
|
+
* stack above it — the one the form's own `useUndoableState` calls, made above
|
|
2436
|
+
* it in the tree, register with. Either form of the claim makes the whole
|
|
2437
|
+
* window read-only; the hook just needs no extra element. `useUndoCanEdit(true)`
|
|
2438
|
+
* is the positive claim: a window that edits in place outside Edit mode asks
|
|
2439
|
+
* the footer for the pair with it.
|
|
2440
|
+
*/
|
|
2441
|
+
declare function useUndoCanEdit(canEdit: boolean): void;
|
|
2442
|
+
interface UndoableOptions {
|
|
2443
|
+
/** Names the step in a button title: `"qty"`, `"line items"`. */
|
|
2444
|
+
label: string;
|
|
2445
|
+
/**
|
|
2446
|
+
* Consecutive changes sharing a key fold into one step — pass the field name
|
|
2447
|
+
* so a run of typing is one Undo rather than one per keystroke. Omit for a
|
|
2448
|
+
* change that is already whole, like a bulk import or a deleted row.
|
|
2449
|
+
*/
|
|
2450
|
+
coalesceKey?: string | null;
|
|
2451
|
+
}
|
|
2452
|
+
/**
|
|
2453
|
+
* Put one piece of the form's state under the window's undo stack.
|
|
2454
|
+
*
|
|
2455
|
+
* `value` is watched; when it changes, the form as it stood beforehand becomes
|
|
2456
|
+
* a step. `apply` puts a value back — the same setter the form already uses.
|
|
2457
|
+
*
|
|
2458
|
+
* const [items, setItems] = useState<Line[]>([]);
|
|
2459
|
+
* useUndoable(items, setItems, { label: 'line items' });
|
|
2460
|
+
*
|
|
2461
|
+
* Registering more than one slice is the point: a step snapshots all of them
|
|
2462
|
+
* together, so undoing restores a coherent form rather than one slice out of
|
|
2463
|
+
* step with the rest.
|
|
2464
|
+
*
|
|
2465
|
+
* `value` must be the state itself, not something derived from it on the way
|
|
2466
|
+
* in. A change is detected by identity, so `useUndoable(rows.filter(r => r.on),
|
|
2467
|
+
* ...)` hands over a new array on every render and reads as a change every
|
|
2468
|
+
* time — and since recording a step re-renders the provider, that closes a
|
|
2469
|
+
* loop at render speed. The provider trips a runaway guard and says so rather
|
|
2470
|
+
* than letting the tab hang, but the fix is at the call site: register the
|
|
2471
|
+
* state, and derive from it afterwards.
|
|
2472
|
+
*/
|
|
2473
|
+
declare function useUndoable<T>(value: T, apply: (next: T) => void, opts: UndoableOptions): void;
|
|
2474
|
+
/**
|
|
2475
|
+
* `useState`, with the value in the window's undo stack.
|
|
2476
|
+
*
|
|
2477
|
+
* Written to be a rename rather than an extra line, because the forms this is
|
|
2478
|
+
* for hold their state in dozens of separate `useState` calls — one of them has
|
|
2479
|
+
* forty-three. Adopting a form is then a per-line edit:
|
|
2480
|
+
*
|
|
2481
|
+
* const [supplier, setSupplier] = useState('');
|
|
2482
|
+
* const [supplier, setSupplier] = useUndoableState('', { label: 'supplier' });
|
|
2483
|
+
*
|
|
2484
|
+
* Which also makes the choice legible: state left as plain `useState` is state
|
|
2485
|
+
* deliberately kept out of the history. Keep it that way for anything that is
|
|
2486
|
+
* not the user's input — a search box, fetched data, validation output, an
|
|
2487
|
+
* initialisation guard. Undoing those puts stale results back on screen, and a
|
|
2488
|
+
* reverted guard can re-fire the effect it exists to suppress.
|
|
2489
|
+
*
|
|
2490
|
+
* Pass `coalesceKey` for anything typed into, so a run of keystrokes is one
|
|
2491
|
+
* Undo; the field's own name is the obvious key.
|
|
2492
|
+
*/
|
|
2493
|
+
declare function useUndoableState<T>(initial: T | (() => T), opts: UndoableOptions): [T, React.Dispatch<React.SetStateAction<T>>];
|
|
2494
|
+
|
|
2495
|
+
export { type ActivityKind, type AsyncEnvironment, BLOCKED_PCT_CPU, type BatteryInfo, BehaviorPanel, type BottleneckKind, type BulkColumn, type BulkColumnKind, BulkImportGrid, type BulkImportGridProps, type BulkRow, CancelButton, ChangePasswordForm, type ChangePasswordFormProps, type ChangelogEntry, type ClockCalendarConfig, ColumnDef, CopyButton, Customization, type CustomizationOmitSection, type CustomizationProps, type CustomizationSection, DEV_BANNER_TEXT, Desktop, type DesktopContextMenuItem, type DesktopHostConfig, DesktopHostProvider, DevIndicator, DocFavStar, type DuplicateGroup, type EntityFetcher, EntityList, type EntityListColumn, type EntityListContextAction, type EntityListProps, EntityWindowFallback, type EntityWindowFallbackProps, EntityWindowLoading, EntityWindowState, type EntityWindowStateProps, FilterBar, type FilterOption, type FpsGroup, type HealthCheckResult, ImageAnnotator, type ImageAnnotatorHandle, type ImageAnnotatorProps, LOG_CAP, Layout, type LayoutProps, type LogSummary, MIN_EVENT_SAMPLES, MIN_GROUP_SAMPLES, MODAL_SUBMIT_SELECTOR, type MenuLayer, type MergeBulkOptions, type MergeBulkResult, type MobileAppConfig, Modal, ModalActions, NATIVE_MENU_ATTR, NotificationBell, type NotificationsConfig, PaginatedResponse, PdfActionButton, type PdfActionButtonProps, type PerfEnvironment, type PerfLogRecord, type PerfReading, type PerfReport, PerfStats, type PerfStatsProps, type PlatformHints, ResizableTable, SMOOTH_FPS, SearchConfig, ServerStatusIndicator, type ServerStatusIndicatorProps, type ServerStatusUser, type ShellAuth, ShellAuthProvider, type ShellBranding, type ShellContextKind, ShellContextMenu, type ShellContextMenuProps, type ShellContextTarget, ShellEntityFetcherProvider, type ShellNotification, ShortcutHelp, SortState, type SortableColumn, SoundsPanel, type StagedTarget, StartMenu, type StickyEntityRef, type StickyResolver, SystemPreferences, type SystemPreferencesProps, type SystemPreferencesSection, type TodoProvider, type TodoTask, UndoControls, type UndoControlsApi, type UndoControlsProps, UndoProvider, type UndoProviderProps, type UndoSnapshot, type UndoState, type UndoStep, type UndoableOptions, type Verdict, WidgetManager, WindowCrashedFallback, WindowErrorBoundary, WindowManagerProvider, WindowRegistry, type WindowTarget, type WindowTargetOptions, WindowTitle, appendRecord as appendPerfRecord, applyDevTitle, beginWindowGesture, classifyActivity, classify as classifyPerf, commitExposeHighlight, copyToClipboard, createWindowRegistry, createWindowTarget, describeContextTarget, describeMachine, describePerfReport, exitExposeMode, findDuplicateKeys, getActiveWindowRoute, getExposeHighlight, getWindowPosition, isDevEnv, isInteracting, keepsNativeMenu, markMenuOpen, mergeBulkItems, toCsv as perfLogToCsv, perfReportFile, readEnvironment as readPerfEnvironment, registerModalEscapeInterceptor, requestAsyncEnvironment, requestWindowFront, setExposeHighlight, setShellApiClient, setShellAuthBridge, setShellNavIcons, setShellTodoProvider, setWindowDefaultPosition, setWindowPosition, subscribeExposeHighlight, summariseFrames, summariseLog as summarisePerfLog, toggleExposeMode, useColumnConfig, useDesktopHost, useEditHotkey, useEnclosingModalId, useFilters, useInfiniteScroll, useIsActiveWindow, useModalActive, useModalDuplicate, useModalSave, useNewHotkey, useShellAuth, useShellEntityFetcher, useSort, useTableNav, useUndo, useUndoCanEdit, useUndoable, useUndoableState, useWidgetSettings, useWindowDirty, useWindowManager, useWindowMenuItem, useWindowTarget, useWindowTitle };
|