@geops/rvf-mobility-web-component 0.1.55 → 0.1.57

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 (124) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/docutils.js +21 -6
  3. package/global.d.ts +1 -0
  4. package/iframe.html +15 -0
  5. package/index.html +2 -1
  6. package/index.js +295 -335
  7. package/package.json +16 -17
  8. package/src/{RvfExportMenu/RvfExportMenu.tsx → ExportMenu/ExportMenu.tsx} +12 -12
  9. package/src/ExportMenu/index.tsx +1 -0
  10. package/src/{RvfExportMenuButton/RvfExportMenuButton.tsx → ExportMenuButton/ExportMenuButton.tsx} +5 -5
  11. package/src/ExportMenuButton/index.tsx +1 -0
  12. package/src/FeatureDetails/FeatureDetails.tsx +57 -0
  13. package/src/FeatureDetails/index.ts +1 -0
  14. package/src/FeaturesInfosListener/FeaturesInfosListener.tsx +73 -0
  15. package/src/FeaturesInfosListener/index.tsx +1 -0
  16. package/src/GeolocationButton/GeolocationButton.tsx +0 -1
  17. package/src/LayerTree/LayerTree.tsx +58 -0
  18. package/src/LayerTree/TreeItem/TreeItem.tsx +151 -0
  19. package/src/LayerTree/TreeItem/index.tsx +1 -0
  20. package/src/LayerTree/index.tsx +1 -0
  21. package/src/LayerTree/layersTreeContext.ts +4 -0
  22. package/src/LayerTree/layersTreeReducer.ts +158 -0
  23. package/src/{RvfLayerTreeButton/RvfLayerTreeButton.tsx → LayerTreeButton/LayerTreeButton.tsx} +5 -5
  24. package/src/LayerTreeButton/index.tsx +1 -0
  25. package/src/{RvfTopics/RvfTopics.tsx → LayerTreeMenu/LayerTreeMenu.tsx} +17 -13
  26. package/src/LayerTreeMenu/index.tsx +1 -0
  27. package/src/LayoutState/LayoutState.tsx +277 -0
  28. package/src/LayoutState/index.tsx +1 -0
  29. package/src/LinesNetworkPlanDetails/LinesNetworkPlanDetails.tsx +292 -0
  30. package/src/LinesNetworkPlanDetails/index.tsx +1 -0
  31. package/src/{RvfLineNetworkPlanLayer/RvfLineNetworkPlanLayer.tsx → LinesNetworkPlanLayer/LinesNetworkPlanLayer.tsx} +7 -9
  32. package/src/LinesNetworkPlanLayer/index.tsx +1 -0
  33. package/src/MobilityMap/MobilityMap.tsx +274 -60
  34. package/src/MobilityMap/MobilityMapAttributes.ts +46 -50
  35. package/src/NotificationDetails/NotificationDetails.tsx +468 -0
  36. package/src/NotificationDetails/index.ts +1 -0
  37. package/src/{NotificationLayer/NotificationLayer.tsx → NotificationsLayer/NotificationsLayer.tsx} +9 -4
  38. package/src/NotificationsLayer/index.tsx +1 -0
  39. package/src/Overlay/Overlay.tsx +1 -6
  40. package/src/OverlayContent/OverlayContent.tsx +87 -0
  41. package/src/OverlayContent/index.ts +1 -0
  42. package/src/OverlayDetails/OverlayDetails.tsx +47 -0
  43. package/src/OverlayDetails/index.ts +1 -0
  44. package/src/OverlayDetailsFooter/OverlayDetailsFooter.tsx +51 -0
  45. package/src/OverlayDetailsFooter/index.tsx +1 -0
  46. package/src/OverlayDetailsHeader/OverlayDetailsHeader.tsx +35 -0
  47. package/src/OverlayDetailsHeader/index.ts +1 -0
  48. package/src/OverlayFooter/OverlayFooter.tsx +41 -0
  49. package/src/OverlayFooter/index.tsx +1 -0
  50. package/src/OverlayHeader/OverlayHeader.tsx +44 -0
  51. package/src/OverlayHeader/index.tsx +1 -0
  52. package/src/PermalinkInput/PermalinkInput.tsx +28 -0
  53. package/src/PermalinkInput/index.tsx +1 -0
  54. package/src/RouteSchedule/RouteSchedule.tsx +22 -18
  55. package/src/RvfFeatureDetails/RvfFeatureDetails.tsx +22 -50
  56. package/src/RvfFeatureDetails/RvfLineNetworkDetails/RvfLineNetworkDetails.tsx +108 -104
  57. package/src/RvfFeatureDetails/RvfNotificationDetails/RvfNotificationDetails.tsx +189 -154
  58. package/src/RvfFeatureDetails/RvfSharedMobilityDetail/RvfSharedMobilityDetails.tsx +12 -14
  59. package/src/RvfFeatureDetailsTitle/RvfFeatureDetailsTitle.tsx +5 -5
  60. package/src/RvfMainLinkButton/RvfMainLinkButton.tsx +65 -0
  61. package/src/RvfMainLinkButton/index.tsx +1 -0
  62. package/src/RvfMobilityMap/RvfMobilityMap.tsx +183 -395
  63. package/src/RvfOverlayContent/RvfOverlayContent.tsx +2 -2
  64. package/src/RvfPoisLayer/RvfPoisLayer.tsx +2 -2
  65. package/src/RvfSearch/RvfSearch.tsx +1 -1
  66. package/src/RvfSellingPointsLayer/RvfSellingPointsLayer.tsx +2 -2
  67. package/src/RvfSharedMobilityLayerGroup/RvfSharedMobilityLayerGroup.tsx +23 -23
  68. package/src/RvfTarifZonenLayer/RvfTarifZonenLayer.tsx +2 -2
  69. package/src/Search/Search.tsx +11 -2
  70. package/src/{RvfSearchButton/RvfSearchButton.tsx → SearchButton/SearchButton.tsx} +5 -5
  71. package/src/SearchButton/index.tsx +1 -0
  72. package/src/ShadowOverflow/ShadowOverflow.tsx +20 -0
  73. package/src/ShadowOverflow/index.tsx +1 -0
  74. package/src/{RvfShare/RvfPermalinkButton/RvfPermalinkButton.tsx → ShareMenu/PermalinkButton/PermalinkButton.tsx} +4 -4
  75. package/src/ShareMenu/PermalinkButton/index.tsx +1 -0
  76. package/src/{RvfShare/RvfShare.tsx → ShareMenu/ShareMenu.tsx} +9 -11
  77. package/src/ShareMenu/index.tsx +1 -0
  78. package/src/{RvfShareMenuButton/RvfShareMenuButton.tsx → ShareMenuButton/ShareMenuButton.tsx} +6 -6
  79. package/src/ShareMenuButton/index.tsx +1 -0
  80. package/src/SingleClickListener/SingleClickListener.tsx +55 -113
  81. package/src/SingleClickListener/index.tsx +1 -1
  82. package/src/Station/Station.tsx +10 -3
  83. package/src/StationsLayer/StationsLayer.tsx +0 -1
  84. package/src/StopsSearch/StopsSearch.tsx +3 -4
  85. package/src/StopsSearch/index.tsx +2 -1
  86. package/src/WindowMessageListener/WindowMessageListener.tsx +7 -1
  87. package/src/{RvfZoomButtons/RvfZoomButtons.tsx → ZoomButtons/ZoomButtons.tsx} +9 -12
  88. package/src/ZoomButtons/index.tsx +1 -0
  89. package/src/icons/Geolocation/airport-14-svgrepo-com.svg +41 -0
  90. package/src/ui/Button/Button.tsx +9 -2
  91. package/src/ui/Checkbox/Checkbox.tsx +32 -0
  92. package/src/ui/Checkbox/index.tsx +1 -0
  93. package/src/ui/IconButton/IconButton.tsx +24 -4
  94. package/src/ui/Input/Input.tsx +17 -0
  95. package/src/ui/Input/index.tsx +1 -0
  96. package/src/ui/InputCopy/InputCopy.tsx +86 -0
  97. package/src/ui/InputCopy/index.tsx +1 -0
  98. package/src/ui/Select/Select.tsx +24 -0
  99. package/src/ui/Select/index.tsx +1 -0
  100. package/src/utils/constants.ts +43 -42
  101. package/src/utils/exportPdf.ts +3 -5
  102. package/src/utils/hooks/useInitialLayersVisiblity.tsx +1 -1
  103. package/src/utils/hooks/useLayerConfig.tsx +2 -2
  104. package/src/utils/hooks/useLayersConfig.tsx +3 -3
  105. package/src/utils/hooks/useMapContext.tsx +67 -8
  106. package/src/utils/hooks/useRvfContext.tsx +0 -44
  107. package/src/NotificationLayer/index.tsx +0 -1
  108. package/src/RvfEmbedNavigation/DragPanWarning.ts +0 -124
  109. package/src/RvfEmbedNavigation/RvfEmbedNavigation.tsx +0 -51
  110. package/src/RvfEmbedNavigation/index.js +0 -1
  111. package/src/RvfExportMenu/index.tsx +0 -1
  112. package/src/RvfExportMenuButton/index.tsx +0 -1
  113. package/src/RvfFloatingMenu/RvfFloatingMenu.tsx +0 -44
  114. package/src/RvfFloatingMenu/index.tsx +0 -1
  115. package/src/RvfLayerTreeButton/index.tsx +0 -1
  116. package/src/RvfLineNetworkPlanLayer/index.tsx +0 -1
  117. package/src/RvfPermalink/RvfPermalink.tsx +0 -18
  118. package/src/RvfPermalink/index.tsx +0 -1
  119. package/src/RvfSearchButton/index.tsx +0 -1
  120. package/src/RvfShare/RvfPermalinkButton/index.tsx +0 -1
  121. package/src/RvfShare/index.tsx +0 -1
  122. package/src/RvfShareMenuButton/index.tsx +0 -1
  123. package/src/RvfTopics/index.tsx +0 -1
  124. package/src/RvfZoomButtons/index.tsx +0 -1
