@pathscale/ui 1.2.10 → 1.3.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 +22 -22
- package/README.md +132 -59
- package/dist/components/accordion/Accordion.css +158 -158
- package/dist/components/alert/Alert.css +116 -116
- package/dist/components/avatar/Avatar.css +124 -124
- package/dist/components/badge/Badge.css +164 -164
- package/dist/components/breadcrumbs/Breadcrumbs.css +63 -63
- package/dist/components/button/Button.css +194 -194
- package/dist/components/button-group/ButtonGroup.css +117 -117
- package/dist/components/calendar/Calendar.css +258 -258
- package/dist/components/card/Card.css +110 -110
- package/dist/components/chatbubble/ChatBubble.css +164 -164
- package/dist/components/checkbox/Checkbox.css +183 -183
- package/dist/components/checkbox-group/CheckboxGroup.css +10 -10
- package/dist/components/chip/Chip.css +197 -197
- package/dist/components/close-button/CloseButton.css +81 -81
- package/dist/components/color-area/ColorArea.css +54 -54
- package/dist/components/color-field/ColorField.css +88 -88
- package/dist/components/color-picker/ColorPicker.css +28 -28
- package/dist/components/color-slider/ColorSlider.css +57 -57
- package/dist/components/color-swatch/ColorSwatch.css +84 -84
- package/dist/components/color-swatch-picker/ColorSwatchPicker.css +12 -12
- package/dist/components/color-wheel-flower/ColorWheelFlower.css +147 -147
- package/dist/components/combo-box/ComboBox.css +344 -344
- package/dist/components/date-field/DateField.css +215 -215
- package/dist/components/date-picker/DatePicker.css +143 -143
- package/dist/components/date-range-picker/DateRangePicker.css +149 -149
- package/dist/components/description/Description.css +10 -10
- package/dist/components/disclosure/Disclosure.css +84 -84
- package/dist/components/disclosure-group/DisclosureGroup.css +9 -9
- package/dist/components/drawer/Drawer.css +463 -463
- package/dist/components/dropdown/Dropdown.css +211 -211
- package/dist/components/empty-state/EmptyState.css +39 -39
- package/dist/components/error-message/ErrorMessage.css +20 -20
- package/dist/components/field-error/FieldError.css +27 -27
- package/dist/components/fieldset/Fieldset.css +30 -30
- package/dist/components/flex/Flex.css +5 -5
- package/dist/components/floating-dock/FloatingDock.css +208 -208
- package/dist/components/footer/footer.css +51 -51
- package/dist/components/form/Form.css +5 -5
- package/dist/components/glass-panel/GlassPanel.css +359 -359
- package/dist/components/glow-card/GlowCard.css +100 -100
- package/dist/components/grid/Grid.css +5 -5
- package/dist/components/header/Header.css +13 -13
- package/dist/components/icon/Icon.css +10 -10
- package/dist/components/immersive-landing/ImmersiveLanding.css +565 -565
- package/dist/components/input/Input.css +155 -155
- package/dist/components/input-group/InputGroup.css +172 -172
- package/dist/components/input-otp/InputOTP.css +170 -170
- package/dist/components/kbd/Kbd.css +60 -60
- package/dist/components/label/Label.css +30 -30
- package/dist/components/language-switcher/LanguageSwitcher.css +56 -56
- package/dist/components/language-switcher/createI18n.d.ts +2 -1
- package/dist/components/link/Link.css +131 -131
- package/dist/components/list-box/ListBox.css +148 -148
- package/dist/components/live-chat/LiveChat.css +324 -324
- package/dist/components/menu/Menu.css +199 -199
- package/dist/components/metal-border/MetalBorder.classes.d.ts +26 -0
- package/dist/components/metal-border/MetalBorder.classes.js +27 -0
- package/dist/components/metal-border/MetalBorder.css +40 -0
- package/dist/components/metal-border/MetalBorder.d.ts +20 -0
- package/dist/components/metal-border/MetalBorder.js +322 -0
- package/dist/components/metal-border/engine/color.d.ts +4 -0
- package/dist/components/metal-border/engine/color.js +69 -0
- package/dist/components/metal-border/engine/glow/geometry.d.ts +35 -0
- package/dist/components/metal-border/engine/glow/geometry.js +176 -0
- package/dist/components/metal-border/engine/glow/glow.d.ts +38 -0
- package/dist/components/metal-border/engine/glow/glow.js +246 -0
- package/dist/components/metal-border/engine/index.d.ts +4 -0
- package/dist/components/metal-border/engine/index.js +2 -0
- package/dist/components/metal-border/engine/perfConfig.d.ts +9 -0
- package/dist/components/metal-border/engine/perfConfig.js +10 -0
- package/dist/components/metal-border/engine/presets.d.ts +57 -0
- package/dist/components/metal-border/engine/presets.js +214 -0
- package/dist/components/metal-border/engine/renderer/core.d.ts +69 -0
- package/dist/components/metal-border/engine/renderer/core.js +156 -0
- package/dist/components/metal-border/engine/renderer/loop.d.ts +30 -0
- package/dist/components/metal-border/engine/renderer/loop.js +248 -0
- package/dist/components/metal-border/engine/renderer/sampling.d.ts +14 -0
- package/dist/components/metal-border/engine/renderer/sampling.js +145 -0
- package/dist/components/metal-border/engine/shaders.d.ts +36 -0
- package/dist/components/metal-border/engine/shaders.js +215 -0
- package/dist/components/metal-border/engine/tween.d.ts +17 -0
- package/dist/components/metal-border/engine/tween.js +28 -0
- package/dist/components/metal-border/index.d.ts +2 -0
- package/dist/components/metal-border/index.js +1 -0
- package/dist/components/meter/Meter.css +89 -89
- package/dist/components/modal/Modal.css +320 -320
- package/dist/components/navbar/Navbar.css +110 -110
- package/dist/components/noise-background/NoiseBackground.css +66 -66
- package/dist/components/number-field/NumberField.css +198 -198
- package/dist/components/pagination/Pagination.css +121 -121
- package/dist/components/popover/Popover.css +117 -117
- package/dist/components/progress-bar/ProgressBar.css +104 -104
- package/dist/components/progress-circle/ProgressCircle.css +82 -82
- package/dist/components/radio/Radio.css +129 -129
- package/dist/components/radio-group/RadioGroup.css +58 -58
- package/dist/components/range-calendar/RangeCalendar.css +51 -51
- package/dist/components/scroll-shadow/ScrollShadow.css +93 -93
- package/dist/components/search-field/SearchField.css +183 -183
- package/dist/components/select/Select.css +284 -284
- package/dist/components/separator/Separator.css +35 -35
- package/dist/components/skeleton/Skeleton.css +95 -95
- package/dist/components/slider/Slider.css +122 -122
- package/dist/components/spinner/Spinner.css +131 -131
- package/dist/components/surface/Surface.css +37 -37
- package/dist/components/table/Table.css +426 -426
- package/dist/components/tabs/Tabs.css +203 -203
- package/dist/components/tag/Tag.css +121 -121
- package/dist/components/tag-group/TagGroup.css +22 -22
- package/dist/components/text/Text.css +66 -66
- package/dist/components/text-area/TextArea.css +95 -95
- package/dist/components/text-field/TextField.css +33 -33
- package/dist/components/time-field/TimeField.css +215 -215
- package/dist/components/toast/Toast.css +344 -344
- package/dist/components/toggle/Toggle.css +237 -237
- package/dist/components/toolbar/Toolbar.css +55 -55
- package/dist/components/tooltip/Tooltip.css +153 -153
- package/dist/index.css +74 -74
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/purge-manifest.json +15836 -15583
- package/dist/styles/base/index.css +43 -43
- package/dist/styles/themes/dark.css +141 -141
- package/dist/styles/themes/light.css +143 -143
- package/package.json +17 -8
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { className, effect, insert, memo, mergeProps, spread, template, use } from "solid-js/web";
|
|
2
|
+
import "./MetalBorder.css";
|
|
3
|
+
import { createEffect, createSignal, onCleanup, onMount, splitProps } from "solid-js";
|
|
4
|
+
import { twMerge } from "tailwind-merge";
|
|
5
|
+
import { prefersReducedMotion } from "../../motion/reduced-motion.js";
|
|
6
|
+
import { CLASSES } from "./MetalBorder.classes.js";
|
|
7
|
+
import { createInstance, destroyInstance, injectGlow, registerGlowInstance, resizeGlow, setGlowCallback, setInstanceVisible, unregisterGlowInstance, updateGlow, updateInstance } from "./engine/index.js";
|
|
8
|
+
var _tmpl$ = /*#__PURE__*/ template("<div><div>"), _tmpl$2 = /*#__PURE__*/ template("<div aria-hidden=true><canvas></canvas><div>");
|
|
9
|
+
const DEFAULT_PRESET = "silver";
|
|
10
|
+
const DEFAULT_STRENGTH = 90;
|
|
11
|
+
const DEFAULT_KIND = "pill";
|
|
12
|
+
const DEFAULT_THEME = "auto";
|
|
13
|
+
const clampStrength = (value)=>{
|
|
14
|
+
if ("number" != typeof value || Number.isNaN(value)) return DEFAULT_STRENGTH / 100;
|
|
15
|
+
return Math.min(100, Math.max(0, value)) / 100;
|
|
16
|
+
};
|
|
17
|
+
const toRadiusCssValue = (value)=>{
|
|
18
|
+
if ("number" == typeof value) return `${value}px`;
|
|
19
|
+
return value;
|
|
20
|
+
};
|
|
21
|
+
const readRadiusPx = (element)=>{
|
|
22
|
+
if (!element || "u" < typeof window) return 0;
|
|
23
|
+
const computed = window.getComputedStyle(element);
|
|
24
|
+
const candidates = [
|
|
25
|
+
computed.borderTopLeftRadius,
|
|
26
|
+
computed.borderTopRightRadius,
|
|
27
|
+
computed.borderBottomRightRadius,
|
|
28
|
+
computed.borderBottomLeftRadius,
|
|
29
|
+
computed.borderRadius
|
|
30
|
+
];
|
|
31
|
+
for (const candidate of candidates){
|
|
32
|
+
const parsed = Number.parseFloat(candidate);
|
|
33
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
34
|
+
}
|
|
35
|
+
return 0;
|
|
36
|
+
};
|
|
37
|
+
const resolveThemeValue = (explicitTheme, dataTheme, host)=>{
|
|
38
|
+
if ("dark" === explicitTheme || "light" === explicitTheme) return explicitTheme;
|
|
39
|
+
if ("dark" === dataTheme || "light" === dataTheme) return dataTheme;
|
|
40
|
+
const themeRoot = host?.closest("[data-theme]") ?? document.documentElement;
|
|
41
|
+
const themeAttr = themeRoot?.getAttribute("data-theme")?.toLowerCase();
|
|
42
|
+
if (themeAttr?.includes("light")) return "light";
|
|
43
|
+
if (themeAttr?.includes("dark")) return "dark";
|
|
44
|
+
return window.matchMedia?.("(prefers-color-scheme: dark)")?.matches ? "dark" : "light";
|
|
45
|
+
};
|
|
46
|
+
const getGlowOptions = (host, kind, cornerRadius)=>{
|
|
47
|
+
const rect = host?.getBoundingClientRect();
|
|
48
|
+
return {
|
|
49
|
+
width: Math.max(1, Math.round(rect?.width ?? 1)),
|
|
50
|
+
height: Math.max(1, Math.round(rect?.height ?? 1)),
|
|
51
|
+
cornerRadius,
|
|
52
|
+
kind
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
const glowMap = new Map();
|
|
56
|
+
let glowCallbackSet = false;
|
|
57
|
+
const ensureGlowCallback = ()=>{
|
|
58
|
+
if (glowCallbackSet) return;
|
|
59
|
+
glowCallbackSet = true;
|
|
60
|
+
setGlowCallback((instance, nowMs)=>{
|
|
61
|
+
const entry = glowMap.get(instance);
|
|
62
|
+
if (entry) updateGlow(entry.handles, instance, nowMs, instance.opacityMul, entry.theme);
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
function MetalBorder(props) {
|
|
66
|
+
const [local, others] = splitProps(props, [
|
|
67
|
+
"children",
|
|
68
|
+
"class",
|
|
69
|
+
"className",
|
|
70
|
+
"contentClass",
|
|
71
|
+
"dataTheme",
|
|
72
|
+
"style",
|
|
73
|
+
"preset",
|
|
74
|
+
"strength",
|
|
75
|
+
"kind",
|
|
76
|
+
"glow",
|
|
77
|
+
"paused",
|
|
78
|
+
"theme",
|
|
79
|
+
"cornerRadius",
|
|
80
|
+
"ref"
|
|
81
|
+
]);
|
|
82
|
+
let hostRef;
|
|
83
|
+
let contentRef;
|
|
84
|
+
let canvasRef;
|
|
85
|
+
let glowRef;
|
|
86
|
+
let instance = null;
|
|
87
|
+
let glowHandles = null;
|
|
88
|
+
let resizeObserver = null;
|
|
89
|
+
let intersectionObserver = null;
|
|
90
|
+
let mutationObserver = null;
|
|
91
|
+
let mediaQueryList = null;
|
|
92
|
+
let resizeRaf = 0;
|
|
93
|
+
const [resolvedTheme, setResolvedTheme] = createSignal("dark");
|
|
94
|
+
const [prefersReduced, setPrefersReduced] = createSignal(false);
|
|
95
|
+
const [isWebGlUnavailable, setIsWebGlUnavailable] = createSignal(false);
|
|
96
|
+
const preset = ()=>local.preset ?? DEFAULT_PRESET;
|
|
97
|
+
const kind = ()=>local.kind ?? DEFAULT_KIND;
|
|
98
|
+
const theme = ()=>local.theme ?? DEFAULT_THEME;
|
|
99
|
+
const strength = ()=>clampStrength(local.strength);
|
|
100
|
+
const glow = ()=>local.glow ?? false;
|
|
101
|
+
const effectivePaused = ()=>(local.paused ?? false) || prefersReduced();
|
|
102
|
+
const radiusCssValue = ()=>toRadiusCssValue(local.cornerRadius);
|
|
103
|
+
const effectEnabled = ()=>!isWebGlUnavailable();
|
|
104
|
+
const syncResolvedTheme = ()=>{
|
|
105
|
+
if ("u" < typeof window) return;
|
|
106
|
+
setResolvedTheme(resolveThemeValue(theme(), local.dataTheme, hostRef));
|
|
107
|
+
};
|
|
108
|
+
const readCornerRadius = ()=>{
|
|
109
|
+
if ("number" == typeof local.cornerRadius) return local.cornerRadius;
|
|
110
|
+
if ("string" == typeof local.cornerRadius) return readRadiusPx(hostRef);
|
|
111
|
+
const firstChild = contentRef?.firstElementChild;
|
|
112
|
+
return readRadiusPx(firstChild instanceof HTMLElement ? firstChild : hostRef);
|
|
113
|
+
};
|
|
114
|
+
const syncInstanceGeometry = ()=>{
|
|
115
|
+
if (!instance || !hostRef) return;
|
|
116
|
+
updateInstance(instance, {
|
|
117
|
+
cssWidth: Math.max(1, Math.round(hostRef.getBoundingClientRect().width)),
|
|
118
|
+
cssHeight: Math.max(1, Math.round(hostRef.getBoundingClientRect().height)),
|
|
119
|
+
cornerRadius: readCornerRadius()
|
|
120
|
+
});
|
|
121
|
+
if (glowHandles && glowRef) {
|
|
122
|
+
glowHandles = resizeGlow(glowHandles, glowRef, {
|
|
123
|
+
...getGlowOptions(hostRef, kind(), readCornerRadius()),
|
|
124
|
+
scale: 1
|
|
125
|
+
});
|
|
126
|
+
glowMap.set(instance, {
|
|
127
|
+
handles: glowHandles,
|
|
128
|
+
theme: resolvedTheme()
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
const attachGlow = ()=>{
|
|
133
|
+
if (!instance || !glowRef || glowHandles || !glow()) return;
|
|
134
|
+
ensureGlowCallback();
|
|
135
|
+
glowHandles = injectGlow(glowRef, {
|
|
136
|
+
...getGlowOptions(hostRef, kind(), readCornerRadius()),
|
|
137
|
+
scale: 1
|
|
138
|
+
});
|
|
139
|
+
glowMap.set(instance, {
|
|
140
|
+
handles: glowHandles,
|
|
141
|
+
theme: resolvedTheme()
|
|
142
|
+
});
|
|
143
|
+
registerGlowInstance(instance);
|
|
144
|
+
};
|
|
145
|
+
const detachGlow = ()=>{
|
|
146
|
+
if (!instance) return;
|
|
147
|
+
unregisterGlowInstance(instance);
|
|
148
|
+
glowMap.delete(instance);
|
|
149
|
+
glowHandles = null;
|
|
150
|
+
if (glowRef) glowRef.innerHTML = "";
|
|
151
|
+
};
|
|
152
|
+
onMount(()=>{
|
|
153
|
+
if ("u" < typeof window) return;
|
|
154
|
+
setPrefersReduced(prefersReducedMotion());
|
|
155
|
+
syncResolvedTheme();
|
|
156
|
+
mediaQueryList = window.matchMedia?.("(prefers-reduced-motion: reduce)") ?? null;
|
|
157
|
+
const handleMotionChange = ()=>{
|
|
158
|
+
setPrefersReduced(prefersReducedMotion());
|
|
159
|
+
};
|
|
160
|
+
mediaQueryList?.addEventListener?.("change", handleMotionChange);
|
|
161
|
+
if (hostRef) {
|
|
162
|
+
const themeRoot = hostRef.closest("[data-theme]") ?? document.documentElement;
|
|
163
|
+
mutationObserver = new MutationObserver(()=>{
|
|
164
|
+
syncResolvedTheme();
|
|
165
|
+
});
|
|
166
|
+
mutationObserver.observe(themeRoot, {
|
|
167
|
+
attributes: true,
|
|
168
|
+
attributeFilter: [
|
|
169
|
+
"data-theme"
|
|
170
|
+
]
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
onCleanup(()=>{
|
|
174
|
+
resizeObserver?.disconnect();
|
|
175
|
+
intersectionObserver?.disconnect();
|
|
176
|
+
mutationObserver?.disconnect();
|
|
177
|
+
mediaQueryList?.removeEventListener?.("change", handleMotionChange);
|
|
178
|
+
if (0 !== resizeRaf) cancelAnimationFrame(resizeRaf);
|
|
179
|
+
detachGlow();
|
|
180
|
+
if (instance) destroyInstance(instance);
|
|
181
|
+
instance = null;
|
|
182
|
+
});
|
|
183
|
+
if (!canvasRef || !hostRef) return;
|
|
184
|
+
try {
|
|
185
|
+
instance = createInstance({
|
|
186
|
+
hostCanvas: canvasRef,
|
|
187
|
+
cssWidth: Math.max(1, Math.round(hostRef.getBoundingClientRect().width)),
|
|
188
|
+
cssHeight: Math.max(1, Math.round(hostRef.getBoundingClientRect().height)),
|
|
189
|
+
cornerRadius: readCornerRadius(),
|
|
190
|
+
kind: kind(),
|
|
191
|
+
opacityMul: strength(),
|
|
192
|
+
paused: effectivePaused(),
|
|
193
|
+
presetName: preset(),
|
|
194
|
+
presetTheme: resolvedTheme()
|
|
195
|
+
});
|
|
196
|
+
setIsWebGlUnavailable(false);
|
|
197
|
+
} catch {
|
|
198
|
+
setIsWebGlUnavailable(true);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (glow()) attachGlow();
|
|
202
|
+
resizeObserver = new ResizeObserver(()=>{
|
|
203
|
+
if (0 !== resizeRaf) return;
|
|
204
|
+
resizeRaf = requestAnimationFrame(()=>{
|
|
205
|
+
resizeRaf = 0;
|
|
206
|
+
syncResolvedTheme();
|
|
207
|
+
syncInstanceGeometry();
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
resizeObserver.observe(hostRef);
|
|
211
|
+
if ("u" > typeof IntersectionObserver) {
|
|
212
|
+
intersectionObserver = new IntersectionObserver((entries)=>{
|
|
213
|
+
if (!instance) return;
|
|
214
|
+
for (const entry of entries)setInstanceVisible(instance, entry.isIntersecting);
|
|
215
|
+
}, {
|
|
216
|
+
rootMargin: "64px"
|
|
217
|
+
});
|
|
218
|
+
intersectionObserver.observe(hostRef);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
createEffect(()=>{
|
|
222
|
+
syncResolvedTheme();
|
|
223
|
+
if (!instance) return;
|
|
224
|
+
updateInstance(instance, {
|
|
225
|
+
presetName: preset(),
|
|
226
|
+
presetTheme: resolvedTheme()
|
|
227
|
+
});
|
|
228
|
+
if (glowHandles) glowMap.set(instance, {
|
|
229
|
+
handles: glowHandles,
|
|
230
|
+
theme: resolvedTheme()
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
createEffect(()=>{
|
|
234
|
+
if (!instance) return;
|
|
235
|
+
updateInstance(instance, {
|
|
236
|
+
opacityMul: strength()
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
createEffect(()=>{
|
|
240
|
+
if (!instance) return;
|
|
241
|
+
updateInstance(instance, {
|
|
242
|
+
kind: kind(),
|
|
243
|
+
cornerRadius: readCornerRadius()
|
|
244
|
+
});
|
|
245
|
+
if (glowHandles && glowRef) {
|
|
246
|
+
glowHandles = resizeGlow(glowHandles, glowRef, {
|
|
247
|
+
...getGlowOptions(hostRef, kind(), readCornerRadius()),
|
|
248
|
+
scale: 1
|
|
249
|
+
});
|
|
250
|
+
glowMap.set(instance, {
|
|
251
|
+
handles: glowHandles,
|
|
252
|
+
theme: resolvedTheme()
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
createEffect(()=>{
|
|
257
|
+
if (!instance) return;
|
|
258
|
+
updateInstance(instance, {
|
|
259
|
+
paused: effectivePaused()
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
createEffect(()=>{
|
|
263
|
+
if (!instance) return;
|
|
264
|
+
if (glow()) attachGlow();
|
|
265
|
+
else if (glowHandles) detachGlow();
|
|
266
|
+
});
|
|
267
|
+
return (()=>{
|
|
268
|
+
var _el$ = _tmpl$(), _el$2 = _el$.firstChild;
|
|
269
|
+
use((element)=>{
|
|
270
|
+
hostRef = element;
|
|
271
|
+
if ("function" == typeof local.ref) local.ref(element);
|
|
272
|
+
}, _el$);
|
|
273
|
+
spread(_el$, mergeProps(others, {
|
|
274
|
+
get ["class"] () {
|
|
275
|
+
return twMerge(CLASSES.Root.base, CLASSES.Root.kind[kind()], effectEnabled() && CLASSES.Root.flag.enabled, isWebGlUnavailable() && CLASSES.Root.flag.unavailable, effectivePaused() && CLASSES.Root.flag.paused, local.class, local.className);
|
|
276
|
+
},
|
|
277
|
+
get ["data-kind"] () {
|
|
278
|
+
return kind();
|
|
279
|
+
},
|
|
280
|
+
get ["data-preset"] () {
|
|
281
|
+
return preset();
|
|
282
|
+
},
|
|
283
|
+
get ["data-theme"] () {
|
|
284
|
+
return local.dataTheme;
|
|
285
|
+
},
|
|
286
|
+
get style () {
|
|
287
|
+
return {
|
|
288
|
+
...local.style ?? {},
|
|
289
|
+
"--metal-border-radius": radiusCssValue()
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
}), false, true);
|
|
293
|
+
insert(_el$, (()=>{
|
|
294
|
+
var _c$ = memo(()=>!!effectEnabled());
|
|
295
|
+
return ()=>_c$() && (()=>{
|
|
296
|
+
var _el$3 = _tmpl$2(), _el$4 = _el$3.firstChild, _el$5 = _el$4.nextSibling;
|
|
297
|
+
var _ref$2 = canvasRef;
|
|
298
|
+
"function" == typeof _ref$2 ? use(_ref$2, _el$4) : canvasRef = _el$4;
|
|
299
|
+
var _ref$3 = glowRef;
|
|
300
|
+
"function" == typeof _ref$3 ? use(_ref$3, _el$5) : glowRef = _el$5;
|
|
301
|
+
effect((_p$)=>{
|
|
302
|
+
var _v$ = CLASSES.Effect.base, _v$2 = CLASSES.Canvas.base, _v$3 = CLASSES.Glow.base;
|
|
303
|
+
_v$ !== _p$.e && className(_el$3, _p$.e = _v$);
|
|
304
|
+
_v$2 !== _p$.t && className(_el$4, _p$.t = _v$2);
|
|
305
|
+
_v$3 !== _p$.a && className(_el$5, _p$.a = _v$3);
|
|
306
|
+
return _p$;
|
|
307
|
+
}, {
|
|
308
|
+
e: void 0,
|
|
309
|
+
t: void 0,
|
|
310
|
+
a: void 0
|
|
311
|
+
});
|
|
312
|
+
return _el$3;
|
|
313
|
+
})();
|
|
314
|
+
})(), _el$2);
|
|
315
|
+
var _ref$ = contentRef;
|
|
316
|
+
"function" == typeof _ref$ ? use(_ref$, _el$2) : contentRef = _el$2;
|
|
317
|
+
insert(_el$2, ()=>local.children);
|
|
318
|
+
effect(()=>className(_el$2, twMerge(CLASSES.Content.base, local.contentClass)));
|
|
319
|
+
return _el$;
|
|
320
|
+
})();
|
|
321
|
+
}
|
|
322
|
+
export default MetalBorder;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Converts `#rrggbb` (or `#rgb`) to a normalized `[r, g, b]` triple (0–1). */
|
|
2
|
+
export declare function hexToRgb(hex: string): [number, number, number];
|
|
3
|
+
export declare function rgbToHsv(r: number, g: number, b: number): [number, number, number];
|
|
4
|
+
export declare function hsvToRgb(h: number, s: number, v: number): [number, number, number];
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
function hexToRgb(hex) {
|
|
2
|
+
let h = hex.replace('#', '');
|
|
3
|
+
if (3 === h.length) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
|
|
4
|
+
return [
|
|
5
|
+
parseInt(h.slice(0, 2), 16) / 255,
|
|
6
|
+
parseInt(h.slice(2, 4), 16) / 255,
|
|
7
|
+
parseInt(h.slice(4, 6), 16) / 255
|
|
8
|
+
];
|
|
9
|
+
}
|
|
10
|
+
function rgbToHsv(r, g, b) {
|
|
11
|
+
r /= 255;
|
|
12
|
+
g /= 255;
|
|
13
|
+
b /= 255;
|
|
14
|
+
const max = Math.max(r, g, b), min = Math.min(r, g, b), d = max - min;
|
|
15
|
+
let h = 0;
|
|
16
|
+
const s = 0 === max ? 0 : d / max;
|
|
17
|
+
if (0 !== d) {
|
|
18
|
+
h = max === r ? ((g - b) / d + 6) % 6 : max === g ? (b - r) / d + 2 : (r - g) / d + 4;
|
|
19
|
+
h /= 6;
|
|
20
|
+
}
|
|
21
|
+
return [
|
|
22
|
+
h,
|
|
23
|
+
s,
|
|
24
|
+
max
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
function hsvToRgb(h, s, v) {
|
|
28
|
+
const i = Math.floor(6 * h), f = 6 * h - i;
|
|
29
|
+
const p = v * (1 - s), q = v * (1 - f * s), t = v * (1 - (1 - f) * s);
|
|
30
|
+
let r = 0, g = 0, b = 0;
|
|
31
|
+
switch(i % 6){
|
|
32
|
+
case 0:
|
|
33
|
+
r = v;
|
|
34
|
+
g = t;
|
|
35
|
+
b = p;
|
|
36
|
+
break;
|
|
37
|
+
case 1:
|
|
38
|
+
r = q;
|
|
39
|
+
g = v;
|
|
40
|
+
b = p;
|
|
41
|
+
break;
|
|
42
|
+
case 2:
|
|
43
|
+
r = p;
|
|
44
|
+
g = v;
|
|
45
|
+
b = t;
|
|
46
|
+
break;
|
|
47
|
+
case 3:
|
|
48
|
+
r = p;
|
|
49
|
+
g = q;
|
|
50
|
+
b = v;
|
|
51
|
+
break;
|
|
52
|
+
case 4:
|
|
53
|
+
r = t;
|
|
54
|
+
g = p;
|
|
55
|
+
b = v;
|
|
56
|
+
break;
|
|
57
|
+
case 5:
|
|
58
|
+
r = v;
|
|
59
|
+
g = p;
|
|
60
|
+
b = q;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
return [
|
|
64
|
+
Math.round(255 * r),
|
|
65
|
+
Math.round(255 * g),
|
|
66
|
+
Math.round(255 * b)
|
|
67
|
+
];
|
|
68
|
+
}
|
|
69
|
+
export { hexToRgb, hsvToRgb, rgbToHsv };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure geometry + SVG markup for the glow overlay.
|
|
3
|
+
*
|
|
4
|
+
* Perimeter math (rounded-rect / circle arc-length sampling), blob path
|
|
5
|
+
* generation, SVG filter/mask construction, and HSV colour helpers.
|
|
6
|
+
* No state — every function is a pure transform.
|
|
7
|
+
*/
|
|
8
|
+
import { PERIM_SAMPLES } from '../perfConfig';
|
|
9
|
+
export { PERIM_SAMPLES };
|
|
10
|
+
export interface GlowOptions {
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
cornerRadius: number;
|
|
14
|
+
kind: 'pill' | 'circle';
|
|
15
|
+
/** Master multiplier for absolute SVG units (stroke widths, blur,
|
|
16
|
+
* fade-circle radius). 1 is the canonical 1× rendering. Set to 2 when
|
|
17
|
+
* the host element is rendered at a 2× layout (e.g. CSS zoom: 2) so
|
|
18
|
+
* the glow grows proportionally. */
|
|
19
|
+
scale?: number;
|
|
20
|
+
}
|
|
21
|
+
export interface Pt {
|
|
22
|
+
x: number;
|
|
23
|
+
y: number;
|
|
24
|
+
}
|
|
25
|
+
export interface PerimSample extends Pt {
|
|
26
|
+
arc: number;
|
|
27
|
+
}
|
|
28
|
+
export declare function rrPerim(w: number, h: number, r: number): number;
|
|
29
|
+
export declare function shapePerim(w: number, h: number, r: number, kind: 'pill' | 'circle'): number;
|
|
30
|
+
export declare function sampleAtArc(s: number, w: number, h: number, r: number, inset: number, outward: number, kind: 'pill' | 'circle', out?: Pt): Pt;
|
|
31
|
+
export declare function buildStaticBlobPath(halfLen: number, segments: number): string;
|
|
32
|
+
export declare function tangentAngleAtArc(s: number, w: number, h: number, r: number, inset: number, kind: 'pill' | 'circle'): number;
|
|
33
|
+
export declare function smoothstep(a: number, b: number, x: number): number;
|
|
34
|
+
export declare function buildPerimTable(opts: GlowOptions): PerimSample[];
|
|
35
|
+
export declare function buildSvgMarkup(opts: GlowOptions, p: string): string;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { PERIM_SAMPLES } from "../perfConfig.js";
|
|
2
|
+
const INSET = 1.5;
|
|
3
|
+
const EXTRA_SCALE = 1 / 3;
|
|
4
|
+
const EXTRA_STROKE_OUTER = 4.0 * EXTRA_SCALE;
|
|
5
|
+
const EXTRA_STROKE_CORE = 2.0 * EXTRA_SCALE;
|
|
6
|
+
const EXTRA_BLUR_OUTER = 2.0 * EXTRA_SCALE;
|
|
7
|
+
const EXTRA_BLUR_CORE = 1.35 * EXTRA_SCALE;
|
|
8
|
+
const EXTRA_FADE_R = 13.0 * EXTRA_SCALE;
|
|
9
|
+
function rrPerim(w, h, r) {
|
|
10
|
+
const rr = Math.max(0, Math.min(r, Math.min(w, h) / 2));
|
|
11
|
+
return 2 * Math.max(0, w - 2 * rr) + 2 * Math.max(0, h - 2 * rr) + 2 * Math.PI * rr;
|
|
12
|
+
}
|
|
13
|
+
function shapePerim(w, h, r, kind) {
|
|
14
|
+
if ('circle' === kind) return 2 * Math.PI * Math.max(0, Math.min(r, Math.min(w, h) / 2));
|
|
15
|
+
return rrPerim(w, h, r);
|
|
16
|
+
}
|
|
17
|
+
function sampleAtArc(s, w, h, r, inset, outward, kind, out) {
|
|
18
|
+
const o = out || {
|
|
19
|
+
x: 0,
|
|
20
|
+
y: 0
|
|
21
|
+
};
|
|
22
|
+
const rr = Math.max(0, Math.min(r, Math.min(w, h) / 2));
|
|
23
|
+
if ('circle' === kind) {
|
|
24
|
+
const perim = 2 * Math.PI * rr;
|
|
25
|
+
if (perim <= 0.0001) {
|
|
26
|
+
o.x = 0.5 * w;
|
|
27
|
+
o.y = 0.5 * h;
|
|
28
|
+
return o;
|
|
29
|
+
}
|
|
30
|
+
s = (s % perim + perim) % perim;
|
|
31
|
+
const theta = -Math.PI / 2 + s / perim * Math.PI * 2;
|
|
32
|
+
const rad = Math.max(0, rr - inset + outward);
|
|
33
|
+
o.x = 0.5 * w + rad * Math.cos(theta);
|
|
34
|
+
o.y = 0.5 * h + rad * Math.sin(theta);
|
|
35
|
+
return o;
|
|
36
|
+
}
|
|
37
|
+
const topLen = Math.max(0, w - 2 * rr), sideLen = Math.max(0, h - 2 * rr);
|
|
38
|
+
const arcLen = Math.PI * rr / 2;
|
|
39
|
+
const perim = 2 * (topLen + sideLen) + 4 * arcLen;
|
|
40
|
+
s = (s % perim + perim) % perim;
|
|
41
|
+
const rad = Math.max(0, rr - inset + outward);
|
|
42
|
+
let d = s;
|
|
43
|
+
if (d < topLen) {
|
|
44
|
+
o.x = rr + d;
|
|
45
|
+
o.y = inset - outward;
|
|
46
|
+
return o;
|
|
47
|
+
}
|
|
48
|
+
d -= topLen;
|
|
49
|
+
if (d < arcLen) {
|
|
50
|
+
const theta = -Math.PI / 2 + Math.PI / 2 * (arcLen > 0 ? d / arcLen : 0);
|
|
51
|
+
o.x = w - rr + rad * Math.cos(theta);
|
|
52
|
+
o.y = rr + rad * Math.sin(theta);
|
|
53
|
+
return o;
|
|
54
|
+
}
|
|
55
|
+
d -= arcLen;
|
|
56
|
+
if (d < sideLen) {
|
|
57
|
+
o.x = w - inset + outward;
|
|
58
|
+
o.y = rr + d;
|
|
59
|
+
return o;
|
|
60
|
+
}
|
|
61
|
+
d -= sideLen;
|
|
62
|
+
if (d < arcLen) {
|
|
63
|
+
const theta = Math.PI / 2 * (arcLen > 0 ? d / arcLen : 0);
|
|
64
|
+
o.x = w - rr + rad * Math.cos(theta);
|
|
65
|
+
o.y = h - rr + rad * Math.sin(theta);
|
|
66
|
+
return o;
|
|
67
|
+
}
|
|
68
|
+
d -= arcLen;
|
|
69
|
+
if (d < topLen) {
|
|
70
|
+
o.x = w - rr - d;
|
|
71
|
+
o.y = h - inset + outward;
|
|
72
|
+
return o;
|
|
73
|
+
}
|
|
74
|
+
d -= topLen;
|
|
75
|
+
if (d < arcLen) {
|
|
76
|
+
const theta = Math.PI / 2 + Math.PI / 2 * (arcLen > 0 ? d / arcLen : 0);
|
|
77
|
+
o.x = rr + rad * Math.cos(theta);
|
|
78
|
+
o.y = h - rr + rad * Math.sin(theta);
|
|
79
|
+
return o;
|
|
80
|
+
}
|
|
81
|
+
d -= arcLen;
|
|
82
|
+
if (d < sideLen) {
|
|
83
|
+
o.x = inset - outward;
|
|
84
|
+
o.y = h - rr - d;
|
|
85
|
+
return o;
|
|
86
|
+
}
|
|
87
|
+
d -= sideLen;
|
|
88
|
+
const theta = Math.PI + Math.PI / 2 * (arcLen > 0 ? d / arcLen : 0);
|
|
89
|
+
o.x = rr + rad * Math.cos(theta);
|
|
90
|
+
o.y = rr + rad * Math.sin(theta);
|
|
91
|
+
return o;
|
|
92
|
+
}
|
|
93
|
+
function buildStaticBlobPath(halfLen, segments) {
|
|
94
|
+
const step = 2 * halfLen / segments;
|
|
95
|
+
let d = '';
|
|
96
|
+
for(let i = 0; i <= segments; i++){
|
|
97
|
+
const x = -halfLen + i * step;
|
|
98
|
+
d += (0 === i ? 'M ' : 'L ') + x.toFixed(3) + ' 0 ';
|
|
99
|
+
}
|
|
100
|
+
return d;
|
|
101
|
+
}
|
|
102
|
+
const _ta = {
|
|
103
|
+
x: 0,
|
|
104
|
+
y: 0
|
|
105
|
+
};
|
|
106
|
+
const _tb = {
|
|
107
|
+
x: 0,
|
|
108
|
+
y: 0
|
|
109
|
+
};
|
|
110
|
+
function tangentAngleAtArc(s, w, h, r, inset, kind) {
|
|
111
|
+
const eps = 0.1;
|
|
112
|
+
sampleAtArc(s - eps, w, h, r, inset, 0, kind, _ta);
|
|
113
|
+
sampleAtArc(s + eps, w, h, r, inset, 0, kind, _tb);
|
|
114
|
+
return Math.atan2(_tb.y - _ta.y, _tb.x - _ta.x);
|
|
115
|
+
}
|
|
116
|
+
function smoothstep(a, b, x) {
|
|
117
|
+
if (a === b) return x < a ? 0 : 1;
|
|
118
|
+
const t = Math.max(0, Math.min(1, (x - a) / (b - a)));
|
|
119
|
+
return t * t * (3 - 2 * t);
|
|
120
|
+
}
|
|
121
|
+
function buildPerimTable(opts) {
|
|
122
|
+
const perim = shapePerim(opts.width, opts.height, opts.cornerRadius, opts.kind);
|
|
123
|
+
const insetS = INSET * (opts.scale ?? 1);
|
|
124
|
+
const table = [];
|
|
125
|
+
for(let i = 0; i < PERIM_SAMPLES; i++){
|
|
126
|
+
const arc = i / PERIM_SAMPLES * perim;
|
|
127
|
+
const pt = sampleAtArc(arc, opts.width, opts.height, opts.cornerRadius, insetS, 0, opts.kind);
|
|
128
|
+
table.push({
|
|
129
|
+
x: pt.x,
|
|
130
|
+
y: pt.y,
|
|
131
|
+
arc
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return table;
|
|
135
|
+
}
|
|
136
|
+
function buildSvgMarkup(opts, p) {
|
|
137
|
+
const { width: W, height: H, cornerRadius: R } = opts;
|
|
138
|
+
const s = opts.scale ?? 1;
|
|
139
|
+
const ringInset = 'circle' === opts.kind ? 2 : 1;
|
|
140
|
+
const innerR = Math.max(0, R - ringInset);
|
|
141
|
+
const fX = (-200 * s).toFixed(0), fY = fX;
|
|
142
|
+
const fW = (540 * s).toFixed(0), fH = (440 * s).toFixed(0);
|
|
143
|
+
const fRect = `x="${fX}" y="${fY}" width="${fW}" height="${fH}"`;
|
|
144
|
+
const fr = `${fRect} filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"`;
|
|
145
|
+
const sw = (n)=>(n * s).toFixed(3);
|
|
146
|
+
const sd = (n)=>(n * s).toFixed(3);
|
|
147
|
+
return [
|
|
148
|
+
'<defs>',
|
|
149
|
+
`<filter id="${p}_bXl" ${fr}><feGaussianBlur stdDeviation="${sd(8.4)}"/></filter>`,
|
|
150
|
+
`<filter id="${p}_bLg" ${fr}><feGaussianBlur stdDeviation="${sd(4.8)}"/></filter>`,
|
|
151
|
+
`<filter id="${p}_bMd" ${fr}><feGaussianBlur stdDeviation="${sd(2.1)}"/></filter>`,
|
|
152
|
+
`<filter id="${p}_bSm" ${fr}><feGaussianBlur stdDeviation="${sd(0.9)}"/></filter>`,
|
|
153
|
+
`<filter id="${p}_ebO" ${fr}><feGaussianBlur stdDeviation="${sd(EXTRA_BLUR_OUTER)}"/></filter>`,
|
|
154
|
+
`<filter id="${p}_ebC" ${fr}><feGaussianBlur stdDeviation="${sd(EXTRA_BLUR_CORE)}"/></filter>`,
|
|
155
|
+
`<radialGradient id="${p}_fg" cx="0.5" cy="0.5" r="0.5"><stop offset="0" stop-color="white"/><stop offset="0.30" stop-color="white"/><stop offset="0.65" stop-color="#404040"/><stop offset="1" stop-color="black"/></radialGradient>`,
|
|
156
|
+
`<mask id="${p}_fm" maskUnits="userSpaceOnUse" ${fRect}><rect ${fRect} fill="black"/><circle id="${p}_fc" cx="0" cy="0" r="${(EXTRA_FADE_R * s).toFixed(3)}" fill="url(#${p}_fg)"/></mask>`,
|
|
157
|
+
`<mask id="${p}_rm" maskUnits="userSpaceOnUse" ${fRect}><rect ${fRect} fill="#808080"/><rect x="0" y="0" width="${W}" height="${H}" rx="${R}" ry="${R}" fill="white"/><rect x="${ringInset}" y="${ringInset}" width="${W - 2 * ringInset}" height="${H - 2 * ringInset}" rx="${innerR}" ry="${innerR}" fill="black"/></mask>`,
|
|
158
|
+
'</defs>',
|
|
159
|
+
`<g id="${p}_h" mask="url(#${p}_rm)" opacity="0">`,
|
|
160
|
+
`<rect ${fRect} fill="none" pointer-events="none"/>`,
|
|
161
|
+
`<g id="${p}_hI" stroke="white">`,
|
|
162
|
+
`<path id="${p}_pXl" stroke-width="${sw(26.4)}" stroke-linecap="round" stroke-linejoin="round" fill="none" opacity="0.385" filter="url(#${p}_bXl)"/>`,
|
|
163
|
+
`<path id="${p}_pLg" stroke-width="${sw(15.6)}" stroke-linecap="round" stroke-linejoin="round" fill="none" opacity="0.595" filter="url(#${p}_bLg)"/>`,
|
|
164
|
+
`<path id="${p}_pMd" stroke-width="${sw(7.2)}" stroke-linecap="round" stroke-linejoin="round" fill="none" opacity="0.70" filter="url(#${p}_bMd)"/>`,
|
|
165
|
+
`<path id="${p}_pSm" stroke-width="${sw(3.0)}" stroke-linecap="round" stroke-linejoin="round" fill="none" opacity="0.70" filter="url(#${p}_bSm)"/>`,
|
|
166
|
+
'</g></g>',
|
|
167
|
+
`<g id="${p}_e" mask="url(#${p}_rm)" opacity="0">`,
|
|
168
|
+
`<rect ${fRect} fill="none" pointer-events="none"/>`,
|
|
169
|
+
`<g mask="url(#${p}_fm)">`,
|
|
170
|
+
`<g id="${p}_eI" stroke="white">`,
|
|
171
|
+
`<path id="${p}_eO" stroke-width="${sw(EXTRA_STROKE_OUTER)}" stroke-linecap="round" stroke-linejoin="round" fill="none" opacity="0.85" filter="url(#${p}_ebO)"/>`,
|
|
172
|
+
`<path id="${p}_eC" stroke-width="${sw(EXTRA_STROKE_CORE)}" stroke-linecap="round" stroke-linejoin="round" fill="none" opacity="1.0" filter="url(#${p}_ebC)"/>`,
|
|
173
|
+
'</g></g></g>'
|
|
174
|
+
].join('');
|
|
175
|
+
}
|
|
176
|
+
export { PERIM_SAMPLES, buildPerimTable, buildStaticBlobPath, buildSvgMarkup, rrPerim, sampleAtArc, shapePerim, smoothstep, tangentAngleAtArc };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { MetalFxInstance, ShaderRGB } from '../renderer/core';
|
|
2
|
+
import { type Tween } from '../tween';
|
|
3
|
+
import { type GlowOptions, type PerimSample } from './geometry';
|
|
4
|
+
export type { GlowOptions } from './geometry';
|
|
5
|
+
export interface GlowHandles {
|
|
6
|
+
svg: SVGSVGElement;
|
|
7
|
+
haloGroup: SVGGElement;
|
|
8
|
+
haloInner: SVGGElement;
|
|
9
|
+
extraGroup: SVGGElement;
|
|
10
|
+
extraInner: SVGGElement;
|
|
11
|
+
fadeCircle: SVGCircleElement;
|
|
12
|
+
width: number;
|
|
13
|
+
height: number;
|
|
14
|
+
cornerRadius: number;
|
|
15
|
+
kind: 'pill' | 'circle';
|
|
16
|
+
/** Master scale used at injection time so per-frame position math
|
|
17
|
+
* (INSET / EXTRA_OUTWARD) stays consistent with the strokes already
|
|
18
|
+
* baked into the SVG markup. */
|
|
19
|
+
scale: number;
|
|
20
|
+
perim: PerimSample[];
|
|
21
|
+
currentIdx: number;
|
|
22
|
+
appearedAt: number;
|
|
23
|
+
glowOpacity: number;
|
|
24
|
+
relocTween: Tween | null;
|
|
25
|
+
relocNextIdx: number;
|
|
26
|
+
wanderS: number;
|
|
27
|
+
wanderTargetS: number;
|
|
28
|
+
wanderFrames: number;
|
|
29
|
+
tintFrom: ShaderRGB;
|
|
30
|
+
tintTarget: ShaderRGB;
|
|
31
|
+
tintTween: Tween | null;
|
|
32
|
+
tintHoldUntil: number;
|
|
33
|
+
lastHaloStroke: string;
|
|
34
|
+
lastExtraStroke: string;
|
|
35
|
+
}
|
|
36
|
+
export declare function injectGlow(container: HTMLElement, opts: GlowOptions): GlowHandles;
|
|
37
|
+
export declare function updateGlow(h: GlowHandles, inst: MetalFxInstance, nowMs: number, strengthMul: number, theme?: 'dark' | 'light'): void;
|
|
38
|
+
export declare function resizeGlow(handles: GlowHandles, container: HTMLElement, opts: GlowOptions): GlowHandles;
|