@ahrowe/ui 0.40.0 → 0.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +13 -13
  2. package/dist/cjs-types/common/ahroweProvider/ahroweProvider.d.ts +4 -0
  3. package/dist/cjs-types/common/ahroweProvider/ahroweProvider.types.d.ts +12 -0
  4. package/dist/cjs-types/common/ahroweProvider/index.d.ts +2 -0
  5. package/dist/cjs-types/common/bodyEnd/portalHostContext.d.ts +6 -0
  6. package/dist/cjs-types/common/errorBoundary/errorBoundary.types.d.ts +2 -2
  7. package/dist/cjs-types/common/themeProvider/index.d.ts +1 -1
  8. package/dist/cjs-types/common/themeProvider/theme.types.d.ts +8 -0
  9. package/dist/cjs-types/common/themeProvider/themeProvider.d.ts +7 -9
  10. package/dist/cjs-types/common/themeProvider/themeVariablesContext.d.ts +2 -2
  11. package/dist/cjs-types/common/toast/index.d.ts +1 -2
  12. package/dist/cjs-types/common/toast/toast.types.d.ts +4 -4
  13. package/dist/cjs-types/common/toast/toastHost.d.ts +4 -0
  14. package/dist/cjs-types/common/toast/toastService.d.ts +1 -1
  15. package/dist/cjs-types/index.d.ts +2 -1
  16. package/dist/esm/common/ahroweProvider/ahroweProvider.mjs +2 -0
  17. package/dist/esm/common/ahroweProvider/ahroweProvider.mjs.map +1 -0
  18. package/dist/esm/common/bodyEnd/bodyEnd.mjs +1 -1
  19. package/dist/esm/common/bodyEnd/bodyEnd.mjs.map +1 -1
  20. package/dist/esm/common/bodyEnd/portalHostContext.mjs +2 -0
  21. package/dist/esm/common/bodyEnd/portalHostContext.mjs.map +1 -0
  22. package/dist/esm/common/carousel/carousel.module.mjs.map +1 -1
  23. package/dist/esm/common/errorBoundary/errorBoundary.mjs +2 -2
  24. package/dist/esm/common/hooks/useAnchorTracking.mjs.map +1 -1
  25. package/dist/esm/common/scrollbarProvider/scrollbarProvider.module.mjs.map +1 -1
  26. package/dist/esm/common/themeProvider/themeProvider.mjs +1 -1
  27. package/dist/esm/common/themeProvider/themeProvider.mjs.map +1 -1
  28. package/dist/esm/common/themeProvider/themeVariablesContext.mjs.map +1 -1
  29. package/dist/esm/common/toast/toastHost.mjs +2 -0
  30. package/dist/esm/common/toast/toastHost.mjs.map +1 -0
  31. package/dist/esm/common/toast/toastService.mjs +1 -1
  32. package/dist/esm/common/toast/toastService.mjs.map +1 -1
  33. package/dist/esm/common/tooltip/tooltip.module.mjs.map +1 -1
  34. package/dist/esm/index.mjs +1 -1
  35. package/dist/index.cjs +3 -3
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/mcp.js +2 -2
  38. package/dist/style.css +1 -1
  39. package/dist/types/common/ahroweProvider/ahroweProvider.d.ts +4 -0
  40. package/dist/types/common/ahroweProvider/ahroweProvider.types.d.ts +12 -0
  41. package/dist/types/common/ahroweProvider/index.d.ts +2 -0
  42. package/dist/types/common/bodyEnd/portalHostContext.d.ts +6 -0
  43. package/dist/types/common/errorBoundary/errorBoundary.types.d.ts +2 -2
  44. package/dist/types/common/themeProvider/index.d.ts +1 -1
  45. package/dist/types/common/themeProvider/theme.types.d.ts +8 -0
  46. package/dist/types/common/themeProvider/themeProvider.d.ts +7 -9
  47. package/dist/types/common/themeProvider/themeVariablesContext.d.ts +2 -2
  48. package/dist/types/common/toast/index.d.ts +1 -2
  49. package/dist/types/common/toast/toast.types.d.ts +4 -4
  50. package/dist/types/common/toast/toastHost.d.ts +4 -0
  51. package/dist/types/common/toast/toastService.d.ts +1 -1
  52. package/dist/types/index.d.ts +2 -1
  53. package/docs/AhroweProvider.md +90 -0
  54. package/docs/BodyEnd.md +5 -12
  55. package/docs/Breadcrumb.md +0 -2
  56. package/docs/CLAUDE.md +14 -11
  57. package/docs/CommandPalette.md +0 -2
  58. package/docs/ConfigProvider.md +11 -8
  59. package/docs/ConfirmModal.md +0 -2
  60. package/docs/ErrorBoundary.md +5 -3
  61. package/docs/FormValidatorGroup.md +1 -1
  62. package/docs/KlipyPicker.md +1 -1
  63. package/docs/Menu.md +0 -2
  64. package/docs/Modal.md +0 -2
  65. package/docs/MultiSelect.md +0 -2
  66. package/docs/OtpInput.md +0 -2
  67. package/docs/Popover.md +0 -2
  68. package/docs/RadioGroup.md +1 -1
  69. package/docs/ScrollbarProvider.md +4 -0
  70. package/docs/SplitButton.md +0 -2
  71. package/docs/ThemeProvider.md +13 -10
  72. package/docs/Toast.md +18 -17
  73. package/docs/Tooltip.md +1 -1
  74. package/docs/VirtualList.md +0 -2
  75. package/package.json +1 -1
  76. package/dist/cjs-types/common/toast/toastProvider.d.ts +0 -8
  77. package/dist/esm/common/toast/toastProvider.mjs +0 -2
  78. package/dist/esm/common/toast/toastProvider.mjs.map +0 -1
  79. package/dist/types/common/toast/toastProvider.d.ts +0 -8
package/README.md CHANGED
@@ -14,22 +14,21 @@ npm install @ahrowe/ui
14
14
 
15
15
  ```tsx
16
16
  import '@ahrowe/ui/style.css';
17
- import { ThemeProvider } from '@ahrowe/ui';
17
+ import { AhroweProvider } from '@ahrowe/ui';
18
18
 
19
- function App() {
19
+ function Root() {
20
20
  return (
21
- <ThemeProvider>
22
- App content here
23
- {/* Modal, Tooltip, Popover, Toast and Menu portal render into this. */}
24
- <div id="bodyEnd" />
25
- </ThemeProvider>
21
+ <AhroweProvider>
22
+ <App />
23
+ </AhroweProvider>
26
24
  );
27
25
  }
28
26
  ```
29
27
 
30
- `ThemeProvider` is required. It injects the CSS variables the components paint with, so without it
31
- they render unstyled. Two themes ship, `defaultTheme` (dark) and `lightTheme`. Pass your own to
32
- `themes` to replace them.
28
+ `AhroweProvider` is required, once, at the root. It injects the CSS variables the components paint
29
+ with, so without it they render unstyled. It also renders the element Modal, Tooltip, Popover, Toast
30
+ and Menu portal into, and shows toasts. Two themes ship, `defaultTheme` (dark) and `lightTheme`.
31
+ Pass your own to `themes` to replace them. See [docs/AhroweProvider.md](docs/AhroweProvider.md).
33
32
 
34
33
  ## Before you install
35
34
 
@@ -118,12 +117,13 @@ runs where `@ahrowe/ui` is installed.
118
117
  | `Accordion` | Collapsible sections for progressive disclosure — FAQs, settings panels, long-form content that users expand on demand |
119
118
  | `ActionButtons` | Standardised Submit / Cancel button pair for forms, dialogs, and drawer footers |
120
119
  | `ActionIcon` | Clickable icon button without a label — toolbar actions, table row actions, icon-only controls |
120
+ | `AhroweProvider` | The one wrapper an app needs, at its root: it applies the theme, sets default props for every component, shows toasts and renders the element that Modal, Tooltip, Popover, Menu and Toast portal into |
121
121
  | `Alert`, `AlertStyleType` | A persistent inline status message, used for form-level errors, page/section banners, empty-config warnings, and "here's what changed" notices |
122
122
  | `AnimatedLogo` | A self-drawing SVG mark for splash/loading screens, empty states, or anywhere a brand or icon mark should feel alive rather than static |
123
123
  | `AnimatedText`, `AnimatedTextVariant`, `AnimatedTextUnit` | Decorative text entrance or emphasis effect, for hero headlines, empty-state messages, "premium"/"new" callouts, loading/decoding states |
124
124
  | `Avatar`, `AvatarGroup` | User or entity image — profile pictures, comment authors, member lists |
125
125
  | `Badge`, `BadgeStyleType`, `BadgePosition` | Small count or status indicator — unread counts, notification dots, "new" markers |
126
- | `BodyEnd` | Renders children via a React portal into a `#bodyEnd` DOM node |
126
+ | `BodyEnd` | Renders children through a React portal, to the end of the app |
127
127
  | `Breadcrumb` | A trail showing the current page's position within a hierarchy, with links back to each ancestor, such as a file browser path, a category/subcategory drill-down, or a multi-level settings page |
128
128
  | `Button`, `ButtonStyleType`, `ButtonSize`, `ButtonType` | Primary interactive action trigger |
129
129
  | `ButtonGroup`, `ButtonGroupOrientation` | Joins a row (or column) of buttons into a single seamless control, merging their borders and rounding only the outer corners |
@@ -186,12 +186,12 @@ runs where `@ahrowe/ui` is installed.
186
186
  | `Switch` | Sliding boolean toggle — settings panels, feature flags, preference lists |
187
187
  | `TabHeader` | A horizontal row of tabs to switch between sibling views or sections — settings panels, dashboards, detail pages |
188
188
  | `Textarea` | Multiline text input — comments, descriptions, notes, code snippets, long-form content |
189
- | `ThemeProvider` | Required wrapper for the entire component library |
189
+ | `ThemeProvider` | Applies a theme to its subtree, injecting its CSS custom properties as inline styles so every component inside can use them |
190
190
  | `Tilt`, `TiltGroup` | Wrap content that should tilt toward the cursor in 3D |
191
191
  | `TimeInput` | Time-of-day selection — appointment times, opening hours, shift starts, reminders |
192
192
  | `Timeline`, `TimelineAlignment`, `TimelineItemStatus` | A vertical list of dated events, connected by a line, for audit logs, activity feeds, order status history, and deploy logs |
193
193
  | `Timer` | A countdown timer with a depleting ring, add/remove-time and pause/cancel controls, and an imperative ref API |
194
- | `ToastProvider`, `showToast` | Non-blocking notifications — save confirmations, error alerts, info messages, warnings |
194
+ | `showToast` | Non-blocking notifications — save confirmations, error alerts, info messages, warnings |
195
195
  | `Tooltip` | Contextual help, info icons, field explanations, error pointers |
196
196
  | `Tree`, `flattenTree` | A hierarchical list the user can expand, collapse and pick from: categories, nested locations, folder structures, org charts |
197
197
  | `VirtualList` | Efficiently render large lists or tables — thousands of rows rendered with a virtualised scroll window so only visible rows are in the DOM |
@@ -0,0 +1,4 @@
1
+ import { ReactElement } from 'react';
2
+ import { AhroweProviderProps } from './ahroweProvider.types.js';
3
+ declare function AhroweProvider({ children, themes, currentThemeId, defaultProps, presentation, toast, scrollbars, }: AhroweProviderProps): ReactElement;
4
+ export default AhroweProvider;
@@ -0,0 +1,12 @@
1
+ import { ConfigProviderProps } from '../configProvider/configProvider.types.js';
2
+ import { ThemeProviderProps } from '../themeProvider/theme.types.js';
3
+ import { ToastContextConfig } from '../toast/toast.types.js';
4
+ export interface AhroweProviderProps extends ConfigProviderProps, ThemeProviderProps {
5
+ /** Defaults for every toast `showToast` shows. Read by the outermost provider only. */
6
+ toast?: ToastContextConfig;
7
+ /**
8
+ * Styles every scrollbar inside the app, portalled panels included, in the theme's colours.
9
+ * Defaults to `true`. Read by the outermost provider only.
10
+ */
11
+ scrollbars?: boolean;
12
+ }
@@ -0,0 +1,2 @@
1
+ export { default } from './ahroweProvider.js';
2
+ export * from './ahroweProvider.types.js';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * The element `AhroweProvider` portals into. `undefined` means there is no provider, so BodyEnd
3
+ * falls back to `#bodyEnd`; `null` means a provider that cannot portal yet, on the server and
4
+ * while hydrating, and must not be reported missing.
5
+ */
6
+ export declare const PortalHostContext: import('react').Context<HTMLElement | null | undefined>;
@@ -19,9 +19,9 @@ export interface ErrorBoundaryLabels {
19
19
  copied?: string;
20
20
  /** Reset button label. Default: "Try again". */
21
21
  reset?: string;
22
- /** Toast shown after a successful copy (only visible if `ToastProvider` is mounted). */
22
+ /** Toast shown after a successful copy, when the boundary is inside an `AhroweProvider`. */
23
23
  copySuccessToast?: string;
24
- /** Toast shown if the copy fails (only visible if `ToastProvider` is mounted). */
24
+ /** Toast shown if the copy fails, when the boundary is inside an `AhroweProvider`. */
25
25
  copyErrorToast?: string;
26
26
  }
27
27
  export interface ErrorBoundaryProps extends Omit<HtmlProps, 'title' | 'onError'> {
@@ -1,4 +1,4 @@
1
1
  export { default } from './themeProvider.js';
2
2
  export { defaultTheme } from './defaultTheme.js';
3
3
  export { lightTheme } from './lightTheme.js';
4
- export type { Theme, ThemeVariables } from './theme.types.js';
4
+ export type { Theme, ThemeProviderProps, ThemeVariables } from './theme.types.js';
@@ -1,3 +1,4 @@
1
+ import { ReactNode } from 'react';
1
2
  /**
2
3
  * All CSS custom properties used by the component library.
3
4
  * Every key is optional so a theme only needs to declare the variables it overrides.
@@ -278,3 +279,10 @@ export interface Theme {
278
279
  baseThemeId?: string;
279
280
  variables: ThemeVariables;
280
281
  }
282
+ export interface ThemeProviderProps {
283
+ children: ReactNode;
284
+ /** The themes to choose from. Defaults to `[defaultTheme]`. */
285
+ themes?: Theme[];
286
+ /** The `id` of the theme to apply, from `themes`. */
287
+ currentThemeId?: string;
288
+ }
@@ -1,10 +1,6 @@
1
1
  import { default as React, Component } from 'react';
2
- import { Theme } from './theme.types.js';
3
- interface Props {
4
- children: React.ReactNode;
5
- themes?: Theme[];
6
- currentThemeId?: string;
7
- }
2
+ import { Theme, ThemeProviderProps } from './theme.types.js';
3
+ import { ThemeVariablesContext } from './themeVariablesContext.js';
8
4
  interface State {
9
5
  resolvedVariables: Record<string, string>;
10
6
  colorFadeActive: boolean;
@@ -15,15 +11,17 @@ interface State {
15
11
  currentThemeId: string | undefined;
16
12
  };
17
13
  }