package/package.json CHANGED
@@ -2,17 +2,17 @@
2
2
  "name": "@geops/rvf-mobility-web-component",
3
3
  "license": "UNLICENSED",
4
4
  "description": "Web components for rvf in the domains of mobility and logistics.",
5
- "version": "0.1.55",
5
+ "version": "0.1.57",
6
6
  "homepage": "https://rvf-mobility-web-component-geops.vercel.app/",
7
7
  "type": "module",
8
8
  "main": "index.js",
9
9
  "dependencies": {
10
10
  "graphql": "^16.10.0",
11
11
  "graphql-request": "^7.1.2",
12
- "jspdf": "^2.5.2",
12
+ "jspdf": "^3.0.3",
13
13
  "lodash.debounce": "^4.0.8",
14
- "maplibre-gl": "^5.7.2",
15
- "mobility-toolbox-js": "3.3.4-beta.31",
14
+ "maplibre-gl": "^5.8.0",
15
+ "mobility-toolbox-js": "3.4.0",
16
16
  "ol": "^10.6.1",
17
17
  "preact": "^10.27.2",
18
18
  "preact-custom-element": "^4.5.1",
@@ -20,43 +20,42 @@
20
20
  "react-dom": "npm:@preact/compat@^18.3.1",
21
21
  "react-icons": "^5.5.0",
22
22
  "react-spatial": "^2.0.0",
23
- "rosetta": "^1.1.0",
24
- "showdown": "^2.1.0"
23
+ "rosetta": "^1.1.0"
25
24
  },
