@opalui/react 0.0.0-stage → 0.0.1
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/dist/components/button/button.css +157 -0
- package/dist/components/button/button.css.js +1 -0
- package/dist/components/button/button.d.ts +26 -0
- package/dist/components/button/button.js +39 -0
- package/dist/components/button/button.js.map +1 -0
- package/dist/components/glass/glass.css +16 -0
- package/dist/components/glass/glass.css.js +1 -0
- package/dist/components/glass/glass.d.ts +5 -0
- package/dist/components/glass/glass.js +19 -0
- package/dist/components/glass/glass.js.map +1 -0
- package/dist/components/input/input.css +43 -0
- package/dist/components/input/input.css.js +1 -0
- package/dist/components/input/input.d.ts +17 -0
- package/dist/components/input/input.js +36 -0
- package/dist/components/input/input.js.map +1 -0
- package/dist/components/menu/menu.css +85 -0
- package/dist/components/menu/menu.css.js +1 -0
- package/dist/components/menu/menu.d.ts +18 -0
- package/dist/components/menu/menu.js +125 -0
- package/dist/components/menu/menu.js.map +1 -0
- package/dist/components/segmented-control/segmented-control.css +109 -0
- package/dist/components/segmented-control/segmented-control.css.js +1 -0
- package/dist/components/segmented-control/segmented-control.d.ts +21 -0
- package/dist/components/segmented-control/segmented-control.js +147 -0
- package/dist/components/segmented-control/segmented-control.js.map +1 -0
- package/dist/components/select/select.css +137 -0
- package/dist/components/select/select.css.js +1 -0
- package/dist/components/select/select.d.ts +27 -0
- package/dist/components/select/select.js +140 -0
- package/dist/components/select/select.js.map +1 -0
- package/dist/components/slider/slider.css +87 -0
- package/dist/components/slider/slider.css.js +1 -0
- package/dist/components/slider/slider.d.ts +17 -0
- package/dist/components/slider/slider.js +40 -0
- package/dist/components/slider/slider.js.map +1 -0
- package/dist/components/switch/switch.css +103 -0
- package/dist/components/switch/switch.css.js +1 -0
- package/dist/components/switch/switch.d.ts +14 -0
- package/dist/components/switch/switch.js +74 -0
- package/dist/components/switch/switch.js.map +1 -0
- package/dist/components/textarea/textarea.css +29 -0
- package/dist/components/textarea/textarea.css.js +1 -0
- package/dist/components/textarea/textarea.d.ts +8 -0
- package/dist/components/textarea/textarea.js +17 -0
- package/dist/components/textarea/textarea.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/glass/lens.d.ts +44 -0
- package/dist/internal/glass/lens.js +116 -0
- package/dist/internal/glass/lens.js.map +1 -0
- package/dist/internal/glass/map.d.ts +1 -0
- package/dist/internal/glass/map.js +48 -0
- package/dist/internal/glass/map.js.map +1 -0
- package/dist/internal/glass/material.d.ts +48 -0
- package/dist/internal/glass/material.js +22 -0
- package/dist/internal/glass/material.js.map +1 -0
- package/dist/internal/glass/styles.d.ts +2 -0
- package/dist/internal/glass/styles.js +4 -0
- package/dist/internal/glass/styles.js.map +1 -0
- package/dist/internal/glass/support.d.ts +5 -0
- package/dist/internal/glass/support.js +8 -0
- package/dist/internal/glass/support.js.map +1 -0
- package/dist/internal/glass.css +7 -0
- package/dist/internal/glass.css.js +1 -0
- package/dist/internal/styles.d.ts +2 -0
- package/dist/internal/styles.js +9 -0
- package/dist/internal/styles.js.map +1 -0
- package/dist/styles.css +11 -0
- package/dist/styles.css.js +1 -0
- package/package.json +90 -4
- package/README.md +0 -3
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/* Shared material. Public variables can be set once on a theme container. */
|
|
2
|
+
:where(.opal-glass, .opal-button, .opal-input, .opal-menu-panel, .opal-switch, .opal-slider, .opal-segmented, .opal-textarea, .opal-select) {
|
|
3
|
+
--_opal-glass-rim: var(--opal-glass-rim, inset 0 0 0 .5px rgb(255 255 255 / 7%), inset .65px 1px .65px rgb(255 255 255 / var(--opal-material-rim, .28)), inset -.4px -.65px .8px rgb(255 255 255 / calc(var(--opal-material-rim, .28) * .43)));
|
|
4
|
+
--_opal-glass-shadow: var(--opal-glass-shadow, 0 3px 8px rgb(15 34 47 / 12%));
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
:where(.opal-input,.opal-textarea,.opal-select) { --opal-material-frost:12px; --opal-material-rim:0.22; }
|
|
8
|
+
:where(.opal-menu-panel) { --opal-material-frost:24px; --opal-material-rim:0.3; }
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
.opal-switch {
|
|
12
|
+
--opal-switch-track-color: var(--opal-switch-off-background, #73767c);
|
|
13
|
+
--opal-switch-offset: 0px;
|
|
14
|
+
position: relative;
|
|
15
|
+
display: inline-block;
|
|
16
|
+
flex: 0 0 auto;
|
|
17
|
+
width: 64px;
|
|
18
|
+
height: 28px;
|
|
19
|
+
vertical-align: middle;
|
|
20
|
+
overflow: visible;
|
|
21
|
+
-webkit-tap-highlight-color: transparent;
|
|
22
|
+
}
|
|
23
|
+
.opal-switch:has(.opal-switch-input:checked) {
|
|
24
|
+
--opal-switch-track-color: var(--opal-switch-on-background, #28cd50);
|
|
25
|
+
--opal-switch-offset: 22px;
|
|
26
|
+
}
|
|
27
|
+
.opal-switch-input {
|
|
28
|
+
position: absolute;
|
|
29
|
+
z-index: 3;
|
|
30
|
+
inset: -8px 0;
|
|
31
|
+
width: 100%;
|
|
32
|
+
height: 44px;
|
|
33
|
+
margin: 0;
|
|
34
|
+
opacity: 0;
|
|
35
|
+
cursor: pointer;
|
|
36
|
+
touch-action: pan-y;
|
|
37
|
+
}
|
|
38
|
+
.opal-switch-track, .opal-switch-thumb { position:absolute; pointer-events:none; box-sizing:border-box; }
|
|
39
|
+
.opal-switch-track {
|
|
40
|
+
inset: 0;
|
|
41
|
+
border-radius: 999px;
|
|
42
|
+
background: var(--opal-switch-track-color);
|
|
43
|
+
transition: background-color 320ms ease;
|
|
44
|
+
}
|
|
45
|
+
.opal-switch-thumb {
|
|
46
|
+
left: 3px;
|
|
47
|
+
top: 3px;
|
|
48
|
+
width: 36px;
|
|
49
|
+
height: 22px;
|
|
50
|
+
border-radius: 999px;
|
|
51
|
+
translate: var(--opal-switch-offset) 0;
|
|
52
|
+
background: #fff;
|
|
53
|
+
box-shadow: 0 2px 5px rgb(0 0 0 / 13%), inset 0 0 0 0.5px rgb(255 255 255 / 70%);
|
|
54
|
+
transition:
|
|
55
|
+
translate 480ms cubic-bezier(0.22, 0.61, 0.36, 1),
|
|
56
|
+
transform 500ms cubic-bezier(0.22, 0.61, 0.36, 1),
|
|
57
|
+
background-color 300ms ease,
|
|
58
|
+
box-shadow 400ms ease;
|
|
59
|
+
}
|
|
60
|
+
.opal-switch-input:active:not(:disabled) ~ .opal-switch-thumb {
|
|
61
|
+
transform: scale(calc(1.45 + var(--opal-switch-stretch, 0) * .12), calc(1.45 - var(--opal-switch-stretch, 0) * .08));
|
|
62
|
+
background: rgb(255 255 255 / 8%);
|
|
63
|
+
-webkit-backdrop-filter: blur(var(--opal-material-frost, .25px));
|
|
64
|
+
backdrop-filter: blur(var(--opal-material-frost, .25px));
|
|
65
|
+
box-shadow: 0 7px 16px rgb(0 0 0 / 18%), var(--_opal-glass-rim);
|
|
66
|
+
}
|
|
67
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
68
|
+
.opal-switch-input:active:not(:disabled) ~ .opal-switch-thumb {
|
|
69
|
+
transition:
|
|
70
|
+
translate 480ms cubic-bezier(0.22, 0.61, 0.36, 1),
|
|
71
|
+
transform 170ms cubic-bezier(0.16, 1, 0.3, 1),
|
|
72
|
+
background-color 140ms ease-out,
|
|
73
|
+
box-shadow 170ms ease-out;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/* Follow the pointer directly; restore the soft settling transition on release. */
|
|
77
|
+
.opal-switch[data-dragging] .opal-switch-thumb { transition: background-color 140ms ease-out, box-shadow 170ms ease-out; }
|
|
78
|
+
.opal-switch-input:focus-visible ~ .opal-switch-optical-track .opal-switch-track {
|
|
79
|
+
outline: 2px solid var(--opal-focus-color, #2885ff);
|
|
80
|
+
outline-offset: 5px;
|
|
81
|
+
}
|
|
82
|
+
.opal-switch:has(.opal-switch-input:disabled) { opacity: 0.4; }
|
|
83
|
+
.opal-switch-input:disabled { cursor: not-allowed; }
|
|
84
|
+
@media (prefers-reduced-motion: reduce) {
|
|
85
|
+
.opal-switch *, .opal-switch *::before, .opal-switch *::after { transition: none; }
|
|
86
|
+
.opal-switch-input:active:not(:disabled) ~ .opal-switch-thumb { transform: none; }
|
|
87
|
+
.opal-switch:has(.opal-switch-input:active) .opal-switch-track { mask-image: none; -webkit-mask-image: none; }
|
|
88
|
+
.opal-switch-input:active:not(:disabled) ~ .opal-switch-thumb { background: #fff; }
|
|
89
|
+
}
|
|
90
|
+
@media (prefers-reduced-transparency: reduce) {
|
|
91
|
+
.opal-switch-input:active:not(:disabled) ~ .opal-switch-thumb { background: #fff; backdrop-filter: none; -webkit-backdrop-filter: none; }
|
|
92
|
+
.opal-switch:has(.opal-switch-input:active) .opal-switch-track { mask-image: none; -webkit-mask-image: none; }
|
|
93
|
+
}
|
|
94
|
+
@media (forced-colors: active) {
|
|
95
|
+
.opal-switch { opacity: 1 !important; }
|
|
96
|
+
.opal-switch-track { background: Canvas; border: 1px solid ButtonText; mask-image: none !important; -webkit-mask-image: none !important; }
|
|
97
|
+
.opal-switch:has(.opal-switch-input:checked) .opal-switch-track { background: Highlight; }
|
|
98
|
+
.opal-switch .opal-switch-thumb { background: ButtonText !important; box-shadow: none; backdrop-filter: none; -webkit-backdrop-filter: none; transform: none !important; }
|
|
99
|
+
.opal-switch-input:disabled ~ .opal-switch-thumb { background: GrayText !important; }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.opal-switch-optical-track { position:absolute; inset:-24px; pointer-events:none; }
|
|
103
|
+
.opal-switch-optical-track .opal-switch-track { left:24px; right:24px; width:calc(100% - 48px); top:24px; bottom:24px; }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default ".opal-switch {\n --opal-switch-track-color: var(--opal-switch-off-background, #73767c);\n --opal-switch-offset: 0px;\n position: relative;\n display: inline-block;\n flex: 0 0 auto;\n width: 64px;\n height: 28px;\n vertical-align: middle;\n overflow: visible;\n -webkit-tap-highlight-color: transparent;\n}\n.opal-switch:has(.opal-switch-input:checked) {\n --opal-switch-track-color: var(--opal-switch-on-background, #28cd50);\n --opal-switch-offset: 22px;\n}\n.opal-switch-input {\n position: absolute;\n z-index: 3;\n inset: -8px 0;\n width: 100%;\n height: 44px;\n margin: 0;\n opacity: 0;\n cursor: pointer;\n touch-action: pan-y;\n}\n.opal-switch-track, .opal-switch-thumb { position:absolute; pointer-events:none; box-sizing:border-box; }\n.opal-switch-track {\n inset: 0;\n border-radius: 999px;\n background: var(--opal-switch-track-color);\n transition: background-color 320ms ease;\n}\n.opal-switch-thumb {\n left: 3px;\n top: 3px;\n width: 36px;\n height: 22px;\n border-radius: 999px;\n translate: var(--opal-switch-offset) 0;\n background: #fff;\n box-shadow: 0 2px 5px rgb(0 0 0 / 13%), inset 0 0 0 0.5px rgb(255 255 255 / 70%);\n transition:\n translate 480ms cubic-bezier(0.22, 0.61, 0.36, 1),\n transform 500ms cubic-bezier(0.22, 0.61, 0.36, 1),\n background-color 300ms ease,\n box-shadow 400ms ease;\n}\n.opal-switch-input:active:not(:disabled) ~ .opal-switch-thumb {\n transform: scale(calc(1.45 + var(--opal-switch-stretch, 0) * .12), calc(1.45 - var(--opal-switch-stretch, 0) * .08));\n background: rgb(255 255 255 / 8%);\n -webkit-backdrop-filter: blur(var(--opal-material-frost, .25px));\n backdrop-filter: blur(var(--opal-material-frost, .25px));\n box-shadow: 0 7px 16px rgb(0 0 0 / 18%), var(--_opal-glass-rim);\n}\n@media (prefers-reduced-motion: no-preference) {\n .opal-switch-input:active:not(:disabled) ~ .opal-switch-thumb {\n transition:\n translate 480ms cubic-bezier(0.22, 0.61, 0.36, 1),\n transform 170ms cubic-bezier(0.16, 1, 0.3, 1),\n background-color 140ms ease-out,\n box-shadow 170ms ease-out;\n }\n}\n/* Follow the pointer directly; restore the soft settling transition on release. */\n.opal-switch[data-dragging] .opal-switch-thumb { transition: background-color 140ms ease-out, box-shadow 170ms ease-out; }\n.opal-switch-input:focus-visible ~ .opal-switch-optical-track .opal-switch-track {\n outline: 2px solid var(--opal-focus-color, #2885ff);\n outline-offset: 5px;\n}\n.opal-switch:has(.opal-switch-input:disabled) { opacity: 0.4; }\n.opal-switch-input:disabled { cursor: not-allowed; }\n@media (prefers-reduced-motion: reduce) {\n .opal-switch *, .opal-switch *::before, .opal-switch *::after { transition: none; }\n .opal-switch-input:active:not(:disabled) ~ .opal-switch-thumb { transform: none; }\n .opal-switch:has(.opal-switch-input:active) .opal-switch-track { mask-image: none; -webkit-mask-image: none; }\n .opal-switch-input:active:not(:disabled) ~ .opal-switch-thumb { background: #fff; }\n}\n@media (prefers-reduced-transparency: reduce) {\n .opal-switch-input:active:not(:disabled) ~ .opal-switch-thumb { background: #fff; backdrop-filter: none; -webkit-backdrop-filter: none; }\n .opal-switch:has(.opal-switch-input:active) .opal-switch-track { mask-image: none; -webkit-mask-image: none; }\n}\n@media (forced-colors: active) {\n .opal-switch { opacity: 1 !important; }\n .opal-switch-track { background: Canvas; border: 1px solid ButtonText; mask-image: none !important; -webkit-mask-image: none !important; }\n .opal-switch:has(.opal-switch-input:checked) .opal-switch-track { background: Highlight; }\n .opal-switch .opal-switch-thumb { background: ButtonText !important; box-shadow: none; backdrop-filter: none; -webkit-backdrop-filter: none; transform: none !important; }\n .opal-switch-input:disabled ~ .opal-switch-thumb { background: GrayText !important; }\n}\n\n.opal-switch-optical-track { position:absolute; inset:-24px; pointer-events:none; }\n.opal-switch-optical-track .opal-switch-track { left:24px; right:24px; width:calc(100% - 48px); top:24px; bottom:24px; }\n";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ComponentPropsWithoutRef } from 'react';
|
|
2
|
+
export type SwitchProps = Omit<ComponentPropsWithoutRef<'input'>, 'type' | 'role' | 'size' | 'children'> & {
|
|
3
|
+
/** Active track color as an opaque #RGB or #RRGGBB hex. Defaults to green. */
|
|
4
|
+
tint?: `#${string}`;
|
|
5
|
+
/** Called after the native change handler, unless it prevents default. */
|
|
6
|
+
onCheckedChange?: (checked: boolean) => void;
|
|
7
|
+
};
|
|
8
|
+
/** A native checkbox with switch semantics, automatic styles, and a glass thumb. */
|
|
9
|
+
export declare const Switch: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").DetailedHTMLProps<import("react").InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, "ref">, "size" | "type" | "role" | "children"> & {
|
|
10
|
+
/** Active track color as an opaque #RGB or #RRGGBB hex. Defaults to green. */
|
|
11
|
+
tint?: `#${string}`;
|
|
12
|
+
/** Called after the native change handler, unless it prevents default. */
|
|
13
|
+
onCheckedChange?: (checked: boolean) => void;
|
|
14
|
+
} & import("react").RefAttributes<HTMLInputElement>>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef, useRef, useState } from 'react';
|
|
4
|
+
import { useGlassHeld, useGlassLens } from '../../internal/glass/lens.js';
|
|
5
|
+
import { materials } from '../../internal/glass/material.js';
|
|
6
|
+
import { stylesheetHref } from '../../internal/styles.js';
|
|
7
|
+
import componentCss from './switch.css.js';
|
|
8
|
+
import glassCss from '../../internal/glass/styles.js';
|
|
9
|
+
const css = `${glassCss}\n${componentCss}`;
|
|
10
|
+
const styleHref = stylesheetHref('switch', css);
|
|
11
|
+
/** A native checkbox with switch semantics, automatic styles, and a glass thumb. */
|
|
12
|
+
export const Switch = forwardRef(function Switch({ className, style, tint, onChange, onCheckedChange, onPointerDown, onPointerMove, onPointerUp, onPointerCancel, onLostPointerCapture, onClick, ...props }, ref) {
|
|
13
|
+
const root = useRef(null);
|
|
14
|
+
const thumb = useRef(null);
|
|
15
|
+
const track = useRef(null);
|
|
16
|
+
const held = useGlassHeld(root);
|
|
17
|
+
const glass = useGlassLens(thumb, materials.switch, { contract: true, source: track, active: held && !props.disabled });
|
|
18
|
+
const gesture = useRef(null);
|
|
19
|
+
const releaseValue = useRef(null);
|
|
20
|
+
const [drag, setDrag] = useState(null);
|
|
21
|
+
function resetDrag() { gesture.current = null; setDrag(null); }
|
|
22
|
+
const tintStyle = tint && /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i.test(tint)
|
|
23
|
+
? { '--opal-switch-on-background': tint }
|
|
24
|
+
: {};
|
|
25
|
+
return _jsxs(_Fragment, { children: [_jsx("style", { href: styleHref, precedence: "opalui", children: css }), glass.filter, _jsxs("span", { ref: root, className: ['opal-switch', className].filter(Boolean).join(' '), "data-dragging": drag ? '' : undefined, style: { ...tintStyle, ...glass.surfaceStyle, ...style, ...(drag ? { '--opal-switch-offset': `${drag.offset}px`, '--opal-switch-stretch': drag.stretch } : {}) }, children: [_jsx("input", { ...props, ref: ref, type: "checkbox", role: "switch", className: "opal-switch-input", onPointerDown: event => {
|
|
26
|
+
onPointerDown?.(event);
|
|
27
|
+
releaseValue.current = null;
|
|
28
|
+
if (event.defaultPrevented || props.disabled || event.button !== 0 || !event.isPrimary)
|
|
29
|
+
return;
|
|
30
|
+
gesture.current = { id: event.pointerId, x: event.clientX, offset: event.currentTarget.checked ? 22 : 0, moved: false };
|
|
31
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
32
|
+
}, onPointerMove: event => {
|
|
33
|
+
onPointerMove?.(event);
|
|
34
|
+
const current = gesture.current;
|
|
35
|
+
if (!current || current.id !== event.pointerId || event.defaultPrevented)
|
|
36
|
+
return;
|
|
37
|
+
const delta = event.clientX - current.x;
|
|
38
|
+
if (!current.moved && Math.abs(delta) < 3)
|
|
39
|
+
return;
|
|
40
|
+
current.moved = true;
|
|
41
|
+
const raw = current.offset + delta;
|
|
42
|
+
const clamped = Math.max(0, Math.min(22, raw));
|
|
43
|
+
const overshoot = 5 * Math.tanh((raw - clamped) / 30);
|
|
44
|
+
setDrag({ offset: clamped + overshoot, stretch: Math.abs(overshoot) / 5 });
|
|
45
|
+
}, onPointerUp: event => {
|
|
46
|
+
onPointerUp?.(event);
|
|
47
|
+
const current = gesture.current;
|
|
48
|
+
if (!current || current.id !== event.pointerId)
|
|
49
|
+
return;
|
|
50
|
+
resetDrag();
|
|
51
|
+
if (current.moved && !event.defaultPrevented) {
|
|
52
|
+
releaseValue.current = current.offset + event.clientX - current.x >= 11;
|
|
53
|
+
}
|
|
54
|
+
// Keep capture through release: the browser delivers one native click
|
|
55
|
+
// to this input, even when the pointer ends outside the switch.
|
|
56
|
+
}, onPointerCancel: event => { onPointerCancel?.(event); releaseValue.current = null; resetDrag(); }, onLostPointerCapture: event => { onLostPointerCapture?.(event); resetDrag(); }, onClick: event => {
|
|
57
|
+
const next = releaseValue.current;
|
|
58
|
+
releaseValue.current = null;
|
|
59
|
+
// Native checkbox activation toggles checked before click handlers run.
|
|
60
|
+
// Cancel only when that toggle disagrees with the drag destination.
|
|
61
|
+
if (next !== null && event.currentTarget.checked !== next) {
|
|
62
|
+
event.preventDefault();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
onClick?.(event);
|
|
66
|
+
}, onChange: (event) => {
|
|
67
|
+
if (event.defaultPrevented)
|
|
68
|
+
return;
|
|
69
|
+
onChange?.(event);
|
|
70
|
+
if (!event.defaultPrevented)
|
|
71
|
+
onCheckedChange?.(event.currentTarget.checked);
|
|
72
|
+
} }), _jsx("span", { ref: track, className: "opal-switch-optical-track", style: glass.sourceStyle, "aria-hidden": "true", children: _jsx("span", { className: "opal-switch-track" }) }), _jsx("span", { ref: thumb, className: "opal-switch-thumb", "aria-hidden": "true" })] })] });
|
|
73
|
+
});
|
|
74
|
+
//# sourceMappingURL=switch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"switch.js","sourceRoot":"","sources":["../../../src/components/switch/switch.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client'\n\nimport { forwardRef, useRef, useState, type CSSProperties, type ComponentPropsWithoutRef } from 'react'\nimport { useGlassHeld, useGlassLens } from '../../internal/glass/lens.js'\nimport { materials } from '../../internal/glass/material.js'\nimport { stylesheetHref } from '../../internal/styles.js'\nimport componentCss from './switch.css?inline'\nimport glassCss from '../../internal/glass/styles.js'\nconst css = `${glassCss}\\n${componentCss}`\n\nconst styleHref = stylesheetHref('switch', css)\n\nexport type SwitchProps = Omit<ComponentPropsWithoutRef<'input'>, 'type' | 'role' | 'size' | 'children'> & {\n /** Active track color as an opaque #RGB or #RRGGBB hex. Defaults to green. */\n tint?: `#${string}`\n /** Called after the native change handler, unless it prevents default. */\n onCheckedChange?: (checked: boolean) => void\n}\n\n/** A native checkbox with switch semantics, automatic styles, and a glass thumb. */\nexport const Switch = forwardRef<HTMLInputElement, SwitchProps>(function Switch(\n { className, style, tint, onChange, onCheckedChange, onPointerDown, onPointerMove, onPointerUp, onPointerCancel, onLostPointerCapture, onClick, ...props }, ref,\n) {\n const root = useRef<HTMLSpanElement>(null)\n const thumb = useRef<HTMLSpanElement>(null)\n const track = useRef<HTMLSpanElement>(null)\n const held = useGlassHeld(root)\n const glass = useGlassLens(thumb, materials.switch, { contract: true, source: track, active: held && !props.disabled })\n const gesture = useRef<{ id: number; x: number; offset: number; moved: boolean } | null>(null)\n const releaseValue = useRef<boolean | null>(null)\n const [drag, setDrag] = useState<{ offset: number; stretch: number } | null>(null)\n function resetDrag() { gesture.current = null; setDrag(null) }\n const tintStyle = tint && /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i.test(tint)\n ? { '--opal-switch-on-background': tint }\n : {}\n return <>\n <style href={styleHref} precedence=\"opalui\">{css}</style>\n {glass.filter}\n <span ref={root} className={['opal-switch', className].filter(Boolean).join(' ')} data-dragging={drag ? '' : undefined}\n style={{ ...tintStyle, ...glass.surfaceStyle, ...style, ...(drag ? { '--opal-switch-offset': `${drag.offset}px`, '--opal-switch-stretch': drag.stretch } : {}) } as CSSProperties}>\n <input {...props} ref={ref} type=\"checkbox\" role=\"switch\" className=\"opal-switch-input\"\n onPointerDown={event => {\n onPointerDown?.(event)\n releaseValue.current = null\n if (event.defaultPrevented || props.disabled || event.button !== 0 || !event.isPrimary) return\n gesture.current = { id: event.pointerId, x: event.clientX, offset: event.currentTarget.checked ? 22 : 0, moved: false }\n event.currentTarget.setPointerCapture(event.pointerId)\n }}\n onPointerMove={event => {\n onPointerMove?.(event)\n const current = gesture.current\n if (!current || current.id !== event.pointerId || event.defaultPrevented) return\n const delta = event.clientX - current.x\n if (!current.moved && Math.abs(delta) < 3) return\n current.moved = true\n const raw = current.offset + delta\n const clamped = Math.max(0, Math.min(22, raw))\n const overshoot = 5 * Math.tanh((raw - clamped) / 30)\n setDrag({ offset: clamped + overshoot, stretch: Math.abs(overshoot) / 5 })\n }}\n onPointerUp={event => {\n onPointerUp?.(event)\n const current = gesture.current\n if (!current || current.id !== event.pointerId) return\n resetDrag()\n if (current.moved && !event.defaultPrevented) {\n releaseValue.current = current.offset + event.clientX - current.x >= 11\n }\n // Keep capture through release: the browser delivers one native click\n // to this input, even when the pointer ends outside the switch.\n }}\n onPointerCancel={event => { onPointerCancel?.(event); releaseValue.current = null; resetDrag() }}\n onLostPointerCapture={event => { onLostPointerCapture?.(event); resetDrag() }}\n onClick={event => {\n const next = releaseValue.current\n releaseValue.current = null\n // Native checkbox activation toggles checked before click handlers run.\n // Cancel only when that toggle disagrees with the drag destination.\n if (next !== null && event.currentTarget.checked !== next) { event.preventDefault(); return }\n onClick?.(event)\n }}\n onChange={(event) => {\n if (event.defaultPrevented) return\n onChange?.(event)\n if (!event.defaultPrevented) onCheckedChange?.(event.currentTarget.checked)\n }} />\n <span ref={track} className=\"opal-switch-optical-track\" style={glass.sourceStyle} aria-hidden=\"true\"><span className=\"opal-switch-track\" /></span>\n <span ref={thumb} className=\"opal-switch-thumb\" aria-hidden=\"true\" />\n </span>\n </>\n})\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/* Shared material. Public variables can be set once on a theme container. */
|
|
2
|
+
:where(.opal-glass, .opal-button, .opal-input, .opal-menu-panel, .opal-switch, .opal-slider, .opal-segmented, .opal-textarea, .opal-select) {
|
|
3
|
+
--_opal-glass-rim: var(--opal-glass-rim, inset 0 0 0 .5px rgb(255 255 255 / 7%), inset .65px 1px .65px rgb(255 255 255 / var(--opal-material-rim, .28)), inset -.4px -.65px .8px rgb(255 255 255 / calc(var(--opal-material-rim, .28) * .43)));
|
|
4
|
+
--_opal-glass-shadow: var(--opal-glass-shadow, 0 3px 8px rgb(15 34 47 / 12%));
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
:where(.opal-input,.opal-textarea,.opal-select) { --opal-material-frost:12px; --opal-material-rim:0.22; }
|
|
8
|
+
:where(.opal-menu-panel) { --opal-material-frost:24px; --opal-material-rim:0.3; }
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
.opal-textarea {
|
|
12
|
+
position: relative; display: inline-flex; box-sizing: border-box;
|
|
13
|
+
width: 360px; max-width: 100%; padding: 6px; border-radius: 20px;
|
|
14
|
+
background: var(--opal-input-background, rgb(255 255 255 / 65%)); color: var(--opal-input-color, #171e22);
|
|
15
|
+
box-shadow: var(--opal-input-shadow, var(--_opal-glass-shadow));
|
|
16
|
+
backdrop-filter: blur(var(--opal-material-frost,12px)); -webkit-backdrop-filter: blur(var(--opal-material-frost,12px));
|
|
17
|
+
transition: background-color 180ms ease;
|
|
18
|
+
}
|
|
19
|
+
.opal-textarea::after { content: ''; position: absolute; inset: 0; border-radius: inherit; pointer-events: none; box-shadow: var(--opal-input-rim, var(--_opal-glass-rim)); opacity: .7; }
|
|
20
|
+
.opal-textarea:focus-within { background: var(--opal-input-focus-background, rgb(255 255 255 / 90%)); }
|
|
21
|
+
/* Inset the native resize grip from the outer rounded glass corner. */
|
|
22
|
+
.opal-textarea-field { display: block; box-sizing: border-box; width: 100%; min-width: 0; min-height: 88px; resize: vertical; padding: 8px 10px; border: 0; border-radius: 14px; outline: none; background: transparent; color: inherit; font: inherit; line-height: 1.5; }
|
|
23
|
+
.opal-textarea-field::placeholder { color: inherit; opacity: .5; }
|
|
24
|
+
.opal-textarea:has([aria-invalid="true"]) { outline: 2px solid #e5484d; outline-offset: 2px; }
|
|
25
|
+
.opal-textarea[data-disabled] { opacity: .4; }
|
|
26
|
+
.opal-textarea-field:disabled { cursor: not-allowed; resize: none; }
|
|
27
|
+
@media (prefers-reduced-motion: reduce) { .opal-textarea { transition: none; } }
|
|
28
|
+
@media (prefers-reduced-transparency: reduce) { .opal-textarea { background: var(--opal-input-solid, #fff); backdrop-filter: none; -webkit-backdrop-filter: none; } }
|
|
29
|
+
@media (forced-colors: active) { .opal-textarea { border: 1px solid ButtonText; } .opal-textarea:focus-within { outline: 2px solid Highlight; } }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default ".opal-textarea {\n position: relative; display: inline-flex; box-sizing: border-box;\n width: 360px; max-width: 100%; padding: 6px; border-radius: 20px;\n background: var(--opal-input-background, rgb(255 255 255 / 65%)); color: var(--opal-input-color, #171e22);\n box-shadow: var(--opal-input-shadow, var(--_opal-glass-shadow));\n backdrop-filter: blur(var(--opal-material-frost,12px)); -webkit-backdrop-filter: blur(var(--opal-material-frost,12px));\n transition: background-color 180ms ease;\n}\n.opal-textarea::after { content: ''; position: absolute; inset: 0; border-radius: inherit; pointer-events: none; box-shadow: var(--opal-input-rim, var(--_opal-glass-rim)); opacity: .7; }\n.opal-textarea:focus-within { background: var(--opal-input-focus-background, rgb(255 255 255 / 90%)); }\n/* Inset the native resize grip from the outer rounded glass corner. */\n.opal-textarea-field { display: block; box-sizing: border-box; width: 100%; min-width: 0; min-height: 88px; resize: vertical; padding: 8px 10px; border: 0; border-radius: 14px; outline: none; background: transparent; color: inherit; font: inherit; line-height: 1.5; }\n.opal-textarea-field::placeholder { color: inherit; opacity: .5; }\n.opal-textarea:has([aria-invalid=\"true\"]) { outline: 2px solid #e5484d; outline-offset: 2px; }\n.opal-textarea[data-disabled] { opacity: .4; }\n.opal-textarea-field:disabled { cursor: not-allowed; resize: none; }\n@media (prefers-reduced-motion: reduce) { .opal-textarea { transition: none; } }\n@media (prefers-reduced-transparency: reduce) { .opal-textarea { background: var(--opal-input-solid, #fff); backdrop-filter: none; -webkit-backdrop-filter: none; } }\n@media (forced-colors: active) { .opal-textarea { border: 1px solid ButtonText; } .opal-textarea:focus-within { outline: 2px solid Highlight; } }\n";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ComponentPropsWithoutRef } from 'react';
|
|
2
|
+
export type TextareaProps = ComponentPropsWithoutRef<'textarea'> & {
|
|
3
|
+
onValueChange?: (value: string) => void;
|
|
4
|
+
};
|
|
5
|
+
/** Native multiline input, using the same material as Input. */
|
|
6
|
+
export declare const Textarea: import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").TextareaHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>, "ref"> & {
|
|
7
|
+
onValueChange?: (value: string) => void;
|
|
8
|
+
} & import("react").RefAttributes<HTMLTextAreaElement>>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef } from 'react';
|
|
4
|
+
import { stylesheetHref } from '../../internal/styles.js';
|
|
5
|
+
import componentCss from './textarea.css.js';
|
|
6
|
+
import glassCss from '../../internal/glass/styles.js';
|
|
7
|
+
const css = `${glassCss}\n${componentCss}`;
|
|
8
|
+
const styleHref = stylesheetHref('textarea', css);
|
|
9
|
+
/** Native multiline input, using the same material as Input. */
|
|
10
|
+
export const Textarea = forwardRef(function Textarea({ className, style, onChange, onValueChange, rows = 5, disabled, ...props }, ref) {
|
|
11
|
+
return _jsxs(_Fragment, { children: [_jsx("style", { href: styleHref, precedence: "opalui", children: css }), _jsx("span", { className: ['opal-textarea', className].filter(Boolean).join(' '), style: style, "data-disabled": disabled || undefined, children: _jsx("textarea", { ...props, ref: ref, rows: rows, disabled: disabled, className: "opal-textarea-field", onChange: event => {
|
|
12
|
+
onChange?.(event);
|
|
13
|
+
if (!event.defaultPrevented)
|
|
14
|
+
onValueChange?.(event.currentTarget.value);
|
|
15
|
+
} }) })] });
|
|
16
|
+
});
|
|
17
|
+
//# sourceMappingURL=textarea.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"textarea.js","sourceRoot":"","sources":["../../../src/components/textarea/textarea.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,EAAE,UAAU,EAAiC,MAAM,OAAO,CAAA;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AACzD,OAAO,YAAY,MAAM,uBAAuB,CAAA;AAChD,OAAO,QAAQ,MAAM,gCAAgC,CAAA;AACrD,MAAM,GAAG,GAAG,GAAG,QAAQ,KAAK,YAAY,EAAE,CAAA;AAC1C,MAAM,SAAS,GAAG,cAAc,CAAC,UAAU,EAAE,GAAG,CAAC,CAAA;AAGjD,gEAAgE;AAChE,MAAM,CAAC,MAAM,QAAQ,GAAG,UAAU,CAAqC,SAAS,QAAQ,CACtF,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,IAAI,GAAG,CAAC,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG;IAEhF,OAAO,8BACL,gBAAO,IAAI,EAAE,SAAS,EAAE,UAAU,EAAC,QAAQ,YAAE,GAAG,GAAS,EACzD,eAAM,SAAS,EAAE,CAAC,eAAe,EAAE,SAAS,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,KAAK,mBAAiB,QAAQ,IAAI,SAAS,YACzH,sBAAc,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAC,qBAAqB,EAAC,QAAQ,EAAE,KAAK,CAAC,EAAE;wBAC/G,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAA;wBACjB,IAAI,CAAC,KAAK,CAAC,gBAAgB;4BAAE,aAAa,EAAE,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;oBACzE,CAAC,GAAI,GACA,IACN,CAAA;AACL,CAAC,CAAC,CAAA","sourcesContent":["'use client'\n\nimport { forwardRef, type ComponentPropsWithoutRef } from 'react'\nimport { stylesheetHref } from '../../internal/styles.js'\nimport componentCss from './textarea.css?inline'\nimport glassCss from '../../internal/glass/styles.js'\nconst css = `${glassCss}\\n${componentCss}`\nconst styleHref = stylesheetHref('textarea', css)\nexport type TextareaProps = ComponentPropsWithoutRef<'textarea'> & { onValueChange?: (value: string) => void }\n\n/** Native multiline input, using the same material as Input. */\nexport const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(function Textarea(\n { className, style, onChange, onValueChange, rows = 5, disabled, ...props }, ref,\n) {\n return <>\n <style href={styleHref} precedence=\"opalui\">{css}</style>\n <span className={['opal-textarea', className].filter(Boolean).join(' ')} style={style} data-disabled={disabled || undefined}>\n <textarea {...props} ref={ref} rows={rows} disabled={disabled} className=\"opal-textarea-field\" onChange={event => {\n onChange?.(event)\n if (!event.defaultPrevented) onValueChange?.(event.currentTarget.value)\n }} />\n </span>\n </>\n})\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { Button, type ButtonProps } from './components/button/button.js';
|
|
2
|
+
export { Switch, type SwitchProps } from './components/switch/switch.js';
|
|
3
|
+
export { SegmentedControl, type SegmentedControlProps, type SegmentedControlItem } from './components/segmented-control/segmented-control.js';
|
|
4
|
+
export { Menu, type MenuProps, type MenuItem } from './components/menu/menu.js';
|
|
5
|
+
export { Input, type InputProps } from './components/input/input.js';
|
|
6
|
+
export { Slider, type SliderProps } from './components/slider/slider.js';
|
|
7
|
+
export { Textarea, type TextareaProps } from './components/textarea/textarea.js';
|
|
8
|
+
export { Select, type SelectProps, type SelectItem } from './components/select/select.js';
|
|
9
|
+
export { Glass, type GlassProps } from './components/glass/glass.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { Button } from './components/button/button.js';
|
|
2
|
+
export { Switch } from './components/switch/switch.js';
|
|
3
|
+
export { SegmentedControl } from './components/segmented-control/segmented-control.js';
|
|
4
|
+
export { Menu } from './components/menu/menu.js';
|
|
5
|
+
export { Input } from './components/input/input.js';
|
|
6
|
+
export { Slider } from './components/slider/slider.js';
|
|
7
|
+
export { Textarea } from './components/textarea/textarea.js';
|
|
8
|
+
export { Select } from './components/select/select.js';
|
|
9
|
+
export { Glass } from './components/glass/glass.js';
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAoB,MAAM,+BAA+B,CAAA;AACxE,OAAO,EAAE,MAAM,EAAoB,MAAM,+BAA+B,CAAA;AACxE,OAAO,EAAE,gBAAgB,EAAyD,MAAM,qDAAqD,CAAA;AAC7I,OAAO,EAAE,IAAI,EAAiC,MAAM,2BAA2B,CAAA;AAC/E,OAAO,EAAE,KAAK,EAAmB,MAAM,6BAA6B,CAAA;AACpE,OAAO,EAAE,MAAM,EAAoB,MAAM,+BAA+B,CAAA;AACxE,OAAO,EAAE,QAAQ,EAAsB,MAAM,mCAAmC,CAAA;AAChF,OAAO,EAAE,MAAM,EAAqC,MAAM,+BAA+B,CAAA;AACzF,OAAO,EAAE,KAAK,EAAmB,MAAM,6BAA6B,CAAA","sourcesContent":["export { Button, type ButtonProps } from './components/button/button.js'\nexport { Switch, type SwitchProps } from './components/switch/switch.js'\nexport { SegmentedControl, type SegmentedControlProps, type SegmentedControlItem } from './components/segmented-control/segmented-control.js'\nexport { Menu, type MenuProps, type MenuItem } from './components/menu/menu.js'\nexport { Input, type InputProps } from './components/input/input.js'\nexport { Slider, type SliderProps } from './components/slider/slider.js'\nexport { Textarea, type TextareaProps } from './components/textarea/textarea.js'\nexport { Select, type SelectProps, type SelectItem } from './components/select/select.js'\nexport { Glass, type GlassProps } from './components/glass/glass.js'\n"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type CSSProperties, type RefObject } from 'react';
|
|
2
|
+
import { type GlassSettings } from './material.js';
|
|
3
|
+
type Box = {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
radius: number;
|
|
9
|
+
map: string;
|
|
10
|
+
mask: string;
|
|
11
|
+
};
|
|
12
|
+
type Frame = Box & {
|
|
13
|
+
amount: number;
|
|
14
|
+
sourceWidth: number;
|
|
15
|
+
sourceHeight: number;
|
|
16
|
+
light: boolean;
|
|
17
|
+
};
|
|
18
|
+
/** A single filter graph serves live backgrounds and content owned by a control.
|
|
19
|
+
* Content is composited back outside the lens, so neighbors stay untouched. */
|
|
20
|
+
export declare function LensFilter({ id, frame: f, material: m, ink, contract }: {
|
|
21
|
+
id: string;
|
|
22
|
+
frame: Frame;
|
|
23
|
+
material: GlassSettings;
|
|
24
|
+
ink?: boolean;
|
|
25
|
+
contract?: boolean;
|
|
26
|
+
}): import("react").JSX.Element;
|
|
27
|
+
/** Lifecycle, measurements, preferences and progressive enhancement live here.
|
|
28
|
+
* Owned content uses ordinary SVG filters; arbitrary backdrops use Chromium's
|
|
29
|
+
* reference-filter support. No DOM or canvas work happens during SSR. */
|
|
30
|
+
export declare function useGlassLens(surface: RefObject<HTMLElement | null>, material: GlassSettings, options?: {
|
|
31
|
+
source?: RefObject<HTMLElement | null>;
|
|
32
|
+
active?: boolean;
|
|
33
|
+
ink?: boolean;
|
|
34
|
+
contract?: boolean;
|
|
35
|
+
enabled?: boolean;
|
|
36
|
+
}): {
|
|
37
|
+
filter: import("react").JSX.Element | null;
|
|
38
|
+
sourceStyle: CSSProperties | undefined;
|
|
39
|
+
surfaceStyle: CSSProperties;
|
|
40
|
+
lensing: boolean;
|
|
41
|
+
};
|
|
42
|
+
/** Observe native presses without replacing the control's input handlers. */
|
|
43
|
+
export declare function useGlassHeld(root: RefObject<HTMLElement | null>): boolean;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useId, useRef, useState } from 'react';
|
|
4
|
+
import { glassLensMap } from './map.js';
|
|
5
|
+
import { resolveMaterial } from './material.js';
|
|
6
|
+
import { supportsOwnedGlassLens } from './support.js';
|
|
7
|
+
/** A single filter graph serves live backgrounds and content owned by a control.
|
|
8
|
+
* Content is composited back outside the lens, so neighbors stay untouched. */
|
|
9
|
+
export function LensFilter({ id, frame: f, material: m, ink = false, contract = false }) {
|
|
10
|
+
// Reversing the sampling direction compresses owned content inside the lens.
|
|
11
|
+
const strength = Math.min(f.width, f.height) * m.bend * f.amount * (contract ? -1 : 1);
|
|
12
|
+
const invert = ink && f.light;
|
|
13
|
+
return _jsx("svg", { width: "0", height: "0", style: { position: 'absolute', pointerEvents: 'none' }, "aria-hidden": "true", focusable: "false", children: _jsx("defs", { children: _jsxs("filter", { id: id, filterUnits: "userSpaceOnUse", primitiveUnits: "userSpaceOnUse", x: -f.sourceWidth * .5, y: -f.sourceHeight * .5, width: f.sourceWidth * 2, height: f.sourceHeight * 2, colorInterpolationFilters: "sRGB", children: [_jsx("feColorMatrix", { in: "SourceGraphic", type: "matrix", values: invert ? '-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0' : '1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0', result: "ink" }), _jsx("feGaussianBlur", { in: "ink", stdDeviation: m.frost * f.amount, result: "soft" }), _jsx("feImage", { href: f.map, x: f.x, y: f.y, width: f.width, height: f.height, preserveAspectRatio: "none", result: "map" }), _jsx("feImage", { href: f.mask, x: f.x, y: f.y, width: f.width, height: f.height, preserveAspectRatio: "none", result: "shape" }), _jsx("feDisplacementMap", { in: "soft", in2: "map", scale: strength * (1 - m.dispersion), xChannelSelector: "R", yChannelSelector: "G", result: "rays-r" }), _jsx("feDisplacementMap", { in: "soft", in2: "map", scale: strength, xChannelSelector: "R", yChannelSelector: "G", result: "rays-g" }), _jsx("feDisplacementMap", { in: "soft", in2: "map", scale: strength * (1 + m.dispersion), xChannelSelector: "R", yChannelSelector: "G", result: "rays-b" }), _jsx("feColorMatrix", { in: "rays-r", values: "1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0", result: "r" }), _jsx("feColorMatrix", { in: "rays-g", values: "0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0", result: "g" }), _jsx("feColorMatrix", { in: "rays-b", values: "0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0", result: "b" }), _jsx("feBlend", { in: "r", in2: "g", mode: "screen", result: "rg" }), _jsx("feBlend", { in: "rg", in2: "b", mode: "screen", result: "rgb" }), _jsx("feColorMatrix", { in: "rgb", values: invert ? '-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0' : '1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0', result: "colored" }), _jsx("feComposite", { in: "colored", in2: "shape", operator: "in", result: "inside" }), _jsx("feComposite", { in: "SourceGraphic", in2: "shape", operator: "out", result: "outside" }), _jsx("feComposite", { in: "inside", in2: "outside", operator: "arithmetic", k2: "1", k3: "1" })] }) }) });
|
|
14
|
+
}
|
|
15
|
+
/** Lifecycle, measurements, preferences and progressive enhancement live here.
|
|
16
|
+
* Owned content uses ordinary SVG filters; arbitrary backdrops use Chromium's
|
|
17
|
+
* reference-filter support. No DOM or canvas work happens during SSR. */
|
|
18
|
+
export function useGlassLens(surface, material, options = {}) {
|
|
19
|
+
const { source, active = true, ink = false, contract = false, enabled = true } = options;
|
|
20
|
+
const id = `opal-glass-${useId().replace(/[^a-zA-Z0-9_-]/g, '')}`;
|
|
21
|
+
const [frame, setFrame] = useState();
|
|
22
|
+
const amount = useRef(0);
|
|
23
|
+
const m = resolveMaterial(material);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
const glass = surface.current;
|
|
26
|
+
if (!glass || !enabled) {
|
|
27
|
+
setFrame(undefined);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (source && !supportsOwnedGlassLens(navigator.userAgent)) {
|
|
31
|
+
amount.current = 0;
|
|
32
|
+
setFrame(undefined);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (!source && (!/(?:Chrome|Chromium|Edg)\//.test(navigator.userAgent) || !CSS.supports('backdrop-filter', 'url("#lens")')))
|
|
36
|
+
return;
|
|
37
|
+
const transparency = matchMedia('(prefers-reduced-transparency: reduce)');
|
|
38
|
+
const motion = matchMedia('(prefers-reduced-motion: reduce)');
|
|
39
|
+
const contrast = matchMedia('(forced-colors: active)');
|
|
40
|
+
let raf = 0, previous = 0, stopped = false;
|
|
41
|
+
const measure = (now) => {
|
|
42
|
+
if (stopped)
|
|
43
|
+
return;
|
|
44
|
+
const dt = previous ? Math.min(64, now - previous) : 16;
|
|
45
|
+
previous = now;
|
|
46
|
+
const target = active && !transparency.matches && !contrast.matches ? 1 : 0;
|
|
47
|
+
amount.current = motion.matches || !source ? target : amount.current + (target - amount.current) * (1 - Math.exp(-dt / 65));
|
|
48
|
+
if (Math.abs(amount.current - target) < .002)
|
|
49
|
+
amount.current = target;
|
|
50
|
+
if (!amount.current)
|
|
51
|
+
setFrame(undefined);
|
|
52
|
+
else {
|
|
53
|
+
const rect = glass.getBoundingClientRect();
|
|
54
|
+
const content = source?.current;
|
|
55
|
+
const parent = content?.getBoundingClientRect();
|
|
56
|
+
const width = content ? rect.width : glass.offsetWidth;
|
|
57
|
+
const height = content ? rect.height : glass.offsetHeight;
|
|
58
|
+
const scale = glass.offsetWidth ? width / glass.offsetWidth : 1;
|
|
59
|
+
const radius = Math.min(width / 2, height / 2, (parseFloat(getComputedStyle(glass).borderTopLeftRadius) || 0) * scale);
|
|
60
|
+
try {
|
|
61
|
+
const map = width && height ? glassLensMap(Math.round(width), Math.round(height), Math.round(radius)) : undefined;
|
|
62
|
+
if (map) {
|
|
63
|
+
const mask = `data:image/svg+xml,${encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}"><rect width="100%" height="100%" rx="${radius}" fill="white"/></svg>`)}`;
|
|
64
|
+
const next = { x: parent ? rect.left - parent.left : 0, y: parent ? rect.top - parent.top : 0, width, height, radius, map, mask, amount: amount.current, sourceWidth: content?.offsetWidth ?? width, sourceHeight: content?.offsetHeight ?? height, light: glass.closest('[data-theme]')?.getAttribute('data-theme') !== 'dark' && getComputedStyle(glass).colorScheme !== 'dark' };
|
|
65
|
+
setFrame(old => old && old.x === next.x && old.y === next.y && old.width === next.width && old.height === next.height && old.amount === next.amount && old.light === next.light && old.sourceWidth === next.sourceWidth && old.sourceHeight === next.sourceHeight ? old : next);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
setFrame(undefined);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (source && ((active && !transparency.matches && !contrast.matches) || amount.current > 0))
|
|
73
|
+
raf = requestAnimationFrame(measure);
|
|
74
|
+
};
|
|
75
|
+
const refresh = () => { cancelAnimationFrame(raf); previous = 0; raf = requestAnimationFrame(measure); };
|
|
76
|
+
const resize = new ResizeObserver(refresh);
|
|
77
|
+
resize.observe(glass);
|
|
78
|
+
if (source?.current)
|
|
79
|
+
resize.observe(source.current);
|
|
80
|
+
const theme = new MutationObserver(refresh);
|
|
81
|
+
theme.observe(document.documentElement, { attributes: true, subtree: true, attributeFilter: ['data-theme'] });
|
|
82
|
+
transparency.addEventListener('change', refresh);
|
|
83
|
+
motion.addEventListener('change', refresh);
|
|
84
|
+
contrast.addEventListener('change', refresh);
|
|
85
|
+
refresh();
|
|
86
|
+
return () => { stopped = true; cancelAnimationFrame(raf); resize.disconnect(); theme.disconnect(); transparency.removeEventListener('change', refresh); motion.removeEventListener('change', refresh); contrast.removeEventListener('change', refresh); };
|
|
87
|
+
}, [surface, source, active, enabled, m.bend, m.dispersion, m.frost, m.rim]);
|
|
88
|
+
const filterId = source && frame ? `${id}-${Math.round(frame.x * 10)}-${Math.round(frame.y * 10)}-${Math.round(frame.width * 10)}-${Math.round(frame.amount * 100)}` : id;
|
|
89
|
+
return {
|
|
90
|
+
filter: frame ? _jsx(LensFilter, { id: filterId, frame: frame, material: m, ink: ink, contract: contract }) : null,
|
|
91
|
+
sourceStyle: frame && source ? { filter: `url("#${filterId}")` } : undefined,
|
|
92
|
+
surfaceStyle: { '--opal-material-frost': `${m.frost}px`, '--opal-material-rim': m.rim, ...(frame && !source ? { '--opal-material-filter': `url("#${id}")` } : {}) },
|
|
93
|
+
lensing: !!frame,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/** Observe native presses without replacing the control's input handlers. */
|
|
97
|
+
export function useGlassHeld(root) {
|
|
98
|
+
const [held, setHeld] = useState(false);
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
const element = root.current;
|
|
101
|
+
if (!element)
|
|
102
|
+
return;
|
|
103
|
+
const down = (event) => {
|
|
104
|
+
if (event.isPrimary && event.button === 0 && !event.target.disabled)
|
|
105
|
+
setHeld(true);
|
|
106
|
+
};
|
|
107
|
+
const up = () => setHeld(false);
|
|
108
|
+
element.addEventListener('pointerdown', down);
|
|
109
|
+
window.addEventListener('pointerup', up);
|
|
110
|
+
window.addEventListener('pointercancel', up);
|
|
111
|
+
window.addEventListener('blur', up);
|
|
112
|
+
return () => { element.removeEventListener('pointerdown', down); window.removeEventListener('pointerup', up); window.removeEventListener('pointercancel', up); window.removeEventListener('blur', up); };
|
|
113
|
+
}, [root]);
|
|
114
|
+
return held;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=lens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lens.js","sourceRoot":"","sources":["../../../src/internal/glass/lens.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client'\n\nimport { useEffect, useId, useRef, useState, type CSSProperties, type RefObject } from 'react'\nimport { glassLensMap } from './map.js'\nimport { resolveMaterial, type GlassSettings } from './material.js'\nimport { supportsOwnedGlassLens } from './support.js'\n\ntype Box = { x: number; y: number; width: number; height: number; radius: number; map: string; mask: string }\ntype Frame = Box & { amount: number; sourceWidth: number; sourceHeight: number; light: boolean }\n\n/** A single filter graph serves live backgrounds and content owned by a control.\n * Content is composited back outside the lens, so neighbors stay untouched. */\nexport function LensFilter({ id, frame: f, material: m, ink = false, contract = false }: { id: string; frame: Frame; material: GlassSettings; ink?: boolean; contract?: boolean }) {\n // Reversing the sampling direction compresses owned content inside the lens.\n const strength = Math.min(f.width, f.height) * m.bend * f.amount * (contract ? -1 : 1)\n const invert = ink && f.light\n return <svg width=\"0\" height=\"0\" style={{ position: 'absolute', pointerEvents: 'none' }} aria-hidden=\"true\" focusable=\"false\"><defs>\n <filter id={id} filterUnits=\"userSpaceOnUse\" primitiveUnits=\"userSpaceOnUse\" x={-f.sourceWidth * .5} y={-f.sourceHeight * .5} width={f.sourceWidth * 2} height={f.sourceHeight * 2} colorInterpolationFilters=\"sRGB\">\n <feColorMatrix in=\"SourceGraphic\" type=\"matrix\" values={invert ? '-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0' : '1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0'} result=\"ink\" />\n <feGaussianBlur in=\"ink\" stdDeviation={m.frost * f.amount} result=\"soft\" />\n <feImage href={f.map} x={f.x} y={f.y} width={f.width} height={f.height} preserveAspectRatio=\"none\" result=\"map\" />\n <feImage href={f.mask} x={f.x} y={f.y} width={f.width} height={f.height} preserveAspectRatio=\"none\" result=\"shape\" />\n <feDisplacementMap in=\"soft\" in2=\"map\" scale={strength * (1 - m.dispersion)} xChannelSelector=\"R\" yChannelSelector=\"G\" result=\"rays-r\" />\n <feDisplacementMap in=\"soft\" in2=\"map\" scale={strength} xChannelSelector=\"R\" yChannelSelector=\"G\" result=\"rays-g\" />\n <feDisplacementMap in=\"soft\" in2=\"map\" scale={strength * (1 + m.dispersion)} xChannelSelector=\"R\" yChannelSelector=\"G\" result=\"rays-b\" />\n <feColorMatrix in=\"rays-r\" values=\"1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\" result=\"r\" />\n <feColorMatrix in=\"rays-g\" values=\"0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0\" result=\"g\" />\n <feColorMatrix in=\"rays-b\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0\" result=\"b\" />\n <feBlend in=\"r\" in2=\"g\" mode=\"screen\" result=\"rg\" />\n <feBlend in=\"rg\" in2=\"b\" mode=\"screen\" result=\"rgb\" />\n <feColorMatrix in=\"rgb\" values={invert ? '-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0' : '1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0'} result=\"colored\" />\n <feComposite in=\"colored\" in2=\"shape\" operator=\"in\" result=\"inside\" />\n <feComposite in=\"SourceGraphic\" in2=\"shape\" operator=\"out\" result=\"outside\" />\n <feComposite in=\"inside\" in2=\"outside\" operator=\"arithmetic\" k2=\"1\" k3=\"1\" />\n </filter>\n </defs></svg>\n}\n\n/** Lifecycle, measurements, preferences and progressive enhancement live here.\n * Owned content uses ordinary SVG filters; arbitrary backdrops use Chromium's\n * reference-filter support. No DOM or canvas work happens during SSR. */\nexport function useGlassLens(surface: RefObject<HTMLElement | null>, material: GlassSettings, options: { source?: RefObject<HTMLElement | null>; active?: boolean; ink?: boolean; contract?: boolean; enabled?: boolean } = {}) {\n const { source, active = true, ink = false, contract = false, enabled = true } = options\n const id = `opal-glass-${useId().replace(/[^a-zA-Z0-9_-]/g, '')}`\n const [frame, setFrame] = useState<Frame>()\n const amount = useRef(0)\n const m = resolveMaterial(material)\n useEffect(() => {\n const glass = surface.current\n if (!glass || !enabled) { setFrame(undefined); return }\n if (source && !supportsOwnedGlassLens(navigator.userAgent)) {\n amount.current = 0\n setFrame(undefined)\n return\n }\n if (!source && (!/(?:Chrome|Chromium|Edg)\\//.test(navigator.userAgent) || !CSS.supports('backdrop-filter', 'url(\"#lens\")'))) return\n const transparency = matchMedia('(prefers-reduced-transparency: reduce)')\n const motion = matchMedia('(prefers-reduced-motion: reduce)')\n const contrast = matchMedia('(forced-colors: active)')\n let raf = 0, previous = 0, stopped = false\n const measure = (now: number) => {\n if (stopped) return\n const dt = previous ? Math.min(64, now - previous) : 16\n previous = now\n const target = active && !transparency.matches && !contrast.matches ? 1 : 0\n amount.current = motion.matches || !source ? target : amount.current + (target - amount.current) * (1 - Math.exp(-dt / 65))\n if (Math.abs(amount.current - target) < .002) amount.current = target\n if (!amount.current) setFrame(undefined)\n else {\n const rect = glass.getBoundingClientRect()\n const content = source?.current\n const parent = content?.getBoundingClientRect()\n const width = content ? rect.width : glass.offsetWidth\n const height = content ? rect.height : glass.offsetHeight\n const scale = glass.offsetWidth ? width / glass.offsetWidth : 1\n const radius = Math.min(width / 2, height / 2, (parseFloat(getComputedStyle(glass).borderTopLeftRadius) || 0) * scale)\n try {\n const map = width && height ? glassLensMap(Math.round(width), Math.round(height), Math.round(radius)) : undefined\n if (map) {\n const mask = `data:image/svg+xml,${encodeURIComponent(`<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"${height}\"><rect width=\"100%\" height=\"100%\" rx=\"${radius}\" fill=\"white\"/></svg>`)}`\n const next: Frame = { x: parent ? rect.left - parent.left : 0, y: parent ? rect.top - parent.top : 0, width, height, radius, map, mask, amount: amount.current, sourceWidth: content?.offsetWidth ?? width, sourceHeight: content?.offsetHeight ?? height, light: glass.closest('[data-theme]')?.getAttribute('data-theme') !== 'dark' && getComputedStyle(glass).colorScheme !== 'dark' }\n setFrame(old => old && old.x === next.x && old.y === next.y && old.width === next.width && old.height === next.height && old.amount === next.amount && old.light === next.light && old.sourceWidth === next.sourceWidth && old.sourceHeight === next.sourceHeight ? old : next)\n }\n } catch { setFrame(undefined) }\n }\n if (source && ((active && !transparency.matches && !contrast.matches) || amount.current > 0)) raf = requestAnimationFrame(measure)\n }\n const refresh = () => { cancelAnimationFrame(raf); previous = 0; raf = requestAnimationFrame(measure) }\n const resize = new ResizeObserver(refresh)\n resize.observe(glass)\n if (source?.current) resize.observe(source.current)\n const theme = new MutationObserver(refresh)\n theme.observe(document.documentElement, { attributes: true, subtree: true, attributeFilter: ['data-theme'] })\n transparency.addEventListener('change', refresh)\n motion.addEventListener('change', refresh)\n contrast.addEventListener('change', refresh)\n refresh()\n return () => { stopped = true; cancelAnimationFrame(raf); resize.disconnect(); theme.disconnect(); transparency.removeEventListener('change', refresh); motion.removeEventListener('change', refresh); contrast.removeEventListener('change', refresh) }\n }, [surface, source, active, enabled, m.bend, m.dispersion, m.frost, m.rim])\n const filterId = source && frame ? `${id}-${Math.round(frame.x * 10)}-${Math.round(frame.y * 10)}-${Math.round(frame.width * 10)}-${Math.round(frame.amount * 100)}` : id\n return {\n filter: frame ? <LensFilter id={filterId} frame={frame} material={m} ink={ink} contract={contract} /> : null,\n sourceStyle: frame && source ? { filter: `url(\"#${filterId}\")` } as CSSProperties : undefined,\n surfaceStyle: { '--opal-material-frost': `${m.frost}px`, '--opal-material-rim': m.rim, ...(frame && !source ? { '--opal-material-filter': `url(\"#${id}\")` } : {}) } as CSSProperties,\n lensing: !!frame,\n }\n}\n\n/** Observe native presses without replacing the control's input handlers. */\nexport function useGlassHeld(root: RefObject<HTMLElement | null>) {\n const [held, setHeld] = useState(false)\n useEffect(() => {\n const element = root.current\n if (!element) return\n const down = (event: PointerEvent) => {\n if (event.isPrimary && event.button === 0 && !(event.target as HTMLInputElement).disabled) setHeld(true)\n }\n const up = () => setHeld(false)\n element.addEventListener('pointerdown', down)\n window.addEventListener('pointerup', up)\n window.addEventListener('pointercancel', up)\n window.addEventListener('blur', up)\n return () => { element.removeEventListener('pointerdown', down); window.removeEventListener('pointerup', up); window.removeEventListener('pointercancel', up); window.removeEventListener('blur', up) }\n }, [root])\n return held\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function glassLensMap(width: number, height: number, radius: number): string | undefined;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// A small, reusable displacement texture. Only geometry changes regenerate it.
|
|
2
|
+
const maps = new Map();
|
|
3
|
+
export function glassLensMap(width, height, radius) {
|
|
4
|
+
const key = `${width}:${height}:${radius}`;
|
|
5
|
+
const cached = maps.get(key);
|
|
6
|
+
if (cached)
|
|
7
|
+
return cached;
|
|
8
|
+
const canvas = document.createElement('canvas');
|
|
9
|
+
const resolution = Math.min(1, 256 / Math.max(width, height));
|
|
10
|
+
canvas.width = Math.max(2, Math.round(width * resolution));
|
|
11
|
+
canvas.height = Math.max(2, Math.round(height * resolution));
|
|
12
|
+
const context = canvas.getContext('2d');
|
|
13
|
+
if (!context)
|
|
14
|
+
return;
|
|
15
|
+
const pixels = context.createImageData(canvas.width, canvas.height);
|
|
16
|
+
const band = Math.min(width, height) / 2;
|
|
17
|
+
for (let y = 0; y < canvas.height; y++) {
|
|
18
|
+
for (let x = 0; x < canvas.width; x++) {
|
|
19
|
+
const px = (x + 0.5) / canvas.width * width - width / 2;
|
|
20
|
+
const py = (y + 0.5) / canvas.height * height - height / 2;
|
|
21
|
+
const qx = Math.abs(px) - (width / 2 - radius);
|
|
22
|
+
const qy = Math.abs(py) - (height / 2 - radius);
|
|
23
|
+
const dx = Math.max(qx, 0);
|
|
24
|
+
const dy = Math.max(qy, 0);
|
|
25
|
+
const length = Math.hypot(dx, dy);
|
|
26
|
+
const distance = length + Math.min(Math.max(qx, qy), 0) - radius;
|
|
27
|
+
const nx = length ? dx / length : Number(qx > qy);
|
|
28
|
+
const ny = length ? dy / length : Number(qy >= qx);
|
|
29
|
+
// One continuous convex lens, strongest at the outer edge and fading
|
|
30
|
+
// with zero slope at the center. The previous sine band returned to
|
|
31
|
+
// neutral at BOTH edges, producing an inner pill at high strengths.
|
|
32
|
+
const t = Math.max(0, Math.min(1, 1 + distance / band));
|
|
33
|
+
const bend = t * t;
|
|
34
|
+
const offset = (y * canvas.width + x) * 4;
|
|
35
|
+
pixels.data[offset] = Math.round(128 - 120 * Math.sign(px) * nx * bend);
|
|
36
|
+
pixels.data[offset + 1] = Math.round(128 - 120 * Math.sign(py) * ny * bend);
|
|
37
|
+
pixels.data[offset + 2] = 128;
|
|
38
|
+
pixels.data[offset + 3] = 255;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
context.putImageData(pixels, 0, 0);
|
|
42
|
+
const map = canvas.toDataURL();
|
|
43
|
+
if (maps.size >= 32)
|
|
44
|
+
maps.delete(maps.keys().next().value);
|
|
45
|
+
maps.set(key, map);
|
|
46
|
+
return map;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=map.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"map.js","sourceRoot":"","sources":["../../../src/internal/glass/map.ts"],"names":[],"mappings":"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","sourcesContent":["// A small, reusable displacement texture. Only geometry changes regenerate it.\nconst maps = new Map<string, string>()\n\nexport function glassLensMap(width: number, height: number, radius: number): string | undefined {\n const key = `${width}:${height}:${radius}`\n const cached = maps.get(key)\n if (cached) return cached\n const canvas = document.createElement('canvas')\n const resolution = Math.min(1, 256 / Math.max(width, height))\n canvas.width = Math.max(2, Math.round(width * resolution))\n canvas.height = Math.max(2, Math.round(height * resolution))\n const context = canvas.getContext('2d')\n if (!context) return\n const pixels = context.createImageData(canvas.width, canvas.height)\n const band = Math.min(width, height) / 2\n for (let y = 0; y < canvas.height; y++) {\n for (let x = 0; x < canvas.width; x++) {\n const px = (x + 0.5) / canvas.width * width - width / 2\n const py = (y + 0.5) / canvas.height * height - height / 2\n const qx = Math.abs(px) - (width / 2 - radius)\n const qy = Math.abs(py) - (height / 2 - radius)\n const dx = Math.max(qx, 0)\n const dy = Math.max(qy, 0)\n const length = Math.hypot(dx, dy)\n const distance = length + Math.min(Math.max(qx, qy), 0) - radius\n const nx = length ? dx / length : Number(qx > qy)\n const ny = length ? dy / length : Number(qy >= qx)\n // One continuous convex lens, strongest at the outer edge and fading\n // with zero slope at the center. The previous sine band returned to\n // neutral at BOTH edges, producing an inner pill at high strengths.\n const t = Math.max(0, Math.min(1, 1 + distance / band))\n const bend = t * t\n const offset = (y * canvas.width + x) * 4\n pixels.data[offset] = Math.round(128 - 120 * Math.sign(px) * nx * bend)\n pixels.data[offset + 1] = Math.round(128 - 120 * Math.sign(py) * ny * bend)\n pixels.data[offset + 2] = 128\n pixels.data[offset + 3] = 255\n }\n }\n context.putImageData(pixels, 0, 0)\n const map = canvas.toDataURL()\n if (maps.size >= 32) maps.delete(maps.keys().next().value!)\n maps.set(key, map)\n return map\n}\n"]}
|