mac-human-design 0.1.10 → 0.1.12
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
CHANGED
|
@@ -2,6 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
This file tracks changes between published npm versions of `mac-human-design`.
|
|
4
4
|
|
|
5
|
+
## 0.1.12
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Rendered `SymbolIconButton` glyphs as direct children of the shared
|
|
10
|
+
`MacIconButton` render element, avoiding tooltip slot child placement
|
|
11
|
+
ambiguity.
|
|
12
|
+
- Tightened icon-button centering with explicit grid content centering,
|
|
13
|
+
zero line-height, vertical alignment, and grid-area placement for symbol
|
|
14
|
+
glyphs.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Bumped the package to `0.1.12`.
|
|
19
|
+
|
|
20
|
+
## 0.1.11
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- Trimmed transparent padding from generated SF Symbol PNGs before caching them,
|
|
25
|
+
so icon buttons center the visible glyph instead of the symbol image's
|
|
26
|
+
asymmetric advance box.
|
|
27
|
+
- Routed `SymbolIconButton` through the shared system-symbol hook so icon
|
|
28
|
+
buttons and standalone symbol images share the same normalized symbol data.
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- Bumped the package to `0.1.11`.
|
|
33
|
+
|
|
5
34
|
## 0.1.10
|
|
6
35
|
|
|
7
36
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,21 +1,5 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
2
|
-
import { invoke } from "@tauri-apps/api/core";
|
|
3
1
|
import { MacIconButton, MacTooltip } from "./MacBaseUI";
|
|
4
|
-
|
|
5
|
-
const SYMBOL_SCALE_MULTIPLIER = 2.5;
|
|
6
|
-
const SYMBOL_MIN_POINT_SIZE = 10;
|
|
7
|
-
const SYMBOL_MAX_POINT_SIZE = 160;
|
|
8
|
-
|
|
9
|
-
const systemSymbolIconCache = new Map<string, string | null>();
|
|
10
|
-
|
|
11
|
-
function getSymbolPointSize(cssPx: number): number {
|
|
12
|
-
const pixelRatio = typeof window === "undefined" ? 1 : window.devicePixelRatio || 1;
|
|
13
|
-
return Math.round(Math.max(SYMBOL_MIN_POINT_SIZE, Math.min(SYMBOL_MAX_POINT_SIZE, cssPx * pixelRatio * SYMBOL_SCALE_MULTIPLIER)));
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function symbolCacheKey(symbolName: string, pointSize: number) {
|
|
17
|
-
return `${symbolName}@${pointSize}`;
|
|
18
|
-
}
|
|
2
|
+
import { useSystemSymbol } from "../symbols/useSystemSymbol";
|
|
19
3
|
|
|
20
4
|
type SymbolIconButtonProps = {
|
|
21
5
|
label: string;
|
|
@@ -39,43 +23,10 @@ export function SymbolIconButton({
|
|
|
39
23
|
isLoading,
|
|
40
24
|
iconSizePx = 18,
|
|
41
25
|
}: SymbolIconButtonProps) {
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const [symbolDataUrl, setSymbolDataUrl] = useState<string | null>(() => {
|
|
46
|
-
return systemSymbolIconCache.get(symbolCacheLookupKey) ?? null;
|
|
26
|
+
const { dataUrl: symbolDataUrl } = useSystemSymbol({
|
|
27
|
+
symbolName,
|
|
28
|
+
cssPixelSize: iconSizePx,
|
|
47
29
|
});
|
|
48
|
-
|
|
49
|
-
useEffect(() => {
|
|
50
|
-
const cachedDataUrl = systemSymbolIconCache.get(symbolCacheLookupKey);
|
|
51
|
-
if (cachedDataUrl !== undefined) {
|
|
52
|
-
setSymbolDataUrl(cachedDataUrl);
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
let isMounted = true;
|
|
57
|
-
void invoke<string | null>("system_symbol_png_data_url", {
|
|
58
|
-
name: symbolName,
|
|
59
|
-
point_size: symbolPointSize,
|
|
60
|
-
})
|
|
61
|
-
.then((dataUrl) => {
|
|
62
|
-
systemSymbolIconCache.set(symbolCacheLookupKey, dataUrl);
|
|
63
|
-
if (isMounted) {
|
|
64
|
-
setSymbolDataUrl(dataUrl);
|
|
65
|
-
}
|
|
66
|
-
})
|
|
67
|
-
.catch(() => {
|
|
68
|
-
systemSymbolIconCache.set(symbolCacheLookupKey, null);
|
|
69
|
-
if (isMounted) {
|
|
70
|
-
setSymbolDataUrl(null);
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
return () => {
|
|
75
|
-
isMounted = false;
|
|
76
|
-
};
|
|
77
|
-
}, [symbolName, symbolPointSize, symbolCacheLookupKey]);
|
|
78
|
-
|
|
79
30
|
const glyphPx = `${iconSizePx}px`;
|
|
80
31
|
|
|
81
32
|
return (
|
|
@@ -96,34 +47,34 @@ export function SymbolIconButton({
|
|
|
96
47
|
minHeight: ICON_BUTTON_SIZE,
|
|
97
48
|
borderRadius: ICON_BUTTON_RADIUS,
|
|
98
49
|
}}
|
|
99
|
-
|
|
50
|
+
>
|
|
51
|
+
<span
|
|
52
|
+
aria-hidden
|
|
53
|
+
className="hd-mac-symbol-icon"
|
|
54
|
+
style={{
|
|
55
|
+
width: glyphPx,
|
|
56
|
+
height: glyphPx,
|
|
57
|
+
color: "currentColor",
|
|
58
|
+
backgroundColor: symbolDataUrl ? "currentColor" : "transparent",
|
|
59
|
+
fontFamily: "-apple-system, BlinkMacSystemFont, sans-serif",
|
|
60
|
+
fontSize: symbolDataUrl ? "0" : glyphPx,
|
|
61
|
+
fontWeight: 500,
|
|
62
|
+
lineHeight: "1",
|
|
63
|
+
WebkitMaskImage: symbolDataUrl ? `url("${symbolDataUrl}")` : undefined,
|
|
64
|
+
WebkitMaskPosition: symbolDataUrl ? "center" : undefined,
|
|
65
|
+
WebkitMaskRepeat: symbolDataUrl ? "no-repeat" : undefined,
|
|
66
|
+
WebkitMaskSize: symbolDataUrl ? "contain" : undefined,
|
|
67
|
+
maskImage: symbolDataUrl ? `url("${symbolDataUrl}")` : undefined,
|
|
68
|
+
maskPosition: symbolDataUrl ? "center" : undefined,
|
|
69
|
+
maskRepeat: symbolDataUrl ? "no-repeat" : undefined,
|
|
70
|
+
maskSize: symbolDataUrl ? "contain" : undefined,
|
|
71
|
+
}}
|
|
72
|
+
>
|
|
73
|
+
{symbolDataUrl ? null : fallback}
|
|
74
|
+
</span>
|
|
75
|
+
</MacIconButton>
|
|
100
76
|
}
|
|
101
|
-
|
|
102
|
-
<span
|
|
103
|
-
aria-hidden
|
|
104
|
-
className="hd-mac-symbol-icon"
|
|
105
|
-
style={{
|
|
106
|
-
width: glyphPx,
|
|
107
|
-
height: glyphPx,
|
|
108
|
-
color: "currentColor",
|
|
109
|
-
backgroundColor: symbolDataUrl ? "currentColor" : "transparent",
|
|
110
|
-
fontFamily: "-apple-system, BlinkMacSystemFont, sans-serif",
|
|
111
|
-
fontSize: symbolDataUrl ? "0" : glyphPx,
|
|
112
|
-
fontWeight: 500,
|
|
113
|
-
lineHeight: "1",
|
|
114
|
-
WebkitMaskImage: symbolDataUrl ? `url("${symbolDataUrl}")` : undefined,
|
|
115
|
-
WebkitMaskPosition: symbolDataUrl ? "center" : undefined,
|
|
116
|
-
WebkitMaskRepeat: symbolDataUrl ? "no-repeat" : undefined,
|
|
117
|
-
WebkitMaskSize: symbolDataUrl ? "contain" : undefined,
|
|
118
|
-
maskImage: symbolDataUrl ? `url("${symbolDataUrl}")` : undefined,
|
|
119
|
-
maskPosition: symbolDataUrl ? "center" : undefined,
|
|
120
|
-
maskRepeat: symbolDataUrl ? "no-repeat" : undefined,
|
|
121
|
-
maskSize: symbolDataUrl ? "contain" : undefined,
|
|
122
|
-
}}
|
|
123
|
-
>
|
|
124
|
-
{symbolDataUrl ? null : fallback}
|
|
125
|
-
</span>
|
|
126
|
-
</MacTooltip.Trigger>
|
|
77
|
+
/>
|
|
127
78
|
<MacTooltip.Portal>
|
|
128
79
|
<MacTooltip.Positioner sideOffset={5}>
|
|
129
80
|
<MacTooltip.Popup>{label}</MacTooltip.Popup>
|
|
@@ -232,7 +232,10 @@
|
|
|
232
232
|
.hd-mac-symbol-icon-button {
|
|
233
233
|
display: inline-grid;
|
|
234
234
|
place-items: center;
|
|
235
|
+
place-content: center;
|
|
235
236
|
padding: 0;
|
|
237
|
+
line-height: 0;
|
|
238
|
+
vertical-align: middle;
|
|
236
239
|
}
|
|
237
240
|
|
|
238
241
|
.hd-mac-icon-button {
|
|
@@ -251,11 +254,13 @@
|
|
|
251
254
|
}
|
|
252
255
|
|
|
253
256
|
.hd-mac-symbol-icon {
|
|
257
|
+
grid-area: 1 / 1;
|
|
254
258
|
display: inline-flex;
|
|
255
259
|
align-items: center;
|
|
256
260
|
justify-content: center;
|
|
257
261
|
text-align: center;
|
|
258
262
|
flex-shrink: 0;
|
|
263
|
+
margin: auto;
|
|
259
264
|
}
|
|
260
265
|
|
|
261
266
|
.hd-mac-help-button {
|
|
@@ -11,6 +11,7 @@ const DEFAULT_POINT_SIZE = 18;
|
|
|
11
11
|
const MIN_POINT_SIZE = 10;
|
|
12
12
|
const MAX_POINT_SIZE = 256;
|
|
13
13
|
const SYMBOL_SCALE_MULTIPLIER = 2.5;
|
|
14
|
+
const TRANSPARENT_ALPHA_THRESHOLD = 2;
|
|
14
15
|
|
|
15
16
|
export function calculateSystemSymbolPointSize(
|
|
16
17
|
cssPx: number,
|
|
@@ -58,7 +59,7 @@ export async function getSystemSymbolDataUrl(
|
|
|
58
59
|
point_size: pointSize,
|
|
59
60
|
});
|
|
60
61
|
|
|
61
|
-
const dataUrl = maybeDataUrl
|
|
62
|
+
const dataUrl = maybeDataUrl ? await trimTransparentSymbolPadding(maybeDataUrl) : null;
|
|
62
63
|
SYSTEM_SYMBOL_CACHE.set(key, dataUrl);
|
|
63
64
|
return dataUrl;
|
|
64
65
|
} catch (error) {
|
|
@@ -66,3 +67,90 @@ export async function getSystemSymbolDataUrl(
|
|
|
66
67
|
throw error;
|
|
67
68
|
}
|
|
68
69
|
}
|
|
70
|
+
|
|
71
|
+
async function trimTransparentSymbolPadding(dataUrl: string): Promise<string> {
|
|
72
|
+
if (
|
|
73
|
+
typeof window === "undefined" ||
|
|
74
|
+
typeof Image === "undefined" ||
|
|
75
|
+
typeof document === "undefined"
|
|
76
|
+
) {
|
|
77
|
+
return dataUrl;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const image = await loadImage(dataUrl);
|
|
81
|
+
const width = image.naturalWidth || image.width;
|
|
82
|
+
const height = image.naturalHeight || image.height;
|
|
83
|
+
if (width <= 0 || height <= 0) {
|
|
84
|
+
return dataUrl;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const canvas = document.createElement("canvas");
|
|
88
|
+
canvas.width = width;
|
|
89
|
+
canvas.height = height;
|
|
90
|
+
|
|
91
|
+
const context = canvas.getContext("2d", { willReadFrequently: true });
|
|
92
|
+
if (!context) {
|
|
93
|
+
return dataUrl;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
context.drawImage(image, 0, 0);
|
|
97
|
+
const pixels = context.getImageData(0, 0, width, height).data;
|
|
98
|
+
let minX = width;
|
|
99
|
+
let minY = height;
|
|
100
|
+
let maxX = -1;
|
|
101
|
+
let maxY = -1;
|
|
102
|
+
|
|
103
|
+
for (let y = 0; y < height; y += 1) {
|
|
104
|
+
for (let x = 0; x < width; x += 1) {
|
|
105
|
+
const alpha = pixels[(y * width + x) * 4 + 3];
|
|
106
|
+
if (alpha > TRANSPARENT_ALPHA_THRESHOLD) {
|
|
107
|
+
minX = Math.min(minX, x);
|
|
108
|
+
minY = Math.min(minY, y);
|
|
109
|
+
maxX = Math.max(maxX, x);
|
|
110
|
+
maxY = Math.max(maxY, y);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (maxX < minX || maxY < minY) {
|
|
116
|
+
return dataUrl;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const trimmedWidth = maxX - minX + 1;
|
|
120
|
+
const trimmedHeight = maxY - minY + 1;
|
|
121
|
+
if (trimmedWidth === width && trimmedHeight === height) {
|
|
122
|
+
return dataUrl;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const trimmedCanvas = document.createElement("canvas");
|
|
126
|
+
trimmedCanvas.width = trimmedWidth;
|
|
127
|
+
trimmedCanvas.height = trimmedHeight;
|
|
128
|
+
|
|
129
|
+
const trimmedContext = trimmedCanvas.getContext("2d");
|
|
130
|
+
if (!trimmedContext) {
|
|
131
|
+
return dataUrl;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
trimmedContext.drawImage(
|
|
135
|
+
canvas,
|
|
136
|
+
minX,
|
|
137
|
+
minY,
|
|
138
|
+
trimmedWidth,
|
|
139
|
+
trimmedHeight,
|
|
140
|
+
0,
|
|
141
|
+
0,
|
|
142
|
+
trimmedWidth,
|
|
143
|
+
trimmedHeight,
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
return trimmedCanvas.toDataURL("image/png");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function loadImage(dataUrl: string): Promise<HTMLImageElement> {
|
|
150
|
+
return new Promise((resolve, reject) => {
|
|
151
|
+
const image = new Image();
|
|
152
|
+
image.onload = () => resolve(image);
|
|
153
|
+
image.onerror = () => reject(new Error("Failed to load SF Symbol PNG data."));
|
|
154
|
+
image.src = dataUrl;
|
|
155
|
+
});
|
|
156
|
+
}
|