@antadesign/anta 0.3.21 → 0.3.23

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.
Files changed (75) hide show
  1. package/dist/avatar-core.d.ts +160 -0
  2. package/dist/avatar-core.js +271 -0
  3. package/dist/bundle.css +1 -0
  4. package/dist/bundle.d.ts +11 -0
  5. package/dist/bundle.js +1239 -0
  6. package/dist/components/Avatar.d.ts +63 -0
  7. package/dist/components/Avatar.js +29 -0
  8. package/dist/components/Button.d.ts +1 -1
  9. package/dist/components/Button.js +12 -5
  10. package/dist/components/Checkbox.js +6 -2
  11. package/dist/components/Input.d.ts +2 -1
  12. package/dist/components/Input.js +1 -1
  13. package/dist/components/InputDate.js +1 -0
  14. package/dist/components/Loader.d.ts +5 -1
  15. package/dist/components/Loader.js +2 -1
  16. package/dist/components/Menu.d.ts +6 -1
  17. package/dist/components/Menu.js +2 -0
  18. package/dist/components/RadioGroup.js +21 -5
  19. package/dist/components/Select.d.ts +3 -1
  20. package/dist/components/Select.js +1 -0
  21. package/dist/components/SelectFaceted.d.ts +4 -2
  22. package/dist/components/Slider.d.ts +1 -1
  23. package/dist/components/Steps.css +1 -0
  24. package/dist/components/Steps.d.ts +93 -0
  25. package/dist/components/Steps.js +128 -0
  26. package/dist/components/Tabs.d.ts +10 -5
  27. package/dist/components/Tabs.js +5 -2
  28. package/dist/components/Tooltip.d.ts +5 -5
  29. package/dist/elements/a-avatar.css +1 -0
  30. package/dist/elements/a-avatar.d.ts +31 -0
  31. package/dist/elements/a-avatar.js +98 -0
  32. package/dist/elements/a-checkbox.css +1 -1
  33. package/dist/elements/a-icon.shapes.css +1 -1
  34. package/dist/elements/a-icon.shapes.d.ts +1 -8
  35. package/dist/elements/a-icon.shapes.js +0 -14
  36. package/dist/elements/a-input-time.css +1 -1
  37. package/dist/elements/a-input-time.js +1 -1
  38. package/dist/elements/a-input.css +1 -1
  39. package/dist/elements/a-input.js +1 -1
  40. package/dist/elements/a-loader.css +1 -1
  41. package/dist/elements/a-menu.d.ts +4 -2
  42. package/dist/elements/a-menu.js +122 -4
  43. package/dist/elements/a-radio.css +1 -1
  44. package/dist/elements/a-slider.css +1 -1
  45. package/dist/elements/a-switch.css +1 -1
  46. package/dist/elements/a-tabs.css +1 -1
  47. package/dist/elements/a-tooltip.d.ts +4 -4
  48. package/dist/elements/a-tooltip.js +18 -15
  49. package/dist/elements/index.d.ts +1 -0
  50. package/dist/elements/index.js +3 -0
  51. package/dist/general_types.d.ts +42 -0
  52. package/dist/index.d.ts +6 -0
  53. package/dist/index.js +28 -0
  54. package/dist/jsx-runtime.d.ts +6 -1
  55. package/dist/theme-anta.css +1 -1
  56. package/docs/changelog.md +68 -0
  57. package/docs/comparison.md +2 -2
  58. package/docs/components/banner.md +1 -1
  59. package/docs/components/button.md +3 -3
  60. package/docs/components/card.md +1 -1
  61. package/docs/components/expander.md +1 -1
  62. package/docs/components/input.md +3 -3
  63. package/docs/components/loader.md +1 -0
  64. package/docs/components/menu.md +34 -1
  65. package/docs/components/radio.md +1 -1
  66. package/docs/components/select-faceted.md +4 -2
  67. package/docs/components/select.md +3 -1
  68. package/docs/components/slider.md +1 -1
  69. package/docs/components/steps.md +324 -0
  70. package/docs/components/tabs.md +24 -8
  71. package/docs/components/tag.md +3 -3
  72. package/docs/components/tooltip.md +5 -5
  73. package/docs/index.md +1 -0
  74. package/docs/install-config.md +17 -0
  75. package/package.json +9 -3
