@zappie/foundations 0.1.4 → 0.1.6
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/CHANGELOG.md +29 -0
- package/README.md +73 -13
- package/dist/branch-map/colors.js +27 -0
- package/dist/branch-map/colors.js.map +7 -0
- package/dist/branch-map/delivery-map.js +60 -0
- package/dist/branch-map/delivery-map.js.map +7 -0
- package/dist/branch-map/delivery-runtime.js +118 -0
- package/dist/branch-map/delivery-runtime.js.map +7 -0
- package/dist/branch-map/fit.js +123 -0
- package/dist/branch-map/fit.js.map +7 -0
- package/dist/branch-map/geometry.js +1 -30
- package/dist/branch-map/geometry.js.map +2 -2
- package/dist/branch-map/index.js +21 -53
- package/dist/branch-map/index.js.map +2 -2
- package/dist/branch-map/markers.js +183 -0
- package/dist/branch-map/markers.js.map +7 -0
- package/dist/branch-map/runtime.js +235 -99
- package/dist/branch-map/runtime.js.map +3 -3
- package/dist/branch-map/use-map-runtime.js +53 -0
- package/dist/branch-map/use-map-runtime.js.map +7 -0
- package/dist/deferred/index.js +42 -11
- package/dist/deferred/index.js.map +2 -2
- package/dist/{engine-XTsgCwwe.d.ts → engine-Bfbv9M0D.d.ts} +1 -2
- package/dist/index.d.ts +74 -23
- package/dist/index.js +2 -1
- package/dist/index.js.map +2 -2
- package/dist/item-form/root.js.map +1 -1
- package/dist/overlay/engine.d.ts +1 -2
- package/dist/overlay/react.js +224 -13
- package/dist/overlay/react.js.map +2 -2
- package/dist/version.js +1 -1
- package/package.json +5 -4
package/dist/branch-map/index.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// src/branch-map/index.tsx
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
useRef,
|
|
7
|
-
useState
|
|
8
|
-
} from "react";
|
|
4
|
+
import { useEffect, useRef } from "react";
|
|
5
|
+
import { COLOR_PROBE_STYLE, readColors } from "./colors.js";
|
|
9
6
|
import {
|
|
10
7
|
validCoordinates,
|
|
11
8
|
visibleBranches
|
|
12
9
|
} from "./geometry.js";
|
|
10
|
+
import { useMapRuntime } from "./use-map-runtime.js";
|
|
11
|
+
import { DeliveryMap } from "./delivery-map.js";
|
|
13
12
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
13
|
+
var loadBranchMap = (mapTypeControl) => (target, key, signal) => import("./runtime.js").then(
|
|
14
|
+
({ createMap }) => createMap(target, key, signal, { mapTypeControl })
|
|
15
|
+
);
|
|
14
16
|
function BranchMap({
|
|
15
17
|
branches,
|
|
16
18
|
selectedBranchId,
|
|
@@ -21,58 +23,24 @@ function BranchMap({
|
|
|
21
23
|
pinImageUrl,
|
|
22
24
|
mapTypeControl = false,
|
|
23
25
|
pinLabel,
|
|
26
|
+
compact = false,
|
|
24
27
|
onLoadError,
|
|
25
28
|
renderLoadError,
|
|
26
29
|
...props
|
|
27
30
|
}) {
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
const [attempt, setAttempt] = useState(0);
|
|
32
|
-
const reportError = useRef(onLoadError);
|
|
33
|
-
useEffect(() => {
|
|
34
|
-
reportError.current = onLoadError;
|
|
35
|
-
}, [onLoadError]);
|
|
31
|
+
const probe = useRef(null);
|
|
32
|
+
const hasCoordinates = visibleBranches(branches).length > 0;
|
|
33
|
+
const { element, runtime, failure, retry } = useMapRuntime(browserKey, hasCoordinates, loadBranchMap(mapTypeControl), onLoadError);
|
|
36
34
|
const failed = failure !== void 0;
|
|
37
35
|
const effectiveMaxZoom = maxZoom ?? (validCoordinates(shopperLocation) ? 15 : 14);
|
|
38
|
-
const hasCoordinates = visibleBranches(branches).length > 0;
|
|
39
|
-
useEffect(() => {
|
|
40
|
-
if (!browserKey || !element.current || !hasCoordinates) {
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
let disposed = false;
|
|
44
|
-
const controller = new AbortController();
|
|
45
|
-
let instance;
|
|
46
|
-
const target = element.current;
|
|
47
|
-
setFailure(void 0);
|
|
48
|
-
void import("./runtime.js").then(
|
|
49
|
-
({ createMap }) => createMap(target, browserKey, controller.signal, { mapTypeControl })
|
|
50
|
-
).then((map) => {
|
|
51
|
-
instance = map;
|
|
52
|
-
if (disposed) {
|
|
53
|
-
map.destroy();
|
|
54
|
-
} else {
|
|
55
|
-
setRuntime(map);
|
|
56
|
-
}
|
|
57
|
-
}).catch((error) => {
|
|
58
|
-
if (!disposed) {
|
|
59
|
-
setFailure({ error });
|
|
60
|
-
reportError.current?.(error);
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
return () => {
|
|
64
|
-
disposed = true;
|
|
65
|
-
controller.abort();
|
|
66
|
-
instance?.destroy();
|
|
67
|
-
setRuntime(void 0);
|
|
68
|
-
};
|
|
69
|
-
}, [browserKey, hasCoordinates, attempt]);
|
|
70
36
|
useEffect(() => {
|
|
71
37
|
runtime?.update(branches, selectedBranchId, onSelect, shopperLocation, {
|
|
72
38
|
maxZoom: effectiveMaxZoom,
|
|
73
39
|
pinImageUrl,
|
|
74
40
|
mapTypeControl,
|
|
75
|
-
pinLabel
|
|
41
|
+
pinLabel,
|
|
42
|
+
compact,
|
|
43
|
+
colors: readColors(probe.current)
|
|
76
44
|
});
|
|
77
45
|
}, [
|
|
78
46
|
runtime,
|
|
@@ -83,15 +51,17 @@ function BranchMap({
|
|
|
83
51
|
effectiveMaxZoom,
|
|
84
52
|
pinImageUrl,
|
|
85
53
|
mapTypeControl,
|
|
86
|
-
pinLabel
|
|
54
|
+
pinLabel,
|
|
55
|
+
compact
|
|
87
56
|
]);
|
|
88
57
|
if (!browserKey) {
|
|
89
58
|
return null;
|
|
90
59
|
}
|
|
91
60
|
const unavailable = branches.filter(
|
|
92
|
-
(branch) => !
|
|
61
|
+
(branch) => !visibleBranches([branch]).length
|
|
93
62
|
);
|
|
94
63
|
return /* @__PURE__ */ jsxs("div", { ...props, "data-branch-map": "", children: [
|
|
64
|
+
/* @__PURE__ */ jsx("span", { ref: probe, hidden: true, style: COLOR_PROBE_STYLE }),
|
|
95
65
|
/* @__PURE__ */ jsx(
|
|
96
66
|
"div",
|
|
97
67
|
{
|
|
@@ -101,10 +71,7 @@ function BranchMap({
|
|
|
101
71
|
style: { height: "100%" }
|
|
102
72
|
}
|
|
103
73
|
),
|
|
104
|
-
failure && renderLoadError ? renderLoadError({
|
|
105
|
-
error: failure.error,
|
|
106
|
-
retry: () => setAttempt((count) => count + 1)
|
|
107
|
-
}) : failed || !hasCoordinates ? /* @__PURE__ */ jsx("p", { role: "status", children: "Map location unavailable" }) : null,
|
|
74
|
+
failure && renderLoadError ? renderLoadError({ error: failure.error, retry }) : failed || !hasCoordinates ? /* @__PURE__ */ jsx("p", { role: "status", children: "Map location unavailable" }) : null,
|
|
108
75
|
unavailable.map((branch) => /* @__PURE__ */ jsxs(
|
|
109
76
|
"button",
|
|
110
77
|
{
|
|
@@ -122,6 +89,7 @@ function BranchMap({
|
|
|
122
89
|
] });
|
|
123
90
|
}
|
|
124
91
|
export {
|
|
125
|
-
BranchMap
|
|
92
|
+
BranchMap,
|
|
93
|
+
DeliveryMap
|
|
126
94
|
};
|
|
127
95
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/branch-map/index.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\";\nimport type { Branch } from \"@zappie/sdk\";\nimport {
|
|
5
|
-
"mappings": ";;;AAEA;AAAA,EACE;AAAA,EACA;AAAA,
|
|
4
|
+
"sourcesContent": ["\"use client\";\nimport type { Branch } from \"@zappie/sdk\";\nimport { useEffect, useRef, type HTMLAttributes, type ReactNode } from \"react\";\nimport { COLOR_PROBE_STYLE, readColors } from \"./colors\";\nimport {\n validCoordinates,\n visibleBranches,\n type Coordinates,\n} from \"./geometry\";\nimport type { createMap } from \"./runtime\";\nimport { useMapRuntime, type MapLoadErrorState } from \"./use-map-runtime\";\n\nexport { DeliveryMap } from \"./delivery-map\";\nexport type { DeliveryMapProps } from \"./delivery-map\";\nexport type { MapLoadErrorState } from \"./use-map-runtime\";\n\n/** What a template gets to render when the map fails to load. */\nexport type BranchMapLoadErrorState = MapLoadErrorState;\n\n/** Branches, selection and optional shopper coordinates for BranchMap. */\nexport type BranchMapProps = Omit<\n HTMLAttributes<HTMLDivElement>,\n \"onSelect\"\n> & {\n branches: Branch[];\n selectedBranchId?: string;\n onSelect: (branch: Branch) => void;\n /**\n * The shopper's coordinates. The map shows them with a house marker and\n * draws a line to the selected branch, or to the nearest one.\n */\n shopperLocation?: Coordinates;\n browserKey?: string;\n /**\n * Highest zoom applied after fitting. Defaults to 15 when a shopper location\n * is shown and 14 otherwise.\n */\n maxZoom?: number;\n /** Image shown inside each branch pin, such as the business logo. */\n pinImageUrl?: string;\n /** Show the map and satellite switch. Defaults to false. */\n mapTypeControl?: boolean;\n /**\n * Show the branch name above the selected pin, or above every pin. Off by\n * default. With a shopper location and no selection, \"selected\" labels the\n * nearest branch.\n */\n pinLabel?: \"selected\" | \"all\";\n /** Frame a small map, with tighter padding and a wider single-branch view. */\n compact?: boolean;\n /** Called each time the map fails to load. */\n onLoadError?: (error: unknown) => void;\n /**\n * Render your own message when the map fails to load, such as a retry\n * button that calls `retry`. Without it the map shows \"Map location unavailable\".\n */\n renderLoadError?: (state: BranchMapLoadErrorState) => ReactNode;\n};\n\nconst loadBranchMap =\n (mapTypeControl: boolean) =>\n (target: HTMLElement, key: string, signal: AbortSignal) =>\n import(\"./runtime\").then(({ createMap }) =>\n createMap(target, key, signal, { mapTypeControl }),\n );\n\n/**\n * Show branch locations. Maps loads after mount when you supply a browser key.\n * Nearby branches group into numbered clusters. Set the container height with\n * CSS. Colour clusters, the shopper marker, labels and the route with\n * --map-accent and --map-accent-foreground. Style dot pins with\n * --branch-map-pin, --branch-map-pin-selected, --branch-map-pin-border and\n * --branch-map-pin-size.\n */\nexport function BranchMap({\n branches,\n selectedBranchId,\n onSelect,\n shopperLocation,\n browserKey,\n maxZoom,\n pinImageUrl,\n mapTypeControl = false,\n pinLabel,\n compact = false,\n onLoadError,\n renderLoadError,\n ...props\n}: BranchMapProps) {\n const probe = useRef<HTMLSpanElement>(null);\n const hasCoordinates = visibleBranches(branches).length > 0;\n const { element, runtime, failure, retry } = useMapRuntime<\n Awaited<ReturnType<typeof createMap>>\n >(browserKey, hasCoordinates, loadBranchMap(mapTypeControl), onLoadError);\n const failed = failure !== undefined;\n const effectiveMaxZoom =\n maxZoom ?? (validCoordinates(shopperLocation) ? 15 : 14);\n useEffect(() => {\n runtime?.update(branches, selectedBranchId, onSelect, shopperLocation, {\n maxZoom: effectiveMaxZoom,\n pinImageUrl,\n mapTypeControl,\n pinLabel,\n compact,\n colors: readColors(probe.current),\n });\n }, [\n runtime,\n branches,\n selectedBranchId,\n onSelect,\n shopperLocation,\n effectiveMaxZoom,\n pinImageUrl,\n mapTypeControl,\n pinLabel,\n compact,\n ]);\n if (!browserKey) {\n return null;\n }\n const unavailable = branches.filter(\n (branch) => !visibleBranches([branch]).length,\n );\n return (\n <div {...props} data-branch-map=\"\">\n <span ref={probe} hidden style={COLOR_PROBE_STYLE} />\n <div\n hidden={failed || !hasCoordinates}\n ref={element}\n data-branch-map-canvas=\"\"\n style={{ height: \"100%\" }}\n />\n {failure && renderLoadError ? (\n renderLoadError({ error: failure.error, retry })\n ) : failed || !hasCoordinates ? (\n <p role=\"status\">Map location unavailable</p>\n ) : null}\n {unavailable.map((branch) => (\n <button\n key={branch.id}\n type=\"button\"\n aria-pressed={branch.id === selectedBranchId}\n onClick={() => onSelect(branch)}\n >\n {branch.name}: Map location unavailable\n </button>\n ))}\n {runtime && validCoordinates(shopperLocation) ? (\n <button type=\"button\" onClick={() => runtime.locate(shopperLocation)}>\n Use my current location\n </button>\n ) : null}\n </div>\n );\n}\n"],
|
|
5
|
+
"mappings": ";;;AAEA,SAAS,WAAW,cAAmD;AACvE,SAAS,mBAAmB,kBAAkB;AAC9C;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AAEP,SAAS,qBAA6C;AAEtD,SAAS,mBAAmB;AAkHtB,cAaE,YAbF;AAnEN,IAAM,gBACJ,CAAC,mBACD,CAAC,QAAqB,KAAa,WACjC,OAAO,cAAW,EAAE;AAAA,EAAK,CAAC,EAAE,UAAU,MACpC,UAAU,QAAQ,KAAK,QAAQ,EAAE,eAAe,CAAC;AACnD;AAUG,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,iBAAiB;AAAA,EACjB;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAmB;AACjB,QAAM,QAAQ,OAAwB,IAAI;AAC1C,QAAM,iBAAiB,gBAAgB,QAAQ,EAAE,SAAS;AAC1D,QAAM,EAAE,SAAS,SAAS,SAAS,MAAM,IAAI,cAE3C,YAAY,gBAAgB,cAAc,cAAc,GAAG,WAAW;AACxE,QAAM,SAAS,YAAY;AAC3B,QAAM,mBACJ,YAAY,iBAAiB,eAAe,IAAI,KAAK;AACvD,YAAU,MAAM;AACd,aAAS,OAAO,UAAU,kBAAkB,UAAU,iBAAiB;AAAA,MACrE,SAAS;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,QAAQ,WAAW,MAAM,OAAO;AAAA,IAClC,CAAC;AAAA,EACH,GAAG;AAAA,IACD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACD,MAAI,CAAC,YAAY;AACf,WAAO;AAAA,EACT;AACA,QAAM,cAAc,SAAS;AAAA,IAC3B,CAAC,WAAW,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE;AAAA,EACzC;AACA,SACE,qBAAC,SAAK,GAAG,OAAO,mBAAgB,IAC9B;AAAA,wBAAC,UAAK,KAAK,OAAO,QAAM,MAAC,OAAO,mBAAmB;AAAA,IACnD;AAAA,MAAC;AAAA;AAAA,QACC,QAAQ,UAAU,CAAC;AAAA,QACnB,KAAK;AAAA,QACL,0BAAuB;AAAA,QACvB,OAAO,EAAE,QAAQ,OAAO;AAAA;AAAA,IAC1B;AAAA,IACC,WAAW,kBACV,gBAAgB,EAAE,OAAO,QAAQ,OAAO,MAAM,CAAC,IAC7C,UAAU,CAAC,iBACb,oBAAC,OAAE,MAAK,UAAS,sCAAwB,IACvC;AAAA,IACH,YAAY,IAAI,CAAC,WAChB;AAAA,MAAC;AAAA;AAAA,QAEC,MAAK;AAAA,QACL,gBAAc,OAAO,OAAO;AAAA,QAC5B,SAAS,MAAM,SAAS,MAAM;AAAA,QAE7B;AAAA,iBAAO;AAAA,UAAK;AAAA;AAAA;AAAA,MALR,OAAO;AAAA,IAMd,CACD;AAAA,IACA,WAAW,iBAAiB,eAAe,IAC1C,oBAAC,YAAO,MAAK,UAAS,SAAS,MAAM,QAAQ,OAAO,eAAe,GAAG,qCAEtE,IACE;AAAA,KACN;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
var __typeError = (msg) => {
|
|
2
|
+
throw TypeError(msg);
|
|
3
|
+
};
|
|
4
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
5
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
6
|
+
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
7
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
8
|
+
|
|
9
|
+
// src/branch-map/markers.ts
|
|
10
|
+
import { DEFAULT_COLORS } from "./colors.js";
|
|
11
|
+
var SHADOW_SM = "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)";
|
|
12
|
+
var SHADOW_MD = "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)";
|
|
13
|
+
var MARKER_BOX = 40;
|
|
14
|
+
var LABEL_GAP = 4;
|
|
15
|
+
function branchMarker(imageUrl, focused) {
|
|
16
|
+
return imageUrl ? {
|
|
17
|
+
kind: "logo",
|
|
18
|
+
size: focused ? 40 : 32,
|
|
19
|
+
imageUrl,
|
|
20
|
+
shadow: focused ? "md" : "sm"
|
|
21
|
+
} : { kind: "dot", selected: focused };
|
|
22
|
+
}
|
|
23
|
+
var SVG_NAMESPACE = "http://www.w3.org/2000/svg";
|
|
24
|
+
function houseIcon() {
|
|
25
|
+
const svg = document.createElementNS(SVG_NAMESPACE, "svg");
|
|
26
|
+
for (const [name, value] of Object.entries({
|
|
27
|
+
width: "20",
|
|
28
|
+
height: "20",
|
|
29
|
+
viewBox: "0 0 24 24",
|
|
30
|
+
fill: "none",
|
|
31
|
+
stroke: "currentColor",
|
|
32
|
+
"stroke-width": "2",
|
|
33
|
+
"stroke-linecap": "round",
|
|
34
|
+
"stroke-linejoin": "round",
|
|
35
|
+
"aria-hidden": "true"
|
|
36
|
+
})) {
|
|
37
|
+
svg.setAttribute(name, value);
|
|
38
|
+
}
|
|
39
|
+
svg.style.display = "block";
|
|
40
|
+
for (const data of [
|
|
41
|
+
[
|
|
42
|
+
"path",
|
|
43
|
+
"d",
|
|
44
|
+
"M15 22a1 1 0 0 1-1-1v-4a1 1 0 0 1 .445-.832l3-2a1 1 0 0 1 1.11 0l3 2A1 1 0 0 1 22 17v4a1 1 0 0 1-1 1z"
|
|
45
|
+
],
|
|
46
|
+
[
|
|
47
|
+
"path",
|
|
48
|
+
"d",
|
|
49
|
+
"M18 10a8 8 0 0 0-16 0c0 4.993 5.539 10.193 7.399 11.799a1 1 0 0 0 .601.2"
|
|
50
|
+
],
|
|
51
|
+
["path", "d", "M18 22v-3"],
|
|
52
|
+
["circle", "cx", "10"]
|
|
53
|
+
]) {
|
|
54
|
+
const child = document.createElementNS(SVG_NAMESPACE, data[0]);
|
|
55
|
+
child.setAttribute(data[1], data[2]);
|
|
56
|
+
if (data[0] === "circle") {
|
|
57
|
+
child.setAttribute("cy", "10");
|
|
58
|
+
child.setAttribute("r", "3");
|
|
59
|
+
}
|
|
60
|
+
svg.appendChild(child);
|
|
61
|
+
}
|
|
62
|
+
return svg;
|
|
63
|
+
}
|
|
64
|
+
function paintMarker(element, spec, colors) {
|
|
65
|
+
const { left, top } = element.style;
|
|
66
|
+
try {
|
|
67
|
+
paint(element, spec, colors);
|
|
68
|
+
} finally {
|
|
69
|
+
element.style.left = left;
|
|
70
|
+
element.style.top = top;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function paint(element, spec, colors) {
|
|
74
|
+
element.dataset.marker = spec.kind;
|
|
75
|
+
if (spec.kind === "logo") {
|
|
76
|
+
const lift = spec.lift ?? MARKER_BOX / 2;
|
|
77
|
+
element.style.cssText = `position:absolute;box-sizing:border-box;transform:translate(-50%,calc(-50% - ${lift}px));width:${spec.size}px;height:${spec.size}px;padding:0;border-radius:9999px;overflow:hidden;border:2px solid white;background:white;box-shadow:${spec.shadow === "md" ? SHADOW_MD : SHADOW_SM};cursor:pointer;transition:width 150ms cubic-bezier(0.4,0,0.2,1),height 150ms cubic-bezier(0.4,0,0.2,1)`;
|
|
78
|
+
let image = element.querySelector("img");
|
|
79
|
+
if (!image) {
|
|
80
|
+
element.replaceChildren();
|
|
81
|
+
image = document.createElement("img");
|
|
82
|
+
image.alt = "";
|
|
83
|
+
image.decoding = "async";
|
|
84
|
+
image.draggable = false;
|
|
85
|
+
image.onerror = () => {
|
|
86
|
+
image.style.visibility = "hidden";
|
|
87
|
+
};
|
|
88
|
+
element.appendChild(image);
|
|
89
|
+
}
|
|
90
|
+
image.style.cssText = "display:block;width:100%;height:100%;border-radius:9999px;object-fit:cover";
|
|
91
|
+
if (image.getAttribute("src") !== spec.imageUrl) {
|
|
92
|
+
image.src = spec.imageUrl;
|
|
93
|
+
}
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (spec.kind === "dot") {
|
|
97
|
+
element.replaceChildren();
|
|
98
|
+
element.style.cssText = `position:absolute;transform:translate(-50%,-100%);width:var(--branch-map-pin-size,24px);height:var(--branch-map-pin-size,24px);border-radius:50%;border:2px solid var(--branch-map-pin-border,currentColor);background:var(${spec.selected ? "--branch-map-pin-selected" : "--branch-map-pin"},currentColor);cursor:pointer`;
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
element.style.cssText = `position:absolute;box-sizing:border-box;transform:translate(-50%,calc(-50% - ${spec.lift ?? MARKER_BOX / 2}px));width:32px;height:32px;padding:4px;border-radius:4px;border:2px solid white;background:${colors.accent};color:${spec.color};box-shadow:${SHADOW_MD}`;
|
|
102
|
+
if (!element.firstChild) {
|
|
103
|
+
element.appendChild(houseIcon());
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function paintLabel(element, options) {
|
|
107
|
+
const { left, top } = element.style;
|
|
108
|
+
element.style.cssText = `position:absolute;box-sizing:border-box;transform:translate(-50%,calc(-100% - ${options.offset}));white-space:nowrap;pointer-events:none;padding:4px 8px;border-radius:8px;font-size:12px;line-height:16px;font-weight:500;${options.capitalize ? "text-transform:capitalize;" : ""}background:var(--branch-map-label-background,white);color:var(--branch-map-label-color,#111);border:2px solid var(--branch-map-label-border,${options.colors.accent});box-shadow:${SHADOW_MD}`;
|
|
109
|
+
element.style.left = left;
|
|
110
|
+
element.style.top = top;
|
|
111
|
+
}
|
|
112
|
+
function labelOffset(spec) {
|
|
113
|
+
return spec.kind === "dot" ? `var(--branch-map-pin-size,24px) + ${LABEL_GAP}px` : `${MARKER_BOX + LABEL_GAP}px`;
|
|
114
|
+
}
|
|
115
|
+
function markerOverlayClass(maps) {
|
|
116
|
+
var _position, _pane, _a;
|
|
117
|
+
return _a = class extends maps.OverlayView {
|
|
118
|
+
constructor(key, position, element) {
|
|
119
|
+
super();
|
|
120
|
+
__privateAdd(this, _position);
|
|
121
|
+
__privateAdd(this, _pane);
|
|
122
|
+
this.key = key;
|
|
123
|
+
this.element = element;
|
|
124
|
+
__privateSet(this, _position, position);
|
|
125
|
+
this.label = document.createElement("span");
|
|
126
|
+
this.label.setAttribute("aria-hidden", "true");
|
|
127
|
+
}
|
|
128
|
+
getPosition() {
|
|
129
|
+
return new maps.LatLng(__privateGet(this, _position));
|
|
130
|
+
}
|
|
131
|
+
getVisible() {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
setPosition(position) {
|
|
135
|
+
__privateSet(this, _position, position);
|
|
136
|
+
if (this.getMap()) {
|
|
137
|
+
this.draw();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// An unlabelled marker has no label node in the map.
|
|
141
|
+
setLabel(text) {
|
|
142
|
+
this.label.textContent = text ?? "";
|
|
143
|
+
if (!text) {
|
|
144
|
+
this.label.remove();
|
|
145
|
+
} else if (__privateGet(this, _pane) && !this.label.isConnected) {
|
|
146
|
+
__privateGet(this, _pane).appendChild(this.label);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
onAdd() {
|
|
150
|
+
__privateSet(this, _pane, this.getPanes()?.overlayMouseTarget);
|
|
151
|
+
__privateGet(this, _pane)?.appendChild(this.element);
|
|
152
|
+
if (this.label.textContent) {
|
|
153
|
+
__privateGet(this, _pane)?.appendChild(this.label);
|
|
154
|
+
}
|
|
155
|
+
maps.OverlayView.preventMapHitsAndGesturesFrom(this.element);
|
|
156
|
+
}
|
|
157
|
+
draw() {
|
|
158
|
+
const point = this.getProjection()?.fromLatLngToDivPixel(
|
|
159
|
+
new maps.LatLng(__privateGet(this, _position))
|
|
160
|
+
);
|
|
161
|
+
if (point) {
|
|
162
|
+
for (const node of [this.element, this.label]) {
|
|
163
|
+
node.style.left = `${point.x}px`;
|
|
164
|
+
node.style.top = `${point.y}px`;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
onRemove() {
|
|
169
|
+
__privateSet(this, _pane, void 0);
|
|
170
|
+
this.element.remove();
|
|
171
|
+
this.label.remove();
|
|
172
|
+
}
|
|
173
|
+
}, _position = new WeakMap(), _pane = new WeakMap(), _a;
|
|
174
|
+
}
|
|
175
|
+
export {
|
|
176
|
+
DEFAULT_COLORS,
|
|
177
|
+
branchMarker,
|
|
178
|
+
labelOffset,
|
|
179
|
+
markerOverlayClass,
|
|
180
|
+
paintLabel,
|
|
181
|
+
paintMarker
|
|
182
|
+
};
|
|
183
|
+
//# sourceMappingURL=markers.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/branch-map/markers.ts"],
|
|
4
|
+
"sourcesContent": ["/// <reference types=\"google.maps\" />\nimport type { MapColors } from \"./colors\";\nimport type { Coordinates } from \"./geometry\";\n\nexport { DEFAULT_COLORS, type MapColors } from \"./colors\";\n\nconst SHADOW_SM =\n \"0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)\";\nconst SHADOW_MD =\n \"0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)\";\n/** Every marker sits in a 40px box whose bottom centre is the location. */\nconst MARKER_BOX = 40;\nconst LABEL_GAP = 4;\n\n/** What a marker shows. */\nexport type MarkerSpec =\n | {\n kind: \"logo\";\n /** Outer diameter, including the white border. */\n size: number;\n imageUrl: string;\n shadow: \"sm\" | \"md\";\n /** Offset from the location to the logo's centre. Defaults to the 40px box. */\n lift?: number;\n }\n | { kind: \"dot\"; selected: boolean }\n | {\n kind: \"house\";\n /** Icon colour. The fill is the accent. */\n color: string;\n /** Offset from the location to the icon's centre. Defaults to the 40px box. */\n lift?: number;\n };\n\n/** Choose the branch marker: the logo when there is an image, otherwise the themed dot. */\nexport function branchMarker(\n imageUrl: string | undefined,\n focused: boolean,\n): MarkerSpec {\n return imageUrl\n ? {\n kind: \"logo\",\n size: focused ? 40 : 32,\n imageUrl,\n shadow: focused ? \"md\" : \"sm\",\n }\n : { kind: \"dot\", selected: focused };\n}\n\nconst SVG_NAMESPACE = \"http://www.w3.org/2000/svg\";\n\nfunction houseIcon(): SVGSVGElement {\n const svg = document.createElementNS(SVG_NAMESPACE, \"svg\");\n for (const [name, value] of Object.entries({\n width: \"20\",\n height: \"20\",\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n \"stroke-width\": \"2\",\n \"stroke-linecap\": \"round\",\n \"stroke-linejoin\": \"round\",\n \"aria-hidden\": \"true\",\n })) {\n svg.setAttribute(name, value);\n }\n svg.style.display = \"block\";\n for (const data of [\n [\n \"path\",\n \"d\",\n \"M15 22a1 1 0 0 1-1-1v-4a1 1 0 0 1 .445-.832l3-2a1 1 0 0 1 1.11 0l3 2A1 1 0 0 1 22 17v4a1 1 0 0 1-1 1z\",\n ],\n [\n \"path\",\n \"d\",\n \"M18 10a8 8 0 0 0-16 0c0 4.993 5.539 10.193 7.399 11.799a1 1 0 0 0 .601.2\",\n ],\n [\"path\", \"d\", \"M18 22v-3\"],\n [\"circle\", \"cx\", \"10\"],\n ] as const) {\n const child = document.createElementNS(SVG_NAMESPACE, data[0]);\n child.setAttribute(data[1], data[2]);\n if (data[0] === \"circle\") {\n child.setAttribute(\"cy\", \"10\");\n child.setAttribute(\"r\", \"3\");\n }\n svg.appendChild(child);\n }\n return svg;\n}\n\n/** Style a marker element in place, so selection changes keep the same node. */\nexport function paintMarker(\n element: HTMLElement,\n spec: MarkerSpec,\n colors: MapColors,\n) {\n const { left, top } = element.style;\n try {\n paint(element, spec, colors);\n } finally {\n element.style.left = left;\n element.style.top = top;\n }\n}\n\nfunction paint(element: HTMLElement, spec: MarkerSpec, colors: MapColors) {\n element.dataset.marker = spec.kind;\n if (spec.kind === \"logo\") {\n const lift = spec.lift ?? MARKER_BOX / 2;\n element.style.cssText = `position:absolute;box-sizing:border-box;transform:translate(-50%,calc(-50% - ${lift}px));width:${spec.size}px;height:${spec.size}px;padding:0;border-radius:9999px;overflow:hidden;border:2px solid white;background:white;box-shadow:${spec.shadow === \"md\" ? SHADOW_MD : SHADOW_SM};cursor:pointer;transition:width 150ms cubic-bezier(0.4,0,0.2,1),height 150ms cubic-bezier(0.4,0,0.2,1)`;\n let image = element.querySelector(\"img\");\n if (!image) {\n element.replaceChildren();\n image = document.createElement(\"img\");\n image.alt = \"\";\n image.decoding = \"async\";\n image.draggable = false;\n image.onerror = () => {\n image!.style.visibility = \"hidden\";\n };\n element.appendChild(image);\n }\n image.style.cssText =\n \"display:block;width:100%;height:100%;border-radius:9999px;object-fit:cover\";\n if (image.getAttribute(\"src\") !== spec.imageUrl) {\n image.src = spec.imageUrl;\n }\n return;\n }\n if (spec.kind === \"dot\") {\n element.replaceChildren();\n element.style.cssText = `position:absolute;transform:translate(-50%,-100%);width:var(--branch-map-pin-size,24px);height:var(--branch-map-pin-size,24px);border-radius:50%;border:2px solid var(--branch-map-pin-border,currentColor);background:var(${spec.selected ? \"--branch-map-pin-selected\" : \"--branch-map-pin\"},currentColor);cursor:pointer`;\n return;\n }\n element.style.cssText = `position:absolute;box-sizing:border-box;transform:translate(-50%,calc(-50% - ${spec.lift ?? MARKER_BOX / 2}px));width:32px;height:32px;padding:4px;border-radius:4px;border:2px solid white;background:${colors.accent};color:${spec.color};box-shadow:${SHADOW_MD}`;\n if (!element.firstChild) {\n element.appendChild(houseIcon());\n }\n}\n\n/** Style a label above a marker. Branch labels are capitalised like the default storefront's. */\nexport function paintLabel(\n element: HTMLElement,\n options: { offset: string; capitalize: boolean; colors: MapColors },\n) {\n const { left, top } = element.style;\n element.style.cssText = `position:absolute;box-sizing:border-box;transform:translate(-50%,calc(-100% - ${options.offset}));white-space:nowrap;pointer-events:none;padding:4px 8px;border-radius:8px;font-size:12px;line-height:16px;font-weight:500;${options.capitalize ? \"text-transform:capitalize;\" : \"\"}background:var(--branch-map-label-background,white);color:var(--branch-map-label-color,#111);border:2px solid var(--branch-map-label-border,${options.colors.accent});box-shadow:${SHADOW_MD}`;\n element.style.left = left;\n element.style.top = top;\n}\n\n/** Offset from the location to the bottom of a label above a marker. */\nexport function labelOffset(spec: MarkerSpec) {\n return spec.kind === \"dot\"\n ? `var(--branch-map-pin-size,24px) + ${LABEL_GAP}px`\n : `${MARKER_BOX + LABEL_GAP}px`;\n}\n\nexport type MarkerOverlay = google.maps.OverlayView & {\n readonly element: HTMLElement;\n readonly label: HTMLElement;\n /** The clusterer reads markers through these two methods. */\n getPosition(): google.maps.LatLng;\n getVisible(): boolean;\n setLabel(text: string | undefined): void;\n setPosition(position: Coordinates): void;\n};\n\n/**\n * An overlay that places one element (a button or a div) and an optional\n * label at a location. The DOM nodes persist across restyles.\n */\nexport function markerOverlayClass(\n maps: typeof google.maps,\n): new (\n key: string,\n position: Coordinates,\n element: HTMLElement,\n) => MarkerOverlay {\n return class Marker extends maps.OverlayView implements MarkerOverlay {\n // Declared first so two markers compare unequal at once (the clusterer deep-compares them).\n readonly key: string;\n readonly element: HTMLElement;\n readonly label: HTMLElement;\n #position: Coordinates;\n #pane: Element | undefined;\n constructor(key: string, position: Coordinates, element: HTMLElement) {\n super();\n this.key = key;\n this.element = element;\n this.#position = position;\n this.label = document.createElement(\"span\");\n this.label.setAttribute(\"aria-hidden\", \"true\");\n }\n getPosition() {\n return new maps.LatLng(this.#position);\n }\n getVisible() {\n return true;\n }\n setPosition(position: Coordinates) {\n this.#position = position;\n if (this.getMap()) {\n this.draw();\n }\n }\n // An unlabelled marker has no label node in the map.\n setLabel(text: string | undefined) {\n this.label.textContent = text ?? \"\";\n if (!text) {\n this.label.remove();\n } else if (this.#pane && !this.label.isConnected) {\n this.#pane.appendChild(this.label);\n }\n }\n onAdd() {\n this.#pane = this.getPanes()?.overlayMouseTarget;\n this.#pane?.appendChild(this.element);\n if (this.label.textContent) {\n this.#pane?.appendChild(this.label);\n }\n maps.OverlayView.preventMapHitsAndGesturesFrom(this.element);\n }\n draw() {\n const point = this.getProjection()?.fromLatLngToDivPixel(\n new maps.LatLng(this.#position),\n );\n if (point) {\n for (const node of [this.element, this.label]) {\n node.style.left = `${point.x}px`;\n node.style.top = `${point.y}px`;\n }\n }\n }\n onRemove() {\n this.#pane = undefined;\n this.element.remove();\n this.label.remove();\n }\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|