18
- declare class ThemeProvider extends Component<Props, State> {
14
+ declare class ThemeProvider extends Component<ThemeProviderProps, State> {
15
+ static contextType: React.Context<Record<string, string> | null>;
16
+ context: React.ContextType<typeof ThemeVariablesContext>;
19
17
  state: State;
20
18
  private colorFadeTimeout;
21
19
  private wrapperRef;
22
20
  private themeColorMeta;
23
21
  private createdThemeColorMeta;
24
- static getDerivedStateFromProps(props: Props, state: State): Partial<State> | null;
22
+ static getDerivedStateFromProps(props: ThemeProviderProps, state: State): Partial<State> | null;
25
23
  componentDidMount(): void;
26
- componentDidUpdate(prevProps: Props, prevState: State): void;
24
+ componentDidUpdate(prevProps: ThemeProviderProps, prevState: State): void;
27
25
  componentWillUnmount(): void;
28
26
  /**
29
27
  * Replaces the former react-helmet usage: keep a single
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * The nearest ThemeProvider's resolved variables, for content that renders outside its DOM
3
3
  * subtree. Theme variables reach a component by CSS inheritance, which a portal breaks: a
4
- * `BodyEnd` child lands wherever `#bodyEnd` happens to be, so it picks up whatever provider
5
- * wraps *that* node rather than the one it was written under.
4
+ * `BodyEnd` child lands wherever the portal target is, so it picks up whatever provider wraps
5
+ * *that* node rather than the one it was written under.
6
6
  *
7
7
  * Deliberately its own module rather than a member of `themeProvider.tsx`: BodyEnd needs the
8
8
  * context and nothing else, and importing the component for it would pull the whole
@@ -1,3 +1,2 @@
1
- export { default } from './toastProvider.js';
2
1
  export { showToast } from './toastService.js';
3
- export type { ToastProviderProps, ToastContextConfig, ToastOptions, ToastType, ToastPosition, ToastSlots, } from './toast.types.js';
2
+ export type { ToastContextConfig, ToastLabels, ToastOptions, ToastType, ToastPosition, ToastSlots, } from './toast.types.js';
@@ -19,17 +19,17 @@ export interface ToastContextConfig {
19
19
  /** Pause the auto-dismiss timer while the toast is hovered or focused. */
20
20
  pauseOnHover?: boolean;
21
21
  /**
22
- * Overrides the text a toast renders of its own. Set on `ToastProvider` for
23
- * the whole app, or per toast through `showToast` options.
22
+ * Overrides the text a toast renders of its own. Set in `AhroweProvider`'s
23
+ * `toast` for the whole app, or per toast through `showToast` options.
24
24
  */
25
25
  labels?: ToastLabels;
26
26
  }
27
- export interface ToastOptions extends ToastContextConfig {
27
+ export interface ToastOptions extends Omit<ToastContextConfig, 'allowStacking'> {
28
28
  type?: ToastType;
29
29
  classNames?: SlotClassNames<ToastSlots>;
30
30
  styles?: SlotStyles<ToastSlots>;
31
31
  }
32
- export interface ToastProviderProps extends ToastContextConfig {
32
+ export interface ToastHostProps extends ToastContextConfig {
33
33
  children: ReactNode;
34
34
  }
35
35
  export interface ToastItem extends Pick<ToastOptions, 'labels'> {
@@ -0,0 +1,4 @@
1
+ import { default as React } from 'react';
2
+ import { ToastHostProps } from './toast.types.js';
3
+ declare function ToastHost({ children, position, duration, showRemainingTime, showCloseButton, allowStacking, pauseOnHover, labels, }: ToastHostProps): React.JSX.Element;
4
+ export default ToastHost;
@@ -2,6 +2,6 @@ import { ReactNode } from 'react';
2
2
  import { ToastOptions } from './toast.types.js';
3
3
  type ShowToastFn = (content: ReactNode, options?: ToastOptions) => void;
4
4
  export declare function _registerToastHandler(fn: ShowToastFn): void;
5
- export declare function _unregisterToastHandler(): void;
5
+ export declare function _unregisterToastHandler(fn: ShowToastFn): void;
6
6
  export declare function showToast(content: ReactNode, options?: ToastOptions): void;
7
7
  export {};
@@ -3,6 +3,8 @@ export * from './common/accordion/index.js';
3
3
  export * from './common/actionButtons/index.js';
4
4
  export { default as ActionIcon } from './common/actionIcon/index.js';
5
5
  export * from './common/actionIcon/index.js';
6
+ export { default as AhroweProvider } from './common/ahroweProvider/index.js';
7
+ export * from './common/ahroweProvider/index.js';
6
8
  export { default as Alert } from './common/alert/index.js';
7
9
  export * from './common/alert/index.js';
8
10
  export { default as AnimatedLogo } from './common/animatedLogo/index.js';
@@ -144,7 +146,6 @@ export { default as Timeline } from './common/timeline/index.js';
144
146
  export * from './common/timeline/index.js';
145
147
  export { default as Timer } from './common/timer/index.js';
146
148
  export * from './common/timer/index.js';
147
- export { default as ToastProvider } from './common/toast/index.js';
148
149
  export * from './common/toast/index.js';
149
150
  export { default as Tooltip } from './common/tooltip/index.js';
150
151
  export * from './common/tooltip/index.js';
@@ -0,0 +1,2 @@
1
+ "use client";import e from"../configProvider/configProvider.mjs";import t from"../scrollbarProvider/index.mjs";import n from"../themeProvider/themeProvider.mjs";import{PortalHostContext as r}from"../bodyEnd/portalHostContext.mjs";import i from"../toast/toastHost.mjs";import{useCallback as a,useContext as o,useEffect as s,useLayoutEffect as c,useState as l,useSyncExternalStore as u}from"react";import{jsx as d,jsxs as f}from"react/jsx-runtime";var p=()=>()=>{};function m({children:m,themes:h,currentThemeId:g,defaultProps:_,presentation:v,toast:y,scrollbars:b}){let x=o(r)!==void 0,S=u(p,()=>!0,()=>!1),[C]=l(()=>typeof document>`u`?null:document.createElement(`div`)),w=a(e=>{let t=e?.parentElement?.lastElementChild;if(t&&C)return t.appendChild(C),()=>C.remove()},[C]),T=!x&&b!==!1;c(()=>{if(!T||!C)return;let e=getComputedStyle(C).getPropertyValue(`--background-accent-light`).trim(),t=document.documentElement.style;return t.setProperty(`--ahrowe-page-scrollbar-thumb`,e),()=>{t.removeProperty(`--ahrowe-page-scrollbar-thumb`)}},[T,C,h,g]);let E=x&&(y!==void 0||b!==void 0);s(()=>{E&&console.warn("[AhroweProvider] `toast` and `scrollbars` are read by the outermost AhroweProvider only. Set them there.")},[E]);let D=f(r.Provider,{value:S?C:null,children:[d(`template`,{ref:w}),d(i,{...y,children:m}),d(`div`,{})]});return d(e,{defaultProps:_,presentation:v,children:d(n,{themes:h,currentThemeId:g,children:x?m:b===!1?D:d(t,{children:D})})})}export{m as default};
2
+ //# sourceMappingURL=ahroweProvider.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ahroweProvider.mjs","names":[],"sources":["../../../../package/common/ahroweProvider/ahroweProvider.tsx"],"sourcesContent":["import {\n useCallback,\n useContext,\n useEffect,\n useLayoutEffect,\n useState,\n useSyncExternalStore,\n type ReactElement,\n} from 'react';\n\nimport ConfigProvider from 'components/common/configProvider';\nimport ScrollbarProvider from 'components/common/scrollbarProvider';\nimport ThemeProvider from 'components/common/themeProvider';\nimport ToastHost from '../toast/toastHost';\nimport { PortalHostContext } from '../bodyEnd/portalHostContext';\nimport type { AhroweProviderProps } from './ahroweProvider.types';\n\nconst subscribeToNothing = () => () => {};\n\nfunction AhroweProvider({\n children,\n themes,\n currentThemeId,\n defaultProps,\n presentation,\n toast,\n scrollbars,\n}: AhroweProviderProps): ReactElement {\n // A nested provider themes and configures its section only. A second toast list would stack\n // over the first in the same corner, and a second host would split the portals in two.\n const isNested = useContext(PortalHostContext) !== undefined;\n\n // The server renders no portal, so hydration must not either: `false` there, and `true` from\n // the first render of an app that is not hydrating.\n const canPortal = useSyncExternalStore(\n subscribeToNothing,\n () => true,\n () => false,\n );\n\n // Created up front instead of rendered: a rendered host exists only after the first commit, so\n // a modal open on the first render mounted a commit late and its initial focus found nothing.\n const [host] = useState(() => (typeof document === 'undefined' ? null : document.createElement('div')));\n\n // Attached from a marker before the app, because refs attach in tree order: this way the host is\n // in the page before any layout effect runs. The slot stays last, so panels paint above the app.\n const attachHost = useCallback(\n (marker: HTMLTemplateElement | null) => {\n const slot = marker?.parentElement?.lastElementChild;\n if (!slot || !host) return;\n slot.appendChild(host);\n return () => host.remove();\n },\n [host],\n );\n\n // Read from the host rather than `themes`, for the resolved value, `var()` references included.\n const stylesPage = !isNested && scrollbars !== false;\n useLayoutEffect(() => {\n if (!stylesPage || !host) return;\n const thumb = getComputedStyle(host).getPropertyValue('--background-accent-light').trim();\n const page = document.documentElement.style;\n page.setProperty('--ahrowe-page-scrollbar-thumb', thumb);\n return () => {\n page.removeProperty('--ahrowe-page-scrollbar-thumb');\n };\n }, [stylesPage, host, themes, currentThemeId]);\n\n const ignoresRootProps = isNested && (toast !== undefined || scrollbars !== undefined);\n useEffect(() => {\n if (ignoresRootProps) {\n console.warn(\n '[AhroweProvider] `toast` and `scrollbars` are read by the outermost AhroweProvider only. Set them there.',\n );\n }\n }, [ignoresRootProps]);\n\n const root = (\n <PortalHostContext.Provider value={canPortal ? host : null}>\n <template ref={attachHost} />\n <ToastHost {...toast}>{children}</ToastHost>\n <div />\n </PortalHostContext.Provider>\n );\n\n return (\n <ConfigProvider defaultProps={defaultProps} presentation={presentation}>\n <ThemeProvider themes={themes} currentThemeId={currentThemeId}>\n {/* Around the portal slot too, so the scrollbars of the panels in it are styled as well. */}\n {isNested ? children : scrollbars === false ? root : <ScrollbarProvider>{root}</ScrollbarProvider>}\n </ThemeProvider>\n </ConfigProvider>\n );\n}\n\nexport default AhroweProvider;\n"],"mappings":"8bAiBA,IAAM,UAAiC,CAAC,EAExC,SAAS,EAAe,CACtB,WACA,SACA,iBACA,eACA,eACA,QACA,cACoC,CAGpC,IAAM,EAAW,EAAW,CAAiB,IAAM,IAAA,GAI7C,EAAY,EAChB,MACM,OACA,EACR,EAIM,CAAC,GAAQ,MAAgB,OAAO,SAAa,IAAc,KAAO,SAAS,cAAc,KAAK,CAAE,EAIhG,EAAa,EAChB,GAAuC,CACtC,IAAM,EAAO,GAAQ,eAAe,iBAChC,GAAC,GAAS,EAEd,OADA,EAAK,YAAY,CAAI,MACR,EAAK,OAAO,CAC3B,EACA,CAAC,CAAI,CACP,EAGM,EAAa,CAAC,GAAY,IAAe,GAC/C,MAAsB,CACpB,GAAI,CAAC,GAAc,CAAC,EAAM,OAC1B,IAAM,EAAQ,iBAAiB,CAAI,CAAC,CAAC,iBAAiB,2BAA2B,CAAC,CAAC,KAAK,EAClF,EAAO,SAAS,gBAAgB,MAEtC,OADA,EAAK,YAAY,gCAAiC,CAAK,MAC1C,CACX,EAAK,eAAe,+BAA+B,CACrD,CACF,EAAG,CAAC,EAAY,EAAM,EAAQ,CAAc,CAAC,EAE7C,IAAM,EAAmB,IAAa,IAAU,IAAA,IAAa,IAAe,IAAA,IAC5E,MAAgB,CACV,GACF,QAAQ,KACN,0GACF,CAEJ,EAAG,CAAC,CAAgB,CAAC,EAErB,IAAM,EACJ,EAAC,EAAkB,SAAnB,CAA4B,MAAO,EAAY,EAAO,KAAtD,SAAA,CACE,EAAC,WAAD,CAAU,IAAK,CAAa,CAAA,EAC5B,EAAC,EAAD,CAAW,GAAI,EAAQ,UAAoB,CAAA,EAC3C,EAAC,MAAD,CAAM,CAAA,CACoB,IAG9B,OACE,EAAC,EAAD,CAA8B,eAA4B,eACxD,SAAA,EAAC,EAAD,CAAuB,SAAwB,iBAE5C,SAAA,EAAW,EAAW,IAAe,GAAQ,EAAO,EAAC,EAAD,CAAA,SAAoB,CAAwB,CAAA,CACpF,CAAA,CACD,CAAA,CAEpB"}
@@ -1,2 +1,2 @@
1
- "use client";import{ThemeVariablesContext as e}from"../themeProvider/themeVariablesContext.mjs";import{useContext as t}from"react";import{jsx as n}from"react/jsx-runtime";import{createPortal as r}from"react-dom";function i({children:i=null}){let a=t(e),o=document.getElementById(`bodyEnd`);return o?r(a?n(`div`,{style:{display:`contents`,...a},children:i}):i,o):null}export{i as default};
1
+ "use client";import{ThemeVariablesContext as e}from"../themeProvider/themeVariablesContext.mjs";import t from"../themeProvider/theme.module.mjs";import{PortalHostContext as n}from"./portalHostContext.mjs";import{useContext as r}from"react";import{jsx as i}from"react/jsx-runtime";import{createPortal as a}from"react-dom";function o({children:o=null}){let s=r(e),c=r(n),l=c===void 0?document.getElementById(`bodyEnd`):c;return l?a(s?i(`div`,{className:t.themeRoot,style:{display:`contents`,...s},children:o}):o,l):null}export{o as default};
2
2
  //# sourceMappingURL=bodyEnd.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"bodyEnd.mjs","names":[],"sources":["../../../../package/common/bodyEnd/bodyEnd.tsx"],"sourcesContent":["import { createPortal } from 'react-dom';\nimport { useContext, type CSSProperties, type ReactElement } from 'react';\n\nimport { ThemeVariablesContext } from '../themeProvider/themeVariablesContext';\nimport type { BodyEndProps } from './bodyEnd.types';\n\n// Module-scoped ambient typing for the bundler-replaced NODE_ENV guard below.\n// Browsers don't define `process`, so the value is read defensively at runtime;\n// bundlers (Vite/webpack) still statically replace the `process.env.NODE_ENV`\n// token. Declared at module scope (not globally), it isn't emitted into the\n// .d.ts and can't clash with a consumer's @types/node.\ndeclare const process: { env: { NODE_ENV?: string } };\n\nfunction BodyEnd({ children = null }: BodyEndProps): ReactElement | null {\n const themeVariables = useContext(ThemeVariablesContext);\n const target = document.getElementById('bodyEnd');\n if (!target) {\n if (typeof process !== 'undefined' && process.env.NODE_ENV !== 'production') {\n console.error('[BodyEnd] No element with id=\"bodyEnd\" found. Add <div id=\"bodyEnd\"></div> to your app root.');\n }\n return null;\n }\n /*\n * Carry the nearest theme through the portal. `display: contents` is what makes this safe:\n * the wrapper generates no box, so positioning, stacking and clipping are untouched —\n * escaping clipping ancestors is the whole point of this component — while custom\n * properties still inherit, because inheritance follows the element tree, not the box tree.\n *\n * Variables this theme leaves unset still fall through to whatever wraps #bodyEnd, which\n * is the same cascade the children would have seen rendered in place.\n */\n if (!themeVariables) return createPortal(children, target);\n return createPortal(\n <div style={{ display: 'contents', ...themeVariables } as CSSProperties}>{children}</div>,\n target,\n );\n}\n\nexport default BodyEnd;\n"],"mappings":"oNAaA,SAAS,EAAQ,CAAE,WAAW,MAA2C,CACvE,IAAM,EAAiB,EAAW,CAAqB,EACjD,EAAS,SAAS,eAAe,SAAS,EAiBhD,OAhBK,EAgBE,EADF,EAEH,EAAC,MAAD,CAAK,MAAO,CAAE,QAAS,WAAY,GAAG,CAAe,EAAqB,UAAc,CAAA,EAFjD,EAGvC,CAHuD,EAXhD,IAgBX"}
1
+ {"version":3,"file":"bodyEnd.mjs","names":[],"sources":["../../../../package/common/bodyEnd/bodyEnd.tsx"],"sourcesContent":["import { createPortal } from 'react-dom';\nimport { useContext, type CSSProperties, type ReactElement } from 'react';\n\nimport { ThemeVariablesContext } from '../themeProvider/themeVariablesContext';\nimport themeStyles from '../themeProvider/theme.module.pcss';\nimport { PortalHostContext } from './portalHostContext';\nimport type { BodyEndProps } from './bodyEnd.types';\n\n// Module-scoped ambient typing for the bundler-replaced NODE_ENV guard below.\n// Browsers don't define `process`, so the value is read defensively at runtime;\n// bundlers (Vite/webpack) still statically replace the `process.env.NODE_ENV`\n// token. Declared at module scope (not globally), it isn't emitted into the\n// .d.ts and can't clash with a consumer's @types/node.\ndeclare const process: { env: { NODE_ENV?: string } };\n\nfunction BodyEnd({ children = null }: BodyEndProps): ReactElement | null {\n const themeVariables = useContext(ThemeVariablesContext);\n const host = useContext(PortalHostContext);\n const target = host === undefined ? document.getElementById('bodyEnd') : host;\n if (!target) {\n if (host === undefined && typeof process !== 'undefined' && process.env.NODE_ENV !== 'production') {\n console.error('[BodyEnd] Nowhere to render. Wrap your app in <AhroweProvider>, or add <div id=\"bodyEnd\"></div>.');\n }\n return null;\n }\n /*\n * Carry the nearest theme through the portal. `display: contents` is what makes this safe:\n * the wrapper generates no box, so positioning, stacking and clipping are untouched —\n * escaping clipping ancestors is the whole point of this component — while custom\n * properties still inherit, because inheritance follows the element tree, not the box tree.\n *\n * Variables this theme leaves unset still fall through to whatever wraps the target, which\n * is the same cascade the children would have seen rendered in place. `themeRoot` sets the\n * base typography again, which the target would otherwise pass down from its own theme.\n */\n if (!themeVariables) return createPortal(children, target);\n return createPortal(\n <div className={themeStyles.themeRoot} style={{ display: 'contents', ...themeVariables } as CSSProperties}>\n {children}\n </div>,\n target,\n );\n}\n\nexport default BodyEnd;\n"],"mappings":"iUAeA,SAAS,EAAQ,CAAE,WAAW,MAA2C,CACvE,IAAM,EAAiB,EAAW,CAAqB,EACjD,EAAO,EAAW,CAAiB,EACnC,EAAS,IAAS,IAAA,GAAY,SAAS,eAAe,SAAS,EAAI,EAkBzE,OAjBK,EAiBE,EADF,EAEH,EAAC,MAAD,CAAK,UAAW,EAAY,UAAW,MAAO,CAAE,QAAS,WAAY,GAAG,CAAe,EACpF,UACE,CAAA,EAJkC,EAKvC,CALuD,EAZhD,IAmBX"}
@@ -0,0 +1,2 @@
1
+ "use client";import{createContext as e}from"react";var t=e(void 0);export{t as PortalHostContext};
2
+ //# sourceMappingURL=portalHostContext.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"portalHostContext.mjs","names":[],"sources":["../../../../package/common/bodyEnd/portalHostContext.ts"],"sourcesContent":["import { createContext } from 'react';\n\n/**\n * The element `AhroweProvider` portals into. `undefined` means there is no provider, so BodyEnd\n * falls back to `#bodyEnd`; `null` means a provider that cannot portal yet, on the server and\n * while hydrating, and must not be reported missing.\n */\nexport const PortalHostContext = createContext<HTMLElement | null | undefined>(undefined);\n"],"mappings":"mDAOA,IAAa,EAAoB,EAA8C,IAAA,EAAS"}
@@ -1 +1 @@
1
- {"version":3,"file":"carousel.module.mjs","names":[],"sources":["../../../../package/common/carousel/carousel.module.pcss"],"sourcesContent":[".carousel {\n position: relative;\n overflow: hidden;\n border-radius: var(--default-border-radius);\n\n &-adaptive {\n transition: height 0.4s ease;\n }\n\n &-track {\n display: flex;\n align-items: stretch;\n transition: transform 0.4s ease;\n touch-action: pan-y;\n user-select: none;\n }\n\n &-trackAdaptive {\n align-items: flex-start;\n }\n\n &-slide {\n flex-shrink: 0;\n box-sizing: border-box;\n }\n\n &-prevButton,\n &-nextButton {\n position: absolute;\n top: 50%;\n transform: translateY(-50%);\n z-index: 1;\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 50%;\n background: var(--background-accent);\n color: var(--text-color);\n box-shadow: var(--card-shadow);\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition:\n opacity 0.15s,\n background 0.15s;\n\n &:hover {\n background: var(--background-accent-light);\n }\n\n &:focus-visible {\n outline: none;\n box-shadow:\n var(--card-shadow),\n 0 0 0 2px var(--primary-lighter);\n }\n }\n\n &-buttonDisabled {\n opacity: 0.35;\n pointer-events: none;\n }\n\n &-prevButton {\n left: 12px;\n }\n\n &-nextButton {\n right: 12px;\n }\n\n /* A scroll strip rather than a row that shrinks, so the dots keep their size at any slide count.\n The padding holds the outer dots clear of the fade until the strip has somewhere to scroll. */\n &-dots {\n position: absolute;\n bottom: 4px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 1;\n display: flex;\n box-sizing: border-box;\n width: max-content;\n max-width: 100%;\n padding-inline: 16px;\n overflow-x: auto;\n overscroll-behavior-x: contain;\n scroll-behavior: smooth;\n\n /* Matches the strip's own `padding-inline`, so the first and last dot start out clear of the\n fade. The shared sheet's private length, not `--scroll-fade-length`, which would inherit. */\n --custom-scroll-fade-length: 16px;\n\n @media (prefers-reduced-motion: reduce) {\n scroll-behavior: auto;\n }\n }\n\n /* ScrollbarProvider reaches every scrollable element through `* { scrollbar-width: thin }`, which\n ties this on specificity and wins on order, so hiding the strip's scrollbar has to outweigh it. */\n & &-dots {\n scrollbar-width: none;\n\n &::-webkit-scrollbar {\n display: none;\n }\n }\n\n /* The dot stays 8px, its padding carries the hit area out to the 24px minimum. */\n &-dot {\n box-sizing: content-box;\n\n /* The strip is width-capped, and shrinking would pull the content box, the only part\n `background-clip` paints, down to nothing. */\n flex-shrink: 0;\n width: 8px;\n height: 8px;\n padding: 8px;\n border-radius: 2000px;\n background: var(--background-accent);\n background-clip: content-box;\n opacity: 0.6;\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition:\n opacity 0.15s,\n background 0.15s,\n width 0.15s;\n\n &:hover {\n opacity: 0.85;\n }\n\n &:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--primary-lighter);\n }\n\n &-active {\n background: var(--primary-color);\n background-clip: content-box;\n opacity: 1;\n width: 20px;\n }\n }\n}\n"],"mappings":""}
1
+ {"version":3,"file":"carousel.module.mjs","names":[],"sources":["../../../../package/common/carousel/carousel.module.pcss"],"sourcesContent":[".carousel {\n position: relative;\n overflow: hidden;\n border-radius: var(--default-border-radius);\n\n &-adaptive {\n transition: height 0.4s ease;\n }\n\n &-track {\n display: flex;\n align-items: stretch;\n transition: transform 0.4s ease;\n touch-action: pan-y;\n user-select: none;\n }\n\n &-trackAdaptive {\n align-items: flex-start;\n }\n\n &-slide {\n flex-shrink: 0;\n box-sizing: border-box;\n }\n\n &-prevButton,\n &-nextButton {\n position: absolute;\n top: 50%;\n transform: translateY(-50%);\n z-index: 1;\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 50%;\n background: var(--background-accent);\n color: var(--text-color);\n box-shadow: var(--card-shadow);\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition:\n opacity 0.15s,\n background 0.15s;\n\n &:hover {\n background: var(--background-accent-light);\n }\n\n &:focus-visible {\n outline: none;\n box-shadow:\n var(--card-shadow),\n 0 0 0 2px var(--primary-lighter);\n }\n }\n\n &-buttonDisabled {\n opacity: 0.35;\n pointer-events: none;\n }\n\n &-prevButton {\n left: 12px;\n }\n\n &-nextButton {\n right: 12px;\n }\n\n /* A scroll strip rather than a row that shrinks, so the dots keep their size at any slide count.\n The padding holds the outer dots clear of the fade until the strip has somewhere to scroll. */\n &-dots {\n position: absolute;\n bottom: 4px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 1;\n display: flex;\n box-sizing: border-box;\n width: max-content;\n max-width: 100%;\n padding-inline: 16px;\n overflow-x: auto;\n overscroll-behavior-x: contain;\n scroll-behavior: smooth;\n\n /* Matches the strip's own `padding-inline`, so the first and last dot start out clear of the\n fade. The shared sheet's private length, not `--scroll-fade-length`, which would inherit. */\n --custom-scroll-fade-length: 16px;\n\n scrollbar-width: none;\n\n &::-webkit-scrollbar {\n display: none;\n }\n\n @media (prefers-reduced-motion: reduce) {\n scroll-behavior: auto;\n }\n }\n\n /* The dot stays 8px, its padding carries the hit area out to the 24px minimum. */\n &-dot {\n box-sizing: content-box;\n\n /* The strip is width-capped, and shrinking would pull the content box, the only part\n `background-clip` paints, down to nothing. */\n flex-shrink: 0;\n width: 8px;\n height: 8px;\n padding: 8px;\n border-radius: 2000px;\n background: var(--background-accent);\n background-clip: content-box;\n opacity: 0.6;\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition:\n opacity 0.15s,\n background 0.15s,\n width 0.15s;\n\n &:hover {\n opacity: 0.85;\n }\n\n &:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--primary-lighter);\n }\n\n &-active {\n background: var(--primary-color);\n background-clip: content-box;\n opacity: 1;\n width: 20px;\n }\n }\n}\n"],"mappings":""}
@@ -1,4 +1,4 @@
1
- "use client";import e from"../accordion/accordion.mjs";import t from"../button/button.mjs";import n from"../copyButton/copyButton.mjs";import{showToast as r}from"../toast/toastService.mjs";import i from"./errorBoundary.module.mjs";import a from"react";import o from"classnames";import{FontAwesomeIcon as s}from"@fortawesome/react-fontawesome";import{faArrowRotateRight as c,faTriangleExclamation as l}from"@fortawesome/free-solid-svg-icons";import{jsx as u,jsxs as d}from"react/jsx-runtime";var f={stackTrace:`Stack trace`,copy:`Copy`,copied:`Copied`,reset:`Try again`,copySuccessToast:`Stack trace copied to clipboard`,copyErrorToast:`Could not copy stack trace`};function p(e,t){let n=[e.stack??`${e.name}: ${e.message}`];return t?.componentStack&&n.push(`Component stack:${t.componentStack}`),n.join(`
1
+ "use client";import e from"../accordion/accordion.mjs";import t from"../button/button.mjs";import{showToast as n}from"../toast/toastService.mjs";import r from"../copyButton/copyButton.mjs";import i from"./errorBoundary.module.mjs";import a from"react";import o from"classnames";import{FontAwesomeIcon as s}from"@fortawesome/react-fontawesome";import{faArrowRotateRight as c,faTriangleExclamation as l}from"@fortawesome/free-solid-svg-icons";import{jsx as u,jsxs as d}from"react/jsx-runtime";var f={stackTrace:`Stack trace`,copy:`Copy`,copied:`Copied`,reset:`Try again`,copySuccessToast:`Stack trace copied to clipboard`,copyErrorToast:`Could not copy stack trace`};function p(e,t){let n=[e.stack??`${e.name}: ${e.message}`];return t?.componentStack&&n.push(`Component stack:${t.componentStack}`),n.join(`
2
2
 
3
- `)}var m=class extends a.Component{constructor(...e){super(...e),this.state={error:null,errorInfo:null},this.reset=()=>{this.setState({error:null,errorInfo:null}),this.props.onReset?.()}}static getDerivedStateFromError(e){return{error:e}}componentDidCatch(e,t){this.setState({errorInfo:t}),this.props.onError?.(e,t)}componentDidUpdate(e){if(!this.state.error)return;let t=e.resetKeys??[],n=this.props.resetKeys??[];(n.length!==t.length||n.some((e,n)=>e!==t[n]))&&this.reset()}render(){let{error:a,errorInfo:m}=this.state,{children:h=null,title:g=`Something went wrong`,description:_,labels:v,fallback:y,hideStack:b=!1,className:x,style:S,classNames:C,styles:w,onError:T,onReset:E,resetKeys:D,role:O=`alert`,...k}=this.props;if(!a)return h;let A={...f,...v};return y?typeof y==`function`?y({error:a,errorInfo:m,resetErrorBoundary:this.reset}):y:d(`div`,{...k,role:O,className:o(i.errorBoundary,x,C?.root),style:{...S,...w?.root},children:[d(`div`,{className:o(i.errorBoundaryHeader,C?.header),style:w?.header,children:[u(`div`,{className:o(i.errorBoundaryBadge,C?.badge),style:w?.badge,children:u(s,{icon:l,className:o(i.errorBoundaryIcon,C?.icon),style:w?.icon})}),u(`div`,{className:o(i.errorBoundaryTitle,C?.title),style:w?.title,children:g}),u(`div`,{className:o(i.errorBoundaryDescription,C?.description),style:w?.description,children:_??a.message})]}),!b&&d(e,{title:A.stackTrace,className:o(i.errorBoundaryStack,C?.stack),style:w?.stack,children:[d(`div`,{className:o(i.errorBoundaryStackHeader,C?.stackHeader),style:w?.stackHeader,children:[u(`span`,{className:o(i.errorBoundaryStackLabel,C?.stackLabel),style:w?.stackLabel,children:a.name||`Error`}),u(n,{value:p(a,m),labels:{copy:A.copy,copied:A.copied},onCopy:()=>r(A.copySuccessToast,{type:`success`}),onError:()=>r(A.copyErrorToast,{type:`error`}),className:o(i.errorBoundaryCopyButton,C?.copyButton),style:w?.copyButton})]}),u(`pre`,{className:o(i.errorBoundaryStackTrace,C?.stackTrace),style:w?.stackTrace,children:p(a,m)})]}),u(`div`,{className:o(i.errorBoundaryActions,C?.actions),style:w?.actions,children:u(t,{onClick:this.reset,icon:c,className:o(i.errorBoundaryResetButton,C?.resetButton),style:w?.resetButton,children:A.reset})})]})}};export{m as default};
3
+ `)}var m=class extends a.Component{constructor(...e){super(...e),this.state={error:null,errorInfo:null},this.reset=()=>{this.setState({error:null,errorInfo:null}),this.props.onReset?.()}}static getDerivedStateFromError(e){return{error:e}}componentDidCatch(e,t){this.setState({errorInfo:t}),this.props.onError?.(e,t)}componentDidUpdate(e){if(!this.state.error)return;let t=e.resetKeys??[],n=this.props.resetKeys??[];(n.length!==t.length||n.some((e,n)=>e!==t[n]))&&this.reset()}render(){let{error:a,errorInfo:m}=this.state,{children:h=null,title:g=`Something went wrong`,description:_,labels:v,fallback:y,hideStack:b=!1,className:x,style:S,classNames:C,styles:w,onError:T,onReset:E,resetKeys:D,role:O=`alert`,...k}=this.props;if(!a)return h;let A={...f,...v};return y?typeof y==`function`?y({error:a,errorInfo:m,resetErrorBoundary:this.reset}):y:d(`div`,{...k,role:O,className:o(i.errorBoundary,x,C?.root),style:{...S,...w?.root},children:[d(`div`,{className:o(i.errorBoundaryHeader,C?.header),style:w?.header,children:[u(`div`,{className:o(i.errorBoundaryBadge,C?.badge),style:w?.badge,children:u(s,{icon:l,className:o(i.errorBoundaryIcon,C?.icon),style:w?.icon})}),u(`div`,{className:o(i.errorBoundaryTitle,C?.title),style:w?.title,children:g}),u(`div`,{className:o(i.errorBoundaryDescription,C?.description),style:w?.description,children:_??a.message})]}),!b&&d(e,{title:A.stackTrace,className:o(i.errorBoundaryStack,C?.stack),style:w?.stack,children:[d(`div`,{className:o(i.errorBoundaryStackHeader,C?.stackHeader),style:w?.stackHeader,children:[u(`span`,{className:o(i.errorBoundaryStackLabel,C?.stackLabel),style:w?.stackLabel,children:a.name||`Error`}),u(r,{value:p(a,m),labels:{copy:A.copy,copied:A.copied},onCopy:()=>n(A.copySuccessToast,{type:`success`}),onError:()=>n(A.copyErrorToast,{type:`error`}),className:o(i.errorBoundaryCopyButton,C?.copyButton),style:w?.copyButton})]}),u(`pre`,{className:o(i.errorBoundaryStackTrace,C?.stackTrace),style:w?.stackTrace,children:p(a,m)})]}),u(`div`,{className:o(i.errorBoundaryActions,C?.actions),style:w?.actions,children:u(t,{onClick:this.reset,icon:c,className:o(i.errorBoundaryResetButton,C?.resetButton),style:w?.resetButton,children:A.reset})})]})}};export{m as default};
4
4
  //# sourceMappingURL=errorBoundary.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"useAnchorTracking.mjs","names":[],"sources":["../../../../package/common/hooks/useAnchorTracking.ts"],"sourcesContent":["import { useLayoutEffect, useRef, type RefObject } from 'react';\nimport { getScrollableAncestors } from 'components/common/utils/scrollAncestors';\n\nexport interface UseAnchorTrackingArgs {\n isOpen: boolean;\n /** The element the popup is anchored to. Its scrollable ancestors are what get tracked. */\n triggerRef: RefObject<HTMLElement | null>;\n /**\n * Every element the positioning maths needs. Tracking starts only once all of them, plus the\n * trigger, are mounted. Read fresh on each setup, so an inline array literal is fine.\n */\n elementRefs: RefObject<HTMLElement | null>[];\n /**\n * Positions the popup. Called once on setup and then on every scroll, resize and tracked size\n * change. Reads the caller's own refs for everything beyond the two arguments.\n */\n update: (trigger: HTMLElement, ancestors: HTMLElement[]) => void;\n /**\n * Elements whose own size changes must re-run `update`: a popup whose content changes size\n * while open (a filtered list shrinking, an async list loading in) otherwise keeps coordinates\n * computed for its old height and ends up detached from the trigger. Re-read on every update,\n * since a panel can mount a commit later than the element it hangs off.\n */\n resizeRefs?: RefObject<HTMLElement | null>[];\n /** Hands the popup's elements back as they were found. Runs when tracking stops. */\n teardown?: () => void;\n /** Values that must restart tracking, and so re-run `update`, when they change. */\n deps?: readonly unknown[];\n}\n\n/**\n * The lifecycle half of anchoring a portaled, `position: fixed` popup to a trigger: waits for the\n * elements to mount, resolves the trigger's scrolling ancestors once, and re-runs the caller's\n * positioning on every scroll, viewport resize and tracked size change until it closes.\n *\n * Callers supply only the geometry (`update`) and the inline styles to clear again (`teardown`);\n * `usePopoverPosition` is built on this.\n *\n * Position is deliberately written straight to the DOM by the caller rather than through React\n * state: going through setState -> re-render -> commit adds a round-trip that lags behind the\n * browser's own scroll painting by at least a frame.\n */\nexport function useAnchorTracking({\n isOpen,\n triggerRef,\n elementRefs,\n update,\n resizeRefs,\n teardown,\n deps = [],\n}: UseAnchorTrackingArgs) {\n // Latest-value refs, so the effect restarts only for `deps` the caller names, never merely\n // because these were passed as fresh array/function literals this render.\n const latest = useRef({ elementRefs, update, resizeRefs, teardown });\n latest.current = { elementRefs, update, resizeRefs, teardown };\n\n useLayoutEffect(() => {\n if (!isOpen) return;\n\n let rafId: number | undefined;\n let stop: (() => void) | undefined;\n\n // The elements may not be mounted yet on the very first invocation of this effect: a portal\n // target (e.g. BodyEnd's `#bodyEnd` div) can still be missing on the initial render, in which\n // case the portaled content — and its ref — commits one render later. Since refs never change\n // identity, that later commit doesn't re-trigger this effect on its own, so retry each frame.\n function trySetup() {\n const trigger = triggerRef.current;\n const elements = latest.current.elementRefs;\n if (!trigger || elements.some((ref) => !ref.current)) {\n rafId = requestAnimationFrame(trySetup);\n return;\n }\n const ancestors = getScrollableAncestors(trigger);\n\n const resizeObserver = new ResizeObserver(() => runUpdate());\n const observed = new Set<Element>();\n\n function runUpdate() {\n latest.current.update(trigger!, ancestors);\n // After the update, so an element that only mounts during it is picked up too. Once\n // observed this is a handful of Set lookups, so it costs nothing on the scroll path.\n latest.current.resizeRefs?.forEach(({ current }) => {\n if (!current || observed.has(current)) return;\n observed.add(current);\n resizeObserver.observe(current);\n });\n }\n\n runUpdate();\n\n const scrollTargets: (Window | HTMLElement)[] = [window, ...ancestors];\n scrollTargets.forEach((t) => t.addEventListener('scroll', runUpdate, { passive: true }));\n window.addEventListener('resize', runUpdate);\n // `window.resize` doesn't reliably fire when a mobile keyboard opens/closes — visualViewport\n // does, and is what the callers' flip/clamp maths is measured against.\n window.visualViewport?.addEventListener('resize', runUpdate);\n window.visualViewport?.addEventListener('scroll', runUpdate);\n\n stop = () => {\n scrollTargets.forEach((t) => t.removeEventListener('scroll', runUpdate));\n window.removeEventListener('resize', runUpdate);\n window.visualViewport?.removeEventListener('resize', runUpdate);\n window.visualViewport?.removeEventListener('scroll', runUpdate);\n resizeObserver.disconnect();\n latest.current.teardown?.();\n };\n }\n\n trySetup();\n\n return () => {\n if (rafId !== undefined) cancelAnimationFrame(rafId);\n stop?.();\n };\n // Spread rather than listed: `deps` is the caller's own set of geometry inputs, and changing\n // any of them has to restart tracking so the new values get applied immediately.\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, [isOpen, triggerRef, ...deps]);\n}\n"],"mappings":"uIA0CA,SAAgB,EAAkB,CAChC,SACA,aACA,cACA,SACA,aACA,WACA,OAAO,CAAC,GACgB,CAGxB,IAAM,EAAS,EAAO,CAAE,cAAa,SAAQ,aAAY,UAAS,CAAC,EACnE,EAAO,QAAU,CAAE,cAAa,SAAQ,aAAY,UAAS,EAE7D,MAAsB,CACpB,GAAI,CAAC,EAAQ,OAEb,IAAI,EACA,EAMJ,SAAS,GAAW,CAClB,IAAM,EAAU,EAAW,QACrB,EAAW,EAAO,QAAQ,YAChC,GAAI,CAAC,GAAW,EAAS,KAAM,GAAQ,CAAC,EAAI,OAAO,EAAG,CACpD,EAAQ,sBAAsB,CAAQ,EACtC,MACF,CACA,IAAM,EAAY,EAAuB,CAAO,EAE1C,EAAiB,IAAI,mBAAqB,EAAU,CAAC,EACrD,EAAW,IAAI,IAErB,SAAS,GAAY,CACnB,EAAO,QAAQ,OAAO,EAAU,CAAS,EAGzC,EAAO,QAAQ,YAAY,SAAS,CAAE,aAAc,CAC7C,GAAW,GAAS,IAAI,CAAO,IACpC,EAAS,IAAI,CAAO,EACpB,EAAe,QAAQ,CAAO,EAChC,CAAC,CACH,CAEA,EAAU,EAEV,IAAM,EAA0C,CAAC,OAAQ,GAAG,CAAS,EACrE,EAAc,QAAS,GAAM,EAAE,iBAAiB,SAAU,EAAW,CAAE,QAAS,EAAK,CAAC,CAAC,EACvF,OAAO,iBAAiB,SAAU,CAAS,EAG3C,OAAO,gBAAgB,iBAAiB,SAAU,CAAS,EAC3D,OAAO,gBAAgB,iBAAiB,SAAU,CAAS,EAE3D,MAAa,CACX,EAAc,QAAS,GAAM,EAAE,oBAAoB,SAAU,CAAS,CAAC,EACvE,OAAO,oBAAoB,SAAU,CAAS,EAC9C,OAAO,gBAAgB,oBAAoB,SAAU,CAAS,EAC9D,OAAO,gBAAgB,oBAAoB,SAAU,CAAS,EAC9D,EAAe,WAAW,EAC1B,EAAO,QAAQ,WAAW,CAC5B,CACF,CAIA,OAFA,EAAS,MAEI,CACP,IAAU,IAAA,IAAW,qBAAqB,CAAK,EACnD,IAAO,CACT,CAIF,EAAG,CAAC,EAAQ,EAAY,GAAG,CAAI,CAAC,CAClC"}
1
+ {"version":3,"file":"useAnchorTracking.mjs","names":[],"sources":["../../../../package/common/hooks/useAnchorTracking.ts"],"sourcesContent":["import { useLayoutEffect, useRef, type RefObject } from 'react';\nimport { getScrollableAncestors } from 'components/common/utils/scrollAncestors';\n\nexport interface UseAnchorTrackingArgs {\n isOpen: boolean;\n /** The element the popup is anchored to. Its scrollable ancestors are what get tracked. */\n triggerRef: RefObject<HTMLElement | null>;\n /**\n * Every element the positioning maths needs. Tracking starts only once all of them, plus the\n * trigger, are mounted. Read fresh on each setup, so an inline array literal is fine.\n */\n elementRefs: RefObject<HTMLElement | null>[];\n /**\n * Positions the popup. Called once on setup and then on every scroll, resize and tracked size\n * change. Reads the caller's own refs for everything beyond the two arguments.\n */\n update: (trigger: HTMLElement, ancestors: HTMLElement[]) => void;\n /**\n * Elements whose own size changes must re-run `update`: a popup whose content changes size\n * while open (a filtered list shrinking, an async list loading in) otherwise keeps coordinates\n * computed for its old height and ends up detached from the trigger. Re-read on every update,\n * since a panel can mount a commit later than the element it hangs off.\n */\n resizeRefs?: RefObject<HTMLElement | null>[];\n /** Hands the popup's elements back as they were found. Runs when tracking stops. */\n teardown?: () => void;\n /** Values that must restart tracking, and so re-run `update`, when they change. */\n deps?: readonly unknown[];\n}\n\n/**\n * The lifecycle half of anchoring a portaled, `position: fixed` popup to a trigger: waits for the\n * elements to mount, resolves the trigger's scrolling ancestors once, and re-runs the caller's\n * positioning on every scroll, viewport resize and tracked size change until it closes.\n *\n * Callers supply only the geometry (`update`) and the inline styles to clear again (`teardown`);\n * `usePopoverPosition` is built on this.\n *\n * Position is deliberately written straight to the DOM by the caller rather than through React\n * state: going through setState -> re-render -> commit adds a round-trip that lags behind the\n * browser's own scroll painting by at least a frame.\n */\nexport function useAnchorTracking({\n isOpen,\n triggerRef,\n elementRefs,\n update,\n resizeRefs,\n teardown,\n deps = [],\n}: UseAnchorTrackingArgs) {\n // Latest-value refs, so the effect restarts only for `deps` the caller names, never merely\n // because these were passed as fresh array/function literals this render.\n const latest = useRef({ elementRefs, update, resizeRefs, teardown });\n latest.current = { elementRefs, update, resizeRefs, teardown };\n\n useLayoutEffect(() => {\n if (!isOpen) return;\n\n let rafId: number | undefined;\n let stop: (() => void) | undefined;\n\n // The elements may not be mounted yet on the very first invocation of this effect: a portal\n // target can still be missing (a `#bodyEnd` the app renders, or any while hydrating), and then\n // the portaled content and its ref commits one render later. Since refs never change\n // identity, that later commit doesn't re-trigger this effect on its own, so retry each frame.\n function trySetup() {\n const trigger = triggerRef.current;\n const elements = latest.current.elementRefs;\n if (!trigger || elements.some((ref) => !ref.current)) {\n rafId = requestAnimationFrame(trySetup);\n return;\n }\n const ancestors = getScrollableAncestors(trigger);\n\n const resizeObserver = new ResizeObserver(() => runUpdate());\n const observed = new Set<Element>();\n\n function runUpdate() {\n latest.current.update(trigger!, ancestors);\n // After the update, so an element that only mounts during it is picked up too. Once\n // observed this is a handful of Set lookups, so it costs nothing on the scroll path.\n latest.current.resizeRefs?.forEach(({ current }) => {\n if (!current || observed.has(current)) return;\n observed.add(current);\n resizeObserver.observe(current);\n });\n }\n\n runUpdate();\n\n const scrollTargets: (Window | HTMLElement)[] = [window, ...ancestors];\n scrollTargets.forEach((t) => t.addEventListener('scroll', runUpdate, { passive: true }));\n window.addEventListener('resize', runUpdate);\n // `window.resize` doesn't reliably fire when a mobile keyboard opens/closes — visualViewport\n // does, and is what the callers' flip/clamp maths is measured against.\n window.visualViewport?.addEventListener('resize', runUpdate);\n window.visualViewport?.addEventListener('scroll', runUpdate);\n\n stop = () => {\n scrollTargets.forEach((t) => t.removeEventListener('scroll', runUpdate));\n window.removeEventListener('resize', runUpdate);\n window.visualViewport?.removeEventListener('resize', runUpdate);\n window.visualViewport?.removeEventListener('scroll', runUpdate);\n resizeObserver.disconnect();\n latest.current.teardown?.();\n };\n }\n\n trySetup();\n\n return () => {\n if (rafId !== undefined) cancelAnimationFrame(rafId);\n stop?.();\n };\n // Spread rather than listed: `deps` is the caller's own set of geometry inputs, and changing\n // any of them has to restart tracking so the new values get applied immediately.\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, [isOpen, triggerRef, ...deps]);\n}\n"],"mappings":"uIA0CA,SAAgB,EAAkB,CAChC,SACA,aACA,cACA,SACA,aACA,WACA,OAAO,CAAC,GACgB,CAGxB,IAAM,EAAS,EAAO,CAAE,cAAa,SAAQ,aAAY,UAAS,CAAC,EACnE,EAAO,QAAU,CAAE,cAAa,SAAQ,aAAY,UAAS,EAE7D,MAAsB,CACpB,GAAI,CAAC,EAAQ,OAEb,IAAI,EACA,EAMJ,SAAS,GAAW,CAClB,IAAM,EAAU,EAAW,QACrB,EAAW,EAAO,QAAQ,YAChC,GAAI,CAAC,GAAW,EAAS,KAAM,GAAQ,CAAC,EAAI,OAAO,EAAG,CACpD,EAAQ,sBAAsB,CAAQ,EACtC,MACF,CACA,IAAM,EAAY,EAAuB,CAAO,EAE1C,EAAiB,IAAI,mBAAqB,EAAU,CAAC,EACrD,EAAW,IAAI,IAErB,SAAS,GAAY,CACnB,EAAO,QAAQ,OAAO,EAAU,CAAS,EAGzC,EAAO,QAAQ,YAAY,SAAS,CAAE,aAAc,CAC7C,GAAW,GAAS,IAAI,CAAO,IACpC,EAAS,IAAI,CAAO,EACpB,EAAe,QAAQ,CAAO,EAChC,CAAC,CACH,CAEA,EAAU,EAEV,IAAM,EAA0C,CAAC,OAAQ,GAAG,CAAS,EACrE,EAAc,QAAS,GAAM,EAAE,iBAAiB,SAAU,EAAW,CAAE,QAAS,EAAK,CAAC,CAAC,EACvF,OAAO,iBAAiB,SAAU,CAAS,EAG3C,OAAO,gBAAgB,iBAAiB,SAAU,CAAS,EAC3D,OAAO,gBAAgB,iBAAiB,SAAU,CAAS,EAE3D,MAAa,CACX,EAAc,QAAS,GAAM,EAAE,oBAAoB,SAAU,CAAS,CAAC,EACvE,OAAO,oBAAoB,SAAU,CAAS,EAC9C,OAAO,gBAAgB,oBAAoB,SAAU,CAAS,EAC9D,OAAO,gBAAgB,oBAAoB,SAAU,CAAS,EAC9D,EAAe,WAAW,EAC1B,EAAO,QAAQ,WAAW,CAC5B,CACF,CAIA,OAFA,EAAS,MAEI,CACP,IAAU,IAAA,IAAW,qBAAqB,CAAK,EACnD,IAAO,CACT,CAIF,EAAG,CAAC,EAAQ,EAAY,GAAG,CAAI,CAAC,CAClC"}
@@ -1 +1 @@
1
- {"version":3,"file":"scrollbarProvider.module.mjs","names":[],"sources":["../../../../package/common/scrollbarProvider/scrollbarProvider.module.pcss"],"sourcesContent":[".scrollbarProvider {\n display: contents;\n\n /* Firefox */\n * {\n scrollbar-width: thin;\n scrollbar-color: var(--background-accent-light) transparent;\n }\n\n /* Chrome, Edge, and Safari */\n *::-webkit-scrollbar {\n width: 12px;\n }\n\n *::-webkit-scrollbar-track {\n background: transparent;\n }\n\n *::-webkit-scrollbar-thumb {\n background-color: var(--background-accent-light);\n border-radius: 10px;\n border: 3px solid transparent;\n background-clip: padding-box;\n }\n}\n"],"mappings":""}
1
+ {"version":3,"file":"scrollbarProvider.module.mjs","names":[],"sources":["../../../../package/common/scrollbarProvider/scrollbarProvider.module.pcss"],"sourcesContent":[".scrollbarProvider {\n display: contents;\n}\n\n/* Zero specificity, so any scrollbar rule of a component's or the app's own wins. As\n `.scrollbarProvider *` these tied with a single class and won or lost on stylesheet order. */\n\n/* Firefox */\n:where(.scrollbarProvider *) {\n scrollbar-width: thin;\n scrollbar-color: var(--background-accent-light) transparent;\n}\n\n/* Chrome, Edge, and Safari */\n:where(.scrollbarProvider *)::-webkit-scrollbar {\n width: 12px;\n}\n\n:where(.scrollbarProvider *)::-webkit-scrollbar-track {\n background: transparent;\n}\n\n:where(.scrollbarProvider *)::-webkit-scrollbar-thumb {\n background-color: var(--background-accent-light);\n border-radius: 10px;\n border: 3px solid transparent;\n background-clip: padding-box;\n}\n\n/* The page's own scrollbar: <html> is outside every provider, so AhroweProvider sets the thumb\n colour on it. Without that property the value is invalid, and the browser's own stays. */\n:where(html) {\n scrollbar-color: var(--ahrowe-page-scrollbar-thumb) transparent;\n}\n"],"mappings":""}
@@ -1,2 +1,2 @@
1
- "use client";import{ThemeVariablesContext as e}from"./themeVariablesContext.mjs";import{defaultTheme as t}from"./defaultTheme.mjs";import n from"./theme.module.mjs";import r,{Component as i}from"react";import a from"classnames";import{jsx as o}from"react/jsx-runtime";function s(e,t){let n=e.find(e=>e.id===t);return n?[...n.baseThemeId?s(e,n.baseThemeId):[],n.variables]:[]}function c({themes:e=[t],currentThemeId:n}){let r=e.find(e=>!e.baseThemeId)??e[0],i=r?r.variables:{},a=n?s(e,n):[];return Object.assign({},i,...a)}var l=class extends i{constructor(...e){super(...e),this.state={resolvedVariables:c(this.props),colorFadeActive:!1,currentThemeColor:null,resolvedFrom:{themes:this.props.themes,currentThemeId:this.props.currentThemeId}},this.colorFadeTimeout=null,this.wrapperRef=r.createRef(),this.themeColorMeta=null,this.createdThemeColorMeta=!1}static getDerivedStateFromProps(e,t){let{themes:n,currentThemeId:r}=t.resolvedFrom;return e.themes===n&&e.currentThemeId===r?null:{resolvedVariables:c(e),colorFadeActive:!0,resolvedFrom:{themes:e.themes,currentThemeId:e.currentThemeId}}}componentDidMount(){this.readThemeColor()}componentDidUpdate(e,t){t.resolvedVariables!==this.state.resolvedVariables&&(this.readThemeColor(),this.colorFadeTimeout&&clearTimeout(this.colorFadeTimeout),this.colorFadeTimeout=setTimeout(()=>{this.setState({colorFadeActive:!1}),this.colorFadeTimeout=null},500)),t.currentThemeColor!==this.state.currentThemeColor&&this.syncThemeColorMeta(this.state.currentThemeColor??``)}componentWillUnmount(){this.colorFadeTimeout&&clearTimeout(this.colorFadeTimeout),this.createdThemeColorMeta&&=(this.themeColorMeta?.remove(),this.themeColorMeta=null,!1)}syncThemeColorMeta(e){if(typeof document<`u`){if(!this.themeColorMeta){let e=document.head.querySelector(`meta[name="theme-color"]`);if(e)this.themeColorMeta=e;else{let e=document.createElement(`meta`);e.setAttribute(`name`,`theme-color`),document.head.appendChild(e),this.themeColorMeta=e,this.createdThemeColorMeta=!0}}this.themeColorMeta.setAttribute(`content`,e)}}readThemeColor(){if(!this.wrapperRef.current)return;let e=getComputedStyle(this.wrapperRef.current).getPropertyValue(`--primary-color`).trim();e!==this.state.currentThemeColor&&this.setState({currentThemeColor:e})}render(){let{children:t}=this.props,{resolvedVariables:r,colorFadeActive:i}=this.state;return o(`div`,{ref:this.wrapperRef,id:`themeProvider`,className:a(n.themeRoot,i&&n.colorFade),style:r,children:o(e.Provider,{value:r,children:t})})}};export{l as default};
1
+ "use client";import{defaultTheme as e}from"./defaultTheme.mjs";import{ThemeVariablesContext as t}from"./themeVariablesContext.mjs";import n from"./theme.module.mjs";import r,{Component as i}from"react";import a from"classnames";import{jsx as o}from"react/jsx-runtime";function s(e,t){let n=e.find(e=>e.id===t);return n?[...n.baseThemeId?s(e,n.baseThemeId):[],n.variables]:[]}function c({themes:t=[e],currentThemeId:n}){let r=t.find(e=>!e.baseThemeId)??t[0],i=r?r.variables:{},a=n?s(t,n):[];return Object.assign({},i,...a)}var l=class extends i{constructor(...e){super(...e),this.state={resolvedVariables:c(this.props),colorFadeActive:!1,currentThemeColor:null,resolvedFrom:{themes:this.props.themes,currentThemeId:this.props.currentThemeId}},this.colorFadeTimeout=null,this.wrapperRef=r.createRef(),this.themeColorMeta=null,this.createdThemeColorMeta=!1}static{this.contextType=t}static getDerivedStateFromProps(e,t){let{themes:n,currentThemeId:r}=t.resolvedFrom;return e.themes===n&&e.currentThemeId===r?null:{resolvedVariables:c(e),colorFadeActive:!0,resolvedFrom:{themes:e.themes,currentThemeId:e.currentThemeId}}}componentDidMount(){this.readThemeColor()}componentDidUpdate(e,t){t.resolvedVariables!==this.state.resolvedVariables&&(this.readThemeColor(),this.colorFadeTimeout&&clearTimeout(this.colorFadeTimeout),this.colorFadeTimeout=setTimeout(()=>{this.setState({colorFadeActive:!1}),this.colorFadeTimeout=null},500)),t.currentThemeColor!==this.state.currentThemeColor&&this.syncThemeColorMeta(this.state.currentThemeColor??``)}componentWillUnmount(){this.colorFadeTimeout&&clearTimeout(this.colorFadeTimeout),this.createdThemeColorMeta&&=(this.themeColorMeta?.remove(),this.themeColorMeta=null,!1)}syncThemeColorMeta(e){if(typeof document<`u`){if(!this.themeColorMeta){let e=document.head.querySelector(`meta[name="theme-color"]`);if(e)this.themeColorMeta=e;else{let e=document.createElement(`meta`);e.setAttribute(`name`,`theme-color`),document.head.appendChild(e),this.themeColorMeta=e,this.createdThemeColorMeta=!0}}this.themeColorMeta.setAttribute(`content`,e)}}readThemeColor(){if(this.context||!this.wrapperRef.current)return;let e=getComputedStyle(this.wrapperRef.current).getPropertyValue(`--primary-color`).trim();e!==this.state.currentThemeColor&&this.setState({currentThemeColor:e})}render(){let{children:e}=this.props,{resolvedVariables:r,colorFadeActive:i}=this.state;return o(`div`,{ref:this.wrapperRef,id:`themeProvider`,className:a(n.themeRoot,i&&n.colorFade),style:r,children:o(t.Provider,{value:r,children:e})})}};export{l as default};
2
2
  //# sourceMappingURL=themeProvider.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"themeProvider.mjs","names":[],"sources":["../../../../package/common/themeProvider/themeProvider.tsx"],"sourcesContent":["import React, { Component } from 'react';\nimport cx from 'classnames';\n\nimport type { Theme } from './theme.types';\nimport { defaultTheme } from './defaultTheme';\nimport { ThemeVariablesContext } from './themeVariablesContext';\nimport styles from './theme.module.pcss';\n\ninterface Props {\n children: React.ReactNode;\n themes?: Theme[];\n currentThemeId?: string;\n}\n\ninterface State {\n resolvedVariables: Record<string, string>;\n colorFadeActive: boolean;\n currentThemeColor: string | null;\n /** What `resolvedVariables` was resolved from, to tell a theme change from any other render. */\n resolvedFrom: { themes: Theme[] | undefined; currentThemeId: string | undefined };\n}\n\n/**\n * Resolves a theme ID's variables by walking up its baseThemeId chain, ordered from deepest\n * ancestor to the requested theme so they merge in order (later entries win).\n */\nfunction resolveChain(themes: Theme[], id: string): Record<string, string>[] {\n const theme = themes.find((t) => t.id === id);\n if (!theme) return [];\n const parentVars = theme.baseThemeId ? resolveChain(themes, theme.baseThemeId) : [];\n return [...parentVars, theme.variables as Record<string, string>];\n}\n\nfunction resolveVariables({ themes = [defaultTheme], currentThemeId }: Props): Record<string, string> {\n // The root theme, the first without a baseThemeId, is always the foundation.\n const rootTheme = themes.find((t) => !t.baseThemeId) ?? themes[0];\n const rootVars = rootTheme ? (rootTheme.variables as Record<string, string>) : {};\n const chainVars = currentThemeId ? resolveChain(themes, currentThemeId) : [];\n return Object.assign({}, rootVars, ...chainVars);\n}\n\nclass ThemeProvider extends Component<Props, State> {\n // Resolved before the first render rather than after mount: resolved in componentDidMount, every\n // component rendered and measured itself unthemed first, and on each page load some 50\n // backgrounds then faded in from nothing.\n state: State = {\n resolvedVariables: resolveVariables(this.props),\n colorFadeActive: false,\n currentThemeColor: null,\n resolvedFrom: { themes: this.props.themes, currentThemeId: this.props.currentThemeId },\n };\n\n private colorFadeTimeout: ReturnType<typeof setTimeout> | null = null;\n private wrapperRef = React.createRef<HTMLDivElement>();\n private themeColorMeta: HTMLMetaElement | null = null;\n private createdThemeColorMeta = false;\n\n // The fade and the new variables have to land in the same render: added a render later, the\n // browser may already have painted the new colours without it.\n static getDerivedStateFromProps(props: Props, state: State): Partial<State> | null {\n const { themes, currentThemeId } = state.resolvedFrom;\n if (props.themes === themes && props.currentThemeId === currentThemeId) return null;\n return {\n resolvedVariables: resolveVariables(props),\n colorFadeActive: true,\n resolvedFrom: { themes: props.themes, currentThemeId: props.currentThemeId },\n };\n }\n\n componentDidMount() {\n this.readThemeColor();\n }\n\n componentDidUpdate(prevProps: Props, prevState: State) {\n if (prevState.resolvedVariables !== this.state.resolvedVariables) {\n this.readThemeColor();\n if (this.colorFadeTimeout) clearTimeout(this.colorFadeTimeout);\n this.colorFadeTimeout = setTimeout(() => {\n this.setState({ colorFadeActive: false });\n this.colorFadeTimeout = null;\n }, 500);\n }\n if (prevState.currentThemeColor !== this.state.currentThemeColor) {\n this.syncThemeColorMeta(this.state.currentThemeColor ?? '');\n }\n }\n\n componentWillUnmount() {\n if (this.colorFadeTimeout) clearTimeout(this.colorFadeTimeout);\n // Only remove the meta tag if we created it; leave a host-app tag intact.\n // Also clear the cached refs — StrictMode's dev-only mount/unmount/remount\n // cycle reuses this instance, and a stale ref would otherwise point at a\n // detached element on remount instead of re-inserting into <head>.\n if (this.createdThemeColorMeta) {\n this.themeColorMeta?.remove();\n this.themeColorMeta = null;\n this.createdThemeColorMeta = false;\n }\n }\n\n /**\n * Replaces the former react-helmet usage: keep a single\n * <meta name=\"theme-color\"> in sync with the active theme's primary colour.\n * Reuses a tag the host app already declares; otherwise creates its own\n * (and removes it again on unmount).\n */\n private syncThemeColorMeta(color: string) {\n if (typeof document === 'undefined') return;\n\n if (!this.themeColorMeta) {\n const existing = document.head.querySelector<HTMLMetaElement>('meta[name=\"theme-color\"]');\n if (existing) {\n this.themeColorMeta = existing;\n } else {\n const meta = document.createElement('meta');\n meta.setAttribute('name', 'theme-color');\n document.head.appendChild(meta);\n this.themeColorMeta = meta;\n this.createdThemeColorMeta = true;\n }\n }\n\n this.themeColorMeta.setAttribute('content', color);\n }\n\n /** `--primary-color` as the browser resolved it, which may be a `var()` of another variable. */\n private readThemeColor() {\n if (!this.wrapperRef.current) return;\n const color = getComputedStyle(this.wrapperRef.current).getPropertyValue('--primary-color').trim();\n if (color !== this.state.currentThemeColor) this.setState({ currentThemeColor: color });\n }\n\n render() {\n const { children } = this.props;\n const { resolvedVariables, colorFadeActive } = this.state;\n\n return (\n <div\n ref={this.wrapperRef}\n id='themeProvider'\n className={cx(styles.themeRoot, colorFadeActive && styles.colorFade)}\n style={resolvedVariables as React.CSSProperties}\n >\n <ThemeVariablesContext.Provider value={resolvedVariables}>{children}</ThemeVariablesContext.Provider>\n </div>\n );\n }\n}\n\nexport default ThemeProvider;\n"],"mappings":"4QA0BA,SAAS,EAAa,EAAiB,EAAsC,CAC3E,IAAM,EAAQ,EAAO,KAAM,GAAM,EAAE,KAAO,CAAE,EAG5C,OAFK,EAEE,CAAC,GADW,EAAM,YAAc,EAAa,EAAQ,EAAM,WAAW,EAAI,CAAC,EAC3D,EAAM,SAAmC,EAF7C,CAAC,CAGtB,CAEA,SAAS,EAAiB,CAAE,SAAS,CAAC,CAAY,EAAG,kBAAiD,CAEpG,IAAM,EAAY,EAAO,KAAM,GAAM,CAAC,EAAE,WAAW,GAAK,EAAO,GACzD,EAAW,EAAa,EAAU,UAAuC,CAAC,EAC1E,EAAY,EAAiB,EAAa,EAAQ,CAAc,EAAI,CAAC,EAC3E,OAAO,OAAO,OAAO,CAAC,EAAG,EAAU,GAAG,CAAS,CACjD,CAEA,IAAM,EAAN,cAA4B,CAAwB,+BAInC,KAAA,MAAA,CACb,kBAAmB,EAAiB,KAAK,KAAK,EAC9C,gBAAiB,GACjB,kBAAmB,KACnB,aAAc,CAAE,OAAQ,KAAK,MAAM,OAAQ,eAAgB,KAAK,MAAM,cAAe,CACvF,EAEiE,KAAA,iBAAA,KAC5C,KAAA,WAAA,EAAM,UAA0B,EACJ,KAAA,eAAA,KACjB,KAAA,sBAAA,GAIhC,OAAO,yBAAyB,EAAc,EAAqC,CACjF,GAAM,CAAE,SAAQ,kBAAmB,EAAM,aAEzC,OADI,EAAM,SAAW,GAAU,EAAM,iBAAmB,EAAuB,KACxE,CACL,kBAAmB,EAAiB,CAAK,EACzC,gBAAiB,GACjB,aAAc,CAAE,OAAQ,EAAM,OAAQ,eAAgB,EAAM,cAAe,CAC7E,CACF,CAEA,mBAAoB,CAClB,KAAK,eAAe,CACtB,CAEA,mBAAmB,EAAkB,EAAkB,CACjD,EAAU,oBAAsB,KAAK,MAAM,oBAC7C,KAAK,eAAe,EAChB,KAAK,kBAAkB,aAAa,KAAK,gBAAgB,EAC7D,KAAK,iBAAmB,eAAiB,CACvC,KAAK,SAAS,CAAE,gBAAiB,EAAM,CAAC,EACxC,KAAK,iBAAmB,IAC1B,EAAG,GAAG,GAEJ,EAAU,oBAAsB,KAAK,MAAM,mBAC7C,KAAK,mBAAmB,KAAK,MAAM,mBAAqB,EAAE,CAE9D,CAEA,sBAAuB,CACjB,KAAK,kBAAkB,aAAa,KAAK,gBAAgB,EAK7D,AAGE,KAAK,yBAFL,KAAK,gBAAgB,OAAO,EAC5B,KAAK,eAAiB,KACO,GAEjC,CAQA,mBAA2B,EAAe,CACpC,UAAO,SAAa,IAExB,IAAI,CAAC,KAAK,eAAgB,CACxB,IAAM,EAAW,SAAS,KAAK,cAA+B,0BAA0B,EACxF,GAAI,EACF,KAAK,eAAiB,MACjB,CACL,IAAM,EAAO,SAAS,cAAc,MAAM,EAC1C,EAAK,aAAa,OAAQ,aAAa,EACvC,SAAS,KAAK,YAAY,CAAI,EAC9B,KAAK,eAAiB,EACtB,KAAK,sBAAwB,EAC/B,CACF,CAEA,KAAK,eAAe,aAAa,UAAW,CAAK,CAFjD,CAGF,CAGA,gBAAyB,CACvB,GAAI,CAAC,KAAK,WAAW,QAAS,OAC9B,IAAM,EAAQ,iBAAiB,KAAK,WAAW,OAAO,CAAC,CAAC,iBAAiB,iBAAiB,CAAC,CAAC,KAAK,EAC7F,IAAU,KAAK,MAAM,mBAAmB,KAAK,SAAS,CAAE,kBAAmB,CAAM,CAAC,CACxF,CAEA,QAAS,CACP,GAAM,CAAE,YAAa,KAAK,MACpB,CAAE,oBAAmB,mBAAoB,KAAK,MAEpD,OACE,EAAC,MAAD,CACE,IAAK,KAAK,WACV,GAAG,gBACH,UAAW,EAAG,EAAO,UAAW,GAAmB,EAAO,SAAS,EACnE,MAAO,EAEP,SAAA,EAAC,EAAsB,SAAvB,CAAgC,MAAO,EAAoB,UAAyC,CAAA,CACjG,CAAA,CAET,CACF"}
1
+ {"version":3,"file":"themeProvider.mjs","names":[],"sources":["../../../../package/common/themeProvider/themeProvider.tsx"],"sourcesContent":["import React, { Component } from 'react';\nimport cx from 'classnames';\n\nimport type { Theme, ThemeProviderProps } from './theme.types';\nimport { defaultTheme } from './defaultTheme';\nimport { ThemeVariablesContext } from './themeVariablesContext';\nimport styles from './theme.module.pcss';\n\ninterface State {\n resolvedVariables: Record<string, string>;\n colorFadeActive: boolean;\n currentThemeColor: string | null;\n /** What `resolvedVariables` was resolved from, to tell a theme change from any other render. */\n resolvedFrom: { themes: Theme[] | undefined; currentThemeId: string | undefined };\n}\n\n/**\n * Resolves a theme ID's variables by walking up its baseThemeId chain, ordered from deepest\n * ancestor to the requested theme so they merge in order (later entries win).\n */\nfunction resolveChain(themes: Theme[], id: string): Record<string, string>[] {\n const theme = themes.find((t) => t.id === id);\n if (!theme) return [];\n const parentVars = theme.baseThemeId ? resolveChain(themes, theme.baseThemeId) : [];\n return [...parentVars, theme.variables as Record<string, string>];\n}\n\nfunction resolveVariables({ themes = [defaultTheme], currentThemeId }: ThemeProviderProps): Record<string, string> {\n // The root theme, the first without a baseThemeId, is always the foundation.\n const rootTheme = themes.find((t) => !t.baseThemeId) ?? themes[0];\n const rootVars = rootTheme ? (rootTheme.variables as Record<string, string>) : {};\n const chainVars = currentThemeId ? resolveChain(themes, currentThemeId) : [];\n return Object.assign({}, rootVars, ...chainVars);\n}\n\nclass ThemeProvider extends Component<ThemeProviderProps, State> {\n // A nested provider themes a section, not the page, so it leaves the page's `theme-color` alone.\n static contextType = ThemeVariablesContext;\n declare context: React.ContextType<typeof ThemeVariablesContext>;\n\n // Resolved before the first render rather than after mount: resolved in componentDidMount, every\n // component rendered and measured itself unthemed first, and on each page load some 50\n // backgrounds then faded in from nothing.\n state: State = {\n resolvedVariables: resolveVariables(this.props),\n colorFadeActive: false,\n currentThemeColor: null,\n resolvedFrom: { themes: this.props.themes, currentThemeId: this.props.currentThemeId },\n };\n\n private colorFadeTimeout: ReturnType<typeof setTimeout> | null = null;\n private wrapperRef = React.createRef<HTMLDivElement>();\n private themeColorMeta: HTMLMetaElement | null = null;\n private createdThemeColorMeta = false;\n\n // The fade and the new variables have to land in the same render: added a render later, the\n // browser may already have painted the new colours without it.\n static getDerivedStateFromProps(props: ThemeProviderProps, state: State): Partial<State> | null {\n const { themes, currentThemeId } = state.resolvedFrom;\n if (props.themes === themes && props.currentThemeId === currentThemeId) return null;\n return {\n resolvedVariables: resolveVariables(props),\n colorFadeActive: true,\n resolvedFrom: { themes: props.themes, currentThemeId: props.currentThemeId },\n };\n }\n\n componentDidMount() {\n this.readThemeColor();\n }\n\n componentDidUpdate(prevProps: ThemeProviderProps, prevState: State) {\n if (prevState.resolvedVariables !== this.state.resolvedVariables) {\n this.readThemeColor();\n if (this.colorFadeTimeout) clearTimeout(this.colorFadeTimeout);\n this.colorFadeTimeout = setTimeout(() => {\n this.setState({ colorFadeActive: false });\n this.colorFadeTimeout = null;\n }, 500);\n }\n if (prevState.currentThemeColor !== this.state.currentThemeColor) {\n this.syncThemeColorMeta(this.state.currentThemeColor ?? '');\n }\n }\n\n componentWillUnmount() {\n if (this.colorFadeTimeout) clearTimeout(this.colorFadeTimeout);\n // Only remove the meta tag if we created it; leave a host-app tag intact.\n // Also clear the cached refs — StrictMode's dev-only mount/unmount/remount\n // cycle reuses this instance, and a stale ref would otherwise point at a\n // detached element on remount instead of re-inserting into <head>.\n if (this.createdThemeColorMeta) {\n this.themeColorMeta?.remove();\n this.themeColorMeta = null;\n this.createdThemeColorMeta = false;\n }\n }\n\n /**\n * Replaces the former react-helmet usage: keep a single\n * <meta name=\"theme-color\"> in sync with the active theme's primary colour.\n * Reuses a tag the host app already declares; otherwise creates its own\n * (and removes it again on unmount).\n */\n private syncThemeColorMeta(color: string) {\n if (typeof document === 'undefined') return;\n\n if (!this.themeColorMeta) {\n const existing = document.head.querySelector<HTMLMetaElement>('meta[name=\"theme-color\"]');\n if (existing) {\n this.themeColorMeta = existing;\n } else {\n const meta = document.createElement('meta');\n meta.setAttribute('name', 'theme-color');\n document.head.appendChild(meta);\n this.themeColorMeta = meta;\n this.createdThemeColorMeta = true;\n }\n }\n\n this.themeColorMeta.setAttribute('content', color);\n }\n\n /** `--primary-color` as the browser resolved it, which may be a `var()` of another variable. */\n private readThemeColor() {\n if (this.context || !this.wrapperRef.current) return;\n const color = getComputedStyle(this.wrapperRef.current).getPropertyValue('--primary-color').trim();\n if (color !== this.state.currentThemeColor) this.setState({ currentThemeColor: color });\n }\n\n render() {\n const { children } = this.props;\n const { resolvedVariables, colorFadeActive } = this.state;\n\n return (\n <div\n ref={this.wrapperRef}\n id='themeProvider'\n className={cx(styles.themeRoot, colorFadeActive && styles.colorFade)}\n style={resolvedVariables as React.CSSProperties}\n >\n <ThemeVariablesContext.Provider value={resolvedVariables}>{children}</ThemeVariablesContext.Provider>\n </div>\n );\n }\n}\n\nexport default ThemeProvider;\n"],"mappings":"4QAoBA,SAAS,EAAa,EAAiB,EAAsC,CAC3E,IAAM,EAAQ,EAAO,KAAM,GAAM,EAAE,KAAO,CAAE,EAG5C,OAFK,EAEE,CAAC,GADW,EAAM,YAAc,EAAa,EAAQ,EAAM,WAAW,EAAI,CAAC,EAC3D,EAAM,SAAmC,EAF7C,CAAC,CAGtB,CAEA,SAAS,EAAiB,CAAE,SAAS,CAAC,CAAY,EAAG,kBAA8D,CAEjH,IAAM,EAAY,EAAO,KAAM,GAAM,CAAC,EAAE,WAAW,GAAK,EAAO,GACzD,EAAW,EAAa,EAAU,UAAuC,CAAC,EAC1E,EAAY,EAAiB,EAAa,EAAQ,CAAc,EAAI,CAAC,EAC3E,OAAO,OAAO,OAAO,CAAC,EAAG,EAAU,GAAG,CAAS,CACjD,CAEA,IAAM,EAAN,cAA4B,CAAqC,+BAQhD,KAAA,MAAA,CACb,kBAAmB,EAAiB,KAAK,KAAK,EAC9C,gBAAiB,GACjB,kBAAmB,KACnB,aAAc,CAAE,OAAQ,KAAK,MAAM,OAAQ,eAAgB,KAAK,MAAM,cAAe,CACvF,EAEiE,KAAA,iBAAA,KAC5C,KAAA,WAAA,EAAM,UAA0B,EACJ,KAAA,eAAA,KACjB,KAAA,sBAAA,UAhBX,KAAA,YAAA,EAoBrB,OAAO,yBAAyB,EAA2B,EAAqC,CAC9F,GAAM,CAAE,SAAQ,kBAAmB,EAAM,aAEzC,OADI,EAAM,SAAW,GAAU,EAAM,iBAAmB,EAAuB,KACxE,CACL,kBAAmB,EAAiB,CAAK,EACzC,gBAAiB,GACjB,aAAc,CAAE,OAAQ,EAAM,OAAQ,eAAgB,EAAM,cAAe,CAC7E,CACF,CAEA,mBAAoB,CAClB,KAAK,eAAe,CACtB,CAEA,mBAAmB,EAA+B,EAAkB,CAC9D,EAAU,oBAAsB,KAAK,MAAM,oBAC7C,KAAK,eAAe,EAChB,KAAK,kBAAkB,aAAa,KAAK,gBAAgB,EAC7D,KAAK,iBAAmB,eAAiB,CACvC,KAAK,SAAS,CAAE,gBAAiB,EAAM,CAAC,EACxC,KAAK,iBAAmB,IAC1B,EAAG,GAAG,GAEJ,EAAU,oBAAsB,KAAK,MAAM,mBAC7C,KAAK,mBAAmB,KAAK,MAAM,mBAAqB,EAAE,CAE9D,CAEA,sBAAuB,CACjB,KAAK,kBAAkB,aAAa,KAAK,gBAAgB,EAK7D,AAGE,KAAK,yBAFL,KAAK,gBAAgB,OAAO,EAC5B,KAAK,eAAiB,KACO,GAEjC,CAQA,mBAA2B,EAAe,CACpC,UAAO,SAAa,IAExB,IAAI,CAAC,KAAK,eAAgB,CACxB,IAAM,EAAW,SAAS,KAAK,cAA+B,0BAA0B,EACxF,GAAI,EACF,KAAK,eAAiB,MACjB,CACL,IAAM,EAAO,SAAS,cAAc,MAAM,EAC1C,EAAK,aAAa,OAAQ,aAAa,EACvC,SAAS,KAAK,YAAY,CAAI,EAC9B,KAAK,eAAiB,EACtB,KAAK,sBAAwB,EAC/B,CACF,CAEA,KAAK,eAAe,aAAa,UAAW,CAAK,CAFjD,CAGF,CAGA,gBAAyB,CACvB,GAAI,KAAK,SAAW,CAAC,KAAK,WAAW,QAAS,OAC9C,IAAM,EAAQ,iBAAiB,KAAK,WAAW,OAAO,CAAC,CAAC,iBAAiB,iBAAiB,CAAC,CAAC,KAAK,EAC7F,IAAU,KAAK,MAAM,mBAAmB,KAAK,SAAS,CAAE,kBAAmB,CAAM,CAAC,CACxF,CAEA,QAAS,CACP,GAAM,CAAE,YAAa,KAAK,MACpB,CAAE,oBAAmB,mBAAoB,KAAK,MAEpD,OACE,EAAC,MAAD,CACE,IAAK,KAAK,WACV,GAAG,gBACH,UAAW,EAAG,EAAO,UAAW,GAAmB,EAAO,SAAS,EACnE,MAAO,EAEP,SAAA,EAAC,EAAsB,SAAvB,CAAgC,MAAO,EAAoB,UAAyC,CAAA,CACjG,CAAA,CAET,CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"themeVariablesContext.mjs","names":[],"sources":["../../../../package/common/themeProvider/themeVariablesContext.ts"],"sourcesContent":["import { createContext } from 'react';\n\n/**\n * The nearest ThemeProvider's resolved variables, for content that renders outside its DOM\n * subtree. Theme variables reach a component by CSS inheritance, which a portal breaks: a\n * `BodyEnd` child lands wherever `#bodyEnd` happens to be, so it picks up whatever provider\n * wraps *that* node rather than the one it was written under.\n *\n * Deliberately its own module rather than a member of `themeProvider.tsx`: BodyEnd needs the\n * context and nothing else, and importing the component for it would pull the whole\n * ThemeProvider into every bundle that uses a portal.\n */\nexport const ThemeVariablesContext = createContext<Record<string, string> | null>(null);\n"],"mappings":"mDAYA,IAAa,EAAwB,EAA6C,IAAI"}
1
+ {"version":3,"file":"themeVariablesContext.mjs","names":[],"sources":["../../../../package/common/themeProvider/themeVariablesContext.ts"],"sourcesContent":["import { createContext } from 'react';\n\n/**\n * The nearest ThemeProvider's resolved variables, for content that renders outside its DOM\n * subtree. Theme variables reach a component by CSS inheritance, which a portal breaks: a\n * `BodyEnd` child lands wherever the portal target is, so it picks up whatever provider wraps\n * *that* node rather than the one it was written under.\n *\n * Deliberately its own module rather than a member of `themeProvider.tsx`: BodyEnd needs the\n * context and nothing else, and importing the component for it would pull the whole\n * ThemeProvider into every bundle that uses a portal.\n */\nexport const ThemeVariablesContext = createContext<Record<string, string> | null>(null);\n"],"mappings":"mDAYA,IAAa,EAAwB,EAA6C,IAAI"}
@@ -0,0 +1,2 @@
1
+ "use client";import e from"../bodyEnd/bodyEnd.mjs";import t from"./toast.module.mjs";import n from"./toast.mjs";import{_registerToastHandler as r,_unregisterToastHandler as i}from"./toastService.mjs";import{useCallback as a,useInsertionEffect as o,useRef as s,useState as c}from"react";import l from"classnames";import{Fragment as u,jsx as d,jsxs as f}from"react/jsx-runtime";var p={position:`top-right`,duration:4e3,showRemainingTime:!0,showCloseButton:!0,allowStacking:!0,pauseOnHover:!0,labels:{close:`Close`}},m=220;function h(e){return t[`container-${e}`]}function g({children:g,position:_=p.position,duration:v=p.duration,showRemainingTime:y=p.showRemainingTime,showCloseButton:b=p.showCloseButton,allowStacking:x=p.allowStacking,pauseOnHover:S=p.pauseOnHover,labels:C}){let[w,T]=c([]),E=s(0),D=a(e=>{T(t=>t.map(t=>t.id===e?{...t,dismissing:!0}:t)),setTimeout(()=>{T(t=>t.filter(t=>t.id!==e))},m)},[]),O=a((e,t)=>{let n={position:t?.position??_,duration:t?.duration??v,showRemainingTime:t?.showRemainingTime??y,showCloseButton:t?.showCloseButton??b,pauseOnHover:t?.pauseOnHover??S,type:t?.type??`default`,classNames:t?.classNames,styles:t?.styles};E.current+=1;let r={id:`toast-${E.current}`,content:e,options:n,dismissing:!1,labels:t?.labels};T(e=>!x&&e.length>0?(setTimeout(()=>{T([r])},m),e.map(e=>({...e,dismissing:!0}))):[...e,r])},[_,v,y,b,x,S]),k=s(O);o(()=>{k.current=O},[O]),o(()=>{let e=(e,t)=>k.current(e,t);return r(e),()=>i(e)},[]);let A=w.reduce((e,t)=>{let n=t.options.position;return e[n]||(e[n]=[]),e[n].push(t),e},{});return f(u,{children:[g,d(e,{children:Object.entries(A).map(([e,r])=>d(`div`,{className:l(t.container,h(e)),children:r.map(t=>d(n,{item:t,isBottom:e.startsWith(`bottom`),onDismiss:D,labels:{...p.labels,...C,...t.labels}},t.id))},e))})]})}export{g as default};
2
+ //# sourceMappingURL=toastHost.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toastHost.mjs","names":[],"sources":["../../../../package/common/toast/toastHost.tsx"],"sourcesContent":["import React, { useCallback, useInsertionEffect, useRef, useState } from 'react';\nimport cx from 'classnames';\nimport BodyEnd from 'components/common/bodyEnd';\n\nimport styles from './toast.module.pcss';\nimport Toast from './toast';\nimport { _registerToastHandler, _unregisterToastHandler } from './toastService';\nimport type {\n ToastContextConfig,\n ToastItem,\n ToastOptions,\n ResolvedToastOptions,\n ToastPosition,\n ToastLabels,\n ToastHostProps,\n} from './toast.types';\nimport type { ReactNode } from 'react';\n\nconst DEFAULTS: Required<ToastContextConfig> = {\n position: 'top-right',\n duration: 4000,\n showRemainingTime: true,\n showCloseButton: true,\n allowStacking: true,\n pauseOnHover: true,\n labels: { close: 'Close' },\n};\n\nconst DISMISS_ANIMATION_MS = 220;\n\nfunction positionToClass(position: ToastPosition): string {\n return styles[`container-${position}`];\n}\n\nfunction ToastHost({\n children,\n position = DEFAULTS.position,\n duration = DEFAULTS.duration,\n showRemainingTime = DEFAULTS.showRemainingTime,\n showCloseButton = DEFAULTS.showCloseButton,\n allowStacking = DEFAULTS.allowStacking,\n pauseOnHover = DEFAULTS.pauseOnHover,\n labels,\n}: ToastHostProps) {\n const [toasts, setToasts] = useState<ToastItem[]>([]);\n const counterRef = useRef(0);\n\n const dismiss = useCallback((id: string) => {\n setToasts((prev) => prev.map((t) => (t.id === id ? { ...t, dismissing: true } : t)));\n setTimeout(() => {\n setToasts((prev) => prev.filter((t) => t.id !== id));\n }, DISMISS_ANIMATION_MS);\n }, []);\n\n const addToast = useCallback(\n (content: ReactNode, options?: ToastOptions) => {\n const resolved: ResolvedToastOptions = {\n position: options?.position ?? position,\n duration: options?.duration ?? duration,\n showRemainingTime: options?.showRemainingTime ?? showRemainingTime,\n showCloseButton: options?.showCloseButton ?? showCloseButton,\n pauseOnHover: options?.pauseOnHover ?? pauseOnHover,\n type: options?.type ?? 'default',\n classNames: options?.classNames,\n styles: options?.styles,\n };\n\n counterRef.current += 1;\n const id = `toast-${counterRef.current}`;\n const newToast: ToastItem = { id, content, options: resolved, dismissing: false, labels: options?.labels };\n\n setToasts((prev) => {\n if (!allowStacking && prev.length > 0) {\n // Mark all existing as dismissing, then replace\n setTimeout(() => {\n setToasts([newToast]);\n }, DISMISS_ANIMATION_MS);\n return prev.map((t) => ({ ...t, dismissing: true }));\n }\n return [...prev, newToast];\n });\n },\n [position, duration, showRemainingTime, showCloseButton, allowStacking, pauseOnHover],\n );\n\n // Insertion effects run before every layout and passive effect, including the app's, which as\n // this host's descendants would otherwise run first. Registered once, so that a settings change\n // does not move this host to the end of the list.\n const latestAddToast = useRef(addToast);\n useInsertionEffect(() => {\n latestAddToast.current = addToast;\n }, [addToast]);\n\n useInsertionEffect(() => {\n const show: typeof addToast = (content, options) => latestAddToast.current(content, options);\n _registerToastHandler(show);\n return () => _unregisterToastHandler(show);\n }, []);\n\n // Group toasts by position so per-toast position overrides work correctly\n const byPosition = toasts.reduce<Record<string, ToastItem[]>>((acc, t) => {\n const pos = t.options.position;\n if (!acc[pos]) acc[pos] = [];\n acc[pos].push(t);\n return acc;\n }, {});\n\n return (\n <>\n {children}\n <BodyEnd>\n {Object.entries(byPosition).map(([pos, items]) => (\n <div key={pos} className={cx(styles.container, positionToClass(pos as typeof position))}>\n {items.map((item) => (\n <Toast\n key={item.id}\n item={item}\n isBottom={pos.startsWith('bottom')}\n onDismiss={dismiss}\n labels={{ ...DEFAULTS.labels, ...labels, ...item.labels } as Required<ToastLabels>}\n />\n ))}\n </div>\n ))}\n </BodyEnd>\n </>\n );\n}\n\nexport default ToastHost;\n"],"mappings":"wXAkBA,IAAM,EAAyC,CAC7C,SAAU,YACV,SAAU,IACV,kBAAmB,GACnB,gBAAiB,GACjB,cAAe,GACf,aAAc,GACd,OAAQ,CAAE,MAAO,OAAQ,CAC3B,EAEM,EAAuB,IAE7B,SAAS,EAAgB,EAAiC,CACxD,OAAO,EAAO,aAAa,IAC7B,CAEA,SAAS,EAAU,CACjB,WACA,WAAW,EAAS,SACpB,WAAW,EAAS,SACpB,oBAAoB,EAAS,kBAC7B,kBAAkB,EAAS,gBAC3B,gBAAgB,EAAS,cACzB,eAAe,EAAS,aACxB,UACiB,CACjB,GAAM,CAAC,EAAQ,GAAa,EAAsB,CAAC,CAAC,EAC9C,EAAa,EAAO,CAAC,EAErB,EAAU,EAAa,GAAe,CAC1C,EAAW,GAAS,EAAK,IAAK,GAAO,EAAE,KAAO,EAAK,CAAE,GAAG,EAAG,WAAY,EAAK,EAAI,CAAE,CAAC,EACnF,eAAiB,CACf,EAAW,GAAS,EAAK,OAAQ,GAAM,EAAE,KAAO,CAAE,CAAC,CACrD,EAAG,CAAoB,CACzB,EAAG,CAAC,CAAC,EAEC,EAAW,GACd,EAAoB,IAA2B,CAC9C,IAAM,EAAiC,CACrC,SAAU,GAAS,UAAY,EAC/B,SAAU,GAAS,UAAY,EAC/B,kBAAmB,GAAS,mBAAqB,EACjD,gBAAiB,GAAS,iBAAmB,EAC7C,aAAc,GAAS,cAAgB,EACvC,KAAM,GAAS,MAAQ,UACvB,WAAY,GAAS,WACrB,OAAQ,GAAS,MACnB,EAEA,EAAW,SAAW,EAEtB,IAAM,EAAsB,CAAE,GAAA,SADV,EAAW,UACG,UAAS,QAAS,EAAU,WAAY,GAAO,OAAQ,GAAS,MAAO,EAEzG,EAAW,GACL,CAAC,GAAiB,EAAK,OAAS,GAElC,eAAiB,CACf,EAAU,CAAC,CAAQ,CAAC,CACtB,EAAG,CAAoB,EAChB,EAAK,IAAK,IAAO,CAAE,GAAG,EAAG,WAAY,EAAK,EAAE,GAE9C,CAAC,GAAG,EAAM,CAAQ,CAC1B,CACH,EACA,CAAC,EAAU,EAAU,EAAmB,EAAiB,EAAe,CAAY,CACtF,EAKM,EAAiB,EAAO,CAAQ,EACtC,MAAyB,CACvB,EAAe,QAAU,CAC3B,EAAG,CAAC,CAAQ,CAAC,EAEb,MAAyB,CACvB,IAAM,GAAyB,EAAS,IAAY,EAAe,QAAQ,EAAS,CAAO,EAE3F,OADA,EAAsB,CAAI,MACb,EAAwB,CAAI,CAC3C,EAAG,CAAC,CAAC,EAGL,IAAM,EAAa,EAAO,QAAqC,EAAK,IAAM,CACxE,IAAM,EAAM,EAAE,QAAQ,SAGtB,OAFK,EAAI,KAAM,EAAI,GAAO,CAAC,GAC3B,EAAI,EAAI,CAAC,KAAK,CAAC,EACR,CACT,EAAG,CAAC,CAAC,EAEL,OACE,EAAA,EAAA,CAAA,SAAA,CACG,EACD,EAAC,EAAD,CAAA,SACG,OAAO,QAAQ,CAAU,CAAC,CAAC,KAAK,CAAC,EAAK,KACrC,EAAC,MAAD,CAAe,UAAW,EAAG,EAAO,UAAW,EAAgB,CAAsB,CAAC,EACnF,SAAA,EAAM,IAAK,GACV,EAAC,EAAD,CAEQ,OACN,SAAU,EAAI,WAAW,QAAQ,EACjC,UAAW,EACX,OAAQ,CAAE,GAAG,EAAS,OAAQ,GAAG,EAAQ,GAAG,EAAK,MAAO,CACzD,EALM,EAAK,EAKX,CACF,CACE,EAVK,CAUL,CACN,CACM,CAAA,CACT,CAAA,CAAA,CAEN"}
@@ -1,2 +1,2 @@
1
- "use client";var e=null;function t(t){e=t}function n(){e=null}function r(t,n){if(!e){console.error(`[Toast] No ToastProvider found. Wrap your app with <ToastProvider>.`);return}e(t,n)}export{t as _registerToastHandler,n as _unregisterToastHandler,r as showToast};
1
+ "use client";var e=[];function t(t){e.length>0&&console.warn(`[Toast] More than one outermost AhroweProvider is mounted, and toasts show in only one of them. Keep one at the root and nest any other inside it.`),e.push(t)}function n(t){let n=e.indexOf(t);n!==-1&&e.splice(n,1)}function r(t,n){let r=e[e.length-1];if(!r){console.error(`[Toast] No AhroweProvider found. Wrap your app in <AhroweProvider>.`);return}r(t,n)}export{t as _registerToastHandler,n as _unregisterToastHandler,r as showToast};
2
2
  //# sourceMappingURL=toastService.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"toastService.mjs","names":[],"sources":["../../../../package/common/toast/toastService.ts"],"sourcesContent":["import type { ReactNode } from 'react';\nimport type { ToastOptions } from './toast.types';\n\ntype ShowToastFn = (content: ReactNode, options?: ToastOptions) => void;\n\nlet _handler: ShowToastFn | null = null;\n\nexport function _registerToastHandler(fn: ShowToastFn): void {\n _handler = fn;\n}\n\nexport function _unregisterToastHandler(): void {\n _handler = null;\n}\n\nexport function showToast(content: ReactNode, options?: ToastOptions): void {\n if (!_handler) {\n console.error('[Toast] No ToastProvider found. Wrap your app with <ToastProvider>.');\n return;\n }\n _handler(content, options);\n}\n"],"mappings":"aAKA,IAAI,EAA+B,KAEnC,SAAgB,EAAsB,EAAuB,CAC3D,EAAW,CACb,CAEA,SAAgB,GAAgC,CAC9C,EAAW,IACb,CAEA,SAAgB,EAAU,EAAoB,EAA8B,CAC1E,GAAI,CAAC,EAAU,CACb,QAAQ,MAAM,qEAAqE,EACnF,MACF,CACA,EAAS,EAAS,CAAO,CAC3B"}
1
+ {"version":3,"file":"toastService.mjs","names":[],"sources":["../../../../package/common/toast/toastService.ts"],"sourcesContent":["import type { ReactNode } from 'react';\nimport type { ToastOptions } from './toast.types';\n\ntype ShowToastFn = (content: ReactNode, options?: ToastOptions) => void;\n\n// A list, so that when the host registered last unmounts, the one before it takes over again.\nconst handlers: ShowToastFn[] = [];\n\nexport function _registerToastHandler(fn: ShowToastFn): void {\n if (handlers.length > 0) {\n console.warn(\n '[Toast] More than one outermost AhroweProvider is mounted, and toasts show in only one of them. Keep one at the root and nest any other inside it.',\n );\n }\n handlers.push(fn);\n}\n\nexport function _unregisterToastHandler(fn: ShowToastFn): void {\n const index = handlers.indexOf(fn);\n if (index !== -1) handlers.splice(index, 1);\n}\n\nexport function showToast(content: ReactNode, options?: ToastOptions): void {\n const handler = handlers[handlers.length - 1];\n if (!handler) {\n console.error('[Toast] No AhroweProvider found. Wrap your app in <AhroweProvider>.');\n return;\n }\n handler(content, options);\n}\n"],"mappings":"aAMA,IAAM,EAA0B,CAAC,EAEjC,SAAgB,EAAsB,EAAuB,CACvD,EAAS,OAAS,GACpB,QAAQ,KACN,oJACF,EAEF,EAAS,KAAK,CAAE,CAClB,CAEA,SAAgB,EAAwB,EAAuB,CAC7D,IAAM,EAAQ,EAAS,QAAQ,CAAE,EAC7B,IAAU,IAAI,EAAS,OAAO,EAAO,CAAC,CAC5C,CAEA,SAAgB,EAAU,EAAoB,EAA8B,CAC1E,IAAM,EAAU,EAAS,EAAS,OAAS,GAC3C,GAAI,CAAC,EAAS,CACZ,QAAQ,MAAM,qEAAqE,EACnF,MACF,CACA,EAAQ,EAAS,CAAO,CAC1B"}
@@ -1 +1 @@
1
- {"version":3,"file":"tooltip.module.mjs","names":[],"sources":["../../../../package/common/tooltip/tooltip.module.pcss"],"sourcesContent":["/* Both bubbles are portaled to the end of the document and placed by\n `usePopoverPosition`, which writes `top`, `left`, `visibility` and\n `data-placement` straight onto them. */\n.tooltipBubble,\n.tooltipError {\n position: fixed;\n z-index: 10;\n width: max-content;\n\n /* So a max-width is the width you actually get, padding included. Without it the\n screen cap below let the box out by its own padding. */\n box-sizing: border-box;\n\n /* Hidden until the first coordinates are written, to avoid a top-left flash. */\n visibility: hidden;\n}\n\n.tooltipBubble {\n /* Resolved on the bubble so it, its arrow and the badge cannot drift apart. The\n default is an inverse of the theme, not a fixed slate: a tooltip has to separate\n from the page it floats over, and a dark bubble sat almost invisibly on a dark\n theme. */\n --custom-tooltip-background: var(--tooltip-background, color-mix(in oklab, var(--text-color) 82%, var(--background)));\n --custom-tooltip-color: var(--tooltip-color, var(--background));\n\n /* The lengths here are em against this bubble's own 0.75em, so the ratios are of 12px,\n not 16, and max-width is a line measure rather than a pixel cap. */\n border-radius: 0.25em;\n\n /* Padding included, per the border-box above. The second term is the screen, with\n the gutter `usePopoverPosition` keeps when it shifts a panel back on-screen: a\n bubble wider than that cannot be clamped into view, only pushed off one edge,\n so it wraps instead. */\n max-width: min(16.667em, calc(100vw - 16px));\n font-size: 0.75em;\n background-color: var(--custom-tooltip-background);\n padding: 0.5em 0.75em;\n color: var(--custom-tooltip-color);\n pointer-events: none;\n\n &::after {\n content: '';\n position: absolute;\n top: 100%;\n\n /* The bubble's leading edge is the trigger's, so half the trigger's width is the\n trigger's centre. The second term must stay half the arrow's own width, or it\n stops pointing at it. */\n left: calc(var(--tooltip-trigger-width, 10px) / 2 - 0.4167em);\n border-width: 0.4167em;\n border-style: solid;\n border-color: var(--custom-tooltip-background) transparent transparent;\n }\n\n &[data-placement='bottom']::after {\n top: auto;\n bottom: 100%;\n border-color: transparent transparent var(--custom-tooltip-background);\n }\n}\n\n.tooltipDetails {\n /* Repeated from .tooltipBubble above: the badge is a second root, rendered without one. */\n --custom-tooltip-background: var(--tooltip-background, color-mix(in oklab, var(--text-color) 82%, var(--background)));\n --custom-tooltip-color: var(--tooltip-color, var(--background));\n\n position: relative;\n display: inline-flex;\n align-items: center;\n\n &-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n\n /* The theme's base, not a multiple of the font below: that landed a fraction of a pixel short. */\n width: var(--default-font-size);\n height: var(--default-font-size);\n border-radius: 50%;\n\n /* calc, not em: this badge renders inline wherever the consumer puts it, so em would\n measure that text. The bubble above can use em, being portaled to #bodyEnd. */\n font-size: calc(var(--default-font-size) * 0.6875);\n font-weight: 700;\n background-color: var(--custom-tooltip-background);\n color: var(--custom-tooltip-color);\n cursor: default;\n user-select: none;\n }\n}\n\n.tooltipError {\n border-radius: 0.3em;\n\n /* No measure of its own: a validator's message is as long as it is. The screen is\n still the limit, though, the same one the bubble above takes. */\n max-width: calc(100vw - 16px);\n background-color: var(--error-lighter);\n color: var(--error-color);\n font-size: 0.625em;\n padding: 0.6em 0.9em;\n\n &::after {\n content: '';\n position: absolute;\n top: 100%;\n right: 1.2em;\n border-width: 0.5em;\n border-style: solid;\n border-color: var(--error-border-color) transparent transparent transparent;\n }\n\n &[data-placement='bottom']::after {\n top: auto;\n bottom: 100%;\n border-color: transparent transparent var(--error-border-color) transparent;\n }\n}\n"],"mappings":""}
1
+ {"version":3,"file":"tooltip.module.mjs","names":[],"sources":["../../../../package/common/tooltip/tooltip.module.pcss"],"sourcesContent":["/* Both bubbles are portaled to the end of the document and placed by\n `usePopoverPosition`, which writes `top`, `left`, `visibility` and\n `data-placement` straight onto them. */\n.tooltipBubble,\n.tooltipError {\n position: fixed;\n z-index: 10;\n width: max-content;\n\n /* So a max-width is the width you actually get, padding included. Without it the\n screen cap below let the box out by its own padding. */\n box-sizing: border-box;\n\n /* Hidden until the first coordinates are written, to avoid a top-left flash. */\n visibility: hidden;\n}\n\n.tooltipBubble {\n /* Resolved on the bubble so it, its arrow and the badge cannot drift apart. The\n default is an inverse of the theme, not a fixed slate: a tooltip has to separate\n from the page it floats over, and a dark bubble sat almost invisibly on a dark\n theme. */\n --custom-tooltip-background: var(--tooltip-background, color-mix(in oklab, var(--text-color) 82%, var(--background)));\n --custom-tooltip-color: var(--tooltip-color, var(--background));\n\n /* The lengths here are em against this bubble's own 0.75em, so the ratios are of 12px,\n not 16, and max-width is a line measure rather than a pixel cap. */\n border-radius: 0.25em;\n\n /* Padding included, per the border-box above. The second term is the screen, with\n the gutter `usePopoverPosition` keeps when it shifts a panel back on-screen: a\n bubble wider than that cannot be clamped into view, only pushed off one edge,\n so it wraps instead. */\n max-width: min(16.667em, calc(100vw - 16px));\n font-size: 0.75em;\n background-color: var(--custom-tooltip-background);\n padding: 0.5em 0.75em;\n color: var(--custom-tooltip-color);\n pointer-events: none;\n\n &::after {\n content: '';\n position: absolute;\n top: 100%;\n\n /* The bubble's leading edge is the trigger's, so half the trigger's width is the\n trigger's centre. The second term must stay half the arrow's own width, or it\n stops pointing at it. */\n left: calc(var(--tooltip-trigger-width, 10px) / 2 - 0.4167em);\n border-width: 0.4167em;\n border-style: solid;\n border-color: var(--custom-tooltip-background) transparent transparent;\n }\n\n &[data-placement='bottom']::after {\n top: auto;\n bottom: 100%;\n border-color: transparent transparent var(--custom-tooltip-background);\n }\n}\n\n.tooltipDetails {\n /* Repeated from .tooltipBubble above: the badge is a second root, rendered without one. */\n --custom-tooltip-background: var(--tooltip-background, color-mix(in oklab, var(--text-color) 82%, var(--background)));\n --custom-tooltip-color: var(--tooltip-color, var(--background));\n\n position: relative;\n display: inline-flex;\n align-items: center;\n\n &-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n\n /* The theme's base, not a multiple of the font below: that landed a fraction of a pixel short. */\n width: var(--default-font-size);\n height: var(--default-font-size);\n border-radius: 50%;\n\n /* calc, not em: this badge renders inline wherever the consumer puts it, so em would\n measure that text. The bubble above can use em, because BodyEnd sets the theme's own\n font size on everything it portals. */\n font-size: calc(var(--default-font-size) * 0.6875);\n font-weight: 700;\n background-color: var(--custom-tooltip-background);\n color: var(--custom-tooltip-color);\n cursor: default;\n user-select: none;\n }\n}\n\n.tooltipError {\n border-radius: 0.3em;\n\n /* No measure of its own: a validator's message is as long as it is. The screen is\n still the limit, though, the same one the bubble above takes. */\n max-width: calc(100vw - 16px);\n background-color: var(--error-lighter);\n color: var(--error-color);\n font-size: 0.625em;\n padding: 0.6em 0.9em;\n\n &::after {\n content: '';\n position: absolute;\n top: 100%;\n right: 1.2em;\n border-width: 0.5em;\n border-style: solid;\n border-color: var(--error-border-color) transparent transparent transparent;\n }\n\n &[data-placement='bottom']::after {\n top: auto;\n bottom: 100%;\n border-color: transparent transparent var(--error-border-color) transparent;\n }\n}\n"],"mappings":""}