@akonwi/mica 0.7.0 → 0.9.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/README.md +5 -0
- package/THIRD_PARTY_LICENSES.md +28 -0
- package/avatar.d.ts +10 -0
- package/avatar.js +301 -0
- package/combobox.js +52 -18
- package/drawer.d.ts +3 -2
- package/drawer.js +74 -10
- package/mica.css +107 -3
- package/package.json +42 -9
- package/types/react.d.ts +4 -0
package/README.md
CHANGED
|
@@ -65,6 +65,11 @@ Every component states where its behavior comes from
|
|
|
65
65
|
`select.js`, `toast.js`. Each is a tiny standalone module that enhances
|
|
66
66
|
working markup — never renders it. There is no shared runtime.
|
|
67
67
|
|
|
68
|
+
`m-avatar` accepts authored initials or a direct native `img`, both CSS-only.
|
|
69
|
+
`avatar.js` is a separate opt-in visual enhancement: importing it locally
|
|
70
|
+
derives deterministic color and features from the `blobatar` seed without a
|
|
71
|
+
network request.
|
|
72
|
+
|
|
68
73
|
## Your CSS always wins
|
|
69
74
|
|
|
70
75
|
All of mica ships inside `@layer mica.*`. Unlayered user CSS beats layered
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Third-party licenses
|
|
2
|
+
|
|
3
|
+
## Blobatar 2.4.0
|
|
4
|
+
|
|
5
|
+
Mica's optional `avatar.js` module incorporates code from
|
|
6
|
+
[Blobatar](https://github.com/Alain00/blobatar).
|
|
7
|
+
|
|
8
|
+
MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2026 Alain
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
package/avatar.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/* Type declarations for mica/avatar.js — side-effect module.
|
|
2
|
+
* Registers <m-avatar> and locally generates Blobatar-derived paint for its
|
|
3
|
+
* `blobatar` seed. Authored initials remain the no-module fallback.
|
|
4
|
+
*/
|
|
5
|
+
declare global {
|
|
6
|
+
interface HTMLElementTagNameMap {
|
|
7
|
+
"m-avatar": HTMLElement;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export {};
|
package/avatar.js
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/*! mica/avatar.js — generated by tools/build-avatar.ts.
|
|
2
|
+
* Includes Blobatar 2.4.0 (MIT); see THIRD_PARTY_LICENSES.md. */
|
|
3
|
+
// node_modules/blobatar/dist/index.js
|
|
4
|
+
function L({ l: t, c: n, h: e }) {
|
|
5
|
+
let o = e * Math.PI / 180, a = n * Math.cos(o), r = n * Math.sin(o), s = t + 0.3963377774 * a + 0.2158037573 * r, c = t - 0.1055613458 * a - 0.0638541728 * r, i = t - 0.0894841775 * a - 1.291485548 * r, m = s * s * s, l = c * c * c, u = i * i * i;
|
|
6
|
+
return [4.0767416621 * m - 3.3077115913 * l + 0.2309699292 * u, -1.2684380046 * m + 2.6097574011 * l - 0.3413193965 * u, -0.0041960863 * m - 0.7034186147 * l + 1.707614701 * u];
|
|
7
|
+
}
|
|
8
|
+
var q = (t) => t.every((n) => n >= -0.0001 && n <= 1.0001);
|
|
9
|
+
function K(t) {
|
|
10
|
+
let n = L(t);
|
|
11
|
+
if (!q(n)) {
|
|
12
|
+
let e = 0, o = t.c;
|
|
13
|
+
for (let a = 0;a < 12; a++) {
|
|
14
|
+
let r = (e + o) / 2;
|
|
15
|
+
if (q(L({ ...t, c: r })))
|
|
16
|
+
e = r;
|
|
17
|
+
else
|
|
18
|
+
o = r;
|
|
19
|
+
}
|
|
20
|
+
n = L({ ...t, c: e });
|
|
21
|
+
}
|
|
22
|
+
return n.map((e) => Math.min(1, Math.max(0, e)));
|
|
23
|
+
}
|
|
24
|
+
function D(t) {
|
|
25
|
+
let [n, e, o] = K(t);
|
|
26
|
+
return 0.2126 * n + 0.7152 * e + 0.0722 * o;
|
|
27
|
+
}
|
|
28
|
+
function k(t, n) {
|
|
29
|
+
let e = D(t), o = D(n);
|
|
30
|
+
return (Math.max(e, o) + 0.05) / (Math.min(e, o) + 0.05);
|
|
31
|
+
}
|
|
32
|
+
function B(t, n, e) {
|
|
33
|
+
if (k(t, n) >= e)
|
|
34
|
+
return t;
|
|
35
|
+
let o = t.l >= n.l ? 1 : -1;
|
|
36
|
+
for (let s of [o, -o]) {
|
|
37
|
+
let c = { ...t };
|
|
38
|
+
for (let i = 0;i < 60; i++) {
|
|
39
|
+
if (c.l = Math.min(1, Math.max(0, c.l + s * 0.02)), k(c, n) >= e)
|
|
40
|
+
return c;
|
|
41
|
+
if (c.l === 0 || c.l === 1)
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
let a = { ...t, l: 0, c: 0 }, r = { ...t, l: 1, c: 0 };
|
|
46
|
+
return k(a, n) >= k(r, n) ? a : r;
|
|
47
|
+
}
|
|
48
|
+
function P(t) {
|
|
49
|
+
return "#" + K(t).map((n) => {
|
|
50
|
+
let e = n <= 0.0031308 ? 12.92 * n : 1.055 * Math.pow(n, 0.4166666666666667) - 0.055;
|
|
51
|
+
return Math.round(e * 255).toString(16).padStart(2, "0");
|
|
52
|
+
}).join("");
|
|
53
|
+
}
|
|
54
|
+
var Z = [[0.2, { l: 0.86, c: 0.085 }], [0.36, { l: 0.9, c: 0.028 }], [0.62, { l: 0.73, c: 0.135 }], [0.8, { l: 0.62, c: 0.165 }], [0.93, { l: 0.87, c: 0.16 }], [1, { l: 0.34, c: 0.035 }]];
|
|
55
|
+
var Et = (t) => Z.find(([n]) => t < n)?.[1] ?? Z[0][1];
|
|
56
|
+
var U = { l: 0.145, c: 0, h: 0 };
|
|
57
|
+
var Q = 1.5;
|
|
58
|
+
var Lt = (t, n) => {
|
|
59
|
+
let e = Et(n), o = B({ l: e.l, c: e.c, h: t }, U, Q);
|
|
60
|
+
return { bg: { l: 0.965, c: 0.01, h: t }, head: o, eye: o.l >= 0.5 ? { l: 0.17, c: 0.02, h: t } : { l: 0.97, c: 0.012, h: t } };
|
|
61
|
+
};
|
|
62
|
+
var X = [["head", "bg", 1.25], ["eye", "head", 4.5]];
|
|
63
|
+
function Y(t, n = true, e = 0) {
|
|
64
|
+
let o = Lt(t, e);
|
|
65
|
+
if (n)
|
|
66
|
+
for (let [a, r, s] of X)
|
|
67
|
+
o[a] = B(o[a], o[r], s);
|
|
68
|
+
return o;
|
|
69
|
+
}
|
|
70
|
+
function w(t, n = true, e = 0) {
|
|
71
|
+
let o = Y(t, n, e), a = {};
|
|
72
|
+
for (let r in o)
|
|
73
|
+
a[r] = P(o[r]);
|
|
74
|
+
return a;
|
|
75
|
+
}
|
|
76
|
+
var y = (t) => {
|
|
77
|
+
let n = Math.round(t * 100) / 100;
|
|
78
|
+
return Object.is(n, -0) ? "0" : String(n);
|
|
79
|
+
};
|
|
80
|
+
function T({ cx: t, cy: n, rx: e, ry: o, n: a = 4, rot: r = 0 }) {
|
|
81
|
+
let s = Math.min(1, (8 * Math.pow(2, -1 / a) - 4) / 3), c = e, i = o, m = c * s, l = i * s, u = [[c, 0], [c, l], [m, i], [0, i], [-m, i], [-c, l], [-c, 0], [-c, -l], [-m, -i], [0, -i], [m, -i], [c, -l], [c, 0]], x = r * Math.PI / 180, p = Math.cos(x), d = Math.sin(x), h = (b) => {
|
|
82
|
+
let [g, M] = u[b];
|
|
83
|
+
return `${y(t + g * p - M * d)} ${y(n + g * d + M * p)}`;
|
|
84
|
+
}, f = `M${h(0)}`;
|
|
85
|
+
for (let b = 1;b < 13; b += 3)
|
|
86
|
+
f += `C${h(b)} ${h(b + 1)} ${h(b + 2)}`;
|
|
87
|
+
return f + "Z";
|
|
88
|
+
}
|
|
89
|
+
function G(t, n, e, o, a, r = 0) {
|
|
90
|
+
let s = a.length, c = r * Math.PI / 180, i = a.map((u, x) => {
|
|
91
|
+
let p = c + 2 * Math.PI * x / s;
|
|
92
|
+
return [t + e * u * Math.cos(p), n + o * u * Math.sin(p)];
|
|
93
|
+
}), m = (u) => i[(u % s + s) % s], l = `M${y(m(0)[0])} ${y(m(0)[1])}`;
|
|
94
|
+
for (let u = 0;u < s; u++) {
|
|
95
|
+
let [x, p] = m(u - 1), [d, h] = m(u), [f, b] = m(u + 1), [g, M] = m(u + 2);
|
|
96
|
+
l += `C${y(d + (f - x) / 6)} ${y(h + (b - p) / 6)} ${y(f - (g - d) / 6)} ${y(b - (M - h) / 6)} ${y(f)} ${y(b)}`;
|
|
97
|
+
}
|
|
98
|
+
return l + "Z";
|
|
99
|
+
}
|
|
100
|
+
function J({ cx: t, cy: n, rx: e, ry: o, sides: a, round: r = 0.3, rot: s = 0 }) {
|
|
101
|
+
let c = r > 0 ? r < 1 ? r / 2 : 0.5 : 0, i = s * Math.PI / 180 - Math.PI / 2, m = Array.from({ length: a }, (p, d) => {
|
|
102
|
+
let h = i + 2 * Math.PI * d / a;
|
|
103
|
+
return [t + e * Math.cos(h), n + o * Math.sin(h)];
|
|
104
|
+
}), l = (p) => m[(p % a + a) % a], u = (p, d) => {
|
|
105
|
+
let [h, f] = l(p), [b, g] = l(d);
|
|
106
|
+
return `${y(h + (b - h) * c)} ${y(f + (g - f) * c)}`;
|
|
107
|
+
}, x = `M${u(0, -1)}`;
|
|
108
|
+
for (let p = 0;p < a; p++) {
|
|
109
|
+
let [d, h] = l(p);
|
|
110
|
+
if (x += `Q${y(d)} ${y(h)} ${u(p, p + 1)}`, c < 0.5)
|
|
111
|
+
x += `L${u(p + 1, p)}`;
|
|
112
|
+
}
|
|
113
|
+
return x + "Z";
|
|
114
|
+
}
|
|
115
|
+
function W(t, n, e, o) {
|
|
116
|
+
let a = y(t - e), r = y(t + e);
|
|
117
|
+
return `M${a} ${y(n - o)}H${r}V${y(n + o)}H${a}Z`;
|
|
118
|
+
}
|
|
119
|
+
function tt(t, n, e, o, a) {
|
|
120
|
+
let r = Math.max(1.05, a), s = e * Math.sqrt(1 - 1 / (r * r)), c = n - o / r, i = n - r * o, m = s * 0.14, l = c + 0.86 * (i - c);
|
|
121
|
+
return `M${y(t - s)} ${y(c)}L${y(t - m)} ${y(l)}Q${y(t)} ${y(i)} ${y(t + m)} ${y(l)}L${y(t + s)} ${y(c)}Z`;
|
|
122
|
+
}
|
|
123
|
+
function I(t, n) {
|
|
124
|
+
for (let e = 0;e < n.length; e++)
|
|
125
|
+
t = Math.imul(t ^ n[e], 3432918353), t = t << 13 | t >>> 19;
|
|
126
|
+
return t;
|
|
127
|
+
}
|
|
128
|
+
function wt(t) {
|
|
129
|
+
return t = Math.imul(t ^ t >>> 16, 2246822507), t = Math.imul(t ^ t >>> 13, 3266489909), (t ^ t >>> 16) >>> 0;
|
|
130
|
+
}
|
|
131
|
+
var nt = new TextEncoder;
|
|
132
|
+
function et(t) {
|
|
133
|
+
return t.normalize("NFC").trim().toLowerCase();
|
|
134
|
+
}
|
|
135
|
+
function rt(t, n = true) {
|
|
136
|
+
let e = n ? et(t) : t;
|
|
137
|
+
return I(1779033703 ^ e.length, nt.encode(e));
|
|
138
|
+
}
|
|
139
|
+
function v(t, n) {
|
|
140
|
+
return wt(I(I(t, Uint8Array.of(255)), nt.encode(n))) / 4294967296;
|
|
141
|
+
}
|
|
142
|
+
function A(t, n = true, e) {
|
|
143
|
+
let o = rt(t, n), a = (r) => {
|
|
144
|
+
let s = e?.[r], c = Array.isArray(s) ? s[Math.floor(v(o, r) * s.length)] : s;
|
|
145
|
+
return c === undefined ? v(o, r) : c > 0 ? c < 1 ? c : 0.999999 : 0;
|
|
146
|
+
};
|
|
147
|
+
return a.num = (r, s, c) => s + a(r) * (c - s), a.int = (r, s, c) => s + Math.floor(a(r) * (c - s + 1)), a.pick = (r, s) => s[Math.floor(a(r) * s.length)], a.bool = (r, s = 0.5) => a(r) < s, a.jitter = (r, s) => (a(r) * 2 - 1) * s, a;
|
|
148
|
+
}
|
|
149
|
+
function ot(t, n, e) {
|
|
150
|
+
let o = n.expression;
|
|
151
|
+
if (e || !o)
|
|
152
|
+
return { l: t, wrap: "" };
|
|
153
|
+
return o.bake(t, o.p);
|
|
154
|
+
}
|
|
155
|
+
var It = (t, n) => n?.tint ? n.tint(t, n.p) : t;
|
|
156
|
+
var at = (t, n) => n ? `<g transform="${n}">${t}</g>` : t;
|
|
157
|
+
var vt = (t) => t.replace(/[&<>]/g, (n) => n === "&" ? "&" : n === "<" ? "<" : ">");
|
|
158
|
+
function S(t, n) {
|
|
159
|
+
let e = A(t, n.normalize ?? true, n.traits);
|
|
160
|
+
return { t: e, palette: { ...w(n.hue ?? e.num("hue", 0, 360), n.contrast ?? true, n.tone ?? e("tone")), ...n.palette } };
|
|
161
|
+
}
|
|
162
|
+
var At = (t) => t.title ? `<title>${vt(t.title)}</title>` : "";
|
|
163
|
+
function st(t, n, e) {
|
|
164
|
+
let o = n.background ?? t.background;
|
|
165
|
+
if (o === false)
|
|
166
|
+
return;
|
|
167
|
+
return { d: o === "square" ? "M0 0H100V100H0Z" : T({ cx: 50, cy: 50, rx: 50, ry: 50, n: o === "circle" ? 2 : 6 }), fill: e.bg };
|
|
168
|
+
}
|
|
169
|
+
var Rt = (t) => t ? `<path d="${t.d}" fill="${t.fill}"/>` : "";
|
|
170
|
+
function ct(t) {
|
|
171
|
+
return (n, e = {}) => {
|
|
172
|
+
let { t: o, palette: a } = S(n, e), r = It(a, e.expression), s = e.size ? ` width="${e.size}" height="${e.size}"` : "", c = ot(t.layout(o), e), i = At(e) + Rt(st(t, e, r)) + at(t.render(c.l, r), c.wrap);
|
|
173
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"${s}>${i}</svg>`;
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
var ut = (t, n, e) => {
|
|
177
|
+
let o = n.rx, a = t.num("eye.rx", 0.075, 0.105) * o, r = t.num("eye.ratio", 1.9, 3.2), s = t.num("eye.scale", 0.78, 1.24), c = t.num("eye.stretch", 0.85, 1.18), i = t.num("eye.gap", 0.1, 0.24) * o, m = a * Math.max(1, s), l = a * r * Math.max(1, s * c), u = m + o * 0.03 + i, x = t.jitter("gaze.x", 0.09) * e.rx, p = t.num("gaze.y", -0.2, 0.08) * e.ry, d = t.jitter("eye.dy", 0.04) * e.ry, h = Math.hypot(m, l), f = Math.hypot((Math.abs(x) + u + h) / e.rx, (Math.abs(p) + Math.abs(d) + h) / e.ry), b = f > 0.9 ? 0.9 / f : 1, g = a * b, M = g * r, F = u * b, Pt = Math.max(0, Math.min(1, i / l)), Tt = Math.min(12, Math.asin(Pt) * 180 / Math.PI), j = t.num("eye.lean", -1, 1) * Tt, Bt = Math.max(-12, Math.min(12, j + t.jitter("eye.lean2", 3.5))), z = e.cx + x * b, C = e.cy + p * b;
|
|
178
|
+
return [{ cx: z - F, cy: C, rx: g, ry: M, n: t.num("eye.n", 3.5, 6), rot: j }, { cx: z + F, cy: C + d * b, rx: g * s, ry: M * s * c, n: t.num("eye.n", 3.5, 6), rot: Bt }];
|
|
179
|
+
};
|
|
180
|
+
function mt(t, n) {
|
|
181
|
+
let e = (r) => (t.find(([, s]) => r < s) ?? t[t.length - 1])[0];
|
|
182
|
+
function o(r) {
|
|
183
|
+
let s = e(r("shape")), c = r.num("body.r", 31, 38) * s.core, i = { cx: 50 + r.jitter("body.x", 1.5), cy: 50 + r.jitter("body.y", 1.5), rx: c, ry: c * r.num("body.ratio", 0.92, 1.08), n: r.num("body.n", 1.9, 2.5), rot: 0, radii: Array.from({ length: r.int("body.pts", 6, 8) }, (u, x) => 1 + r.jitter(`body.r${x}`, 0.16)) };
|
|
184
|
+
s.body?.(r, i);
|
|
185
|
+
let m = s.face?.(i) ?? i, l = { petals: [], extra: [] };
|
|
186
|
+
return s.decorate?.(r, i, l), { shape: s.name, draw: s.path, body: i, face: m, petals: l.petals, extra: l.extra, eyes: n(r, i, m) };
|
|
187
|
+
}
|
|
188
|
+
function a(r, s, c) {
|
|
189
|
+
let i = (u) => Math.round(u * 100) / 100, m = (u, x) => {
|
|
190
|
+
let p = `<path d="${T(u)}"/>`;
|
|
191
|
+
return c ? `<g class="mo-eye" style="--mo-wrap:${x ? 1 : -1};--mo-lean:${i(u.rot)};transform-origin:${i(u.cx)}px ${i(u.cy)}px">${p}</g>` : p;
|
|
192
|
+
}, l = `<g fill="${s.head}">` + r.petals.map((u) => `<circle cx="${i(u.cx)}" cy="${i(u.cy)}" r="${i(u.r)}"/>`).join("") + r.extra.map((u) => `<path d="${u}"/>`).join("") + `<path d="${r.draw ? r.draw(r.body) : T(r.body)}"/></g><g fill="${s.eye}"${c ? ' class="mo-eyes"' : ""}>` + r.eyes.map(m).join("") + "</g>";
|
|
193
|
+
return c ? `<g class="mo-breathe"><g class="mo-bob">${l}</g></g>` : l;
|
|
194
|
+
}
|
|
195
|
+
return { layout: o, render: a, background: false };
|
|
196
|
+
}
|
|
197
|
+
var lt = (t) => J(t);
|
|
198
|
+
var pt = (t) => G(t.cx, t.cy, t.rx, t.ry, t.radii, t.rot);
|
|
199
|
+
var R = (t) => (n) => ({ cx: n.cx, cy: n.cy, rx: n.rx * t, ry: n.ry * t });
|
|
200
|
+
var yt = (t) => R(Math.min(...t.radii) * 0.95)(t);
|
|
201
|
+
var Ft = (t) => R(0.84)(t);
|
|
202
|
+
var xt = { name: "round", core: 1 };
|
|
203
|
+
var bt = { name: "organic", core: 0.98, path: pt, face: yt };
|
|
204
|
+
var ht = { name: "boxy", core: 0.86, body: (t, n) => {
|
|
205
|
+
n.n = t.num("body.n", 3.4, 6), n.rot = t.num("body.rot", -20, 20);
|
|
206
|
+
} };
|
|
207
|
+
var dt = { name: "capsule", core: 1.02, body: (t, n) => {
|
|
208
|
+
n.ry *= t.num("capsule.squat", 0.55, 0.68);
|
|
209
|
+
}, face: R(0.94), decorate: (t, n, e) => {
|
|
210
|
+
for (let o of [-1, 1])
|
|
211
|
+
e.petals.push({ cx: n.cx + o * (n.rx - n.ry), cy: n.cy, r: n.ry });
|
|
212
|
+
}, path: (t) => W(t.cx, t.cy, t.rx - t.ry, t.ry) };
|
|
213
|
+
var ft = { name: "nub", core: 0.88, decorate: (t, n, e) => {
|
|
214
|
+
let o = t.int("nub.n", 1, 2);
|
|
215
|
+
for (let a = 0;a < o; a++) {
|
|
216
|
+
let r = t.num(`nub.a${a}`, 0, 2 * Math.PI);
|
|
217
|
+
e.petals.push({ cx: n.cx + Math.cos(r) * n.rx * 0.88, cy: n.cy + Math.sin(r) * n.rx * 0.88, r: n.rx * t.num(`nub.r${a}`, 0.24, 0.4) });
|
|
218
|
+
}
|
|
219
|
+
} };
|
|
220
|
+
var gt = { name: "cloud", core: 0.78, face: yt, path: pt, decorate: (t, n, e) => {
|
|
221
|
+
let o = t.int("cloud.n", 4, 6);
|
|
222
|
+
for (let a = 0;a < o; a++) {
|
|
223
|
+
let r = Math.PI + Math.PI * (a + 0.5) / o;
|
|
224
|
+
e.petals.push({ cx: n.cx + Math.cos(r) * n.rx * 0.8, cy: n.cy + Math.sin(r) * n.rx * 0.5, r: n.rx * t.num(`cloud.r${a}`, 0.44, 0.62) });
|
|
225
|
+
}
|
|
226
|
+
} };
|
|
227
|
+
var Mt = { name: "droplet", core: 0.78, body: (t, n) => {
|
|
228
|
+
n.cy += 0.22 * n.ry, n.n = 2;
|
|
229
|
+
}, face: (t) => ({ cx: t.cx, cy: t.cy + t.ry * 0.05, rx: t.rx * 0.88, ry: t.ry * 0.88 }), decorate: (t, n, e) => {
|
|
230
|
+
e.extra.push(tt(n.cx, n.cy, n.rx, n.ry, t.num("droplet.tip", 1.4, 1.65)));
|
|
231
|
+
} };
|
|
232
|
+
var $t = { name: "hexagon", core: 1.05, path: lt, face: Ft, body: (t, n) => {
|
|
233
|
+
n.sides = 6, n.rot = t.num("body.rot", -12, 12), n.round = t.num("poly.round", 0.24, 0.5);
|
|
234
|
+
} };
|
|
235
|
+
var kt = { name: "sun", core: 0.7, decorate: (t, n, e) => {
|
|
236
|
+
let o = t.int("sun.n", 6, 9), a = n.rx * t.num("sun.dist", 1, 1.08), r = n.rx * t.num("sun.r", 0.2, 0.26), s = t.num("sun.rot", 0, 2 * Math.PI);
|
|
237
|
+
for (let c = 0;c < o; c++) {
|
|
238
|
+
let i = s + 2 * Math.PI * c / o;
|
|
239
|
+
e.petals.push({ cx: n.cx + Math.cos(i) * a, cy: n.cy + Math.sin(i) * a, r });
|
|
240
|
+
}
|
|
241
|
+
} };
|
|
242
|
+
var Ot = { name: "triangle", core: 1.15, path: lt, body: (t, n) => {
|
|
243
|
+
n.sides = 3, n.rot = t.num("body.rot", -5, 5), n.round = t.num("poly.round", 0.24, 0.5);
|
|
244
|
+
}, face: (t) => ({ cx: t.cx, cy: t.cy + t.ry * 0.1, rx: t.rx * 0.54, ry: t.ry * 0.36 }) };
|
|
245
|
+
var jt = [[xt, 0.22], [bt, 0.48], [ht, 0.6], [dt, 0.7], [ft, 0.79], [gt, 0.86], [Mt, 0.915], [$t, 0.95], [kt, 0.98], [Ot, 1]];
|
|
246
|
+
var E = mt(jt, ut);
|
|
247
|
+
var zt = ct(E);
|
|
248
|
+
|
|
249
|
+
// tools/avatar.js
|
|
250
|
+
var PROPERTIES = [
|
|
251
|
+
"--m-avatar-content-color",
|
|
252
|
+
"--m-avatar-body-color",
|
|
253
|
+
"--m-avatar-eye-color",
|
|
254
|
+
"--m-avatar-body-inset-block",
|
|
255
|
+
"--m-avatar-body-inset-inline",
|
|
256
|
+
"--m-avatar-body-x",
|
|
257
|
+
"--m-avatar-body-y",
|
|
258
|
+
"--m-avatar-eye-top",
|
|
259
|
+
"--m-avatar-eye-left",
|
|
260
|
+
"--m-avatar-eye-width",
|
|
261
|
+
"--m-avatar-eye-height",
|
|
262
|
+
"--m-avatar-eye-gap",
|
|
263
|
+
"--m-avatar-eye-drop"
|
|
264
|
+
];
|
|
265
|
+
var percent = (value) => `${Math.round(value * 100) / 100}%`;
|
|
266
|
+
|
|
267
|
+
class MAvatar extends HTMLElement {
|
|
268
|
+
static observedAttributes = ["blobatar"];
|
|
269
|
+
connectedCallback() {
|
|
270
|
+
this.#update();
|
|
271
|
+
}
|
|
272
|
+
attributeChangedCallback() {
|
|
273
|
+
if (this.isConnected)
|
|
274
|
+
this.#update();
|
|
275
|
+
}
|
|
276
|
+
#update() {
|
|
277
|
+
const seed = this.getAttribute("blobatar");
|
|
278
|
+
if (seed === null) {
|
|
279
|
+
for (const property of PROPERTIES)
|
|
280
|
+
this.style.removeProperty(property);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const t = A(seed);
|
|
284
|
+
const colors = w(t.num("hue", 0, 360), true, t("tone"));
|
|
285
|
+
this.style.setProperty("--m-avatar-content-color", "transparent");
|
|
286
|
+
this.style.setProperty("--m-avatar-body-color", colors.head);
|
|
287
|
+
this.style.setProperty("--m-avatar-eye-color", colors.eye);
|
|
288
|
+
this.style.setProperty("--m-avatar-body-inset-block", percent(t.num("mica.body.block", 11, 16)));
|
|
289
|
+
this.style.setProperty("--m-avatar-body-inset-inline", percent(t.num("mica.body.inline", 10, 16)));
|
|
290
|
+
this.style.setProperty("--m-avatar-body-x", percent(t.jitter("mica.body.x", 2)));
|
|
291
|
+
this.style.setProperty("--m-avatar-body-y", percent(t.jitter("mica.body.y", 2)));
|
|
292
|
+
this.style.setProperty("--m-avatar-eye-top", percent(t.num("mica.eye.top", 42, 49)));
|
|
293
|
+
this.style.setProperty("--m-avatar-eye-left", percent(t.num("mica.eye.left", 36, 40)));
|
|
294
|
+
this.style.setProperty("--m-avatar-eye-width", percent(t.num("mica.eye.width", 2.5, 3.5)));
|
|
295
|
+
this.style.setProperty("--m-avatar-eye-height", percent(t.num("mica.eye.height", 7.5, 10)));
|
|
296
|
+
this.style.setProperty("--m-avatar-eye-gap", percent(t.num("mica.eye.gap", 19, 23)));
|
|
297
|
+
this.style.setProperty("--m-avatar-eye-drop", percent(t.jitter("mica.eye.drop", 2)));
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (!customElements.get("m-avatar"))
|
|
301
|
+
customElements.define("m-avatar", MAvatar);
|
package/combobox.js
CHANGED
|
@@ -20,13 +20,16 @@ let uid = 0;
|
|
|
20
20
|
class MCombobox extends HTMLElement {
|
|
21
21
|
#input;
|
|
22
22
|
#list;
|
|
23
|
+
#datalist;
|
|
24
|
+
#listId;
|
|
23
25
|
#options = [];
|
|
24
26
|
#active = -1;
|
|
27
|
+
#observer;
|
|
25
28
|
|
|
26
29
|
connectedCallback() {
|
|
27
30
|
this.#input = this.querySelector("input");
|
|
28
|
-
|
|
29
|
-
if (!this.#input || !datalist) return;
|
|
31
|
+
this.#datalist = this.querySelector("datalist");
|
|
32
|
+
if (!this.#input || !this.#datalist) return;
|
|
30
33
|
|
|
31
34
|
// disable the native popup; the module takes over
|
|
32
35
|
this.#input.removeAttribute("list");
|
|
@@ -35,16 +38,49 @@ class MCombobox extends HTMLElement {
|
|
|
35
38
|
this.#input.setAttribute("aria-autocomplete", "list");
|
|
36
39
|
this.#input.autocomplete = "off";
|
|
37
40
|
|
|
38
|
-
|
|
41
|
+
this.#listId = `m-cb-${++uid}`;
|
|
39
42
|
this.#list = document.createElement("div");
|
|
40
|
-
this.#list.id = listId;
|
|
43
|
+
this.#list.id = this.#listId;
|
|
41
44
|
this.#list.setAttribute("role", "listbox");
|
|
42
45
|
this.#list.hidden = true;
|
|
43
46
|
|
|
44
|
-
this.#
|
|
47
|
+
this.#buildOptions();
|
|
48
|
+
this.append(this.#list);
|
|
49
|
+
this.#input.setAttribute("aria-controls", this.#listId);
|
|
50
|
+
|
|
51
|
+
// Options may be rendered asynchronously (a search-backed combobox):
|
|
52
|
+
// rebuild the listbox whenever the author's datalist changes.
|
|
53
|
+
this.#observer = new MutationObserver(() => {
|
|
54
|
+
this.#buildOptions();
|
|
55
|
+
if (document.activeElement === this.#input) this.#openAndFilter();
|
|
56
|
+
});
|
|
57
|
+
this.#observer.observe(this.#datalist, {
|
|
58
|
+
childList: true,
|
|
59
|
+
subtree: true,
|
|
60
|
+
attributes: true,
|
|
61
|
+
attributeFilter: ["value"],
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
this.#input.addEventListener("input", (e) => {
|
|
65
|
+
// ignore our own dispatched events from #choose — they'd reopen
|
|
66
|
+
if (e.isTrusted) this.#openAndFilter();
|
|
67
|
+
});
|
|
68
|
+
this.#input.addEventListener("keydown", (e) => this.#onKey(e));
|
|
69
|
+
this.addEventListener("focusout", (e) => {
|
|
70
|
+
if (!this.contains(e.relatedTarget)) this.#close();
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
disconnectedCallback() {
|
|
75
|
+
this.#observer?.disconnect();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
#buildOptions() {
|
|
79
|
+
this.#list.replaceChildren();
|
|
80
|
+
this.#options = [...this.#datalist.options].map((o, i) => {
|
|
45
81
|
const d = document.createElement("div");
|
|
46
82
|
d.setAttribute("role", "option");
|
|
47
|
-
d.id = `${listId}-${i}`;
|
|
83
|
+
d.id = `${this.#listId}-${i}`;
|
|
48
84
|
d.textContent = o.value || o.textContent;
|
|
49
85
|
// pointerdown so the input never loses focus
|
|
50
86
|
d.addEventListener("pointerdown", (e) => {
|
|
@@ -58,17 +94,7 @@ class MCombobox extends HTMLElement {
|
|
|
58
94
|
this.#list.append(d);
|
|
59
95
|
return d;
|
|
60
96
|
});
|
|
61
|
-
this
|
|
62
|
-
this.#input.setAttribute("aria-controls", listId);
|
|
63
|
-
|
|
64
|
-
this.#input.addEventListener("input", (e) => {
|
|
65
|
-
// ignore our own dispatched events from #choose — they'd reopen
|
|
66
|
-
if (e.isTrusted) this.#openAndFilter();
|
|
67
|
-
});
|
|
68
|
-
this.#input.addEventListener("keydown", (e) => this.#onKey(e));
|
|
69
|
-
this.addEventListener("focusout", (e) => {
|
|
70
|
-
if (!this.contains(e.relatedTarget)) this.#close();
|
|
71
|
-
});
|
|
97
|
+
this.#active = -1;
|
|
72
98
|
}
|
|
73
99
|
|
|
74
100
|
#visible() {
|
|
@@ -127,7 +153,15 @@ class MCombobox extends HTMLElement {
|
|
|
127
153
|
}
|
|
128
154
|
|
|
129
155
|
#choose(option) {
|
|
130
|
-
|
|
156
|
+
// Write through the native prototype setter: frameworks that patch
|
|
157
|
+
// the value property to track controlled inputs (React) only observe
|
|
158
|
+
// the change when it lands on the native setter.
|
|
159
|
+
const setter = Object.getOwnPropertyDescriptor(
|
|
160
|
+
HTMLInputElement.prototype,
|
|
161
|
+
"value",
|
|
162
|
+
)?.set;
|
|
163
|
+
if (setter) setter.call(this.#input, option.textContent);
|
|
164
|
+
else this.#input.value = option.textContent;
|
|
131
165
|
this.#close();
|
|
132
166
|
this.#input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
133
167
|
this.#input.dispatchEvent(new Event("change", { bubbles: true }));
|
package/drawer.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* Type declarations for mica/drawer.js — side-effect module.
|
|
2
|
-
* Swipe-to-dismiss drag physics
|
|
3
|
-
*
|
|
2
|
+
* Swipe-to-dismiss drag physics and a managed overlay scrim for
|
|
3
|
+
* dialog[data-drawer] bottom sheets. No exports; sets
|
|
4
|
+
* :root[data-drawer-gestures] when active.
|
|
4
5
|
*/
|
|
5
6
|
export {};
|
package/drawer.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
/* mica/drawer.js — JS-enhanced: swipe-to-dismiss for
|
|
2
|
-
* bottom sheets on small screens.
|
|
1
|
+
/* mica/drawer.js — JS-enhanced: swipe-to-dismiss and a managed scrim for
|
|
2
|
+
* dialog[data-drawer] bottom sheets on small screens.
|
|
3
3
|
*
|
|
4
4
|
* Enhances working markup, never replaces it: without this module the
|
|
5
5
|
* drawer opens/closes via buttons and Esc; with it, the mobile sheet
|
|
6
6
|
* grows a grab handle (CSS keys off :root[data-drawer-gestures] — an
|
|
7
7
|
* affordance may only appear when the behavior exists) and vaul-style
|
|
8
8
|
* drag physics: the sheet follows a downward drag, resists upward,
|
|
9
|
-
* and dismisses past 1/3 of its height or on a flick.
|
|
9
|
+
* and dismisses past 1/3 of its height or on a flick. A fixed visual
|
|
10
|
+
* overlay replaces the CSS-only scrim so its dimming can follow the drag
|
|
11
|
+
* and fade on iOS, where ::backdrop disappears immediately on close.
|
|
10
12
|
*
|
|
11
13
|
* Drag surface: the handle / header strip. Drag-from-content is
|
|
12
14
|
* deliberately out of scope — it requires scroll-vs-drag heuristics
|
|
@@ -18,7 +20,47 @@ const CLOSE_DISTANCE = 1 / 3; // of sheet height
|
|
|
18
20
|
const CLOSE_VELOCITY = 0.6; // px/ms, downward flick
|
|
19
21
|
const DRAWER = "dialog[data-drawer]";
|
|
20
22
|
|
|
23
|
+
let overlay;
|
|
24
|
+
const dismissCleanup = new WeakMap();
|
|
25
|
+
|
|
26
|
+
const overlayElement = () => {
|
|
27
|
+
if (overlay) return overlay;
|
|
28
|
+
overlay = document.createElement("div");
|
|
29
|
+
overlay.setAttribute("data-mica-drawer-overlay", "");
|
|
30
|
+
overlay.setAttribute("aria-hidden", "true");
|
|
31
|
+
overlay.inert = true;
|
|
32
|
+
overlay.style.setProperty("--m-drawer-overlay-opacity", "0");
|
|
33
|
+
document.body.append(overlay);
|
|
34
|
+
// Commit the transparent state before the first open transition.
|
|
35
|
+
overlay.getBoundingClientRect();
|
|
36
|
+
return overlay;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const setOverlayOpacity = (value) => {
|
|
40
|
+
if (!overlay && value === 0) return;
|
|
41
|
+
overlayElement().style.setProperty(
|
|
42
|
+
"--m-drawer-overlay-opacity",
|
|
43
|
+
String(Math.max(0, Math.min(1, value))),
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const syncOverlay = () => {
|
|
48
|
+
const openDrawer = document.querySelector(`${DRAWER}[open]`);
|
|
49
|
+
// iOS can drop the closed dialog before its translate transition runs,
|
|
50
|
+
// so transitionend never clears the drag-dismiss inline style. Clear a
|
|
51
|
+
// pending exit synchronously on reopen, before the next frame paints.
|
|
52
|
+
if (openDrawer) dismissCleanup.get(openDrawer)?.();
|
|
53
|
+
setOverlayOpacity(openDrawer ? 1 : 0);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
new MutationObserver(syncOverlay).observe(document.documentElement, {
|
|
57
|
+
attributes: true,
|
|
58
|
+
attributeFilter: ["open"],
|
|
59
|
+
subtree: true,
|
|
60
|
+
});
|
|
61
|
+
|
|
21
62
|
document.documentElement.setAttribute("data-drawer-gestures", "");
|
|
63
|
+
syncOverlay();
|
|
22
64
|
|
|
23
65
|
document.addEventListener("pointerdown", (down) => {
|
|
24
66
|
if (!SHEET.matches) return;
|
|
@@ -40,7 +82,10 @@ document.addEventListener("pointerdown", (down) => {
|
|
|
40
82
|
|
|
41
83
|
dialog.setPointerCapture(down.pointerId);
|
|
42
84
|
const baseTransition = dialog.style.transition;
|
|
85
|
+
const scrim = overlayElement();
|
|
86
|
+
const baseOverlayTransition = scrim.style.transition;
|
|
43
87
|
dialog.style.transition = "none";
|
|
88
|
+
scrim.style.transition = "none";
|
|
44
89
|
|
|
45
90
|
const move = (e) => {
|
|
46
91
|
dy = e.clientY - down.clientY;
|
|
@@ -49,6 +94,9 @@ document.addEventListener("pointerdown", (down) => {
|
|
|
49
94
|
lastT = e.timeStamp;
|
|
50
95
|
// follow downward; resist upward
|
|
51
96
|
dialog.style.translate = `0 ${dy > 0 ? dy : dy / 4}px`;
|
|
97
|
+
// Clear by half a sheet: a one-to-one distance mapping was too subtle
|
|
98
|
+
// against dark surfaces (a quarter pull only moved alpha .4 → .3).
|
|
99
|
+
setOverlayOpacity(1 - Math.max(0, dy) / (rect.height / 2));
|
|
52
100
|
};
|
|
53
101
|
|
|
54
102
|
const up = () => {
|
|
@@ -56,22 +104,38 @@ document.addEventListener("pointerdown", (down) => {
|
|
|
56
104
|
dialog.removeEventListener("pointerup", up);
|
|
57
105
|
dialog.removeEventListener("pointercancel", up);
|
|
58
106
|
dialog.style.transition = baseTransition;
|
|
107
|
+
scrim.style.transition = baseOverlayTransition;
|
|
59
108
|
if (dy > rect.height * CLOSE_DISTANCE || velocity > CLOSE_VELOCITY) {
|
|
60
109
|
// continue the motion DOWNWARD off-screen. close() fires its exit
|
|
61
|
-
// (
|
|
110
|
+
// (scrim fade + display flip at the end via allow-discrete);
|
|
62
111
|
// the inline translate overrides the small CSS exit offset so the
|
|
63
112
|
// sheet keeps sliding down instead of snapping back up into view
|
|
64
113
|
// (the flash). Clear it only at transitionend — display:none by
|
|
65
|
-
// then, so clearing is invisible
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
114
|
+
// then, so clearing is invisible. iOS may never fire that event,
|
|
115
|
+
// so a timeout clears it while closed and syncOverlay clears it if
|
|
116
|
+
// the drawer reopens first.
|
|
117
|
+
let cleanupTimer;
|
|
118
|
+
const onTransitionEnd = (e) => {
|
|
119
|
+
if (e.propertyName === "translate") clearDismiss();
|
|
120
|
+
};
|
|
121
|
+
const clearDismiss = () => {
|
|
122
|
+
if (dismissCleanup.get(dialog) !== clearDismiss) return;
|
|
123
|
+
clearTimeout(cleanupTimer);
|
|
124
|
+
dialog.removeEventListener("transitionend", onTransitionEnd);
|
|
125
|
+
dialog.style.translate = "";
|
|
126
|
+
dismissCleanup.delete(dialog);
|
|
127
|
+
};
|
|
128
|
+
dismissCleanup.set(dialog, clearDismiss);
|
|
129
|
+
dialog.addEventListener("transitionend", onTransitionEnd);
|
|
130
|
+
cleanupTimer = setTimeout(() => {
|
|
131
|
+
if (!dialog.open) clearDismiss();
|
|
132
|
+
}, 200);
|
|
133
|
+
setOverlayOpacity(0);
|
|
71
134
|
requestAnimationFrame(() => { dialog.style.translate = "0 100dvh"; });
|
|
72
135
|
dialog.close();
|
|
73
136
|
} else {
|
|
74
137
|
dialog.style.translate = ""; // spring back
|
|
138
|
+
setOverlayOpacity(1);
|
|
75
139
|
}
|
|
76
140
|
};
|
|
77
141
|
|
package/mica.css
CHANGED
|
@@ -771,6 +771,82 @@
|
|
|
771
771
|
--m-badge-numerals: tabular-nums;
|
|
772
772
|
}
|
|
773
773
|
|
|
774
|
+
/* --- avatar: authored initials, optionally Blobatar-painted -------- */
|
|
775
|
+
:where(m-avatar) {
|
|
776
|
+
display: inline-grid;
|
|
777
|
+
place-items: center;
|
|
778
|
+
position: relative;
|
|
779
|
+
flex: none;
|
|
780
|
+
box-sizing: border-box;
|
|
781
|
+
inline-size: var(--avatar-size, 3rem);
|
|
782
|
+
block-size: var(--avatar-size, 3rem);
|
|
783
|
+
overflow: hidden;
|
|
784
|
+
border-radius: var(--radius-md);
|
|
785
|
+
background: var(--color-surface-raised);
|
|
786
|
+
color: var(--m-avatar-content-color, var(--color-text));
|
|
787
|
+
font-size: calc(var(--avatar-size, 3rem) * 0.5);
|
|
788
|
+
font-weight: 600;
|
|
789
|
+
line-height: 1;
|
|
790
|
+
text-transform: uppercase;
|
|
791
|
+
vertical-align: middle;
|
|
792
|
+
white-space: nowrap;
|
|
793
|
+
user-select: none;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
:where(m-avatar > img) {
|
|
797
|
+
position: absolute;
|
|
798
|
+
inset: 0;
|
|
799
|
+
z-index: 1;
|
|
800
|
+
inline-size: 100%;
|
|
801
|
+
block-size: 100%;
|
|
802
|
+
object-fit: cover;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/* Attribute selectors only transport values. The pseudo rules below
|
|
806
|
+
render them, so missing avatar.js leaves authored initials untouched. */
|
|
807
|
+
:where(m-avatar[blobatar]) {
|
|
808
|
+
--m-avatar-decoration: "";
|
|
809
|
+
--m-avatar-render-inset-block: 0;
|
|
810
|
+
--m-avatar-render-inset-inline: 0;
|
|
811
|
+
--m-avatar-render-x: 0;
|
|
812
|
+
--m-avatar-render-y: 0;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
:where(m-avatar[blobatar][contained]) {
|
|
816
|
+
--m-avatar-render-inset-block: var(--m-avatar-body-inset-block);
|
|
817
|
+
--m-avatar-render-inset-inline: var(--m-avatar-body-inset-inline);
|
|
818
|
+
--m-avatar-render-x: var(--m-avatar-body-x);
|
|
819
|
+
--m-avatar-render-y: var(--m-avatar-body-y);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
:where(m-avatar)::before {
|
|
823
|
+
content: var(--m-avatar-decoration, none);
|
|
824
|
+
position: absolute;
|
|
825
|
+
inset-block: var(--m-avatar-render-inset-block);
|
|
826
|
+
inset-inline: var(--m-avatar-render-inset-inline);
|
|
827
|
+
border-radius: inherit;
|
|
828
|
+
background: var(--m-avatar-body-color);
|
|
829
|
+
transform: translate(var(--m-avatar-render-x), var(--m-avatar-render-y));
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
:where(m-avatar)::after {
|
|
833
|
+
content: var(--m-avatar-decoration, none);
|
|
834
|
+
position: absolute;
|
|
835
|
+
inset: 0;
|
|
836
|
+
background:
|
|
837
|
+
radial-gradient(
|
|
838
|
+
ellipse var(--m-avatar-eye-width) var(--m-avatar-eye-height)
|
|
839
|
+
at var(--m-avatar-eye-left) var(--m-avatar-eye-top),
|
|
840
|
+
var(--m-avatar-eye-color) 98%, transparent 100%
|
|
841
|
+
),
|
|
842
|
+
radial-gradient(
|
|
843
|
+
ellipse var(--m-avatar-eye-width) var(--m-avatar-eye-height)
|
|
844
|
+
at calc(var(--m-avatar-eye-left) + var(--m-avatar-eye-gap))
|
|
845
|
+
calc(var(--m-avatar-eye-top) + var(--m-avatar-eye-drop)),
|
|
846
|
+
var(--m-avatar-eye-color) 98%, transparent 100%
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
|
|
774
850
|
/* --- content: tables, lists, code, quotes ------------------------ */
|
|
775
851
|
:where(table) {
|
|
776
852
|
inline-size: 100%;
|
|
@@ -1132,6 +1208,14 @@
|
|
|
1132
1208
|
border-block-start: 0;
|
|
1133
1209
|
}
|
|
1134
1210
|
|
|
1211
|
+
/* Internal visual layer injected by drawer.js. It stays ordinary page
|
|
1212
|
+
content beneath the dialog's transparent top-layer ::backdrop, so it
|
|
1213
|
+
survives iOS removing that backdrop on close. Hidden outside the
|
|
1214
|
+
mobile-sheet breakpoint; aria-hidden + inert are set by the module. */
|
|
1215
|
+
:where([data-mica-drawer-overlay]) {
|
|
1216
|
+
display: none;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1135
1219
|
/* small screens: the drawer becomes a bottom sheet — shadcn's own
|
|
1136
1220
|
mobile drawer (vaul/radix) is exactly this. Anchored to the bottom
|
|
1137
1221
|
edge via auto top margin: bottom attachment is the one thing iOS
|
|
@@ -1148,7 +1232,7 @@
|
|
|
1148
1232
|
max-inline-size: none;
|
|
1149
1233
|
border-inline-start: 0;
|
|
1150
1234
|
border-block-start: 1px solid var(--color-border);
|
|
1151
|
-
/*
|
|
1235
|
+
/* CSS-only fallback: two shadows painted by the SHEET so they
|
|
1152
1236
|
persist through the exit (display:allow-discrete keeps the box
|
|
1153
1237
|
rendered) — unlike ::backdrop, which iOS drops from the top
|
|
1154
1238
|
layer the instant you close, cutting the scrim. So the dim
|
|
@@ -1184,8 +1268,28 @@
|
|
|
1184
1268
|
0 0 0 100vmax oklch(0 0 0 / 0);
|
|
1185
1269
|
}
|
|
1186
1270
|
}
|
|
1187
|
-
/*
|
|
1188
|
-
|
|
1271
|
+
/* With drawer.js, keep only the bleed slab; its ordinary fixed
|
|
1272
|
+
overlay owns the visible scrim and follows drag progress. */
|
|
1273
|
+
:where(:root[data-drawer-gestures])
|
|
1274
|
+
:where(dialog[data-drawer]) {
|
|
1275
|
+
box-shadow: 0 4rem 0 0 var(--color-surface-overlay);
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
:where([data-mica-drawer-overlay]) {
|
|
1279
|
+
display: block;
|
|
1280
|
+
position: fixed;
|
|
1281
|
+
inset: 0;
|
|
1282
|
+
z-index: 50;
|
|
1283
|
+
inline-size: 100lvw;
|
|
1284
|
+
block-size: 100lvh;
|
|
1285
|
+
background: oklch(0 0 0 / 0.4);
|
|
1286
|
+
opacity: var(--m-drawer-overlay-opacity, 0);
|
|
1287
|
+
pointer-events: none;
|
|
1288
|
+
transition: opacity 0.15s;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
/* The fallback shadow or managed overlay replaces ::backdrop here;
|
|
1292
|
+
keep it clear so the scrims don't double up or flash on iOS. */
|
|
1189
1293
|
:where(dialog[data-drawer][open])::backdrop {
|
|
1190
1294
|
background: oklch(0 0 0 / 0);
|
|
1191
1295
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akonwi/mica",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Custom elements. Native behavior. Nearly no JavaScript.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"css",
|
|
@@ -21,19 +21,46 @@
|
|
|
21
21
|
"exports": {
|
|
22
22
|
".": "./mica.css",
|
|
23
23
|
"./mica.css": "./mica.css",
|
|
24
|
-
"./
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"./
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
"./avatar.js": {
|
|
25
|
+
"types": "./avatar.d.ts",
|
|
26
|
+
"default": "./avatar.js"
|
|
27
|
+
},
|
|
28
|
+
"./invoker.js": {
|
|
29
|
+
"types": "./invoker.d.ts",
|
|
30
|
+
"default": "./invoker.js"
|
|
31
|
+
},
|
|
32
|
+
"./drawer.js": {
|
|
33
|
+
"types": "./drawer.d.ts",
|
|
34
|
+
"default": "./drawer.js"
|
|
35
|
+
},
|
|
36
|
+
"./field.js": {
|
|
37
|
+
"types": "./field.d.ts",
|
|
38
|
+
"default": "./field.js"
|
|
39
|
+
},
|
|
40
|
+
"./select.js": {
|
|
41
|
+
"types": "./select.d.ts",
|
|
42
|
+
"default": "./select.js"
|
|
43
|
+
},
|
|
44
|
+
"./tabs.js": {
|
|
45
|
+
"types": "./tabs.d.ts",
|
|
46
|
+
"default": "./tabs.js"
|
|
47
|
+
},
|
|
48
|
+
"./toast.js": {
|
|
49
|
+
"types": "./toast.d.ts",
|
|
50
|
+
"default": "./toast.js"
|
|
51
|
+
},
|
|
52
|
+
"./combobox.js": {
|
|
53
|
+
"types": "./combobox.d.ts",
|
|
54
|
+
"default": "./combobox.js"
|
|
55
|
+
},
|
|
31
56
|
"./types/react": {
|
|
32
57
|
"types": "./types/react.d.ts"
|
|
33
58
|
}
|
|
34
59
|
},
|
|
35
60
|
"files": [
|
|
36
61
|
"mica.css",
|
|
62
|
+
"avatar.js",
|
|
63
|
+
"avatar.d.ts",
|
|
37
64
|
"invoker.js",
|
|
38
65
|
"invoker.d.ts",
|
|
39
66
|
"drawer.js",
|
|
@@ -48,16 +75,22 @@
|
|
|
48
75
|
"toast.d.ts",
|
|
49
76
|
"combobox.js",
|
|
50
77
|
"combobox.d.ts",
|
|
51
|
-
"types/react.d.ts"
|
|
78
|
+
"types/react.d.ts",
|
|
79
|
+
"THIRD_PARTY_LICENSES.md"
|
|
52
80
|
],
|
|
53
81
|
"sideEffects": true,
|
|
54
82
|
"devDependencies": {
|
|
55
83
|
"axe-core": "^4.12.1",
|
|
84
|
+
"blobatar": "2.4.0",
|
|
56
85
|
"pixelmatch": "^7.2.0",
|
|
57
86
|
"playwright": "^1.61.1",
|
|
58
87
|
"pngjs": "^7.0.0"
|
|
59
88
|
},
|
|
60
89
|
"scripts": {
|
|
90
|
+
"build:avatar": "bun tools/build-avatar.ts",
|
|
91
|
+
"docs:build": "bun tools/build-docs.ts",
|
|
92
|
+
"docs:check": "bun tools/build-docs.ts --check",
|
|
93
|
+
"docs:serve": "bun run docs:build && bun tools/serve.ts",
|
|
61
94
|
"snapshot": "bun tools/snapshot.ts",
|
|
62
95
|
"snapshot:check": "bun tools/snapshot.ts --check",
|
|
63
96
|
"snapshot:setup": "playwright install chromium webkit"
|
package/types/react.d.ts
CHANGED
|
@@ -56,6 +56,10 @@ declare module 'react' {
|
|
|
56
56
|
variant?: 'primary' | 'success' | 'warning' | 'danger'
|
|
57
57
|
count?: boolean
|
|
58
58
|
}>
|
|
59
|
+
'm-avatar': MicaElement<{
|
|
60
|
+
blobatar?: string
|
|
61
|
+
contained?: boolean
|
|
62
|
+
}>
|
|
59
63
|
'm-toast': MicaElement<{
|
|
60
64
|
popover?: 'manual'
|
|
61
65
|
variant?: 'success' | 'warning' | 'danger'
|