mac-human-design 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is furnished
10
+ to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,92 @@
1
+ # human-design
2
+
3
+ A reusable shared library for Tauri macOS apps. It includes:
4
+
5
+ - `MacSegmentedControl` and macOS-focused BaseUI theme tokens.
6
+ - A reusable SF Symbols fetch layer with React hooks/components.
7
+ - A Rust/Tauri plugin crate that exposes a native command:
8
+ `system_symbol_png_data_url`.
9
+
10
+ ## Structure
11
+
12
+ - `src/` - TypeScript/React library exports
13
+ - `src-tauri/` - Rust plugin crate (`human-design-tauri-system-symbols`)
14
+
15
+ ## Install
16
+
17
+ From the consumer app:
18
+
19
+ ```bash
20
+ npm i human-design
21
+ ```
22
+
23
+ In the Tauri Rust side, add the local path dependency while developing:
24
+
25
+ ```toml
26
+ [dependencies]
27
+ human-design-tauri-system-symbols = { path = "../human-design/src-tauri" }
28
+ ```
29
+
30
+ Then register the plugin in your `src-tauri/src/lib.rs`:
31
+
32
+ ```rust
33
+ fn main() {
34
+ tauri::Builder::default()
35
+ .plugin(human_design_tauri_system_symbols::init())
36
+ .run(tauri::generate_context!())
37
+ .expect("error while running app");
38
+ }
39
+ ```
40
+
41
+ ## Usage (React)
42
+
43
+ ```ts
44
+ import { useState } from "react";
45
+ import { MacSegmentedControl } from "human-design";
46
+ import { SystemSymbolImage } from "human-design/symbols";
47
+
48
+ export function Demo() {
49
+ const [tab, setTab] = useState<"files" | "settings">("files");
50
+
51
+ return (
52
+ <div>
53
+ <SystemSymbolImage symbolName="folder" sizePx={18} fallback="▢" />
54
+ <MacSegmentedControl
55
+ options={[
56
+ { value: "files", label: "Files" },
57
+ { value: "settings", label: "Settings" },
58
+ ]}
59
+ value={tab}
60
+ onChange={setTab}
61
+ />
62
+ </div>
63
+ );
64
+ }
65
+ ```
66
+
67
+ ## Why it works cross-platform
68
+
69
+ ## Shared UI components moved in
70
+
71
+ The following components are now in the shared library and can be imported by any app:
72
+
73
+ - `AppWindowShell`
74
+ - `StatusMessage`
75
+ - `SymbolIconButton`
76
+ - `ViewDragRegion`
77
+ - `isDraggableAreaTarget` utility
78
+
79
+ Example import:
80
+
81
+ ```ts
82
+ import {
83
+ AppWindowShell,
84
+ StatusMessage,
85
+ SymbolIconButton,
86
+ ViewDragRegion,
87
+ isDraggableAreaTarget,
88
+ } from "human-design";
89
+ ```
90
+
91
+ - On macOS, Rust command renders the symbol to PNG via `NSImage`.
92
+ - On non-macOS, command returns `None` so invocations stay safe and predictable.
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "mac-human-design",
3
+ "version": "0.1.0",
4
+ "description": "Reusable macOS-oriented UI primitives and SF Symbols bridge for Tauri apps.",
5
+ "type": "module",
6
+ "main": "src/index.ts",
7
+ "types": "src/index.ts",
8
+ "exports": {
9
+ ".": "./src/index.ts",
10
+ "./components": "./src/components/index.ts",
11
+ "./theme": "./src/theme/index.ts",
12
+ "./symbols": "./src/symbols/index.ts",
13
+ "./tauri": "./src/tauri/index.ts",
14
+ "./utils": "./src/utils/index.ts",
15
+ "./rust": "./src-tauri"
16
+ },
17
+ "files": [
18
+ "src/**/*",
19
+ "src-tauri/**/*",
20
+ "README.md",
21
+ "LICENSE"
22
+ ],
23
+ "keywords": [
24
+ "tauri",
25
+ "tauri-plugin",
26
+ "macos",
27
+ "sf-symbols",
28
+ "react",
29
+ "components"
30
+ ],
31
+ "scripts": {
32
+ "publish": "npm publish --access public"
33
+ },
34
+ "author": "human-design",
35
+ "license": "MIT",
36
+ "peerDependencies": {
37
+ "@tauri-apps/api": "^2",
38
+ "baseui": "^16.1.1",
39
+ "react": "^18 || ^19",
40
+ "react-dom": "^18 || ^19",
41
+ "styletron-react": "^6.1.1",
42
+ "styletron-standard": "^4.0.0"
43
+ },
44
+ "peerDependenciesMeta": {
45
+ "@tauri-apps/api": {
46
+ "optional": true
47
+ },
48
+ "baseui": {
49
+ "optional": true
50
+ },
51
+ "styletron-react": {
52
+ "optional": true
53
+ },
54
+ "styletron-standard": {
55
+ "optional": true
56
+ }
57
+ },
58
+ "dependencies": {}
59
+ }
@@ -0,0 +1,25 @@
1
+ import { ReactNode } from "react";
2
+ import { Block } from "baseui/block";
3
+ import { useStyletron } from "baseui";
4
+
5
+ export function AppWindowShell({ children }: { children: ReactNode }) {
6
+ const [, theme] = useStyletron();
7
+
8
+ return (
9
+ <Block
10
+ width="100%"
11
+ $style={{
12
+ minHeight: "100vh",
13
+ boxSizing: "border-box",
14
+ display: "flex",
15
+ flexDirection: "column",
16
+ backgroundColor: theme.colors.backgroundPrimary,
17
+ color: theme.colors.contentPrimary,
18
+ }}
19
+ >
20
+ <Block flex="1" $style={{ minHeight: 0, display: "flex", flexDirection: "column" }}>
21
+ {children}
22
+ </Block>
23
+ </Block>
24
+ );
25
+ }
@@ -0,0 +1,54 @@
1
+ import { ReactNode } from "react";
2
+ import { FILL, Segment, SegmentedControl } from "baseui/segmented-control";
3
+ import {
4
+ macosSegmentedControlOverrides,
5
+ macosSegmentOverrides,
6
+ } from "../theme/macosTheme";
7
+
8
+ export type MacSegmentedControlOption<T extends string> = {
9
+ value: T;
10
+ label: ReactNode;
11
+ disabled?: boolean;
12
+ };
13
+
14
+ type MacSegmentedControlProps<T extends string> = {
15
+ options: readonly MacSegmentedControlOption<T>[];
16
+ value: T;
17
+ onChange: (value: T) => void;
18
+ disabled?: boolean;
19
+ fullWidth?: boolean;
20
+ };
21
+
22
+ export function MacSegmentedControl<T extends string>({
23
+ options,
24
+ value,
25
+ onChange,
26
+ disabled,
27
+ fullWidth,
28
+ }: MacSegmentedControlProps<T>) {
29
+ return (
30
+ <SegmentedControl
31
+ activeKey={value}
32
+ disabled={disabled}
33
+ fill={FILL.fixed}
34
+ onChange={({ activeKey }) => {
35
+ const nextOption = options.find((option) => option.value === activeKey);
36
+ if (!nextOption) {
37
+ return;
38
+ }
39
+ onChange(nextOption.value);
40
+ }}
41
+ overrides={macosSegmentedControlOverrides}
42
+ width={fullWidth ? "100%" : undefined}
43
+ >
44
+ {options.map((option) => (
45
+ <Segment
46
+ key={option.value}
47
+ label={option.label}
48
+ disabled={option.disabled}
49
+ overrides={macosSegmentOverrides}
50
+ />
51
+ ))}
52
+ </SegmentedControl>
53
+ );
54
+ }
@@ -0,0 +1,52 @@
1
+ import { ReactNode } from "react";
2
+ import { Block } from "baseui/block";
3
+ import { useStyletron } from "baseui";
4
+
5
+ type StatusMessageProps = {
6
+ intent: "error" | "success" | "info";
7
+ message: string;
8
+ };
9
+
10
+ export function StatusMessage({ intent, message }: StatusMessageProps) {
11
+ const [, theme] = useStyletron();
12
+ const isDark = (theme.name ?? "").toLowerCase().includes("dark");
13
+
14
+ const palette =
15
+ intent === "error"
16
+ ? {
17
+ background: isDark ? "rgba(255, 84, 89, 0.16)" : "rgba(255, 84, 89, 0.12)",
18
+ border: isDark ? "rgba(255, 84, 89, 0.45)" : "rgba(255, 84, 89, 0.35)",
19
+ text: isDark ? "#ff9ea2" : "#8f1c1f",
20
+ }
21
+ : intent === "success"
22
+ ? {
23
+ background: isDark ? "rgba(52, 199, 89, 0.16)" : "rgba(52, 199, 89, 0.12)",
24
+ border: isDark ? "rgba(52, 199, 89, 0.45)" : "rgba(52, 199, 89, 0.3)",
25
+ text: isDark ? "#8ff0ad" : "#0f6b2b",
26
+ }
27
+ : {
28
+ background: isDark ? "rgba(10, 132, 255, 0.16)" : "rgba(10, 132, 255, 0.08)",
29
+ border: isDark ? "rgba(10, 132, 255, 0.45)" : "rgba(10, 132, 255, 0.25)",
30
+ text: isDark ? "#9fcbff" : "#0b4e98",
31
+ };
32
+
33
+ return (
34
+ <Block
35
+ marginTop="scale400"
36
+ paddingTop="scale300"
37
+ paddingRight="scale400"
38
+ paddingBottom="scale300"
39
+ paddingLeft="scale400"
40
+ $style={{
41
+ backgroundColor: palette.background,
42
+ border: `1px solid ${palette.border}`,
43
+ borderRadius: "10px",
44
+ color: palette.text,
45
+ fontSize: "13px",
46
+ lineHeight: "1.35",
47
+ }}
48
+ >
49
+ {message}
50
+ </Block>
51
+ );
52
+ }
@@ -0,0 +1,138 @@
1
+ import { useEffect, useState } from "react";
2
+ import { invoke } from "@tauri-apps/api/core";
3
+ import { Button, KIND, SIZE } from "baseui/button";
4
+ import { StatefulTooltip } from "baseui/tooltip";
5
+ import { Block } from "baseui/block";
6
+
7
+ const SYMBOL_SCALE_MULTIPLIER = 2.5;
8
+ const SYMBOL_MIN_POINT_SIZE = 10;
9
+ const SYMBOL_MAX_POINT_SIZE = 160;
10
+ const DEFAULT_SYMBOL_POINT_SIZE = 18;
11
+
12
+ const systemSymbolIconCache = new Map<string, string | null>();
13
+
14
+ function getSymbolPointSize(cssPx: number): number {
15
+ const pixelRatio = typeof window === "undefined" ? 1 : window.devicePixelRatio || 1;
16
+ return Math.round(Math.max(SYMBOL_MIN_POINT_SIZE, Math.min(SYMBOL_MAX_POINT_SIZE, cssPx * pixelRatio * SYMBOL_SCALE_MULTIPLIER)));
17
+ }
18
+
19
+ function symbolCacheKey(symbolName: string, pointSize: number) {
20
+ return `${symbolName}@${pointSize}`;
21
+ }
22
+
23
+ type SymbolIconButtonProps = {
24
+ label: string;
25
+ symbolName: string;
26
+ fallback: string;
27
+ onClick: () => void;
28
+ disabled?: boolean;
29
+ isLoading?: boolean;
30
+ iconSizePx?: number;
31
+ };
32
+
33
+ const ICON_BUTTON_SIZE = "30px";
34
+ const ICON_BUTTON_RADIUS = "7px";
35
+
36
+ export function SymbolIconButton({
37
+ label,
38
+ symbolName,
39
+ fallback,
40
+ onClick,
41
+ disabled,
42
+ isLoading,
43
+ iconSizePx = 18,
44
+ }: SymbolIconButtonProps) {
45
+ const symbolPointSize = getSymbolPointSize(iconSizePx);
46
+ const symbolCacheLookupKey = symbolCacheKey(symbolName, symbolPointSize);
47
+
48
+ const [symbolDataUrl, setSymbolDataUrl] = useState<string | null>(() => {
49
+ return systemSymbolIconCache.get(symbolCacheLookupKey) ?? null;
50
+ });
51
+
52
+ useEffect(() => {
53
+ const cachedDataUrl = systemSymbolIconCache.get(symbolCacheLookupKey);
54
+ if (cachedDataUrl !== undefined) {
55
+ setSymbolDataUrl(cachedDataUrl);
56
+ return;
57
+ }
58
+
59
+ let isMounted = true;
60
+ void invoke<string | null>("system_symbol_png_data_url", {
61
+ name: symbolName,
62
+ point_size: symbolPointSize,
63
+ })
64
+ .then((dataUrl) => {
65
+ systemSymbolIconCache.set(symbolCacheLookupKey, dataUrl);
66
+ if (isMounted) {
67
+ setSymbolDataUrl(dataUrl);
68
+ }
69
+ })
70
+ .catch(() => {
71
+ systemSymbolIconCache.set(symbolCacheLookupKey, null);
72
+ if (isMounted) {
73
+ setSymbolDataUrl(null);
74
+ }
75
+ });
76
+
77
+ return () => {
78
+ isMounted = false;
79
+ };
80
+ }, [symbolName, symbolPointSize, symbolCacheLookupKey]);
81
+
82
+ const glyphPx = `${iconSizePx}px`;
83
+
84
+ return (
85
+ <StatefulTooltip content={label}>
86
+ <Button
87
+ aria-label={label}
88
+ title={label}
89
+ kind={KIND.tertiary}
90
+ size={SIZE.compact}
91
+ isLoading={isLoading}
92
+ disabled={disabled}
93
+ onClick={onClick}
94
+ overrides={{
95
+ BaseButton: {
96
+ style: {
97
+ width: ICON_BUTTON_SIZE,
98
+ minWidth: ICON_BUTTON_SIZE,
99
+ height: ICON_BUTTON_SIZE,
100
+ minHeight: ICON_BUTTON_SIZE,
101
+ borderRadius: ICON_BUTTON_RADIUS,
102
+ paddingTop: 0,
103
+ paddingRight: 0,
104
+ paddingBottom: 0,
105
+ paddingLeft: 0,
106
+ },
107
+ },
108
+ }}
109
+ >
110
+ <Block
111
+ as="span"
112
+ aria-hidden
113
+ $style={{
114
+ display: "block",
115
+ width: glyphPx,
116
+ height: glyphPx,
117
+ color: "currentColor",
118
+ backgroundColor: symbolDataUrl ? "currentColor" : "transparent",
119
+ fontFamily: "-apple-system, BlinkMacSystemFont, sans-serif",
120
+ fontSize: symbolDataUrl ? "0" : glyphPx,
121
+ fontWeight: 500,
122
+ lineHeight: "1",
123
+ WebkitMaskImage: symbolDataUrl ? `url("${symbolDataUrl}")` : undefined,
124
+ WebkitMaskPosition: symbolDataUrl ? "center" : undefined,
125
+ WebkitMaskRepeat: symbolDataUrl ? "no-repeat" : undefined,
126
+ WebkitMaskSize: symbolDataUrl ? "contain" : undefined,
127
+ maskImage: symbolDataUrl ? `url("${symbolDataUrl}")` : undefined,
128
+ maskPosition: symbolDataUrl ? "center" : undefined,
129
+ maskRepeat: symbolDataUrl ? "no-repeat" : undefined,
130
+ maskSize: symbolDataUrl ? "contain" : undefined,
131
+ }}
132
+ >
133
+ {symbolDataUrl ? null : fallback}
134
+ </Block>
135
+ </Button>
136
+ </StatefulTooltip>
137
+ );
138
+ }
@@ -0,0 +1,18 @@
1
+ import { Block } from "baseui/block";
2
+
3
+ type ViewDragRegionProps = {
4
+ height?: string;
5
+ };
6
+
7
+ export function ViewDragRegion({ height = "32px" }: ViewDragRegionProps) {
8
+ return (
9
+ <Block
10
+ data-tauri-drag-region
11
+ $style={{
12
+ height,
13
+ minHeight: height,
14
+ flexShrink: 0,
15
+ }}
16
+ />
17
+ );
18
+ }
@@ -0,0 +1,5 @@
1
+ export { MacSegmentedControl, type MacSegmentedControlOption } from "./MacSegmentedControl";
2
+ export { StatusMessage } from "./StatusMessage";
3
+ export { SymbolIconButton } from "./SymbolIconButton";
4
+ export { AppWindowShell } from "./AppWindowShell";
5
+ export { ViewDragRegion } from "./ViewDragRegion";
package/src/index.ts ADDED
@@ -0,0 +1,18 @@
1
+ export { AppWindowShell } from "./components/AppWindowShell";
2
+ export { MacSegmentedControl, type MacSegmentedControlOption } from "./components/MacSegmentedControl";
3
+ export { SymbolIconButton } from "./components/SymbolIconButton";
4
+ export { StatusMessage } from "./components/StatusMessage";
5
+ export { ViewDragRegion } from "./components/ViewDragRegion";
6
+ export * as macosTheme from "./theme/macosTheme";
7
+
8
+ export {
9
+ getSystemSymbolDataUrl,
10
+ clearSystemSymbolCache,
11
+ buildSystemSymbolCacheKey,
12
+ calculateSystemSymbolPointSize,
13
+ type SystemSymbolFetchOptions,
14
+ } from "./symbols/systemSymbolService";
15
+ export { SystemSymbolImage, type SystemSymbolImageProps } from "./symbols/SystemSymbolImage";
16
+ export { useSystemSymbol } from "./symbols/useSystemSymbol";
17
+
18
+ export { isDraggableAreaTarget } from "./utils/isDraggableAreaTarget";
@@ -0,0 +1,72 @@
1
+ import { useMemo } from "react";
2
+ import type { CSSProperties } from "react";
3
+ import { useSystemSymbol } from "./useSystemSymbol";
4
+
5
+ export type SystemSymbolImageProps = {
6
+ symbolName: string;
7
+ fallback?: string;
8
+ sizePx?: number;
9
+ ariaLabel?: string;
10
+ className?: string;
11
+ style?: CSSProperties;
12
+ pointSize?: number;
13
+ enabled?: boolean;
14
+ };
15
+
16
+ export function SystemSymbolImage({
17
+ symbolName,
18
+ fallback = "?",
19
+ sizePx = 18,
20
+ ariaLabel,
21
+ className,
22
+ style,
23
+ pointSize,
24
+ enabled = true,
25
+ }: SystemSymbolImageProps) {
26
+ const { dataUrl, loading } = useSystemSymbol({
27
+ symbolName,
28
+ cssPixelSize: sizePx,
29
+ options: { fallbackPointSize: pointSize },
30
+ enabled,
31
+ });
32
+
33
+ const sharedStyles = useMemo<CSSProperties>(() => {
34
+ const pixels = `${sizePx}px`;
35
+ return {
36
+ display: "inline-flex",
37
+ width: pixels,
38
+ height: pixels,
39
+ alignItems: "center",
40
+ justifyContent: "center",
41
+ flexShrink: 0,
42
+ ...style,
43
+ };
44
+ }, [sizePx, style]);
45
+
46
+ if (!enabled) {
47
+ return <span className={className} style={sharedStyles} aria-label={ariaLabel} />;
48
+ }
49
+
50
+ if (loading) {
51
+ return <span className={className} style={sharedStyles} aria-label={ariaLabel} />;
52
+ }
53
+
54
+ if (!dataUrl) {
55
+ return (
56
+ <span
57
+ className={className}
58
+ style={{
59
+ ...sharedStyles,
60
+ fontFamily: "-apple-system, BlinkMacSystemFont, sans-serif",
61
+ fontSize: `${sizePx}px`,
62
+ lineHeight: "1",
63
+ }}
64
+ aria-label={ariaLabel}
65
+ >
66
+ {fallback}
67
+ </span>
68
+ );
69
+ }
70
+
71
+ return <img className={className} src={dataUrl} alt={ariaLabel ?? symbolName} style={sharedStyles} />;
72
+ }
@@ -0,0 +1,9 @@
1
+ export {
2
+ getSystemSymbolDataUrl,
3
+ clearSystemSymbolCache,
4
+ buildSystemSymbolCacheKey,
5
+ calculateSystemSymbolPointSize,
6
+ type SystemSymbolFetchOptions,
7
+ } from "./systemSymbolService";
8
+ export { SystemSymbolImage, type SystemSymbolImageProps } from "./SystemSymbolImage";
9
+ export { useSystemSymbol } from "./useSystemSymbol";
@@ -0,0 +1,68 @@
1
+ import { invoke } from "@tauri-apps/api/core";
2
+
3
+ export type SystemSymbolFetchOptions = {
4
+ pointSize?: number;
5
+ fallbackPointSize?: number;
6
+ };
7
+
8
+ const SYSTEM_SYMBOL_CACHE = new Map<string, string | null>();
9
+
10
+ const DEFAULT_POINT_SIZE = 18;
11
+ const MIN_POINT_SIZE = 10;
12
+ const MAX_POINT_SIZE = 256;
13
+ const SYMBOL_SCALE_MULTIPLIER = 2.5;
14
+
15
+ export function calculateSystemSymbolPointSize(
16
+ cssPx: number,
17
+ options: { fallbackPointSize?: number } = {},
18
+ ): number {
19
+ const fallback = options.fallbackPointSize ?? DEFAULT_POINT_SIZE;
20
+ const rawPointSize = Math.max(1, Math.floor(cssPx * SYMBOL_SCALE_MULTIPLIER));
21
+ const devicePixelRatio =
22
+ typeof window === "undefined" ? 1 : Math.max(1, window.devicePixelRatio ?? 1);
23
+
24
+ if (!Number.isFinite(rawPointSize) || rawPointSize <= 0) {
25
+ return clampPointSize(fallback * devicePixelRatio);
26
+ }
27
+
28
+ return clampPointSize(Math.round(rawPointSize * devicePixelRatio));
29
+ }
30
+
31
+ function clampPointSize(value: number): number {
32
+ return Math.min(MAX_POINT_SIZE, Math.max(MIN_POINT_SIZE, Math.round(value)));
33
+ }
34
+
35
+ export function buildSystemSymbolCacheKey(symbolName: string, pointSize: number): string {
36
+ return `${symbolName}@${pointSize}`;
37
+ }
38
+
39
+ export function clearSystemSymbolCache(): void {
40
+ SYSTEM_SYMBOL_CACHE.clear();
41
+ }
42
+
43
+ export async function getSystemSymbolDataUrl(
44
+ name: string,
45
+ options: SystemSymbolFetchOptions = {},
46
+ ): Promise<string | null> {
47
+ const pointSize = options.pointSize;
48
+ const resolvedPointSize = pointSize ?? DEFAULT_POINT_SIZE;
49
+ const key = buildSystemSymbolCacheKey(name, resolvedPointSize);
50
+
51
+ if (SYSTEM_SYMBOL_CACHE.has(key)) {
52
+ return SYSTEM_SYMBOL_CACHE.get(key) ?? null;
53
+ }
54
+
55
+ try {
56
+ const maybeDataUrl = await invoke<string | null>("system_symbol_png_data_url", {
57
+ name,
58
+ point_size: pointSize,
59
+ });
60
+
61
+ const dataUrl = maybeDataUrl ?? null;
62
+ SYSTEM_SYMBOL_CACHE.set(key, dataUrl);
63
+ return dataUrl;
64
+ } catch (error) {
65
+ SYSTEM_SYMBOL_CACHE.set(key, null);
66
+ throw error;
67
+ }
68
+ }