@trackunit/react-components 1.17.48 → 1.17.49

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
@@ -8672,6 +8672,87 @@ const useElevatedState = (initialState, customState) => {
8672
8672
  return react.useMemo(() => customState ?? [fallbackValue, fallbackSetter], [customState, fallbackValue, fallbackSetter]);
8673
8673
  };
8674
8674
 
8675
+ // ============================================================================
8676
+ // Permission Helper
8677
+ // ============================================================================
8678
+ /**
8679
+ * Query the current geolocation permission state without triggering a prompt.
8680
+ * Returns `null` when the Permissions API is unavailable or the browser throws.
8681
+ */
8682
+ const queryGeolocationPermission = async () => {
8683
+ try {
8684
+ const status = await navigator.permissions.query({ name: "geolocation" });
8685
+ return status.state;
8686
+ }
8687
+ catch {
8688
+ return null;
8689
+ }
8690
+ };
8691
+ // ============================================================================
8692
+ // Position Helper
8693
+ // ============================================================================
8694
+ /** Wrap `getCurrentPosition` in a Promise that resolves to `GeoJsonPosition | null`. */
8695
+ const getCurrentPosition = () => new Promise(resolve => {
8696
+ if (typeof navigator === "undefined") {
8697
+ resolve(null);
8698
+ return;
8699
+ }
8700
+ navigator.geolocation.getCurrentPosition(pos => resolve([pos.coords.longitude, pos.coords.latitude]), () => resolve(null));
8701
+ });
8702
+ /**
8703
+ * Lightweight geolocation hook.
8704
+ *
8705
+ * By default does nothing on mount — the consumer decides when (and how) to
8706
+ * request a position via `getPosition`. Set `requestOnMount: true` to
8707
+ * automatically populate `position` when permission is already granted.
8708
+ *
8709
+ * @example
8710
+ * ```ts
8711
+ * // Auto-populate position if permission is already granted
8712
+ * const { position, getPosition } = useGeolocation({ requestOnMount: true });
8713
+ *
8714
+ * // User-initiated request (e.g. "My Location" button)
8715
+ * onClick: () => {
8716
+ * void getPosition().then(pos => { if (pos) fitBounds(pos); });
8717
+ * }
8718
+ * ```
8719
+ */
8720
+ const useGeolocation = (options) => {
8721
+ const [position, setPosition] = react.useState(null);
8722
+ const getPosition = react.useCallback(async (opts) => {
8723
+ const prompt = opts?.prompt !== false;
8724
+ if (!prompt) {
8725
+ const permissionState = await queryGeolocationPermission();
8726
+ if (permissionState !== "granted")
8727
+ return null;
8728
+ }
8729
+ const pos = await getCurrentPosition();
8730
+ if (pos !== null) {
8731
+ setPosition(pos);
8732
+ }
8733
+ return pos;
8734
+ }, []);
8735
+ useWatch({
8736
+ value: options?.requestOnMount ?? false,
8737
+ onChange: () => {
8738
+ void queryGeolocationPermission()
8739
+ .then(state => {
8740
+ if (state !== "granted")
8741
+ return;
8742
+ return getCurrentPosition();
8743
+ })
8744
+ .then(pos => {
8745
+ if (pos !== null && pos !== undefined) {
8746
+ setPosition(pos);
8747
+ }
8748
+ });
8749
+ },
8750
+ immediate: true,
8751
+ skip: options?.requestOnMount !== true,
8752
+ });
8753
+ return react.useMemo(() => ({ position, getPosition }), [position, getPosition]);
8754
+ };
8755
+
8675
8756
  /**
8676
8757
  * The useHover hook returns a onMouseEnter, onMouseLeave and a boolean indicating whether the element is being hovered.
8677
8758
  * The boolean will be true if the element is being hovered, and false if it is not.
@@ -9653,6 +9734,7 @@ exports.useDebounce = useDebounce;
9653
9734
  exports.useDevicePixelRatio = useDevicePixelRatio;
9654
9735
  exports.useElevatedReducer = useElevatedReducer;
9655
9736
  exports.useElevatedState = useElevatedState;
9737
+ exports.useGeolocation = useGeolocation;
9656
9738
  exports.useGridAreas = useGridAreas;
9657
9739
  exports.useHover = useHover;
9658
9740
  exports.useInfiniteScroll = useInfiniteScroll;
package/index.esm.js CHANGED
@@ -8670,6 +8670,87 @@ const useElevatedState = (initialState, customState) => {
8670
8670
  return useMemo(() => customState ?? [fallbackValue, fallbackSetter], [customState, fallbackValue, fallbackSetter]);
8671
8671
  };
8672
8672
 
8673
+ // ============================================================================
8674
+ // Permission Helper
8675
+ // ============================================================================
8676
+ /**
8677
+ * Query the current geolocation permission state without triggering a prompt.
8678
+ * Returns `null` when the Permissions API is unavailable or the browser throws.
8679
+ */
8680
+ const queryGeolocationPermission = async () => {
8681
+ try {
8682
+ const status = await navigator.permissions.query({ name: "geolocation" });
8683
+ return status.state;
8684
+ }
8685
+ catch {
8686
+ return null;
8687
+ }
8688
+ };
8689
+ // ============================================================================
8690
+ // Position Helper
8691
+ // ============================================================================
8692
+ /** Wrap `getCurrentPosition` in a Promise that resolves to `GeoJsonPosition | null`. */
8693
+ const getCurrentPosition = () => new Promise(resolve => {
8694
+ if (typeof navigator === "undefined") {
8695
+ resolve(null);
8696
+ return;
8697
+ }
8698
+ navigator.geolocation.getCurrentPosition(pos => resolve([pos.coords.longitude, pos.coords.latitude]), () => resolve(null));
8699
+ });
8700
+ /**
8701
+ * Lightweight geolocation hook.
8702
+ *
8703
+ * By default does nothing on mount — the consumer decides when (and how) to
8704
+ * request a position via `getPosition`. Set `requestOnMount: true` to
8705
+ * automatically populate `position` when permission is already granted.
8706
+ *
8707
+ * @example
8708
+ * ```ts
8709
+ * // Auto-populate position if permission is already granted
8710
+ * const { position, getPosition } = useGeolocation({ requestOnMount: true });
8711
+ *
8712
+ * // User-initiated request (e.g. "My Location" button)
8713
+ * onClick: () => {
8714
+ * void getPosition().then(pos => { if (pos) fitBounds(pos); });
8715
+ * }
8716
+ * ```
8717
+ */
8718
+ const useGeolocation = (options) => {
8719
+ const [position, setPosition] = useState(null);
8720
+ const getPosition = useCallback(async (opts) => {
8721
+ const prompt = opts?.prompt !== false;
8722
+ if (!prompt) {
8723
+ const permissionState = await queryGeolocationPermission();
8724
+ if (permissionState !== "granted")
8725
+ return null;
8726
+ }
8727
+ const pos = await getCurrentPosition();
8728
+ if (pos !== null) {
8729
+ setPosition(pos);
8730
+ }
8731
+ return pos;
8732
+ }, []);
8733
+ useWatch({
8734
+ value: options?.requestOnMount ?? false,
8735
+ onChange: () => {
8736
+ void queryGeolocationPermission()
8737
+ .then(state => {
8738
+ if (state !== "granted")
8739
+ return;
8740
+ return getCurrentPosition();
8741
+ })
8742
+ .then(pos => {
8743
+ if (pos !== null && pos !== undefined) {
8744
+ setPosition(pos);
8745
+ }
8746
+ });
8747
+ },
8748
+ immediate: true,
8749
+ skip: options?.requestOnMount !== true,
8750
+ });
8751
+ return useMemo(() => ({ position, getPosition }), [position, getPosition]);
8752
+ };
8753
+
8673
8754
  /**
8674
8755
  * The useHover hook returns a onMouseEnter, onMouseLeave and a boolean indicating whether the element is being hovered.
8675
8756
  * The boolean will be true if the element is being hovered, and false if it is not.
@@ -9514,4 +9595,4 @@ const useWindowActivity = ({ onFocus, onBlur, skip = false } = { onBlur: undefin
9514
9595
  return useMemo(() => ({ focused }), [focused]);
9515
9596
  };
9516
9597
 
9517
- export { ActionRenderer, Alert, Badge, Breadcrumb, BreadcrumbContainer, Button, Card, CardBody, CardFooter, CardHeader, Collapse, CompletionStatusIndicator, CopyableText, DEFAULT_SKELETON_PREFERENCE_CARD_PROPS, DetailsList, EmptyState, EmptyValue, ExternalLink, GridAreas, Heading, Highlight, HorizontalOverflowScroller, Icon, IconButton, Indicator, KPI, KPICard, KPICardSkeleton, KPISkeleton, List, ListItem, MenuDivider, MenuItem, MenuList, MoreMenu, Notice, PackageNameStoryComponent, Page, PageContent, PageHeader, PageHeaderKpiMetrics, PageHeaderSecondaryActions, PageHeaderTitle, Pagination, Polygon, Popover, PopoverContent, PopoverTitle, PopoverTrigger, Portal, PreferenceCard, PreferenceCardSkeleton, Prompt, ROLE_CARD, SectionHeader, SegmentedValueBar, Sidebar, SkeletonBlock, SkeletonLabel, SkeletonLines, Spacer, Spinner, StarButton, Tab, TabContent, TabList, Tabs, Tag, Text, ToggleGroup, Tooltip, TrendIndicator, TrendIndicators, ValueBar, ZStack, createGrid, cvaButton, cvaButtonPrefixSuffix, cvaButtonSpinner, cvaButtonSpinnerContainer, cvaClickable, cvaContainerStyles, cvaContentContainer, cvaContentWrapper, cvaDescriptionCard, cvaIconBackground, cvaIconButton, cvaImgStyles, cvaIndicator, cvaIndicatorIcon, cvaIndicatorIconBackground, cvaIndicatorLabel, cvaIndicatorPing, cvaInputContainer, cvaInteractableItem, cvaList, cvaListContainer, cvaListItem$1 as cvaListItem, cvaMenu, cvaMenuItem, cvaMenuItemLabel, cvaMenuItemPrefix, cvaMenuItemStyle, cvaMenuItemSuffix, cvaMenuList, cvaMenuListDivider, cvaMenuListItem, cvaMenuListMultiSelect, cvaPageHeader, cvaPageHeaderContainer, cvaPageHeaderHeading, cvaPreferenceCard, cvaTitleCard, cvaToggleGroup, cvaToggleGroupWithSlidingBackground, cvaToggleItem, cvaToggleItemContent, cvaToggleItemText, cvaZStackContainer, cvaZStackItem, defaultPageSize, docs, getDevicePixelRatio, getValueBarColorByValue, iconColorNames, iconPalette, noPagination, preferenceCardGrid, useClickOutside, useContainerBreakpoints, useContinuousTimeout, useCopyToClipboard, useCustomEncoding, useDebounce, useDevicePixelRatio, useElevatedReducer, useElevatedState, useGridAreas, useHover, useInfiniteScroll, useIsFirstRender, useIsFullscreen, useIsTextTruncated, useKeyboardShortcut, useList, useListItemHeight, useLocalStorage, useLocalStorageReducer, useMeasure, useMergeRefs, useModifierKey, useOverflowItems, usePopoverContext, usePrevious, usePrompt, useRandomCSSLengths, useRelayPagination, useResize, useScrollBlock, useScrollDetection, useSelfUpdatingRef, useTextSearch, useTimeout, useViewportBreakpoints, useWatch, useWindowActivity };
9598
+ export { ActionRenderer, Alert, Badge, Breadcrumb, BreadcrumbContainer, Button, Card, CardBody, CardFooter, CardHeader, Collapse, CompletionStatusIndicator, CopyableText, DEFAULT_SKELETON_PREFERENCE_CARD_PROPS, DetailsList, EmptyState, EmptyValue, ExternalLink, GridAreas, Heading, Highlight, HorizontalOverflowScroller, Icon, IconButton, Indicator, KPI, KPICard, KPICardSkeleton, KPISkeleton, List, ListItem, MenuDivider, MenuItem, MenuList, MoreMenu, Notice, PackageNameStoryComponent, Page, PageContent, PageHeader, PageHeaderKpiMetrics, PageHeaderSecondaryActions, PageHeaderTitle, Pagination, Polygon, Popover, PopoverContent, PopoverTitle, PopoverTrigger, Portal, PreferenceCard, PreferenceCardSkeleton, Prompt, ROLE_CARD, SectionHeader, SegmentedValueBar, Sidebar, SkeletonBlock, SkeletonLabel, SkeletonLines, Spacer, Spinner, StarButton, Tab, TabContent, TabList, Tabs, Tag, Text, ToggleGroup, Tooltip, TrendIndicator, TrendIndicators, ValueBar, ZStack, createGrid, cvaButton, cvaButtonPrefixSuffix, cvaButtonSpinner, cvaButtonSpinnerContainer, cvaClickable, cvaContainerStyles, cvaContentContainer, cvaContentWrapper, cvaDescriptionCard, cvaIconBackground, cvaIconButton, cvaImgStyles, cvaIndicator, cvaIndicatorIcon, cvaIndicatorIconBackground, cvaIndicatorLabel, cvaIndicatorPing, cvaInputContainer, cvaInteractableItem, cvaList, cvaListContainer, cvaListItem$1 as cvaListItem, cvaMenu, cvaMenuItem, cvaMenuItemLabel, cvaMenuItemPrefix, cvaMenuItemStyle, cvaMenuItemSuffix, cvaMenuList, cvaMenuListDivider, cvaMenuListItem, cvaMenuListMultiSelect, cvaPageHeader, cvaPageHeaderContainer, cvaPageHeaderHeading, cvaPreferenceCard, cvaTitleCard, cvaToggleGroup, cvaToggleGroupWithSlidingBackground, cvaToggleItem, cvaToggleItemContent, cvaToggleItemText, cvaZStackContainer, cvaZStackItem, defaultPageSize, docs, getDevicePixelRatio, getValueBarColorByValue, iconColorNames, iconPalette, noPagination, preferenceCardGrid, useClickOutside, useContainerBreakpoints, useContinuousTimeout, useCopyToClipboard, useCustomEncoding, useDebounce, useDevicePixelRatio, useElevatedReducer, useElevatedState, useGeolocation, useGridAreas, useHover, useInfiniteScroll, useIsFirstRender, useIsFullscreen, useIsTextTruncated, useKeyboardShortcut, useList, useListItemHeight, useLocalStorage, useLocalStorageReducer, useMeasure, useMergeRefs, useModifierKey, useOverflowItems, usePopoverContext, usePrevious, usePrompt, useRandomCSSLengths, useRelayPagination, useResize, useScrollBlock, useScrollDetection, useSelfUpdatingRef, useTextSearch, useTimeout, useViewportBreakpoints, useWatch, useWindowActivity };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/react-components",
3
- "version": "1.17.48",
3
+ "version": "1.17.49",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "engines": {
@@ -14,10 +14,10 @@
14
14
  "@floating-ui/react": "^0.26.25",
15
15
  "string-ts": "^2.0.0",
16
16
  "tailwind-merge": "^2.0.0",
17
- "@trackunit/ui-design-tokens": "1.11.59",
18
- "@trackunit/css-class-variance-utilities": "1.11.60",
19
- "@trackunit/shared-utils": "1.13.60",
20
- "@trackunit/ui-icons": "1.11.58",
17
+ "@trackunit/ui-design-tokens": "1.11.60",
18
+ "@trackunit/css-class-variance-utilities": "1.11.61",
19
+ "@trackunit/shared-utils": "1.13.61",
20
+ "@trackunit/ui-icons": "1.11.59",
21
21
  "@tanstack/react-router": "1.114.29",
22
22
  "es-toolkit": "^1.39.10",
23
23
  "@tanstack/react-virtual": "3.13.12",
@@ -0,0 +1,51 @@
1
+ type GeoJsonPosition = [number, number] | [number, number, number];
2
+ type UseGeolocationReturn = Readonly<{
3
+ /** Last successfully resolved position as [longitude, latitude], or null if no position has been obtained yet */
4
+ position: GeoJsonPosition | null;
5
+ /**
6
+ * Get the current geographic position.
7
+ *
8
+ * - `prompt: true` (default) — calls `getCurrentPosition` directly, which may
9
+ * show the browser permission dialog if permission hasn't been granted yet.
10
+ * Intended for user-initiated actions (e.g. "My Location" button).
11
+ *
12
+ * - `prompt: false` — checks the permission state first and only retrieves the
13
+ * position if permission is already `"granted"`. Never triggers a prompt.
14
+ * Returns `null` when permission is not granted or the API is unavailable.
15
+ *
16
+ * On success, also updates the `position` state so dependent renders pick it up.
17
+ */
18
+ getPosition: (options?: Readonly<{
19
+ prompt?: boolean;
20
+ }>) => Promise<GeoJsonPosition | null>;
21
+ }>;
22
+ type UseGeolocationOptions = Readonly<{
23
+ /**
24
+ * When `true`, passively checks geolocation permission on mount and
25
+ * populates `position` if permission is already `"granted"`.
26
+ * Never triggers a browser prompt.
27
+ *
28
+ * @default false
29
+ */
30
+ requestOnMount?: boolean;
31
+ }>;
32
+ /**
33
+ * Lightweight geolocation hook.
34
+ *
35
+ * By default does nothing on mount — the consumer decides when (and how) to
36
+ * request a position via `getPosition`. Set `requestOnMount: true` to
37
+ * automatically populate `position` when permission is already granted.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * // Auto-populate position if permission is already granted
42
+ * const { position, getPosition } = useGeolocation({ requestOnMount: true });
43
+ *
44
+ * // User-initiated request (e.g. "My Location" button)
45
+ * onClick: () => {
46
+ * void getPosition().then(pos => { if (pos) fitBounds(pos); });
47
+ * }
48
+ * ```
49
+ */
50
+ export declare const useGeolocation: (options?: UseGeolocationOptions) => UseGeolocationReturn;
51
+ export {};
package/src/index.d.ts CHANGED
@@ -116,6 +116,7 @@ export * from "./hooks/useDebounce";
116
116
  export * from "./hooks/useDevicePixelRatio";
117
117
  export * from "./hooks/useElevatedReducer";
118
118
  export * from "./hooks/useElevatedState";
119
+ export * from "./hooks/useGeolocation";
119
120
  export * from "./hooks/useHover";
120
121
  export * from "./hooks/useInfiniteScroll";
121
122
  export * from "./hooks/useIsFirstRender";