@@ -0,0 +1,160 @@
1
+ /**
2
+ * avatar-core — pure, framework-free generation for the `<Avatar>` userpic.
3
+ *
4
+ * No DOM, no React. An alphanumeric seed string is hashed to drive a
5
+ * deterministic PRNG, which samples each enabled generation dimension within its
6
+ * configured space. The result is a renderer-agnostic description (colors +
7
+ * geometry, or an initials fallback) that `avatarToSvg` turns into SVG markup.
8
+ * `<a-avatar>` uses it to render inside its shadow root; a non-React consumer can
9
+ * call the same functions. Published as `@antadesign/anta` named exports.
10
+ *
11
+ * Same `(config, seed)` always yields the same avatar, so a user's picture is
12
+ * stable and different users are visually distinguishable.
13
+ */
14
+ /** Hash an arbitrary string to a 32-bit unsigned integer (xmur3 finalizer). */
15
+ export declare function hashSeed(str: string): number;
16
+ /** A deterministic `() => [0, 1)` generator seeded by a 32-bit integer. */
17
+ export declare function mulberry32(seed: number): () => number;
18
+ /** Build a PRNG straight from a seed string. */
19
+ export declare const rngFromSeed: (seed: string) => (() => number);
20
+ /**
21
+ * Up to `max` letters for the initials fallback: the first letter of each of the
22
+ * first `max` whitespace-separated words, uppercased. `"Vlad Korobov"` → `"VK"`.
23
+ */
24
+ export declare function getInitials(name: string | undefined | null, max?: number): string;
25
+ /** Largest in-sRGB chroma at this lightness and hue (binary search). */
26
+ export declare function maxChroma(L: number, H: number): number;
27
+ /** `oklch(L C H)` with chroma clamped into sRGB. */
28
+ export declare function oklchString(L: number, C: number, H: number): string;
29
+ /** Approximate OKLCH lightness of a CSS color — reads the `L` from an
30
+ * `oklch(L C H)` string, or estimates it from a `#rgb` / `#rrggbb` hex; falls
31
+ * back to a mid value for anything else. Used only to pick readable initials. */
32
+ export declare function colorLightness(color: string): number;
33
+ /** How a dimension varies with the seed.
34
+ * - `off` — excluded; the dimension holds its neutral default.
35
+ * - `any` — varies across its full natural range.
36
+ * - `range` — varies within an explicit `[min, max]`.
37
+ * - `list` — picks one value at random from an explicit set. */
38
+ export type DimMode = 'off' | 'any' | 'range' | 'list';
39
+ /** A numeric dimension (scale, angle, corner radius). */
40
+ export interface ScalarDim {
41
+ mode?: DimMode;
42
+ /** RANGE bounds (defaults to the dimension's natural range when omitted). */
43
+ min?: number;
44
+ max?: number;
45
+ /** LIST of allowed values the seed picks from. */
46
+ values?: number[];
47
+ }
48
+ /** The figure's x/y offset from center (viewBox units, −50…50). */
49
+ export interface Vec2Dim {
50
+ mode?: DimMode;
51
+ /** RANGE bounds per axis. */
52
+ x?: [number, number];
53
+ y?: [number, number];
54
+ /** LIST of allowed `[x, y]` points. */
55
+ values?: [number, number][];
56
+ }
57
+ /**
58
+ * A color dimension. RANGE caps OKLCH `l`/`c`/`h` (chroma clamped in-gamut);
59
+ * LIST passes an explicit palette (a brand scheme) the seed picks from.
60
+ */
61
+ export interface ColorDim {
62
+ mode?: DimMode;
63
+ /** RANGE OKLCH bounds `[min, max]` (defaults to the natural range). */
64
+ l?: [number, number];
65
+ c?: [number, number];
66
+ h?: [number, number];
67
+ /** LIST of CSS colors the seed picks from (a palette / scheme). */
68
+ values?: string[];
69
+ }
70
+ /** Full generation config. Every field is optional; `DEFAULT_CONFIG` fills gaps. */
71
+ export interface AvatarGenConfig {
72
+ bgColor?: ColorDim;
73
+ headColor?: ColorDim;
74
+ bodyColor?: ColorDim;
75
+ figureScale?: ScalarDim;
76
+ figureTranslate?: Vec2Dim;
77
+ figureAngle?: ScalarDim;
78
+ headAngle?: ScalarDim;
79
+ bodyAngle?: ScalarDim;
80
+ /** Space between head and body, as a fraction of head height (0 to 1). */
81
+ figureGap?: ScalarDim;
82
+ /** Head top corner radius. 0 is a square corner and 1 is fully round; above 1
83
+ * the head elongates upward into an oval. */
84
+ headRadiusTop?: ScalarDim;
85
+ /** Head bottom corner radius, on the same 0-to-1-and-beyond scale. The bottom
86
+ * is never rounded less than the top — a head reads with its jaw at least as
87
+ * round as its crown — so a smaller value here is raised to match the top. */
88
+ headRadiusBottom?: ScalarDim;
89
+ /** Shoulder corner radius, 0 (square) to 1 (fully round). */
90
+ bodyBorderRadius?: ScalarDim;
91
+ /** Derive `headColor` and `bodyColor` hue from the background so the parts
92
+ * stay coordinated with the background. */
93
+ harmony?: boolean;
94
+ }
95
+ /** A config that produces a pleasant, fully-varied figure avatar. */
96
+ export declare const DEFAULT_CONFIG: Required<Omit<AvatarGenConfig, 'harmony'>> & {
97
+ harmony: boolean;
98
+ };
99
+ export interface ResolvedHead {
100
+ color: string;
101
+ /** Top corner radius in user units. At `HEAD_SIZE / 2` the top is a
102
+ * semicircle; equal to `radiusBottom` at that value the head is a circle. */
103
+ radiusTop: number;
104
+ /** Bottom corner radius in user units. */
105
+ radiusBottom: number;
106
+ /** How far the head's top edge extends past the base box, in user units. A
107
+ * radius above 1 spends the excess here, elongating the head into an oval. */
108
+ extendTop: number;
109
+ /** How far the head's bottom edge extends past the base box. */
110
+ extendBottom: number;
111
+ angle: number;
112
+ }
113
+ export interface ResolvedBody {
114
+ color: string;
115
+ /** Corner radius in user units (half the body's width is fully round). */
116
+ radius: number;
117
+ /** Body height in user units — one frame height as *rendered*, so the
118
+ * silhouette always runs past the bottom edge however far down its top starts
119
+ * (a wide gap, an elongated head) and however much the figure shrinks. */
120
+ height: number;
121
+ angle: number;
122
+ }
123
+ /** Renderer-agnostic description of one avatar. */
124
+ export interface ResolvedAvatar {
125
+ /** Ink for the initials fallback: the background's own hue at a text-like
126
+ * lightness, so the letters read as tinted ink on the generated color. It is
127
+ * derived from the background rather than a theme text token because the
128
+ * background does not follow the theme — a theme token would go light-on-light
129
+ * in dark mode. */
130
+ initialsColor: string;
131
+ /** `true` for a generated head + shoulders figure; `false` for the initials
132
+ * fallback (no figure shape was configured). */
133
+ figure: boolean;
134
+ bg: string;
135
+ bgLightness: number;
136
+ scale: number;
137
+ translate: [number, number];
138
+ angle: number;
139
+ /** Space between head and body in user units. */
140
+ gap: number;
141
+ head: ResolvedHead;
142
+ body: ResolvedBody;
143
+ }
144
+ /** A figure is drawn when at least one shape dimension is enabled. */
145
+ export declare function hasFigure(config: AvatarGenConfig): boolean;
146
+ /**
147
+ * Resolve a config + seed to a concrete avatar. Deterministic: the same inputs
148
+ * always return the same result. Dimensions are sampled in a fixed order so a
149
+ * config change to one dimension does not shuffle the others.
150
+ */
151
+ export declare function resolveAvatar(config: AvatarGenConfig, seed: string): ResolvedAvatar;
152
+ export interface SvgOptions {
153
+ /** Initials to draw when `resolved.figure` is false. */
154
+ initials?: string;
155
+ /** Accessible title placed in the SVG. */
156
+ title?: string;
157
+ }
158
+ /** Build SVG markup for a resolved avatar. Fills a 0–100 square; the host clips
159
+ * it to the container shape (round or rounded-square) via `overflow: hidden`. */
160
+ export declare function avatarToSvg(resolved: ResolvedAvatar, opts?: SvgOptions): string;
@@ -0,0 +1,271 @@
1
+ function hashSeed(str) {
2
+ let h = 1779033703 ^ str.length;
3
+ for (let i = 0; i < str.length; i++) {
4
+ h = Math.imul(h ^ str.charCodeAt(i), 3432918353);
5
+ h = h << 13 | h >>> 19;
6
+ }
7
+ h = Math.imul(h ^ h >>> 16, 2246822507);
8
+ h = Math.imul(h ^ h >>> 13, 3266489909);
9
+ return (h ^= h >>> 16) >>> 0;
10
+ }
11
+ function mulberry32(seed) {
12
+ let a = seed >>> 0;
13
+ return () => {
14
+ a = a + 1831565813 | 0;
15
+ let t = Math.imul(a ^ a >>> 15, 1 | a);
16
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
17
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
18
+ };
19
+ }
20
+ const rngFromSeed = (seed) => mulberry32(hashSeed(seed));
21
+ function getInitials(name, max = 3) {
22
+ if (!name) return "";
23
+ return name.trim().split(/\s+/).filter(Boolean).slice(0, max).map((w) => [...w][0].toUpperCase()).join("");
24
+ }
25
+ const C_CEIL = 0.4;
26
+ function oklchToLinear(L, C, H) {
27
+ const hr = H * Math.PI / 180;
28
+ const a = C * Math.cos(hr);
29
+ const b = C * Math.sin(hr);
30
+ const l_ = (L + 0.3963377774 * a + 0.2158037573 * b) ** 3;
31
+ const m_ = (L - 0.1055613458 * a - 0.0638541728 * b) ** 3;
32
+ const s_ = (L - 0.0894841775 * a - 1.291485548 * b) ** 3;
33
+ return [
34
+ 4.0767416621 * l_ - 3.3077115913 * m_ + 0.2309699292 * s_,
35
+ -1.2684380046 * l_ + 2.6097574011 * m_ - 0.3413193965 * s_,
36
+ -0.0041960863 * l_ - 0.7034186147 * m_ + 1.707614701 * s_
37
+ ];
38
+ }
39
+ function inGamut(L, C, H) {
40
+ const [r, g, b] = oklchToLinear(L, C, H);
41
+ const e = 8e-4;
42
+ return r >= -e && r <= 1 + e && g >= -e && g <= 1 + e && b >= -e && b <= 1 + e;
43
+ }
44
+ function maxChroma(L, H) {
45
+ let lo = 0;
46
+ let hi = C_CEIL;
47
+ for (let i = 0; i < 18; i++) {
48
+ const m = (lo + hi) / 2;
49
+ if (inGamut(L, m, H)) lo = m;
50
+ else hi = m;
51
+ }
52
+ return lo;
53
+ }
54
+ const round = (n, d = 3) => Math.round(n * 10 ** d) / 10 ** d;
55
+ function oklchString(L, C, H) {
56
+ const c = Math.min(C, maxChroma(L, H));
57
+ return `oklch(${round(L)} ${round(c)} ${round((H % 360 + 360) % 360, 1)})`;
58
+ }
59
+ function colorLightness(color) {
60
+ const c = color.trim();
61
+ const ok = /^oklch\(\s*([\d.]+)/i.exec(c);
62
+ if (ok) return Math.max(0, Math.min(1, parseFloat(ok[1])));
63
+ const hex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(c);
64
+ if (hex) {
65
+ let h = hex[1];
66
+ if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
67
+ const r = parseInt(h.slice(0, 2), 16) / 255;
68
+ const g = parseInt(h.slice(2, 4), 16) / 255;
69
+ const b = parseInt(h.slice(4, 6), 16) / 255;
70
+ return 0.299 * r + 0.587 * g + 0.114 * b;
71
+ }
72
+ return 0.6;
73
+ }
74
+ const NATURAL = {
75
+ figureScale: { range: [0.76, 1.2], off: 1 },
76
+ figureAngle: { range: [-15, 15], off: 0 },
77
+ headAngle: { range: [-20, 20], off: 0 },
78
+ bodyAngle: { range: [-16, 16], off: 0 },
79
+ figureGap: { range: [0.12, 0.42], off: 0.25 },
80
+ headRadiusTop: { range: [0.35, 1.25], off: 1, bias: 0.6 },
81
+ headRadiusBottom: { range: [0.45, 1.5], off: 1, bias: 0.6 },
82
+ bodyBorderRadius: { range: [0.25, 1], off: 1 }
83
+ };
84
+ const TRANSLATE_NATURAL = {
85
+ x: [-15, 15],
86
+ y: [-14, 4],
87
+ off: [0, 0]
88
+ };
89
+ const COLOR_NATURAL = {
90
+ bgColor: { l: [0.5, 0.82], c: [0.02, 0.12], h: [0, 360], off: "oklch(0.7 0 0)" },
91
+ headColor: { l: [0.78, 0.96], c: [0.015, 0.075], h: [0, 360], off: "oklch(0.9 0 0)" },
92
+ bodyColor: { l: [0.68, 0.9], c: [0.015, 0.09], h: [0, 360], off: "oklch(0.88 0 0)" }
93
+ };
94
+ const DEFAULT_CONFIG = {
95
+ bgColor: { mode: "any" },
96
+ headColor: { mode: "any" },
97
+ bodyColor: { mode: "any" },
98
+ figureScale: { mode: "any" },
99
+ figureTranslate: { mode: "any" },
100
+ figureAngle: { mode: "any" },
101
+ headAngle: { mode: "any" },
102
+ bodyAngle: { mode: "any" },
103
+ figureGap: { mode: "any" },
104
+ headRadiusTop: { mode: "any" },
105
+ headRadiusBottom: { mode: "any" },
106
+ bodyBorderRadius: { mode: "any" },
107
+ harmony: true
108
+ };
109
+ const lerp = (t, a, b) => a + (b - a) * t;
110
+ const pickOf = (t, arr) => arr[Math.min(arr.length - 1, Math.floor(t * arr.length))];
111
+ function sampleScalar(rng, dim, meta) {
112
+ const mode = dim?.mode ?? "any";
113
+ if (mode === "off") return meta.off;
114
+ if (mode === "list" && dim?.values?.length) return pickOf(rng(), dim.values);
115
+ if (mode === "range") return lerp(rng(), dim?.min ?? meta.range[0], dim?.max ?? meta.range[1]);
116
+ return lerp(meta.bias ? rng() ** meta.bias : rng(), meta.range[0], meta.range[1]);
117
+ }
118
+ function sampleVec2(rng, dim) {
119
+ const mode = dim?.mode ?? "off";
120
+ if (mode === "off") return TRANSLATE_NATURAL.off;
121
+ if (mode === "list" && dim?.values?.length) return pickOf(rng(), dim.values);
122
+ const xr = mode === "range" ? dim?.x ?? TRANSLATE_NATURAL.x : TRANSLATE_NATURAL.x;
123
+ const yr = mode === "range" ? dim?.y ?? TRANSLATE_NATURAL.y : TRANSLATE_NATURAL.y;
124
+ return [lerp(rng(), xr[0], xr[1]), lerp(rng(), yr[0], yr[1])];
125
+ }
126
+ function sampleColor(rng, dim, natural, forcedHue) {
127
+ const mode = dim?.mode ?? "any";
128
+ if (mode === "off") return { color: natural.off, l: colorLightness(natural.off) };
129
+ if (mode === "list" && dim?.values?.length) {
130
+ const color = pickOf(rng(), dim.values);
131
+ return { color, l: colorLightness(color) };
132
+ }
133
+ const lr = mode === "range" ? dim?.l ?? natural.l : natural.l;
134
+ const cr = mode === "range" ? dim?.c ?? natural.c : natural.c;
135
+ const hr = mode === "range" ? dim?.h ?? natural.h : natural.h;
136
+ const l = lerp(rng(), lr[0], lr[1]);
137
+ const c = lerp(rng(), cr[0], cr[1]);
138
+ const h = forcedHue ?? lerp(rng(), hr[0], hr[1]);
139
+ return { color: oklchString(l, c, h), l };
140
+ }
141
+ const FRAME = 100;
142
+ const CENTER = FRAME / 2;
143
+ const HEAD_SIZE = 40;
144
+ const BODY_W = 74;
145
+ const SHAPE_DIMS = ["headRadiusTop", "headRadiusBottom", "bodyBorderRadius"];
146
+ function hasFigure(config) {
147
+ return SHAPE_DIMS.some((k) => (config[k]?.mode ?? DEFAULT_CONFIG[k].mode) !== "off");
148
+ }
149
+ function resolveAvatar(config, seed) {
150
+ const cfg = { ...DEFAULT_CONFIG, ...config };
151
+ const rng = rngFromSeed(seed);
152
+ const bg = sampleColor(rng, cfg.bgColor, COLOR_NATURAL.bgColor);
153
+ const bgHue = readHue(bg.color);
154
+ const head = sampleColor(rng, cfg.headColor, COLOR_NATURAL.headColor, cfg.harmony ? bgHue : void 0);
155
+ const body = sampleColor(rng, cfg.bodyColor, COLOR_NATURAL.bodyColor, cfg.harmony ? bgHue : void 0);
156
+ const scale = sampleScalar(rng, cfg.figureScale, NATURAL.figureScale);
157
+ const translate = sampleVec2(rng, cfg.figureTranslate);
158
+ const angle = sampleScalar(rng, cfg.figureAngle, NATURAL.figureAngle);
159
+ const headAngle = sampleScalar(rng, cfg.headAngle, NATURAL.headAngle);
160
+ const bodyAngle = sampleScalar(rng, cfg.bodyAngle, NATURAL.bodyAngle);
161
+ const gapFrac = sampleScalar(rng, cfg.figureGap, NATURAL.figureGap);
162
+ const headTop = sampleScalar(rng, cfg.headRadiusTop, NATURAL.headRadiusTop);
163
+ const headBottom = sampleScalar(rng, cfg.headRadiusBottom, NATURAL.headRadiusBottom);
164
+ const bodyRadiusFrac = sampleScalar(rng, cfg.bodyBorderRadius, NATURAL.bodyBorderRadius);
165
+ const headUnit = HEAD_SIZE / 2;
166
+ const bodyUnit = BODY_W / 2;
167
+ const bodyHeight = FRAME / Math.min(1, scale);
168
+ const topFrac = headTop;
169
+ const bottomFrac = Math.max(headTop, headBottom);
170
+ const cornerTop = Math.min(topFrac, 1) * headUnit;
171
+ const cornerBottom = Math.min(bottomFrac, 1) * headUnit;
172
+ const extendTop = Math.max(0, topFrac - 1) * headUnit;
173
+ const extendBottom = Math.max(0, bottomFrac - 1) * headUnit;
174
+ return {
175
+ figure: hasFigure(config),
176
+ bg: bg.color,
177
+ bgLightness: bg.l,
178
+ initialsColor: initialsInk(bg.color, bg.l),
179
+ scale,
180
+ translate,
181
+ angle,
182
+ // The gap tracks the head's rendered height, so an elongated head keeps the
183
+ // same proportional distance from the shoulders.
184
+ gap: gapFrac * (HEAD_SIZE + extendTop + extendBottom),
185
+ head: {
186
+ color: head.color,
187
+ radiusTop: cornerTop,
188
+ radiusBottom: cornerBottom,
189
+ extendTop,
190
+ extendBottom,
191
+ angle: headAngle
192
+ },
193
+ body: { color: body.color, radius: bodyRadiusFrac * bodyUnit, height: bodyHeight, angle: bodyAngle }
194
+ };
195
+ }
196
+ function initialsInk(bg, bgL) {
197
+ const tinted = /^oklch\(/i.test(bg.trim());
198
+ const hue = tinted ? readHue(bg) : 0;
199
+ return bgL > 0.5 ? oklchString(0.35, tinted ? 0.06 : 0, hue) : oklchString(0.95, tinted ? 0.04 : 0, hue);
200
+ }
201
+ function readHue(color) {
202
+ const m = /^oklch\(\s*[\d.]+\s+[\d.]+\s+([\d.]+)/i.exec(color.trim());
203
+ return m ? parseFloat(m[1]) : 0;
204
+ }
205
+ const esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
206
+ function avatarToSvg(resolved, opts = {}) {
207
+ const bg = esc(resolved.bg);
208
+ const title = opts.title ? `<title>${esc(opts.title)}</title>` : "";
209
+ const bgRect = `<rect x="0" y="0" width="${FRAME}" height="${FRAME}" fill="${bg}"/>`;
210
+ if (!resolved.figure) {
211
+ const text = opts.initials ? `<text x="50" y="50" text-anchor="middle" dominant-baseline="central" font-family="var(--avatar-font, system-ui, sans-serif)" font-weight="500" font-size="${initialsFontSize(opts.initials.length)}" fill="var(--avatar-initials-color, ${esc(resolved.initialsColor)})">${esc(opts.initials)}</text>` : "";
212
+ return svgWrap(title + bgRect + text);
213
+ }
214
+ const [tx, ty] = resolved.translate;
215
+ const group = `translate(${round(tx, 2)} ${round(ty, 2)}) rotate(${round(resolved.angle, 2)} 50 50) translate(50 50) scale(${round(resolved.scale, 3)}) translate(-50 -50)`;
216
+ const { extendTop, extendBottom } = resolved.head;
217
+ const headTopEdge = CENTER - HEAD_SIZE / 2 - extendTop;
218
+ const headHeight = HEAD_SIZE + extendTop + extendBottom;
219
+ const headPath = roundedRectPath(
220
+ CENTER - HEAD_SIZE / 2,
221
+ headTopEdge,
222
+ HEAD_SIZE,
223
+ headHeight,
224
+ resolved.head.radiusTop,
225
+ resolved.head.radiusBottom
226
+ );
227
+ const headShape = `<g transform="rotate(${round(resolved.head.angle, 2)} 50 50)"><path d="${headPath}" fill="${esc(resolved.head.color)}"/></g>`;
228
+ const bodyTop = headTopEdge + headHeight + resolved.gap;
229
+ const bodyPath = roundedRectPath(CENTER - BODY_W / 2, bodyTop, BODY_W, resolved.body.height, resolved.body.radius, resolved.body.radius);
230
+ const bodyShape = `<g transform="rotate(${round(resolved.body.angle, 2)} 50 ${round(bodyTop, 2)})"><path d="${bodyPath}" fill="${esc(resolved.body.color)}"/></g>`;
231
+ return svgWrap(`${title}${bgRect}<g transform="${group}">${bodyShape}${headShape}</g>`);
232
+ }
233
+ function roundedRectPath(x, y, w, h, rTop, rBottom) {
234
+ let t = Math.max(0, Math.min(rTop, w / 2));
235
+ let b = Math.max(0, Math.min(rBottom, w / 2));
236
+ const sum = t + b;
237
+ if (sum > h && sum > 0) {
238
+ const k = h / sum;
239
+ t *= k;
240
+ b *= k;
241
+ }
242
+ const n = (v) => round(v, 2);
243
+ const arc = (r, ex, ey) => `A${n(r)} ${n(r)} 0 0 1 ${n(ex)} ${n(ey)}`;
244
+ return [
245
+ `M${n(x + t)} ${n(y)}`,
246
+ `H${n(x + w - t)}`,
247
+ t ? arc(t, x + w, y + t) : "",
248
+ `V${n(y + h - b)}`,
249
+ b ? arc(b, x + w - b, y + h) : "",
250
+ `H${n(x + b)}`,
251
+ b ? arc(b, x, y + h - b) : "",
252
+ `V${n(y + t)}`,
253
+ t ? arc(t, x + t, y) : "",
254
+ "Z"
255
+ ].filter(Boolean).join(" ");
256
+ }
257
+ const svgWrap = (inner) => `<svg viewBox="0 0 ${FRAME} ${FRAME}" width="100%" height="100%" preserveAspectRatio="xMidYMid slice" xmlns="http://www.w3.org/2000/svg" role="presentation">${inner}</svg>`;
258
+ const initialsFontSize = (len) => len >= 3 ? 30 : len === 2 ? 38 : 46;
259
+ export {
260
+ DEFAULT_CONFIG,
261
+ avatarToSvg,
262
+ colorLightness,
263
+ getInitials,
264
+ hasFigure,
265
+ hashSeed,
266
+ maxChroma,
267
+ mulberry32,
268
+ oklchString,
269
+ resolveAvatar,
270
+ rngFromSeed
271
+ };