26
25
  "devDependencies": {
27
- "@commitlint/cli": "^19.8.1",
28
- "@commitlint/config-conventional": "^19.8.1",
29
- "@eslint/js": "^9.35.0",
26
+ "@commitlint/cli": "^20.1.0",
27
+ "@commitlint/config-conventional": "^20.0.0",
28
+ "@eslint/js": "^9.36.0",
30
29
  "@geops/eslint-config-react": "^1.5.0",
31
30
  "@tailwindcss/cli": "^4.1.13",
32
31
  "@tailwindcss/container-queries": "^0.1.1",
33
32
  "@testing-library/preact": "^3.2.4",
34
33
  "@types/geojson": "^7946.0.16",
35
34
  "@types/jest": "^30.0.0",
35
+ "@types/lodash": "^4.17.20",
36
36
  "@types/preact-custom-element": "^4.0.4",
37
37
  "concurrently": "^9.2.1",
38
38
  "esbuild": "^0.25.10",
39
39
  "esbuild-sass-plugin": "^3.3.1",
40
- "eslint": "^9.35.0",
40
+ "eslint": "^9.36.0",
41
41
  "eslint-plugin-tailwindcss": "^4.0.0-beta.0",
42
42
  "fixpack": "^4.0.0",
43
43
  "generact": "^0.4.0",
44
44
  "husky": "^9.1.7",
45
- "jest": "^30.1.3",
45
+ "jest": "^30.2.0",
46
46
  "jest-canvas-mock": "^2.5.2",
47
- "jest-environment-jsdom": "^30.1.2",
47
+ "jest-environment-jsdom": "^30.2.0",
48
48
  "jest-preset-preact": "^4.1.1",
49
- "lint-staged": "^16.2.0",
50
49
  "next": "15.5.2",
51
- "preact-render-to-string": "^6.6.1",
50
+ "preact-render-to-string": "^6.6.2",
52
51
  "prettier": "^3.6.2",
53
52
  "prettier-plugin-tailwindcss": "^0.6.14",
54
53
  "standard-version": "^9.5.0",
55
54
  "tailwind-merge": "^3.3.1",
56
55
  "tailwindcss": "^4.1.13",
57
- "ts-jest": "^29.4.3",
58
- "typescript": "^5.9.2",
59
- "typescript-eslint": "^8.44.0"
56
+ "ts-jest": "^29.4.4",
57
+ "typescript": "^5.9.3",
58
+ "typescript-eslint": "^8.45.0"
60
59
  },
