@5even7/dlc-ui 0.2.18 → 0.2.20

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 (87) hide show
  1. package/CHANGELOG.md +158 -144
  2. package/EXTENDING.md +114 -0
  3. package/LICENSE +21 -21
  4. package/README.md +389 -338
  5. package/THIRD_PARTY_NOTICES.md +221 -0
  6. package/dist/avatar-creator/styles/humation.mjs +26 -0
  7. package/dist/avatar-creator/styles.mjs +76 -0
  8. package/dist/avatar-creator.cjs +7831 -0
  9. package/dist/avatar-creator.mjs +74 -0
  10. package/dist/capsule.cjs +185 -66
  11. package/dist/capsule.mjs +364 -1340
  12. package/dist/chunks/avatar-creator-DiAmhpRa.mjs +687 -0
  13. package/dist/chunks/color-DnmNceaf.mjs +350 -0
  14. package/dist/chunks/controls-B5_ctvvA.mjs +160 -0
  15. package/dist/chunks/copy-7J0XIj_0.mjs +13 -0
  16. package/dist/chunks/cosmic-fallback-v8G5_ibh.mjs +408 -0
  17. package/dist/chunks/emitter-CxLYMSbw.mjs +38 -0
  18. package/dist/chunks/emo-Dvahy5Qr.mjs +1140 -0
  19. package/dist/chunks/hash-BiO02UNb.mjs +38 -0
  20. package/dist/chunks/humation-B9OPUGL7.mjs +2987 -0
  21. package/dist/chunks/identicon-BUdMd-yf.mjs +3063 -0
  22. package/dist/chunks/index-BchW1tWE.mjs +478 -0
  23. package/dist/chunks/index-BtH8Lbra.mjs +296 -0
  24. package/dist/chunks/index-C02dvGLD.mjs +263 -0
  25. package/dist/chunks/index-CFybYBA1.mjs +278 -0
  26. package/dist/chunks/index-DgI872-7.mjs +241 -0
  27. package/dist/chunks/index-_sf_14Zu.mjs +455 -0
  28. package/dist/chunks/index-w5Pzdwhv.mjs +305 -0
  29. package/dist/chunks/motion-a8_f8_Ui.mjs +177 -0
  30. package/dist/chunks/number-BqWLX0iQ.mjs +231 -0
  31. package/dist/chunks/progress-KweS4Rx0.mjs +1836 -0
  32. package/dist/chunks/size-Bztk2ryJ.mjs +22 -0
  33. package/dist/chunks/wrapper-DXhy1E7Z.mjs +514 -0
  34. package/dist/color.cjs +186 -67
  35. package/dist/color.mjs +341 -1280
  36. package/dist/emo/miao/angry.mjs +4 -0
  37. package/dist/emo/miao/asleep.mjs +4 -0
  38. package/dist/emo/miao/badminton.mjs +4 -0
  39. package/dist/emo/miao/confident.mjs +4 -0
  40. package/dist/emo/miao/cry.mjs +4 -0
  41. package/dist/emo/miao/investigate.mjs +4 -0
  42. package/dist/emo/miao/laugh.mjs +4 -0
  43. package/dist/emo/miao/leisure.mjs +4 -0
  44. package/dist/emo/miao/mock.mjs +4 -0
  45. package/dist/emo/miao/music.mjs +4 -0
  46. package/dist/emo/miao/mute.mjs +4 -0
  47. package/dist/emo/miao/panic.mjs +4 -0
  48. package/dist/emo/miao/ponder.mjs +4 -0
  49. package/dist/emo/miao/question.mjs +4 -0
  50. package/dist/emo/miao/sad.mjs +4 -0
  51. package/dist/emo/miao/shocked.mjs +4 -0
  52. package/dist/emo/miao/shy.mjs +4 -0
  53. package/dist/emo/miao/sigh.mjs +4 -0
  54. package/dist/emo/miao/smile.mjs +4 -0
  55. package/dist/emo/miao/snigger.mjs +4 -0
  56. package/dist/emo/miao/static.mjs +4 -0
  57. package/dist/emo/miao/yawn.mjs +4 -0
  58. package/dist/emo/miao/yummy.mjs +4 -0
  59. package/dist/emo.cjs +27161 -25964
  60. package/dist/emo.mjs +8 -1421
  61. package/dist/index.cjs +32729 -24466
  62. package/dist/index.mjs +16 -4757
  63. package/dist/index.umd.js +33115 -24852
  64. package/dist/index.umd.min.js +3 -1
  65. package/dist/progress.cjs +195 -76
  66. package/dist/progress.mjs +7 -2448
  67. package/dist/vue2.cjs +33282 -25002
  68. package/dist/vue2.mjs +63 -5131
  69. package/dist/vue3.cjs +33282 -25002
  70. package/dist/vue3.mjs +53 -5122
  71. package/package.json +161 -136
  72. package/styles/avatar-creator.css +38 -0
  73. package/styles/base.css +32 -32
  74. package/styles/color.css +18 -18
  75. package/styles/emo.css +110 -89
  76. package/styles/progress.css +96 -96
  77. package/styles/theme.css +1 -0
  78. package/types/avatar-creator-styles.d.ts +10 -0
  79. package/types/avatar-creator.d.ts +143 -0
  80. package/types/capsule.d.ts +15 -15
  81. package/types/color.d.ts +15 -15
  82. package/types/emo-miao.d.ts +9 -0
  83. package/types/emo.d.ts +38 -37
  84. package/types/index.d.ts +593 -554
  85. package/types/progress.d.ts +16 -16
  86. package/types/vue2.d.ts +11 -10
  87. package/types/vue3.d.ts +169 -146
