@trackunit/react-map 0.2.48-alpha-90b6c9b97dd.0 → 0.2.48

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/index.cjs.js CHANGED
@@ -700,12 +700,13 @@ const SHAPE_STYLE_DEFAULTS = {
700
700
  const PANEL_ICON_BUTTON_CLASS_NAME = tailwindMerge.twMerge("inline-flex h-7 w-7 shrink-0 cursor-pointer items-center justify-center rounded-md border-0 bg-transparent p-1.5 text-neutral-600", "hover:bg-black/5 hover:text-neutral-700", "active:bg-black/10 active:text-neutral-800", "focus-visible:bg-black/10 focus-visible:text-neutral-800 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-1", "disabled:cursor-not-allowed disabled:bg-transparent disabled:text-neutral-400");
701
701
  /**
702
702
  * Lightweight ghost icon button for map panel chrome. Skips `IconButton`'s
703
- * Tooltip/Button stack so icon glyphs paint on the same frame as panel content.
703
+ * Button stack so icon glyphs paint on the same frame as panel content, but
704
+ * still wraps the trigger in the shared `Tooltip` for hover labels.
704
705
  *
705
706
  * Consumers should pass decorative icons with `ariaHidden` and rely on
706
- * `aria-label` (and the native `title` tooltip) for accessible naming.
707
+ * `aria-label` for accessible naming and tooltip text.
707
708
  */
708
- const PanelIconButton = ({ "aria-label": ariaLabel, children, disabled = false, onClick, "data-testid": dataTestId, }) => (jsxRuntime.jsx("button", { "aria-label": ariaLabel, className: PANEL_ICON_BUTTON_CLASS_NAME, "data-testid": dataTestId, disabled: disabled, onClick: onClick, title: ariaLabel, type: "button", children: children }));
709
+ const PanelIconButton = ({ "aria-label": ariaLabel, children, disabled = false, onClick, "data-testid": dataTestId, }) => (jsxRuntime.jsx(reactComponents.Tooltip, { delayed: true, disabled: disabled, interactable: false, label: ariaLabel, children: jsxRuntime.jsx("button", { "aria-label": ariaLabel, className: PANEL_ICON_BUTTON_CLASS_NAME, "data-testid": dataTestId, disabled: disabled, onClick: onClick, type: "button", children: children }) }));
709
710
 
710
711
  /**
711
712
  * CSS `max-height` of the Panel shell — `min(320px, 40vh)`.
@@ -823,7 +824,8 @@ const PANEL_SCROLL_INTERNAL_CLASS_NAME = "flex flex-1 min-h-0 flex-col overflow-
823
824
  * "" — element is in DOM (for Floating UI to measure) but hidden via
824
825
  * CSS `visibility: hidden` so the pre-commit translate(0,0)
825
826
  * position is never visible.
826
- * "open" — fades in via CSS opacity transition once placement is committed.
827
+ * "open" — appears instantly once placement is committed (no enter
828
+ * animation — see docs/adr/0027-instant-panel-first-paint.md).
827
829
  * "closing" — fades out during the EXIT_DURATION_MS window before unmount.
828
830
  *
829
831
  * @internal
@@ -972,9 +974,9 @@ const Panel = ({ anchor, placement = DEFAULT_PLACEMENT$1, onDismiss, closeLabel
972
974
  if (effectiveAnchor === null) {
973
975
  return null;
974
976
  }
975
- // data-panel drives the CSS animation state machine (Panel.css):
977
+ // data-panel drives the CSS visibility state machine (Panel.css):
976
978
  // "" — pre-commit: visibility:hidden so translate(0,0) is never seen
977
- // "open" — fade in via opacity transition
979
+ // "open" — instant appearance (no enter animation)
978
980
  // "closing" — fade out before unmount
979
981
  const panelState = isClosing ? "closing" : isCommitted ? "open" : "";
980
982
  return (jsxRuntime.jsxs("div", { className: PANEL_SHELL_CLASS_NAME, "data-panel": panelState, ref: mergedShellRef, style: floatingStyles, ...getFloatingProps(), "aria-label": ariaLabel, children: [jsxRuntime.jsx("div", { className: PANEL_CHROME_CLASS_NAME, style: { maxHeight: `min(${PANEL_MAX_HEIGHT_PX}px, ${PANEL_MAX_HEIGHT_VH}vh)` }, children: jsxRuntime.jsx("div", { className: scrollSlotClassName, "data-testid": "panel-scroll-slot", ref: scrollRef, children: children }) }), onClickDismiss !== undefined ? (jsxRuntime.jsx("div", { className: "absolute right-0 top-0 z-10 -translate-y-1/2 translate-x-1/2 rounded-2xl border border-neutral-300 bg-white shadow-sm", children: jsxRuntime.jsx(PanelIconButton, { "aria-label": closeLabel, "data-testid": "panel-close-button", onClick: onClickDismiss, children: jsxRuntime.jsx(reactComponents.Icon, { ariaHidden: true, className: "text-neutral-700", name: "XMark", size: "small" }) }) })) : null] }));
@@ -2063,6 +2065,10 @@ const computePlacement = (triggerEl, containerEl) => {
2063
2065
  const usePopoverPlacement = (triggerRef, containerRef, options = {}) => {
2064
2066
  const { skip = false } = options;
2065
2067
  const [placement, setPlacement] = react.useState(DEFAULT_PLACEMENT);
2068
+ // Post-paint measurement is sufficient: both callers invoke this hook where
2069
+ // the always-mounted trigger renders, so placement settles with the map —
2070
+ // long before a popover can open. A layout effect would only add pre-paint
2071
+ // measurement work during map load (SAGA-675 review).
2066
2072
  react.useEffect(() => {
2067
2073
  if (skip) {
2068
2074
  return;
package/index.esm.js CHANGED
@@ -4,7 +4,7 @@ import { INITIAL_CAMERA_STATE, INITIAL_MAP_STATUS, KEYBOARD_PAN_AMOUNT, KEYBOARD
4
4
  export { ANCHOR_SELECTOR, DEFAULT_MAP_APPEARANCE, HIT_SURFACE_SELECTOR, INITIAL_CAMERA_STATE, INITIAL_INTERACTION_STATE, INITIAL_MAP_STATE, INITIAL_MAP_STATUS, computeMarkerDomPortalZIndex, defineAdapter, geometryTypeToShapeType, mapAppearanceSchema, mapThemeSchema, mapTypeSchema } from '@trackunit/react-map-adapter-shared';
5
5
  import { useSyncExternalStore, useMemo, useState, useRef, useCallback, useLayoutEffect, useEffect, useReducer, createContext, useContext, memo, Suspense, useId, Fragment as Fragment$1, forwardRef } from 'react';
6
6
  import { cvaMerge } from '@trackunit/css-class-variance-utilities';
7
- import { useMergeRefs, Icon, Spinner, Text, useMeasure, useContainerBreakpoints, useDebounce, useWatch, useKeyboardShortcut, Tooltip, IconButton, ToggleGroup as ToggleGroup$1, useIsFullscreen, Popover, PopoverTrigger, PopoverContent, MoreMenu, useHover as useHover$1, cvaInteractableItem, useLocalStorageReducer } from '@trackunit/react-components';
7
+ import { Tooltip, useMergeRefs, Icon, Spinner, Text, useMeasure, useContainerBreakpoints, useDebounce, useWatch, useKeyboardShortcut, IconButton, ToggleGroup as ToggleGroup$1, useIsFullscreen, Popover, PopoverTrigger, PopoverContent, MoreMenu, useHover as useHover$1, cvaInteractableItem, useLocalStorageReducer } from '@trackunit/react-components';
8
8
  import { twMerge } from 'tailwind-merge';
9
9
  import { useFloating, autoUpdate, offset, flip, useHover, safePolygon, useInteractions, shift, hide, useDismiss, useRole } from '@floating-ui/react';
10
10
  import { createPortal } from 'react-dom';
@@ -699,12 +699,13 @@ const SHAPE_STYLE_DEFAULTS = {
699
699
  const PANEL_ICON_BUTTON_CLASS_NAME = twMerge("inline-flex h-7 w-7 shrink-0 cursor-pointer items-center justify-center rounded-md border-0 bg-transparent p-1.5 text-neutral-600", "hover:bg-black/5 hover:text-neutral-700", "active:bg-black/10 active:text-neutral-800", "focus-visible:bg-black/10 focus-visible:text-neutral-800 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-1", "disabled:cursor-not-allowed disabled:bg-transparent disabled:text-neutral-400");
700
700
  /**
701
701
  * Lightweight ghost icon button for map panel chrome. Skips `IconButton`'s
702
- * Tooltip/Button stack so icon glyphs paint on the same frame as panel content.
702
+ * Button stack so icon glyphs paint on the same frame as panel content, but
703
+ * still wraps the trigger in the shared `Tooltip` for hover labels.
703
704
  *
704
705
  * Consumers should pass decorative icons with `ariaHidden` and rely on
705
- * `aria-label` (and the native `title` tooltip) for accessible naming.
706
+ * `aria-label` for accessible naming and tooltip text.
706
707
  */
707
- const PanelIconButton = ({ "aria-label": ariaLabel, children, disabled = false, onClick, "data-testid": dataTestId, }) => (jsx("button", { "aria-label": ariaLabel, className: PANEL_ICON_BUTTON_CLASS_NAME, "data-testid": dataTestId, disabled: disabled, onClick: onClick, title: ariaLabel, type: "button", children: children }));
708
+ const PanelIconButton = ({ "aria-label": ariaLabel, children, disabled = false, onClick, "data-testid": dataTestId, }) => (jsx(Tooltip, { delayed: true, disabled: disabled, interactable: false, label: ariaLabel, children: jsx("button", { "aria-label": ariaLabel, className: PANEL_ICON_BUTTON_CLASS_NAME, "data-testid": dataTestId, disabled: disabled, onClick: onClick, type: "button", children: children }) }));
708
709
 
709
710
  /**
710
711
  * CSS `max-height` of the Panel shell — `min(320px, 40vh)`.
@@ -822,7 +823,8 @@ const PANEL_SCROLL_INTERNAL_CLASS_NAME = "flex flex-1 min-h-0 flex-col overflow-
822
823
  * "" — element is in DOM (for Floating UI to measure) but hidden via
823
824
  * CSS `visibility: hidden` so the pre-commit translate(0,0)
824
825
  * position is never visible.
825
- * "open" — fades in via CSS opacity transition once placement is committed.
826
+ * "open" — appears instantly once placement is committed (no enter
827
+ * animation — see docs/adr/0027-instant-panel-first-paint.md).
826
828
  * "closing" — fades out during the EXIT_DURATION_MS window before unmount.
827
829
  *
828
830
  * @internal
@@ -971,9 +973,9 @@ const Panel = ({ anchor, placement = DEFAULT_PLACEMENT$1, onDismiss, closeLabel
971
973
  if (effectiveAnchor === null) {
972
974
  return null;
973
975
  }
974
- // data-panel drives the CSS animation state machine (Panel.css):
976
+ // data-panel drives the CSS visibility state machine (Panel.css):
975
977
  // "" — pre-commit: visibility:hidden so translate(0,0) is never seen
976
- // "open" — fade in via opacity transition
978
+ // "open" — instant appearance (no enter animation)
977
979
  // "closing" — fade out before unmount
978
980
  const panelState = isClosing ? "closing" : isCommitted ? "open" : "";
979
981
  return (jsxs("div", { className: PANEL_SHELL_CLASS_NAME, "data-panel": panelState, ref: mergedShellRef, style: floatingStyles, ...getFloatingProps(), "aria-label": ariaLabel, children: [jsx("div", { className: PANEL_CHROME_CLASS_NAME, style: { maxHeight: `min(${PANEL_MAX_HEIGHT_PX}px, ${PANEL_MAX_HEIGHT_VH}vh)` }, children: jsx("div", { className: scrollSlotClassName, "data-testid": "panel-scroll-slot", ref: scrollRef, children: children }) }), onClickDismiss !== undefined ? (jsx("div", { className: "absolute right-0 top-0 z-10 -translate-y-1/2 translate-x-1/2 rounded-2xl border border-neutral-300 bg-white shadow-sm", children: jsx(PanelIconButton, { "aria-label": closeLabel, "data-testid": "panel-close-button", onClick: onClickDismiss, children: jsx(Icon, { ariaHidden: true, className: "text-neutral-700", name: "XMark", size: "small" }) }) })) : null] }));
@@ -2062,6 +2064,10 @@ const computePlacement = (triggerEl, containerEl) => {
2062
2064
  const usePopoverPlacement = (triggerRef, containerRef, options = {}) => {
2063
2065
  const { skip = false } = options;
2064
2066
  const [placement, setPlacement] = useState(DEFAULT_PLACEMENT);
2067
+ // Post-paint measurement is sufficient: both callers invoke this hook where
2068
+ // the always-mounted trigger renders, so placement settles with the map —
2069
+ // long before a popover can open. A layout effect would only add pre-paint
2070
+ // measurement work during map load (SAGA-675 review).
2065
2071
  useEffect(() => {
2066
2072
  if (skip) {
2067
2073
  return;
package/package.json CHANGED
@@ -1,22 +1,22 @@
1
1
  {
2
2
  "name": "@trackunit/react-map",
3
- "version": "0.2.48-alpha-90b6c9b97dd.0",
3
+ "version": "0.2.48",
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": "2.4.2-alpha-90b6c9b97dd.0",
11
- "@trackunit/css-class-variance-utilities": "1.13.92-alpha-90b6c9b97dd.0",
12
- "@trackunit/react-form-components": "2.3.8-alpha-90b6c9b97dd.0",
13
- "@trackunit/react-core-hooks": "1.19.7-alpha-90b6c9b97dd.0",
14
- "@trackunit/geo-json-utils": "1.14.95-alpha-90b6c9b97dd.0",
15
- "@trackunit/i18n-library-translation": "2.2.7-alpha-90b6c9b97dd.0",
10
+ "@trackunit/react-components": "2.4.2",
11
+ "@trackunit/css-class-variance-utilities": "1.13.92",
12
+ "@trackunit/react-form-components": "2.3.8",
13
+ "@trackunit/react-core-hooks": "1.19.7",
14
+ "@trackunit/geo-json-utils": "1.14.95",
15
+ "@trackunit/i18n-library-translation": "2.2.7",
16
16
  "react-minimal-pie-chart": "^8.4.0",
17
- "@trackunit/react-map-adapter-shared": "0.0.76-alpha-90b6c9b97dd.0",
18
- "@trackunit/react-map-color-utils": "0.0.61-alpha-90b6c9b97dd.0",
19
- "@trackunit/ui-design-tokens": "1.13.91-alpha-90b6c9b97dd.0",
17
+ "@trackunit/react-map-adapter-shared": "0.0.76",
18
+ "@trackunit/react-map-color-utils": "0.0.61",
19
+ "@trackunit/ui-design-tokens": "1.13.91",
20
20
  "@floating-ui/react": "^0.26.25",
21
21
  "es-toolkit": "^1.39.10",
22
22
  "tailwind-merge": "^2.0.0",
@@ -56,7 +56,8 @@ export type PanelProps = Readonly<{
56
56
  * "" — element is in DOM (for Floating UI to measure) but hidden via
57
57
  * CSS `visibility: hidden` so the pre-commit translate(0,0)
58
58
  * position is never visible.
59
- * "open" — fades in via CSS opacity transition once placement is committed.
59
+ * "open" — appears instantly once placement is committed (no enter
60
+ * animation — see docs/adr/0027-instant-panel-first-paint.md).
60
61
  * "closing" — fades out during the EXIT_DURATION_MS window before unmount.
61
62
  *
62
63
  * @internal
@@ -8,9 +8,10 @@ export type PanelIconButtonProps = Readonly<{
8
8
  }>;
9
9
  /**
10
10
  * Lightweight ghost icon button for map panel chrome. Skips `IconButton`'s
11
- * Tooltip/Button stack so icon glyphs paint on the same frame as panel content.
11
+ * Button stack so icon glyphs paint on the same frame as panel content, but
12
+ * still wraps the trigger in the shared `Tooltip` for hover labels.
12
13
  *
13
14
  * Consumers should pass decorative icons with `ariaHidden` and rely on
14
- * `aria-label` (and the native `title` tooltip) for accessible naming.
15
+ * `aria-label` for accessible naming and tooltip text.
15
16
  */
16
17
  export declare const PanelIconButton: ({ "aria-label": ariaLabel, children, disabled, onClick, "data-testid": dataTestId, }: PanelIconButtonProps) => ReactElement;