61
60
  "scripts": {
62
61
  "build": "yarn build:css && yarn build:js && cp index*.js* doc/public/",
@@ -1,23 +1,23 @@
1
1
  import { memo, useId, useState } from "preact/compat";
2
2
 
3
- import RvfCheckbox from "../RvfCheckbox";
4
- import RvfSelect from "../RvfSelect";
5
3
  import Button from "../ui/Button";
6
- import { LAYER_PROP_IS_EXPORTING } from "../utils/constants";
4
+ import Checkbox from "../ui/Checkbox";
5
+ import Select from "../ui/Select";
6
+ import { LAYER_PROP_IS_EXPORTING, MAX_EXTENT } from "../utils/constants";
7
7
  import exportPdf from "../utils/exportPdf";
8
8
  import getAllLayers from "../utils/getAllLayers";
9
9
  import useMapContext from "../utils/hooks/useMapContext";
10
10
 
11
- import type { JSX, PreactDOMAttributes } from "preact";
11
+ import type { HTMLAttributes, PreactDOMAttributes } from "preact";
12
12
 
13
- export type RvfExportMenuButtonProps = JSX.HTMLAttributes<HTMLDivElement> &
13
+ export type ExportMenuButtonProps = HTMLAttributes<HTMLDivElement> &
14
14
  PreactDOMAttributes;
15
15
 
16
16
  const formats = ["A4", "A3", "A1", "A0"];
17
17
 
18
18
  let prevRealtimeLayerVisibility = false;
19
19
 
20
- function RvfExportMenu({ ...props }: RvfExportMenuButtonProps) {
20
+ function ExportMenu({ ...props }: ExportMenuButtonProps) {
21
21
  const { map, realtimeLayer } = useMapContext();
22
22
  const [useMaxExtent, setUseMaxExtent] = useState(false);
23
23
  const [format, setFormat] = useState<string>(formats[0]);
@@ -30,7 +30,7 @@ function RvfExportMenu({ ...props }: RvfExportMenuButtonProps) {
30
30
  {/* <!-- Content --> */}
31
31
  <div className="flex flex-1 flex-col gap-4">
32
32
  <div className={"flex flex-wrap items-center gap-2"}>
33
- <RvfCheckbox
33
+ <Checkbox
34
34
  checked={useMaxExtent}
35
35
  id={checkboxId}
36
36
  onChange={() => {
@@ -41,7 +41,7 @@ function RvfExportMenu({ ...props }: RvfExportMenuButtonProps) {
41
41
  </div>
42
42
  <div className={"flex flex-wrap items-center gap-2"}>
43
43
  <label htmlFor={selectId}>Format:</label>
44
- <RvfSelect
44
+ <Select
45
45
  className={"w-24"}
46
46
  id={selectId}
47
47
  onChange={(evt) => {
@@ -51,7 +51,7 @@ function RvfExportMenu({ ...props }: RvfExportMenuButtonProps) {
51
51
  {formats.map((formatt) => {
52
52
  return <option key={formatt}>{formatt}</option>;
53
53
  })}
54
- </RvfSelect>
54
+ </Select>
55
55
  </div>
56
56
  </div>
57
57
  {/* <!-- Footer --> */}
@@ -66,6 +66,8 @@ function RvfExportMenu({ ...props }: RvfExportMenuButtonProps) {
66
66
  map,
67
67
  { format },
68
68
  {
69
+ maxExtent: useMaxExtent ? MAX_EXTENT : undefined,
70
+
69
71
  onAfter: (mapp, layers) => {
70
72
  if (
71
73
  realtimeLayer &&
@@ -78,7 +80,6 @@ function RvfExportMenu({ ...props }: RvfExportMenuButtonProps) {
78
80
  });
79
81
  mapp.set(LAYER_PROP_IS_EXPORTING, false);
80
82
  },
81
-
82
83
  onBefore: (mapp, layers) => {
83
84
  mapp.set(LAYER_PROP_IS_EXPORTING, true);
84
85
  if (realtimeLayer) {
@@ -91,7 +92,6 @@ function RvfExportMenu({ ...props }: RvfExportMenuButtonProps) {
91
92
  layer.set(LAYER_PROP_IS_EXPORTING, true);
92
93
  });
93
94
  },
94
- useMaxExtent,
95
95
  },
96
96
  );
97
97
  setTimeout(() => {
@@ -112,4 +112,4 @@ function RvfExportMenu({ ...props }: RvfExportMenuButtonProps) {
112
112
  );
113
113
  }
114
114
 
115
- export default memo(RvfExportMenu);
115
+ export default memo(ExportMenu);
@@ -0,0 +1 @@
1
+ export { default } from "./ExportMenu";
@@ -3,14 +3,14 @@ import { useCallback } from "preact/hooks";
3
3
 
4
4
  import Download from "../icons/Download";
5
5
  import IconButton from "../ui/IconButton";
6
- import useRvfContext from "../utils/hooks/useRvfContext";
6
+ import useMapContext from "../utils/hooks/useMapContext";
7
7
 
8
8
  import type { IconButtonProps } from "../ui/IconButton/IconButton";
9
9
 
10
- export type RvfExportMenuButtonProps = IconButtonProps;
10
+ export type ExportMenuButtonProps = IconButtonProps;
11
11
 
12
- function RvfExportMenuButton({ ...props }: RvfExportMenuButtonProps) {
13
- const { isExportMenuOpen, setIsExportMenuOpen } = useRvfContext();
12
+ function ExportMenuButton({ ...props }: ExportMenuButtonProps) {
13
+ const { isExportMenuOpen, setIsExportMenuOpen } = useMapContext();
14
14
 
15
15
  const onClick = useCallback(() => {
16
16
  setIsExportMenuOpen(!isExportMenuOpen);
@@ -23,4 +23,4 @@ function RvfExportMenuButton({ ...props }: RvfExportMenuButtonProps) {
23
23
  );
24
24
  }
25
25
 
26
- export default memo(RvfExportMenuButton);
26
+ export default memo(ExportMenuButton);
@@ -0,0 +1 @@
1
+ export { default } from "./ExportMenuButton";
@@ -0,0 +1,57 @@
1
+ import { memo } from "preact/compat";
2
+
3
+ import LinesNetworkPlanDetails from "../LinesNetworkPlanDetails";
4
+ import NotificationDetails from "../NotificationDetails";
5
+ import RouteSchedule from "../RouteSchedule";
6
+ import Station from "../Station";
7
+ import useMapContext from "../utils/hooks/useMapContext";
8
+
9
+ import type { FeatureInfo } from "mobility-toolbox-js/common/typedefs";
10
+ import type { Feature } from "ol";
11
+ import type BaseLayer from "ol/layer/Base";
12
+
13
+ const contentClassName = ""; //`h-full overflow-x-hidden text-base bg-white`;
14
+
15
+ export interface FeatureDetailsProps {
16
+ feature?: Feature;
17
+ featuresInfo?: FeatureInfo;
18
+ layer?: BaseLayer;
19
+ }
20
+
21
+ /**
22
+ * This component is repsonsible to display the details of a feature passed as prop.
23
+ */
24
+ function FeatureDetails({ feature, featuresInfo, layer }: FeatureDetailsProps) {
25
+ const {
26
+ linesNetworkPlanLayer,
27
+ notificationsLayer,
28
+ realtimeLayer,
29
+ stationId,
30
+ stationsLayer,
31
+ tenant,
32
+ trainId,
33
+ } = useMapContext();
34
+
35
+ if (!feature) {
36
+ return null;
37
+ }
38
+
39
+ return (
40
+ <>
41
+ {feature && layer === realtimeLayer && trainId && <RouteSchedule />}
42
+ {feature && layer === stationsLayer && tenant && stationId && <Station />}
43
+ {feature &&
44
+ layer === linesNetworkPlanLayer &&
45
+ featuresInfo?.features?.length && (
46
+ <LinesNetworkPlanDetails
47
+ className={contentClassName}
48
+ features={featuresInfo?.features || []}
49
+ />
50
+ )}
51
+ {feature && layer === notificationsLayer && (
52
+ <NotificationDetails className={contentClassName} feature={feature} />
53
+ )}
54
+ </>
55
+ );
56
+ }
57
+ export default memo(FeatureDetails);
@@ -0,0 +1 @@
1
+ export { default } from "../RvfFeatureDetails";
@@ -0,0 +1,73 @@
1
+ import { useEffect } from "preact/hooks";
2
+
3
+ import useMapContext from "../utils/hooks/useMapContext";
4
+
5
+ /**
6
+ * This component defines the behavior when user select or hover a queryable feature
7
+ */
8
+ function FeaturesInfosListener() {
9
+ const {
10
+ featuresInfos,
11
+ featuresInfosHovered,
12
+ realtimeLayer,
13
+ setSelectedFeature,
14
+ setSelectedFeatures,
15
+ setStationId,
16
+ setTrainId,
17
+ stationsLayer,
18
+ tenant,
19
+ } = useMapContext();
20
+
21
+ useEffect(() => {
22
+ const [realtimeFeature] =
23
+ featuresInfosHovered?.find((info) => {
24
+ return info.layer === realtimeLayer;
25
+ })?.features || [];
26
+ realtimeLayer?.highlight(realtimeFeature);
27
+ }, [featuresInfosHovered, realtimeLayer]);
28
+
29
+ useEffect(() => {
30
+ const [realtimeFeature] =
31
+ featuresInfos?.find((info) => {
32
+ return info.layer === realtimeLayer;
33
+ })?.features || [];
34
+
35
+ const stationsFeatures =
36
+ featuresInfos?.find((info) => {
37
+ return info.layer === stationsLayer;
38
+ })?.features || [];
39
+
40
+ const [stationFeature] = stationsFeatures.filter((feat) => {
41
+ return feat.get("tralis_network")?.includes(tenant);
42
+ });
43
+
44
+ const features =
45
+ featuresInfos?.flatMap((info) => {
46
+ return info.features;
47
+ }) || [];
48
+
49
+ if (realtimeFeature || stationFeature || !features.length) {
50
+ setSelectedFeature(realtimeFeature || stationFeature || null);
51
+ setSelectedFeatures(
52
+ realtimeFeature || stationFeature
53
+ ? [realtimeFeature || stationFeature]
54
+ : [],
55
+ );
56
+ } else {
57
+ setSelectedFeatures(features);
58
+ setSelectedFeature(realtimeFeature || stationFeature || features[0]);
59
+ }
60
+ }, [
61
+ featuresInfos,
62
+ realtimeLayer,
63
+ setSelectedFeature,
64
+ setSelectedFeatures,
65
+ setStationId,
66
+ setTrainId,
67
+ stationsLayer,
68
+ tenant,
69
+ ]);
70
+
71
+ return null;
72
+ }
73
+ export default FeaturesInfosListener;
@@ -0,0 +1 @@
1
+ export { default } from "./FeaturesInfosListener";
@@ -59,7 +59,6 @@ function GeolocationButton({ ...props }: GeolocationButtonProps) {
59
59
 
60
60
  return (
61
61
  <IconButton
62
- // @ts-expect-error - onClick exists
63
62
  onClick={() => {
64
63
  return setIsTracking(!isTracking);
65
64
  }}
@@ -0,0 +1,58 @@
1
+ import { memo } from "preact/compat";
2
+ import { useEffect, useReducer } from "preact/hooks";
3
+ import { twMerge } from "tailwind-merge";
4
+
5
+ import {
6
+ LayersTreeContext,
7
+ LayersTreeDispatchContext,
8
+ } from "./layersTreeContext";
9
+ import layersTreeReducer from "./layersTreeReducer";
10
+ import TreeItem from "./TreeItem";
11
+
12
+ import type { HTMLAttributes, PreactDOMAttributes } from "preact";
13
+
14
+ import type { TreeItemProps } from "./TreeItem";
15
+
16
+ export type LayerTreeProps = {
17
+ className?: string;
18
+ layers: TreeItemProps[];
19
+ treeItemProps?: Partial<TreeItemProps>;
20
+ } & HTMLAttributes<HTMLDivElement> &
21
+ PreactDOMAttributes;
22
+
23
+ function LayerTree({
24
+ className,
25
+ layers,
26
+ treeItemProps,
27
+ ...props
28
+ }: LayerTreeProps) {
29
+ const [tree, dispatch] = useReducer(layersTreeReducer, layers);
30
+
31
+ useEffect(() => {
32
+ dispatch({ payload: layers, type: "INIT" });
33
+ }, [layers]);
34
+
35
+ return (
36
+ <LayersTreeContext.Provider value={tree}>
37
+ <LayersTreeDispatchContext.Provider value={dispatch}>
38
+ <div
39
+ className={twMerge("relative flex flex-col", className)}
40
+ {...props}
41
+ >
42
+ {layers.map((item) => {
43
+ return (
44
+ <TreeItem
45
+ {...(treeItemProps || {})}
46
+ className={twMerge("w-full", treeItemProps?.className)}
47
+ key={item.id}
48
+ {...item}
49
+ />
50
+ );
51
+ })}
52
+ </div>
53
+ </LayersTreeDispatchContext.Provider>
54
+ </LayersTreeContext.Provider>
55
+ );
56
+ }
57
+
58
+ export default memo(LayerTree);
@@ -0,0 +1,151 @@
1
+ import { useContext, useEffect, useId, useState } from "preact/compat";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ import ArrowDown from "../../icons/ArrowDown";
5
+ import ArrowUp from "../../icons/ArrowUp";
6
+ import minusGrey from "../../icons/Minus/minus.svg";
7
+ import Checkbox from "../../ui/Checkbox";
8
+ // import RvfRadioButton from "../../RvfRadioButton";
9
+ import { LayersTreeDispatchContext } from "../layersTreeContext";
10
+
11
+ import type BaseLayer from "ol/layer/Base";
12
+ import type { HTMLAttributes, PreactDOMAttributes } from "preact";
13
+ import type { SVGProps } from "preact/compat";
14
+
15
+ export enum SelectionType {
16
+ CHECKBOX = "checkbox",
17
+ RADIO = "radio",
18
+ }
19
+
20
+ export type TreeItemProps = {
21
+ childContainerClassName?: string;
22
+ childItems: TreeItemProps[];
23
+ className?: string;
24
+ Icon?: (props: SVGProps<SVGSVGElement>) => preact.JSX.Element;
25
+ id: string;
26
+ isCollapsedOnControlClick?: boolean;
27
+ isControlChecked?: boolean;
28
+ layer?: BaseLayer;
29
+ onIconClick?: () => void;
30
+ selectionType: SelectionType;
31
+ title: string;
32
+ } & HTMLAttributes<HTMLButtonElement> &
33
+ PreactDOMAttributes;
34
+
35
+ function TreeItem({
36
+ childContainerClassName,
37
+ childItems,
38
+ className,
39
+ isCollapsedOnControlClick,
40
+ isControlChecked,
41
+ layer,
42
+ selectionType,
43
+ title,
44
+ }: TreeItemProps) {
45
+ const [isContainerVisible, setIsContainerVisible] = useState(true);
46
+ const dispatch = useContext(LayersTreeDispatchContext);
47
+ const inputId = useId();
48
+ const buttonId = useId();
49
+
50
+ useEffect(() => {
51
+ if (isCollapsedOnControlClick) {
52
+ setIsContainerVisible(isControlChecked);
53
+ }
54
+ }, [isControlChecked, isCollapsedOnControlClick, layer]);
55
+
56
+ const handleItemClick = () => {
57
+ setIsContainerVisible(!isContainerVisible);
58
+
59
+ if (isCollapsedOnControlClick && !isContainerVisible) {
60
+ dispatch({
61
+ payload: {
62
+ ...this.props,
63
+ isControlChecked: true,
64
+ },
65
+ type: "SELECT_ITEM",
66
+ });
67
+ }
68
+ };
69
+
70
+ const handleSelectionChange = (event) => {
71
+ dispatch({
72
+ payload: {
73
+ ...this.props,
74
+ isControlChecked: event.target.checked,
75
+ },
76
+ type: "SELECT_ITEM",
77
+ });
78
+ };
79
+
80
+ const renderedLayers = childItems
81
+ .filter(({ title: titlee }) => {
82
+ return !!titlee;
83
+ })
84
+ .map((item, idx) => {
85
+ return <TreeItem key={idx} {...item} />;
86
+ });
87
+
88
+ if (!title) {
89
+ return null;
90
+ }
91
+
92
+ const isMiddleState = () => {
93
+ if (childItems.length > 0) {
94
+ const checkedItems = childItems.filter((item) => {
95
+ return item.isControlChecked;
96
+ });
97
+ if (checkedItems.length === childItems.length) {
98
+ return false;
99
+ }
100
+
101
+ return childItems.some((item) => {
102
+ return item.isControlChecked;
103
+ });
104
+ }
105
+
106
+ return false;
107
+ };
108
+
109
+ return (
110
+ <>
111
+ <div className={twMerge("flex items-center gap-2 py-2", className)}>
112
+ {selectionType === SelectionType.RADIO ? null : (
113
+ // <RvfRadioButton
114
+ // checked={isControlChecked}
115
+ // id={inputId}
116
+ // onChange={handleSelectionChange}
117
+ // />
118
+ <Checkbox
119
+ checked={isControlChecked}
120
+ checkedIconUrl={isMiddleState() ? minusGrey : null}
121
+ className={isMiddleState() ? "bg-[length:18px]" : ""}
122
+ id={inputId}
123
+ onChange={handleSelectionChange}
124
+ />
125
+ )}
126
+ <label
127
+ className={`flex-1 cursor-pointer`}
128
+ htmlFor={renderedLayers.length > 0 ? buttonId : inputId}
129
+ >
130
+ {title}
131
+ </label>
132
+ {renderedLayers.length > 0 && (
133
+ <button
134
+ className={`flex cursor-pointer items-center gap-2`}
135
+ id={buttonId}
136
+ onClick={handleItemClick}
137
+ >
138
+ {isContainerVisible ? <ArrowUp /> : <ArrowDown />}
139
+ </button>
140
+ )}
141
+ </div>
142
+ {isContainerVisible && renderedLayers.length > 0 && (
143
+ <div className={twMerge("pl-6", childContainerClassName)}>
144
+ {renderedLayers}
145
+ </div>
146
+ )}
147
+ </>
148
+ );
149
+ }
150
+
151
+ export default TreeItem;
@@ -0,0 +1 @@
1
+ export { default, SelectionType, TreeItemProps } from "./TreeItem";
@@ -0,0 +1 @@
1
+ export { default } from "./LayerTree";
@@ -0,0 +1,4 @@
1
+ import { createContext } from "preact";
2
+
3
+ export const LayersTreeContext = createContext(null);
4
+ export const LayersTreeDispatchContext = createContext(null);