@@ -0,0 +1,350 @@
1
+ /**
2
+ * Color helpers. `normalizeColor` converts any supported CSS color
3
+ * (hex / named / rgb() / rgba() / hsl() / hsla()) into a 6-digit hex string,
4
+ * so renderers can keep working with #rrggbb only. rgba()/hsla() alpha is
5
+ * intentionally dropped: the WebGL shader has no per-color alpha channel, and
6
+ * the component is opaque by design — use component-level `opacity` for
7
+ * transparency.
8
+ *
9
+ * `normalizeColorWithAlpha` keeps the alpha channel: it returns a 6-digit hex
10
+ * when alpha is 1, an 8-digit #rrggbbaa hex when alpha < 1, and the literal
11
+ * string 'transparent' for `transparent`. Use it for renderers (e.g. SVG)
12
+ * that can represent per-color transparency.
13
+ */
14
+
15
+ const NAMED_COLORS = {
16
+ aliceblue: '#f0f8ff',
17
+ antiquewhite: '#faebd7',
18
+ aqua: '#00ffff',
19
+ aquamarine: '#7fffd4',
20
+ azure: '#f0ffff',
21
+ beige: '#f5f5dc',
22
+ bisque: '#ffe4c4',
23
+ black: '#000000',
24
+ blanchedalmond: '#ffebcd',
25
+ blue: '#0000ff',
26
+ blueviolet: '#8a2be2',
27
+ brown: '#a52a2a',
28
+ burlywood: '#deb887',
29
+ cadetblue: '#5f9ea0',
30
+ chartreuse: '#7fff00',
31
+ chocolate: '#d2691e',
32
+ coral: '#ff7f50',
33
+ cornflowerblue: '#6495ed',
34
+ cornsilk: '#fff8dc',
35
+ crimson: '#dc143c',
36
+ cyan: '#00ffff',
37
+ darkblue: '#00008b',
38
+ darkcyan: '#008b8b',
39
+ darkgoldenrod: '#b8860b',
40
+ darkgray: '#a9a9a9',
41
+ darkgreen: '#006400',
42
+ darkgrey: '#a9a9a9',
43
+ darkkhaki: '#bdb76b',
44
+ darkmagenta: '#8b008b',
45
+ darkolivegreen: '#556b2f',
46
+ darkorange: '#ff8c00',
47
+ darkorchid: '#9932cc',
48
+ darkred: '#8b0000',
49
+ darksalmon: '#e9967a',
50
+ darkseagreen: '#8fbc8f',
51
+ darkslateblue: '#483d8b',
52
+ darkslategray: '#2f4f4f',
53
+ darkslategrey: '#2f4f4f',
54
+ darkturquoise: '#00ced1',
55
+ darkviolet: '#9400d3',
56
+ deeppink: '#ff1493',
57
+ deepskyblue: '#00bfff',
58
+ dimgray: '#696969',
59
+ dimgrey: '#696969',
60
+ dodgerblue: '#1e90ff',
61
+ firebrick: '#b22222',
62
+ floralwhite: '#fffaf0',
63
+ forestgreen: '#228b22',
64
+ fuchsia: '#ff00ff',
65
+ gainsboro: '#dcdcdc',
66
+ ghostwhite: '#f8f8ff',
67
+ gold: '#ffd700',
68
+ goldenrod: '#daa520',
69
+ gray: '#808080',
70
+ green: '#008000',
71
+ greenyellow: '#adff2f',
72
+ grey: '#808080',
73
+ honeydew: '#f0fff0',
74
+ hotpink: '#ff69b4',
75
+ indianred: '#cd5c5c',
76
+ indigo: '#4b0082',
77
+ ivory: '#fffff0',
78
+ khaki: '#f0e68c',
79
+ lavender: '#e6e6fa',
80
+ lavenderblush: '#fff0f5',
81
+ lawngreen: '#7cfc00',
82
+ lemonchiffon: '#fffacd',
83
+ lightblue: '#add8e6',
84
+ lightcoral: '#f08080',
85
+ lightcyan: '#e0ffff',
86
+ lightgoldenrodyellow: '#fafad2',
87
+ lightgray: '#d3d3d3',
88
+ lightgreen: '#90ee90',
89
+ lightgrey: '#d3d3d3',
90
+ lightpink: '#ffb6c1',
91
+ lightsalmon: '#ffa07a',
92
+ lightseagreen: '#20b2aa',
93
+ lightskyblue: '#87cefa',
94
+ lightslategray: '#778899',
95
+ lightslategrey: '#778899',
96
+ lightsteelblue: '#b0c4de',
97
+ lightyellow: '#ffffe0',
98
+ lime: '#00ff00',
99
+ limegreen: '#32cd32',
100
+ linen: '#faf0e6',
101
+ magenta: '#ff00ff',
102
+ maroon: '#800000',
103
+ mediumaquamarine: '#66cdaa',
104
+ mediumblue: '#0000cd',
105
+ mediumorchid: '#ba55d3',
106
+ mediumpurple: '#9370db',
107
+ mediumseagreen: '#3cb371',
108
+ mediumslateblue: '#7b68ee',
109
+ mediumspringgreen: '#00fa9a',
110
+ mediumturquoise: '#48d1cc',
111
+ mediumvioletred: '#c71585',
112
+ midnightblue: '#191970',
113
+ mintcream: '#f5fffa',
114
+ mistyrose: '#ffe4e1',
115
+ moccasin: '#ffe4b5',
116
+ navajowhite: '#ffdead',
117
+ navy: '#000080',
118
+ oldlace: '#fdf5e6',
119
+ olive: '#808000',
120
+ olivedrab: '#6b8e23',
121
+ orange: '#ffa500',
122
+ orangered: '#ff4500',
123
+ orchid: '#da70d6',
124
+ palegoldenrod: '#eee8aa',
125
+ palegreen: '#98fb98',
126
+ paleturquoise: '#afeeee',
127
+ palevioletred: '#db7093',
128
+ papayawhip: '#ffefd5',
129
+ peachpuff: '#ffdab9',
130
+ peru: '#cd853f',
131
+ pink: '#ffc0cb',
132
+ plum: '#dda0dd',
133
+ powderblue: '#b0e0e6',
134
+ purple: '#800080',
135
+ rebeccapurple: '#663399',
136
+ red: '#ff0000',
137
+ rosybrown: '#bc8f8f',
138
+ royalblue: '#4169e1',
139
+ saddlebrown: '#8b4513',
140
+ salmon: '#fa8072',
141
+ sandybrown: '#f4a460',
142
+ seagreen: '#2e8b57',
143
+ seashell: '#fff5ee',
144
+ sienna: '#a0522d',
145
+ silver: '#c0c0c0',
146
+ skyblue: '#87ceeb',
147
+ slateblue: '#6a5acd',
148
+ slategray: '#708090',
149
+ slategrey: '#708090',
150
+ snow: '#fffafa',
151
+ springgreen: '#00ff7f',
152
+ steelblue: '#4682b4',
153
+ tan: '#d2b48c',
154
+ teal: '#008080',
155
+ thistle: '#d8bfd8',
156
+ tomato: '#ff6347',
157
+ turquoise: '#40e0d0',
158
+ violet: '#ee82ee',
159
+ wheat: '#f5deb3',
160
+ white: '#ffffff',
161
+ whitesmoke: '#f5f5f5',
162
+ yellow: '#ffff00',
163
+ yellowgreen: '#9acd32'
164
+ };
165
+
166
+ function clampChannel(value) {
167
+ return Math.min(255, Math.max(0, Math.round(value)));
168
+ }
169
+
170
+ function clampUnit(value) {
171
+ return Math.min(1, Math.max(0, value));
172
+ }
173
+
174
+ function hexPair(value) {
175
+ const text = clampChannel(value).toString(16);
176
+ return text.length === 1 ? `0${text}` : text;
177
+ }
178
+
179
+ /**
180
+ * Parse the argument list of rgb()/rgba()/hsl()/hsla() (comma syntax or
181
+ * modern space + slash syntax). Returns { parts, alpha } where alpha is 1 when
182
+ * omitted. Returns null when the argument list cannot be parsed.
183
+ */
184
+ function parseFunctionArgs(args) {
185
+ const parts = args
186
+ .split(/[,\s]+/)
187
+ .map((part) => part.trim())
188
+ .filter((part) => part !== '' && part !== '/');
189
+ if (parts.length < 3) return null;
190
+ let alpha = 1;
191
+ if (parts.length >= 4) {
192
+ const alphaText = parts.pop().trim();
193
+ if (alphaText.endsWith('%')) {
194
+ const percent = parseFloat(alphaText);
195
+ if (!Number.isFinite(percent)) return null;
196
+ alpha = clampUnit(percent / 100);
197
+ } else {
198
+ const number = Number(alphaText);
199
+ if (!Number.isFinite(number)) return null;
200
+ alpha = clampUnit(number);
201
+ }
202
+ }
203
+ return { parts, alpha };
204
+ }
205
+
206
+ function parseChannel(value) {
207
+ if (typeof value !== 'string' || value === '') return null;
208
+ const text = value.trim();
209
+ if (text.endsWith('%')) return clampChannel((parseFloat(text) / 100) * 255);
210
+ const number = Number(text);
211
+ return Number.isFinite(number) ? clampChannel(number) : null;
212
+ }
213
+
214
+ function parsePercent(value) {
215
+ if (typeof value !== 'string' || value === '') return null;
216
+ const text = value.trim();
217
+ if (!text.endsWith('%')) return null;
218
+ const number = parseFloat(text);
219
+ return Number.isFinite(number) ? Math.min(100, Math.max(0, number)) : null;
220
+ }
221
+
222
+ function parseHue(value) {
223
+ if (typeof value !== 'string' || value === '') return null;
224
+ const number = parseFloat(value);
225
+ return Number.isFinite(number) ? number : null;
226
+ }
227
+
228
+ /** Standard HSL -> RGB. h in degrees, s/l in 0..100, returns r/g/b in 0..255. */
229
+ function hslToRgb(h, s, l) {
230
+ const hue = ((h % 360) + 360) % 360 / 360;
231
+ const saturation = s / 100;
232
+ const lightness = l / 100;
233
+ const chroma = (1 - Math.abs(2 * lightness - 1)) * saturation;
234
+ const section = hue * 6;
235
+ const x = chroma * (1 - Math.abs((section % 2) - 1));
236
+ let red = 0;
237
+ let green = 0;
238
+ let blue = 0;
239
+ if (section < 1) { red = chroma; green = x; }
240
+ else if (section < 2) { red = x; green = chroma; }
241
+ else if (section < 3) { green = chroma; blue = x; }
242
+ else if (section < 4) { green = x; blue = chroma; }
243
+ else if (section < 5) { red = x; blue = chroma; }
244
+ else { red = chroma; blue = x; }
245
+ const match = lightness - chroma / 2;
246
+ return [
247
+ Math.round((red + match) * 255),
248
+ Math.round((green + match) * 255),
249
+ Math.round((blue + match) * 255)
250
+ ];
251
+ }
252
+
253
+ /**
254
+ * Parse any supported CSS color. When keepAlpha is true the alpha channel is
255
+ * preserved (8-digit hex for alpha < 1); when false it is dropped. 'transparent'
256
+ * is only preserved when keepAlpha is true.
257
+ */
258
+ function parseColor(value, keepAlpha) {
259
+ if (typeof value !== 'string') return null;
260
+ const input = value.trim();
261
+ if (!input) return null;
262
+ const lower = input.toLowerCase();
263
+ if (keepAlpha && lower === 'transparent') return 'transparent';
264
+ if (NAMED_COLORS[lower]) return NAMED_COLORS[lower];
265
+ // #rgb / #rgba / #rrggbb / #rrggbbaa,以及无 # 前缀的 6/8 位 hex(dicebear 习惯)
266
+ const hexMatch = input.match(/^#?([0-9a-f]{3,8})$/i);
267
+ if (hexMatch) {
268
+ let hex = hexMatch[1].toLowerCase();
269
+ if (hex.length === 3 || hex.length === 4) {
270
+ hex = hex.split('').map((c) => c + c).join('');
271
+ }
272
+ if (keepAlpha && hex.length === 8) return `#${hex}`;
273
+ return `#${hex.slice(0, 6)}`;
274
+ }
275
+ const funcMatch = input.match(/^(rgba?|hsla?)\(([^)]*)\)$/i);
276
+ if (!funcMatch) return null;
277
+ const kind = funcMatch[1].toLowerCase();
278
+ const parsed = parseFunctionArgs(funcMatch[2]);
279
+ if (!parsed) return null;
280
+ const { parts, alpha } = parsed;
281
+ let red;
282
+ let green;
283
+ let blue;
284
+ if (kind === 'rgb' || kind === 'rgba') {
285
+ if (parts.length !== 3) return null;
286
+ red = parseChannel(parts[0]);
287
+ green = parseChannel(parts[1]);
288
+ blue = parseChannel(parts[2]);
289
+ if (red === null || green === null || blue === null) return null;
290
+ } else {
291
+ if (parts.length !== 3) return null;
292
+ const hue = parseHue(parts[0]);
293
+ const saturation = parsePercent(parts[1]);
294
+ const lightness = parsePercent(parts[2]);
295
+ if (hue === null || saturation === null || lightness === null) return null;
296
+ [red, green, blue] = hslToRgb(hue, saturation, lightness);
297
+ }
298
+ const hex = `#${hexPair(red)}${hexPair(green)}${hexPair(blue)}`;
299
+ if (keepAlpha && alpha < 1) return `${hex}${hexPair(alpha * 255)}`;
300
+ return hex;
301
+ }
302
+
303
+ /**
304
+ * Convert any supported CSS color into `#rrggbb`. Returns null when the
305
+ * value cannot be parsed. Supports: #rgb / #rgba / #rrggbb / #rrggbbaa
306
+ * (case-insensitive), bare 6/8-digit hex, CSS named colors, rgb() / rgba() /
307
+ * hsl() / hsla() with comma or modern space syntax, including percentages.
308
+ * Alpha is ignored.
309
+ */
310
+ function normalizeColor(value) {
311
+ return parseColor(value, false);
312
+ }
313
+
314
+ /**
315
+ * Convert any supported CSS color into `#rrggbb` (alpha = 1) or `#rrggbbaa`
316
+ * (alpha < 1). The literal string 'transparent' is returned as-is. Returns
317
+ * null when the value cannot be parsed.
318
+ */
319
+ function normalizeColorWithAlpha(value) {
320
+ return parseColor(value, true);
321
+ }
322
+
323
+ function hexToRgb01(color) {
324
+ const hex = normalizeColor(color);
325
+ if (!hex) return [0, 0, 0];
326
+ const value = Number.parseInt(hex.slice(1), 16);
327
+ return [
328
+ ((value >> 16) & 255) / 255,
329
+ ((value >> 8) & 255) / 255,
330
+ (value & 255) / 255
331
+ ];
332
+ }
333
+
334
+ function hexToRgba(color, alpha = 1) {
335
+ const hex = normalizeColor(color);
336
+ if (!hex) return 'rgba(0, 0, 0, 0)';
337
+ const value = Number.parseInt(hex.slice(1), 16);
338
+ const red = (value >> 16) & 255;
339
+ const green = (value >> 8) & 255;
340
+ const blue = value & 255;
341
+ return `rgba(${red}, ${green}, ${blue}, ${alpha})`;
342
+ }
343
+
344
+ function validateColors(colors) {
345
+ return Array.isArray(colors)
346
+ && colors.length === 4
347
+ && colors.every((color) => normalizeColor(color) !== null);
348
+ }
349
+
350
+ export { hexToRgba as a, normalizeColorWithAlpha as b, hexToRgb01 as h, normalizeColor as n, validateColors as v };
@@ -0,0 +1,160 @@
1
+ /**
2
+ * SVG helpers for the avatar pipeline: XML escaping, size application,
3
+ * data-URI encoding and a light sanitizer for render output.
4
+ */
5
+
6
+ const EVENT_ATTR_PATTERN = /(\son\w+\s*=\s*["'][^"']*["'])/gi;
7
+ const SCRIPT_TAG_PATTERN = /<script[\s\S]*?<\/script\s*>/gi;
8
+
9
+ function escapeXml(content) {
10
+ return content
11
+ .replace(/&/g, '&amp;')
12
+ .replace(/'/g, '&apos;')
13
+ .replace(/"/g, '&quot;')
14
+ .replace(/</g, '&lt;')
15
+ .replace(/>/g, '&gt;');
16
+ }
17
+
18
+ /**
19
+ * Normalize an SVG string's width/height attributes to a target size.
20
+ * Leaves the viewBox untouched so the aspect ratio is preserved.
21
+ */
22
+ function applySize(svg, size) {
23
+ if (size == null || size === '') return svg;
24
+ const value = typeof size === 'number' ? String(size) : String(size);
25
+ return svg
26
+ .replace(/(<svg[^>]*?)\swidth="[^"]*"/i, `$1 width="${escapeXml(value)}"`)
27
+ .replace(/(<svg[^>]*?)\sheight="[^"]*"/i, `$1 height="${escapeXml(value)}"`);
28
+ }
29
+
30
+ function svgToDataUri(svg) {
31
+ return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
32
+ }
33
+
34
+ /**
35
+ * Light sanitizer: removes <script> blocks and on* event handler attributes.
36
+ * Upstream assets are trusted (vendored and licensed), this is defense in
37
+ * depth for user-supplied seed strings that get interpolated into markup.
38
+ */
39
+ function sanitizeSvg(svg) {
40
+ return svg
41
+ .replace(SCRIPT_TAG_PATTERN, '')
42
+ .replace(EVENT_ATTR_PATTERN, '');
43
+ }
44
+
45
+ /** Clean arbitrary user text down to a safe seed string. */
46
+ function cleanText(value, maxLength = 128) {
47
+ if (value == null) return '';
48
+ const text = String(value).trim().slice(0, maxLength);
49
+ return text.replace(/[<>&"']/g, '');
50
+ }
51
+
52
+ /**
53
+ * Derive UI control descriptors from an upstream JSON-Schema.
54
+ *
55
+ * The generated controls are display metadata only — the actual parameter
56
+ * names, types, defaults and array semantics are never changed; the demo/docs
57
+ * layer renders widgets from these descriptors and writes values back into
58
+ * providerOptions verbatim.
59
+ *
60
+ * array + items.enum -> select (multi-choice)
61
+ * array + items.pattern (color) -> color
62
+ * integer + min/max -> range
63
+ * boolean -> switch
64
+ * string + enum -> select (single)
65
+ */
66
+
67
+ function buildControlsFromSchema(schema) {
68
+ const props = (schema && schema.properties) || {};
69
+ const controls = [];
70
+ for (const key of Object.keys(props)) {
71
+ const prop = props[key];
72
+ const label = toLabel(key);
73
+ const definition = prop.default;
74
+
75
+ if (prop.type === 'array' && prop.items) {
76
+ if (prop.items.enum) {
77
+ controls.push({
78
+ id: key,
79
+ label,
80
+ kind: 'select',
81
+ options: prop.items.enum,
82
+ default: definition !== undefined ? [...definition] : undefined
83
+ });
84
+ continue;
85
+ }
86
+ if (isColorArray(key, prop)) {
87
+ controls.push({
88
+ id: key,
89
+ label,
90
+ kind: 'color',
91
+ default: definition !== undefined ? definition[0] : undefined
92
+ });
93
+ continue;
94
+ }
95
+ controls.push({ id: key, label, kind: 'tags', default: definition });
96
+ continue;
97
+ }
98
+ if (prop.type === 'integer') {
99
+ controls.push({
100
+ id: key,
101
+ label,
102
+ kind: 'range',
103
+ min: prop.minimum !== undefined ? prop.minimum : 0,
104
+ max: prop.maximum !== undefined ? prop.maximum : 100,
105
+ step: 1,
106
+ default: definition !== undefined ? definition : undefined
107
+ });
108
+ continue;
109
+ }
110
+ if (prop.type === 'boolean') {
111
+ controls.push({
112
+ id: key,
113
+ label,
114
+ kind: 'switch',
115
+ default: definition !== undefined ? definition : undefined
116
+ });
117
+ continue;
118
+ }
119
+ if (prop.type === 'string' && prop.enum) {
120
+ controls.push({
121
+ id: key,
122
+ label,
123
+ kind: 'select',
124
+ options: prop.enum,
125
+ default: definition
126
+ });
127
+ }
128
+ }
129
+ // UI 简化标记:上游用 "<x>Probability"(0–100)控制“部位出现概率”。
130
+ // 这里把概率滑杆与对应的 "<x>" 下拉配对,渲染层可以把两者合并成
131
+ // 「无(不显示)/ 具体款式」二选一,避免让终端用户理解概率语义。
132
+ const selectsById = new Map(
133
+ controls.filter((c) => c.kind === 'select').map((c) => [c.id, c])
134
+ );
135
+ for (const control of controls) {
136
+ if (control.kind === 'range' && /Probability$/.test(control.id)) {
137
+ const baseId = control.id.slice(0, -'Probability'.length);
138
+ const base = selectsById.get(baseId);
139
+ if (base) {
140
+ base.probability = control.id;
141
+ control.probabilityOf = baseId;
142
+ }
143
+ }
144
+ }
145
+ return controls;
146
+ }
147
+
148
+ function toLabel(key) {
149
+ return key
150
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
151
+ .replace(/^./, (c) => c.toUpperCase());
152
+ }
153
+
154
+ /** Color arrays in dicebear schemas: hex pattern with optional "transparent". */
155
+ function isColorArray(key, prop) {
156
+ if (prop.items.type !== 'string' || !prop.items.pattern) return false;
157
+ return /transparent/.test(prop.items.pattern) || /[Cc]olor$/.test(key);
158
+ }
159
+
160
+ export { applySize as a, buildControlsFromSchema as b, cleanText as c, svgToDataUri as d, escapeXml as e, sanitizeSvg as s };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Internal accessibility labels and small UI text. Since the copy API was
3
+ * removed (slots own the text area), these are no longer user-overridable.
4
+ * Templates use {brand} {code} {name} placeholders.
5
+ */
6
+ const COPY = {
7
+ brandName: '画境观屿',
8
+ valueSuffix: '%',
9
+ progressAria: '{brand} {code} 加载进度',
10
+ capsuleAria: '打开 {name} 沉浸预览'
11
+ };
12
+
13
+ export { COPY as C };