layers-select-control 0.3.0 → 0.4.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.
package/README.md
CHANGED
|
@@ -5,11 +5,13 @@ A high-performance React component for selecting layers, inspired by the **Googl
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
* **Google Maps UI:** Seamlessly transitions between a collapsed thumbnail and an expanded list.
|
|
8
|
-
* **Intelligent Image Loading:** The `ManagedImage` system handles HD transitions and fallbacks without UI flickering.
|
|
9
|
-
* **
|
|
10
|
-
* **
|
|
11
|
-
* **
|
|
12
|
-
* **
|
|
8
|
+
* **Intelligent Image Loading:** The `ManagedImage` system handles HD transitions and fallbacks (`thumbnailHd` -> `thumbnail` -> `noImageThumb`) without UI flickering.
|
|
9
|
+
* **Ref/Imperative API:** Access internal state and control selection programmatically using `useImperativeHandle`.
|
|
10
|
+
* **Adaptive Positioning:** Anchor the control to any corner using `xRel` and `yRel`.
|
|
11
|
+
* **Dynamic Sizing:** Built-in `small`, `medium`, and `large` presets that scale all dimensions via CSS variables.
|
|
12
|
+
* **Interactive Scrolling:** Automatically detects overflow and provides navigation arrows for horizontal scrolling.
|
|
13
|
+
* **Lifecycle Hooks:** `onExpand` and `onCollapse` callbacks for integration with other UI elements.
|
|
14
|
+
* **Touch Optimized:** Includes pointer-event handling and touch-start detection for mobile map environments.
|
|
13
15
|
|
|
14
16
|
## Installation
|
|
15
17
|
|
|
@@ -28,22 +30,25 @@ The component is highly configurable to fit different map layouts and branding r
|
|
|
28
30
|
|
|
29
31
|
| Prop | Type | Default | Description |
|
|
30
32
|
| --- | --- | --- | --- |
|
|
31
|
-
| `items` | `LayerItem[]` | **Required** | Array of items to display in the
|
|
33
|
+
| `items` | `LayerItem[]` | **Required** | Array of items to display in the main scrollable area. |
|
|
32
34
|
| `onSelect` | `(item: LayerItem) => void` | **Required** | Callback triggered when a layer is selected. |
|
|
33
35
|
| `onDefault` | `(item?: LayerItem) => void` | **Required** | Callback for the "Default" tile click. |
|
|
34
36
|
| `value` | `string` | - | **Controlled mode**: The ID of the currently selected item. |
|
|
35
37
|
| `defaultValue` | `string` | - | **Uncontrolled mode**: The initial ID to be selected. |
|
|
36
|
-
| `defaultItem` | `LayerItem` | - |
|
|
37
|
-
| `
|
|
38
|
-
| `
|
|
39
|
-
| `
|
|
38
|
+
| `defaultItem` | `LayerItem` | - | A special tile that appears fixed at the start of the list. |
|
|
39
|
+
| `moreItem` | `LayerItem` | - | Custom title/description for the "More" tile. |
|
|
40
|
+
| `size` | `"small" | "medium" | "large"` |
|
|
41
|
+
| `theme` | `"dark" | "light"` | `"dark"` |
|
|
42
|
+
| `maxVisible` | `number` | `5` | Number of items visible before enabling horizontal scroll. |
|
|
40
43
|
| `x`, `y` | `number` | `8`, `32` | Numerical offset from the anchor point. |
|
|
41
|
-
| `xRel` | `"left" | "right"` | `"left"` |
|
|
42
|
-
| `yRel` | `"top" | "bottom"` | `"bottom"` |
|
|
44
|
+
| `xRel` | `"left" | "right"` | `"left"` |
|
|
45
|
+
| `yRel` | `"top" | "bottom"` | `"bottom"` |
|
|
43
46
|
| `onMore` | `() => void` | - | If provided, a "More..." tile is appended to the list. |
|
|
44
|
-
| `
|
|
45
|
-
| `
|
|
46
|
-
| `
|
|
47
|
+
| `onExpand` | `() => void` | - | Fired when the panel opens. |
|
|
48
|
+
| `onCollapse` | `() => void` | - | Fired when the panel closes. |
|
|
49
|
+
| `defaultThumb` | `string` | - | Override the default "reset" icon URL. |
|
|
50
|
+
| `noImageThumb` | `string` | - | Fallback image for items without thumbnails. |
|
|
51
|
+
| `moreThumb` | `string` | - | Icon for the "More" tile. |
|
|
47
52
|
|
|
48
53
|
## Data Interfaces
|
|
49
54
|
|
|
@@ -55,7 +60,19 @@ export interface LayerItem {
|
|
|
55
60
|
title: string;
|
|
56
61
|
description?: string;
|
|
57
62
|
thumbnail?: string; // Standard thumbnail
|
|
58
|
-
thumbnailHd?: string; //
|
|
63
|
+
thumbnailHd?: string; // HD version loaded only when panel expands
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Imperative Handle (Ref)
|
|
69
|
+
|
|
70
|
+
You can control the component externally by passing a `ref`:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
export interface LayersSelectControlRef {
|
|
74
|
+
setSelectedItem: (id: string) => void;
|
|
75
|
+
getSelectedItem: () => LayerItem | undefined;
|
|
59
76
|
}
|
|
60
77
|
|
|
61
78
|
```
|
|
@@ -63,8 +80,8 @@ export interface LayerItem {
|
|
|
63
80
|
## Usage Example
|
|
64
81
|
|
|
65
82
|
```tsx
|
|
66
|
-
import React from "react";
|
|
67
|
-
import { LayersSelectControl, LayerItem } from "layers-select-control";
|
|
83
|
+
import React, { useRef } from "react";
|
|
84
|
+
import { LayersSelectControl, LayerItem, LayersSelectControlRef } from "layers-select-control";
|
|
68
85
|
|
|
69
86
|
const LAYERS: LayerItem[] = [
|
|
70
87
|
{
|
|
@@ -75,17 +92,20 @@ const LAYERS: LayerItem[] = [
|
|
|
75
92
|
thumbnailHd: "/thumbs/sat-hd.jpg"
|
|
76
93
|
},
|
|
77
94
|
{ id: "terrain", title: "Terrain", description: "Topographic maps", thumbnail: "/thumbs/terrain.jpg" },
|
|
78
|
-
{ id: "streets", title: "Streets", description: "Urban navigation", thumbnail: "/thumbs/streets.jpg" },
|
|
79
95
|
];
|
|
80
96
|
|
|
81
97
|
export const App = () => {
|
|
98
|
+
const controlRef = useRef<LayersSelectControlRef>(null);
|
|
99
|
+
|
|
82
100
|
return (
|
|
83
|
-
<div style={{ position: "relative", width: "100%", height: "
|
|
101
|
+
<div style={{ position: "relative", width: "100%", height: "600px" }}>
|
|
84
102
|
<LayersSelectControl
|
|
103
|
+
ref={controlRef}
|
|
85
104
|
items={LAYERS}
|
|
86
105
|
size="medium"
|
|
87
106
|
theme="light"
|
|
88
|
-
|
|
107
|
+
defaultItem={{ id: "base", title: "Standard", description: "Default Map" }}
|
|
108
|
+
onSelect={(item) => console.log("Active Layer:", item.id)}
|
|
89
109
|
onDefault={() => console.log("Reset to default")}
|
|
90
110
|
onMore={() => alert("Open full catalog")}
|
|
91
111
|
/>
|
|
@@ -97,10 +117,11 @@ export const App = () => {
|
|
|
97
117
|
|
|
98
118
|
## Styling & Theme
|
|
99
119
|
|
|
100
|
-
The component uses scoped CSS
|
|
120
|
+
The component uses scoped CSS logic to manage layouts dynamically:
|
|
101
121
|
|
|
102
|
-
* **
|
|
103
|
-
* **
|
|
122
|
+
* **Responsive Scaling:** Uses internal configurations to calculate exact pixel dimensions for thumbnails and tiles.
|
|
123
|
+
* **Auto-Width:** Utilizes `ResizeObserver` on the parent container to ensure the expanded panel never overflows the screen boundaries.
|
|
124
|
+
* **Pointer Awareness:** Automatically switches between `hover` and `touch` events depending on the user's device.
|
|
104
125
|
|
|
105
126
|
## License
|
|
106
127
|
|
|
@@ -7,9 +7,11 @@ export interface LayerItem {
|
|
|
7
7
|
thumbnailHd?: string;
|
|
8
8
|
}
|
|
9
9
|
interface Props {
|
|
10
|
+
title?: string;
|
|
10
11
|
value?: string;
|
|
11
12
|
defaultValue?: string;
|
|
12
13
|
defaultItem?: LayerItem;
|
|
14
|
+
externalItem?: LayerItem;
|
|
13
15
|
moreItem?: LayerItem;
|
|
14
16
|
items: LayerItem[];
|
|
15
17
|
x?: number;
|
|
@@ -31,6 +33,10 @@ interface Props {
|
|
|
31
33
|
onExpand?: () => void;
|
|
32
34
|
onCollapse?: () => void;
|
|
33
35
|
}
|
|
34
|
-
export
|
|
36
|
+
export interface LayersSelectControlRef {
|
|
37
|
+
setSelectedItem: (id: string) => void;
|
|
38
|
+
getSelectedItem: () => LayerItem | undefined;
|
|
39
|
+
}
|
|
40
|
+
export declare const LayersSelectControl: React.ForwardRefExoticComponent<Props & React.RefAttributes<LayersSelectControlRef>>;
|
|
35
41
|
export {};
|
|
36
42
|
//# sourceMappingURL=LayersSelectControl.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LayersSelectControl.d.ts","sourceRoot":"","sources":["../src/LayersSelectControl.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAEjF,MAAM,WAAW,SAAS;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,UAAU,KAAK;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,EAAE,SAAS,EAAE,CAAC;IAEnB,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACxB,IAAI,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IAExB,SAAS,EAAE,CAAC,IAAI,CAAC,EAAE,SAAS,KAAK,IAAI,CAAC;IACtC,QAAQ,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IACpC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IAEpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAEzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,IAAI,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B;
|
|
1
|
+
{"version":3,"file":"LayersSelectControl.d.ts","sourceRoot":"","sources":["../src/LayersSelectControl.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAEjF,MAAM,WAAW,SAAS;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,UAAU,KAAK;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,EAAE,SAAS,EAAE,CAAC;IAEnB,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACxB,IAAI,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IAExB,SAAS,EAAE,CAAC,IAAI,CAAC,EAAE,SAAS,KAAK,IAAI,CAAC;IACtC,QAAQ,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IACpC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IAEpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAEzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,IAAI,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B;AA+DD,MAAM,WAAW,sBAAsB;IACnC,eAAe,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,eAAe,EAAE,MAAM,SAAS,GAAG,SAAS,CAAC;CAChD;AAKD,eAAO,MAAM,mBAAmB,sFAoR9B,CAAC"}
|
|
@@ -1,8 +1,41 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.LayersSelectControl = void 0;
|
|
4
37
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
-
const react_1 = require("react");
|
|
38
|
+
const react_1 = __importStar(require("react"));
|
|
6
39
|
const DEFAULT_RESET_IMAGE_URL = "./firstplace.svg";
|
|
7
40
|
const DEFAULT_NO_IMAGE_URL = "./noimage.png";
|
|
8
41
|
const DEFAULT_IMAGE_MORE = "./more-images.png";
|
|
@@ -47,7 +80,7 @@ function ManagedImage({ candidates, initial, alt, className, expanded, noImageTh
|
|
|
47
80
|
// ------------------------------------------------------
|
|
48
81
|
// COMPONENT (LOGIC PRESERVED)
|
|
49
82
|
// ------------------------------------------------------
|
|
50
|
-
|
|
83
|
+
exports.LayersSelectControl = react_1.default.forwardRef(({ title, items, value, defaultValue, x = 8, y = 32, xRel = "left", yRel = "bottom", defaultItem, externalItem, moreItem, onSelect, onMore, onDefault, maxVisible = DEFAULT_MAX_VISIBLE, hoverCloseDelayMs = 160, theme = "dark", defaultThumb = DEFAULT_RESET_IMAGE_URL, noImageThumb = DEFAULT_NO_IMAGE_URL, moreThumb = DEFAULT_IMAGE_MORE, size = "small", panelGap = 8, parentGap = 8, onCollapse, onExpand }, ref) => {
|
|
51
84
|
const isControlled = value !== undefined;
|
|
52
85
|
const [internalValue, setInternalValue] = (0, react_1.useState)(defaultValue);
|
|
53
86
|
const selectedId = isControlled ? value : internalValue;
|
|
@@ -108,6 +141,8 @@ const LayersSelectControl = ({ items, value, defaultValue, x = 8, y = 32, xRel =
|
|
|
108
141
|
const selectedItem = (0, react_1.useMemo)(() => {
|
|
109
142
|
if (defaultItem && selectedId === defaultItem.id)
|
|
110
143
|
return defaultItem;
|
|
144
|
+
if (externalItem && selectedId === externalItem.id)
|
|
145
|
+
return externalItem;
|
|
111
146
|
return items.find((x) => x.id === selectedId);
|
|
112
147
|
}, [items, selectedId, defaultItem]);
|
|
113
148
|
const collapsedCandidates = (0, react_1.useMemo)(() => [collapsedThumb(selectedItem)], [collapsedThumb, selectedItem]);
|
|
@@ -147,7 +182,12 @@ const LayersSelectControl = ({ items, value, defaultValue, x = 8, y = 32, xRel =
|
|
|
147
182
|
const toggleExpanded = () => {
|
|
148
183
|
setExpanded((s) => (s ? false : hasContent()));
|
|
149
184
|
};
|
|
150
|
-
|
|
185
|
+
// ----- Expose methods to parent -----
|
|
186
|
+
react_1.default.useImperativeHandle(ref, () => ({
|
|
187
|
+
setSelectedItem: (id) => setSelectedId(id),
|
|
188
|
+
getSelectedItem: () => selectedItem,
|
|
189
|
+
}), [selectedItem, setSelectedId]);
|
|
190
|
+
return ((0, jsx_runtime_1.jsxs)("div", { title: title, ref: rootRef, className: `lsc-root ${theme === "light" ? "lsc-light" : ""}`, style: {
|
|
151
191
|
[xRel]: `${x}px`,
|
|
152
192
|
[yRel]: `${y}px`,
|
|
153
193
|
"--lsc-collapsed": `${cfg.collapsed}px`,
|
|
@@ -186,5 +226,4 @@ const LayersSelectControl = ({ items, value, defaultValue, x = 8, y = 32, xRel =
|
|
|
186
226
|
onMore === null || onMore === void 0 ? void 0 : onMore();
|
|
187
227
|
setExpanded(false);
|
|
188
228
|
}, children: [(0, jsx_runtime_1.jsx)(ManagedImage, { candidates: [moreThumb], initial: moreThumb, expanded: expanded, className: "lsc-t-thumb", noImageThumb: noImageThumb }), (0, jsx_runtime_1.jsx)("div", { className: "lsc-title", children: (moreItem === null || moreItem === void 0 ? void 0 : moreItem.title) ? moreItem === null || moreItem === void 0 ? void 0 : moreItem.title : "More…" }), (0, jsx_runtime_1.jsx)("div", { className: "lsc-desc", children: (moreItem === null || moreItem === void 0 ? void 0 : moreItem.description) ? moreItem === null || moreItem === void 0 ? void 0 : moreItem.description : "Show all" })] }))] }) })] }));
|
|
189
|
-
};
|
|
190
|
-
exports.LayersSelectControl = LayersSelectControl;
|
|
229
|
+
});
|