@trackunit/react-map 0.2.224 → 0.2.225

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,23 +1,23 @@
1
1
  {
2
2
  "name": "@trackunit/react-map",
3
- "version": "0.2.224",
3
+ "version": "0.2.225",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "engines": {
7
7
  "node": ">=24.x"
8
8
  },
9
9
  "dependencies": {
10
- "@trackunit/react-components": "3.10.3",
11
- "@trackunit/css-class-variance-utilities": "2.5.1",
12
- "@trackunit/react-form-components": "2.12.4",
13
- "@trackunit/react-core-hooks": "1.27.2",
14
- "@trackunit/geo-json-utils": "1.20.1",
15
- "@trackunit/i18n-library-translation": "2.10.2",
10
+ "@trackunit/react-components": "3.11.0",
11
+ "@trackunit/css-class-variance-utilities": "2.6.0",
12
+ "@trackunit/react-form-components": "2.13.0",
13
+ "@trackunit/react-core-hooks": "1.28.0",
14
+ "@trackunit/geo-json-utils": "1.21.0",
15
+ "@trackunit/i18n-library-translation": "2.11.0",
16
16
  "react-minimal-pie-chart": "^8.4.0",
17
- "@trackunit/react-map-adapter-shared": "0.0.166",
18
- "@trackunit/react-map-color-utils": "0.0.140",
19
- "@trackunit/shared-utils": "1.21.1",
20
- "@trackunit/ui-design-tokens": "1.20.1",
17
+ "@trackunit/react-map-adapter-shared": "0.0.167",
18
+ "@trackunit/react-map-color-utils": "0.0.141",
19
+ "@trackunit/shared-utils": "1.22.0",
20
+ "@trackunit/ui-design-tokens": "1.21.0",
21
21
  "@floating-ui/react": "^0.26.25",
22
22
  "es-toolkit": "^1.52.0",
23
23
  "tailwind-merge": "^3.7.0",
@@ -1,4 +1,5 @@
1
1
  import type { AdapterConfig } from "@trackunit/react-map-adapter-shared";
2
+ import type { ReactElement } from "react";
2
3
  import type { MapType } from "../core/types";
3
4
  type AppearancePreviewProps = Readonly<{
4
5
  /** Derived adapter config for this preview (with the correct theme baked in) */
@@ -11,6 +12,14 @@ type AppearancePreviewProps = Readonly<{
11
12
  selected: boolean;
12
13
  /** Called when this preview is clicked */
13
14
  onSelect: () => void;
15
+ /**
16
+ * Keeps the cell visible but not selectable. The radio stays focusable (not
17
+ * `disabled`) so keyboard and screen-reader users still reach it and hear the
18
+ * reason; selection is blocked in the change handler instead.
19
+ */
20
+ disabled?: boolean;
21
+ /** Why the cell is disabled: shown as a tooltip and announced through `aria-describedby`. */
22
+ disabledReason?: string;
14
23
  /** Test ID */
15
24
  "data-testid"?: string;
16
25
  }>;
@@ -23,5 +32,5 @@ type AppearancePreviewProps = Readonly<{
23
32
  *
24
33
  * @internal
25
34
  */
26
- export declare const AppearancePreview: ({ adapterConfig, mapType, label, selected, onSelect, "data-testid": dataTestId, }: AppearancePreviewProps) => import("react").JSX.Element;
35
+ export declare const AppearancePreview: ({ adapterConfig, mapType, label, selected, onSelect, disabled, disabledReason, "data-testid": dataTestId, }: AppearancePreviewProps) => ReactElement;
27
36
  export {};
@@ -14,6 +14,13 @@ type AppearancePreviewGridProps = Readonly<{
14
14
  currentMapType: MapType;
15
15
  /** Called when user selects an appearance option */
16
16
  onChange: (theme: MapTheme, mapType: MapType) => void;
17
+ /**
18
+ * Keeps the grid visible but inert, for example while drawing (a style change
19
+ * would recreate the map and drop an in-progress polygon, ADR 0030).
20
+ */
21
+ disabled?: boolean;
22
+ /** Why the grid is disabled, shown as each option's tooltip. */
23
+ disabledReason?: string;
17
24
  }>;
18
25
  /**
19
26
  * Grid of tiny live map previews for selecting map appearance.
@@ -27,5 +34,5 @@ type AppearancePreviewGridProps = Readonly<{
27
34
  *
28
35
  * @internal
29
36
  */
30
- export declare const AppearancePreviewGrid: ({ adapterConfig, ariaLabel, options, currentTheme, currentMapType, onChange, }: AppearancePreviewGridProps) => import("react").JSX.Element;
37
+ export declare const AppearancePreviewGrid: ({ adapterConfig, ariaLabel, options, currentTheme, currentMapType, onChange, disabled, disabledReason, }: AppearancePreviewGridProps) => import("react").JSX.Element;
31
38
  export {};
@@ -0,0 +1,3 @@
1
+ import type { MenuControlConfig } from "../../../types";
2
+ /** A disabled menu's tooltip explains what unblocks it rather than what it opens. */
3
+ export declare const menuTooltipLabel: (config: Pick<MenuControlConfig, "label" | "disabled" | "disabledReason">) => string;
@@ -5,10 +5,19 @@ type StandaloneControlTooltipProps = Readonly<{
5
5
  disabled?: boolean;
6
6
  label: string;
7
7
  overlaySide?: ControlOverlaySide;
8
+ /**
9
+ * Wrap on every overlay side, not only the right one. Needed when the child
10
+ * cannot show the tooltip itself: `IconButton` switches its own `title` tooltip
11
+ * off while the button is disabled, so a disabled control's reason would never
12
+ * appear. Browsers still deliver hover to the disabled button, and the wrapper
13
+ * listens for it.
14
+ */
15
+ required?: boolean;
8
16
  }>;
9
17
  /**
10
- * Wraps standalone map controls with a right-aligned tooltip when `overlaySide` is `"right"`.
11
- * Callers should omit `title` on {@link IconButton} in that mode so tooltips do not stack.
18
+ * Wraps standalone map controls with a right-aligned tooltip when `overlaySide` is `"right"`,
19
+ * or on any side when `required`. Callers should omit `title` on {@link IconButton} whenever
20
+ * this wraps so tooltips do not stack; `isStandaloneTooltipWrapped` tells them.
12
21
  */
13
- export declare const StandaloneControlTooltip: ({ children, disabled, label, overlaySide, }: StandaloneControlTooltipProps) => ReactNode;
22
+ export declare const StandaloneControlTooltip: ({ children, disabled, label, overlaySide, required, }: StandaloneControlTooltipProps) => ReactNode;
14
23
  export {};
@@ -0,0 +1,28 @@
1
+ import { type ReactElement } from "react";
2
+ type UseDisabledReasonInput = Readonly<{
3
+ disabled?: boolean;
4
+ disabledReason?: string;
5
+ }>;
6
+ export type UseDisabledReasonResult = Readonly<{
7
+ /** True when the control is disabled and has a reason to show. */
8
+ showsReason: true;
9
+ /** The reason, for the tooltip. */
10
+ reason: string;
11
+ /** Value for the control's `aria-describedby`. */
12
+ describedBy: string;
13
+ /** The visually hidden copy of the reason; render it next to the control. */
14
+ description: ReactElement;
15
+ }> | Readonly<{
16
+ showsReason: false;
17
+ reason: undefined;
18
+ describedBy: undefined;
19
+ description: null;
20
+ }>;
21
+ /**
22
+ * A disabled control is not focusable and its tooltip is hover-only, so the reason it is
23
+ * disabled is also announced to assistive technology: a visually hidden copy of the reason,
24
+ * linked from the control through `aria-describedby`. One hook for every control that shows a
25
+ * `disabledReason`, so the pattern has one home (BooleanButton, Menu, AppearancePreview).
26
+ */
27
+ export declare const useDisabledReason: ({ disabled, disabledReason, }: UseDisabledReasonInput) => UseDisabledReasonResult;
28
+ export {};
@@ -0,0 +1,8 @@
1
+ import type { ControlOverlaySide } from "../../../controlOverlaySide";
2
+ /**
3
+ * Whether `StandaloneControlTooltip` wraps its child: always on the right overlay
4
+ * side, and on any side when the caller requires it (a disabled `IconButton` drops
5
+ * its own tooltip, so a disabled reason needs the wrapper). Callers omit `title`
6
+ * on the `IconButton` whenever this is true so tooltips do not stack.
7
+ */
8
+ export declare const isStandaloneTooltipWrapped: (overlaySide: ControlOverlaySide | undefined, required: boolean) => boolean;
@@ -195,6 +195,15 @@ export type MenuControlConfig = BaseControlConfig<"menu"> & Readonly<{
195
195
  label: string;
196
196
  /** Items in the menu */
197
197
  items: ReadonlyArray<MenuItemConfig>;
198
+ /**
199
+ * Disables the trigger while keeping it visible, for menus whose every option is
200
+ * unavailable in the current context (the map-style menu while drawing). Only the
201
+ * standalone trigger reads this: when responsive collapsing flattens the menu into
202
+ * the overflow menu, its items render on their own, so disable them too.
203
+ */
204
+ disabled?: boolean;
205
+ /** Why the menu is disabled, shown as the trigger's tooltip instead of `label`. */
206
+ disabledReason?: string;
198
207
  }>;
199
208
  /**
200
209
  * Context passed to custom control render functions.
@@ -1,4 +1,4 @@
1
- import { type DrawMode, type DrawValue } from "@trackunit/react-map-adapter-shared";
1
+ import { type DrawError, type DrawErrors, type DrawMode, type DrawValue } from "@trackunit/react-map-adapter-shared";
2
2
  import type { ControlConfig } from "../controls/types";
3
3
  import type { MapApi } from "../core/types";
4
4
  /** Minimal slice of `MapApi` consumed by `useDraw`. */
@@ -11,6 +11,25 @@ export type UseDrawOptions = Readonly<{
11
11
  * the hook, for example while a feature flag is off. Default `true`.
12
12
  */
13
13
  enabled?: boolean;
14
+ /**
15
+ * Most polygons the drawing may hold. Once `value` holds that many, the "Draw
16
+ * polygon" toggle is disabled with a reason and polygon mode ends by itself after
17
+ * the polygon that reaches the limit is closed; editing and clearing stay
18
+ * available. The library itself is multi-polygon: a site wizard sets `1` here
19
+ * (ADR 0030, decided 19 Oct 2026). Features the engine rejected count too, so a
20
+ * stored boundary that needs a redraw is cleared first. Values below one are
21
+ * ignored. Default: no limit.
22
+ */
23
+ maxPolygons?: number;
24
+ /**
25
+ * Called once per error the drawing reports (same shape as `useHeatmap`): the
26
+ * engine failed to load or update, or part of a committed value could not be
27
+ * rendered (`value-rejected`, with the indices of the features that are not on the
28
+ * map). The committed `value` is never changed by an error. Without a handler the
29
+ * error is logged with `console.warn`; it is reported to the app's error handler
30
+ * either way, when the host provides one, at most once per code per mount.
31
+ */
32
+ onError?: (error: DrawError) => void;
14
33
  }>;
15
34
  export type UseDrawReturn = Readonly<{
16
35
  /** False when the adapter has no drawing capability; `controls` is then empty. */
@@ -24,6 +43,19 @@ export type UseDrawReturn = Readonly<{
24
43
  * polygon, and when `setValue` is called.
25
44
  */
26
45
  value: DrawValue;
46
+ /**
47
+ * The error to show now, or null: the engine failure while one is pending, else the
48
+ * verdict on the committed value. Read `errors` when both matter.
49
+ */
50
+ error: DrawError | null;
51
+ /**
52
+ * Both error slots. `engine` clears when the engine recovers; `value` (a
53
+ * `value-rejected` verdict with the indices of the features that are not on the
54
+ * map) clears when the committed geometry changes. A pending engine failure never
55
+ * hides the value verdict, so a "redraw this boundary" notice can stay up while
56
+ * the engine reloads. See `UseDrawOptions.onError`.
57
+ */
58
+ errors: DrawErrors;
27
59
  /** Enter a drawing mode; `"idle"` ends drawing. */
28
60
  setMode: (mode: DrawMode) => void;
29
61
  /** Replace the committed polygons (controlled-input style). */
package/src/index.d.ts CHANGED
@@ -17,7 +17,7 @@ export type { UseControlsAreas } from "./controls/types";
17
17
  export { useControls } from "./controls/useControls";
18
18
  export { useDefaultControls, type UseDefaultControlsOptions } from "./controls/useDefaultControls";
19
19
  export { EMPTY_DRAW_VALUE } from "@trackunit/react-map-adapter-shared";
20
- export type { DrawMode, DrawPort, DrawValue } from "@trackunit/react-map-adapter-shared";
20
+ export type { DrawEngineError, DrawError, DrawErrorCode, DrawErrors, DrawMode, DrawPort, DrawValue, DrawValueRejection, } from "@trackunit/react-map-adapter-shared";
21
21
  export { useDraw, type UseDrawOptions, type UseDrawReturn } from "./draw/useDraw";
22
22
  export { DEFAULT_APPEARANCE_PERSISTENCE_KEY } from "./appearance/appearanceTypes";
23
23
  export type { AppearanceAction, AppearanceOption, AppearanceOptions, MapAppearanceApi, PersistedAppearance, UseMapAppearanceControlsParams, } from "./appearance/appearanceTypes";
@@ -0,0 +1,25 @@
1
+ import type { Entity } from "@trackunit/react-map-adapter-shared";
2
+ /**
3
+ * Every handler an interaction consumer inside `<Layers>` receives: the adapter event
4
+ * path (`hover`, `select`, `dblClick`, `click`), the fill-tiling settle channel
5
+ * (`settleHover`) and the DOM decoration path (`decorationHover`, `decorationSelect`).
6
+ */
7
+ export type LayerInteractionHandlers = Readonly<{
8
+ hover: (entity: Entity | null) => void;
9
+ select: (entity: Entity | null) => void;
10
+ dblClick: (entity: Entity) => void;
11
+ click: ((entity: Entity) => void) | undefined;
12
+ settleHover: (handleId: string, featureId: string) => void;
13
+ decorationHover: (entity: Entity | null) => void;
14
+ decorationSelect: (entity: Entity | null) => void;
15
+ }>;
16
+ /** The handler set every consumer gets while drawing: nothing reaches the layers. */
17
+ export declare const INERT_LAYER_INTERACTION: LayerInteractionHandlers;
18
+ /**
19
+ * The one choke point for "drawing mode suppresses layer interaction" (ADR 0030).
20
+ * `<Layers>` builds its live handlers once, passes them through here, and hands the
21
+ * result to every consumer, so a new interaction path is gated by default instead
22
+ * of needing its own `if (drawing) return`. Programmatic `layers.select` and
23
+ * `layers.hover` calls from outside `<Layers>` are not affected.
24
+ */
25
+ export declare const useGatedLayerInteraction: (live: LayerInteractionHandlers, drawing: boolean) => LayerInteractionHandlers;