@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.
- package/CHANGELOG.md +158 -144
- package/EXTENDING.md +114 -0
- package/LICENSE +21 -21
- package/README.md +389 -338
- package/THIRD_PARTY_NOTICES.md +221 -0
- package/dist/avatar-creator/styles/humation.mjs +26 -0
- package/dist/avatar-creator/styles.mjs +76 -0
- package/dist/avatar-creator.cjs +7831 -0
- package/dist/avatar-creator.mjs +74 -0
- package/dist/capsule.cjs +185 -66
- package/dist/capsule.mjs +364 -1340
- package/dist/chunks/avatar-creator-DiAmhpRa.mjs +687 -0
- package/dist/chunks/color-DnmNceaf.mjs +350 -0
- package/dist/chunks/controls-B5_ctvvA.mjs +160 -0
- package/dist/chunks/copy-7J0XIj_0.mjs +13 -0
- package/dist/chunks/cosmic-fallback-v8G5_ibh.mjs +408 -0
- package/dist/chunks/emitter-CxLYMSbw.mjs +38 -0
- package/dist/chunks/emo-Dvahy5Qr.mjs +1140 -0
- package/dist/chunks/hash-BiO02UNb.mjs +38 -0
- package/dist/chunks/humation-B9OPUGL7.mjs +2987 -0
- package/dist/chunks/identicon-BUdMd-yf.mjs +3063 -0
- package/dist/chunks/index-BchW1tWE.mjs +478 -0
- package/dist/chunks/index-BtH8Lbra.mjs +296 -0
- package/dist/chunks/index-C02dvGLD.mjs +263 -0
- package/dist/chunks/index-CFybYBA1.mjs +278 -0
- package/dist/chunks/index-DgI872-7.mjs +241 -0
- package/dist/chunks/index-_sf_14Zu.mjs +455 -0
- package/dist/chunks/index-w5Pzdwhv.mjs +305 -0
- package/dist/chunks/motion-a8_f8_Ui.mjs +177 -0
- package/dist/chunks/number-BqWLX0iQ.mjs +231 -0
- package/dist/chunks/progress-KweS4Rx0.mjs +1836 -0
- package/dist/chunks/size-Bztk2ryJ.mjs +22 -0
- package/dist/chunks/wrapper-DXhy1E7Z.mjs +514 -0
- package/dist/color.cjs +186 -67
- package/dist/color.mjs +341 -1280
- package/dist/emo/miao/angry.mjs +4 -0
- package/dist/emo/miao/asleep.mjs +4 -0
- package/dist/emo/miao/badminton.mjs +4 -0
- package/dist/emo/miao/confident.mjs +4 -0
- package/dist/emo/miao/cry.mjs +4 -0
- package/dist/emo/miao/investigate.mjs +4 -0
- package/dist/emo/miao/laugh.mjs +4 -0
- package/dist/emo/miao/leisure.mjs +4 -0
- package/dist/emo/miao/mock.mjs +4 -0
- package/dist/emo/miao/music.mjs +4 -0
- package/dist/emo/miao/mute.mjs +4 -0
- package/dist/emo/miao/panic.mjs +4 -0
- package/dist/emo/miao/ponder.mjs +4 -0
- package/dist/emo/miao/question.mjs +4 -0
- package/dist/emo/miao/sad.mjs +4 -0
- package/dist/emo/miao/shocked.mjs +4 -0
- package/dist/emo/miao/shy.mjs +4 -0
- package/dist/emo/miao/sigh.mjs +4 -0
- package/dist/emo/miao/smile.mjs +4 -0
- package/dist/emo/miao/snigger.mjs +4 -0
- package/dist/emo/miao/static.mjs +4 -0
- package/dist/emo/miao/yawn.mjs +4 -0
- package/dist/emo/miao/yummy.mjs +4 -0
- package/dist/emo.cjs +27161 -25964
- package/dist/emo.mjs +8 -1421
- package/dist/index.cjs +32729 -24466
- package/dist/index.mjs +16 -4757
- package/dist/index.umd.js +33115 -24852
- package/dist/index.umd.min.js +3 -1
- package/dist/progress.cjs +195 -76
- package/dist/progress.mjs +7 -2448
- package/dist/vue2.cjs +33282 -25002
- package/dist/vue2.mjs +63 -5131
- package/dist/vue3.cjs +33282 -25002
- package/dist/vue3.mjs +53 -5122
- package/package.json +161 -136
- package/styles/avatar-creator.css +38 -0
- package/styles/base.css +32 -32
- package/styles/color.css +18 -18
- package/styles/emo.css +110 -89
- package/styles/progress.css +96 -96
- package/styles/theme.css +1 -0
- package/types/avatar-creator-styles.d.ts +10 -0
- package/types/avatar-creator.d.ts +143 -0
- package/types/capsule.d.ts +15 -15
- package/types/color.d.ts +15 -15
- package/types/emo-miao.d.ts +9 -0
- package/types/emo.d.ts +38 -37
- package/types/index.d.ts +593 -554
- package/types/progress.d.ts +16 -16
- package/types/vue2.d.ts +11 -10
- package/types/vue3.d.ts +169 -146
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
import { c as createEmitter } from './emitter-CxLYMSbw.mjs';
|
|
2
|
+
import { b as buildControlsFromSchema, s as sanitizeSvg, a as applySize, d as svgToDataUri, e as escapeXml } from './controls-B5_ctvvA.mjs';
|
|
3
|
+
import { i as identicon, m as mergedDicebearSchema, s as schema, a as schema$1, b as schema$2, c as schema$3, d as schema$4, e as schema$5, f as schema$6, g as schema$7, n as normalizeDicebearOptions, h as defineDicebearStyle } from './identicon-BUdMd-yf.mjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* DiceBear provider — vendored @dicebear/core@9.4.2 + 8 CC0 styles.
|
|
7
|
+
*
|
|
8
|
+
* Style *definitions* (upstream schemas + metadata) are statically bundled so
|
|
9
|
+
* `getStyleDefinitions()` / `getStyleDefinition()` stay synchronous; the heavy
|
|
10
|
+
* per-style *rendering modules* (components) load on demand through
|
|
11
|
+
* `loadDicebearStyle()` / `loadAvatarStyle()` — 这是组件字符串路径内部用的。
|
|
12
|
+
* 纯函数路径请直接 `import { lorelei } from '../styles'` 并传给
|
|
13
|
+
* `createAvatarSvg(styleModule, options)`:同步、tree-shaking、零注册表。
|
|
14
|
+
*
|
|
15
|
+
* The default style (identicon, ~13 KB) is also bundled statically, so the
|
|
16
|
+
* default component path renders with zero network wait.
|
|
17
|
+
*
|
|
18
|
+
* Every style keeps its upstream schema untouched (the only tweak is that
|
|
19
|
+
* color patterns accept 8-digit #rrggbbaa hex for alpha support).
|
|
20
|
+
* `providerOptions` are validated against the merged schema (core options +
|
|
21
|
+
* style options) so that names, types, defaults and array semantics match
|
|
22
|
+
* upstream exactly; unknown or mistyped parameters produce warnings (or
|
|
23
|
+
* errors in strict mode) instead of being silently dropped.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const STYLE_SCHEMAS = {
|
|
27
|
+
identicon: schema$7,
|
|
28
|
+
lorelei: schema$6,
|
|
29
|
+
'lorelei-neutral': schema$5,
|
|
30
|
+
notionists: schema$4,
|
|
31
|
+
'notionists-neutral': schema$3,
|
|
32
|
+
'open-peeps': schema$2,
|
|
33
|
+
rings: schema$1,
|
|
34
|
+
shapes: schema
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Upstream style metadata (title/creator/source/homepage/license), extracted
|
|
39
|
+
* from the vendored style modules so the registry stays synchronous. The
|
|
40
|
+
* full rendering modules are loaded on demand; the metadata never changes
|
|
41
|
+
* per-load, so a static table keeps definitions stable and cheap.
|
|
42
|
+
*/
|
|
43
|
+
const STYLE_META = {
|
|
44
|
+
identicon: {
|
|
45
|
+
title: 'Identicon',
|
|
46
|
+
creator: 'DiceBear',
|
|
47
|
+
source: 'https://www.dicebear.com',
|
|
48
|
+
homepage: 'https://www.dicebear.com',
|
|
49
|
+
license: 'CC0 1.0',
|
|
50
|
+
licenseUrl: 'https://creativecommons.org/publicdomain/zero/1.0/'
|
|
51
|
+
},
|
|
52
|
+
lorelei: {
|
|
53
|
+
title: 'Lorelei',
|
|
54
|
+
creator: 'Lisa Wischofsky',
|
|
55
|
+
source: 'https://www.figma.com/community/file/1198749693280469639',
|
|
56
|
+
homepage: 'https://www.instagram.com/lischi_art/',
|
|
57
|
+
license: 'CC0 1.0',
|
|
58
|
+
licenseUrl: 'https://creativecommons.org/publicdomain/zero/1.0/'
|
|
59
|
+
},
|
|
60
|
+
'lorelei-neutral': {
|
|
61
|
+
title: 'Lorelei Neutral',
|
|
62
|
+
creator: 'Lisa Wischofsky',
|
|
63
|
+
source: 'https://www.figma.com/community/file/1198749693280469639',
|
|
64
|
+
homepage: 'https://www.instagram.com/lischi_art/',
|
|
65
|
+
license: 'CC0 1.0',
|
|
66
|
+
licenseUrl: 'https://creativecommons.org/publicdomain/zero/1.0/'
|
|
67
|
+
},
|
|
68
|
+
notionists: {
|
|
69
|
+
title: 'Notionists',
|
|
70
|
+
creator: 'Zoish',
|
|
71
|
+
source: 'https://heyzoish.gumroad.com/l/notionists',
|
|
72
|
+
homepage: 'https://bio.link/heyzoish',
|
|
73
|
+
license: 'CC0 1.0',
|
|
74
|
+
licenseUrl: 'https://creativecommons.org/publicdomain/zero/1.0/'
|
|
75
|
+
},
|
|
76
|
+
'notionists-neutral': {
|
|
77
|
+
title: 'Notionists Neutral',
|
|
78
|
+
creator: 'Zoish',
|
|
79
|
+
source: 'https://heyzoish.gumroad.com/l/notionists',
|
|
80
|
+
homepage: 'https://bio.link/heyzoish',
|
|
81
|
+
license: 'CC0 1.0',
|
|
82
|
+
licenseUrl: 'https://creativecommons.org/publicdomain/zero/1.0/'
|
|
83
|
+
},
|
|
84
|
+
'open-peeps': {
|
|
85
|
+
title: 'Open Peeps',
|
|
86
|
+
creator: 'Pablo Stanley',
|
|
87
|
+
source: 'https://www.openpeeps.com/',
|
|
88
|
+
homepage: 'https://twitter.com/pablostanley',
|
|
89
|
+
license: 'CC0 1.0',
|
|
90
|
+
licenseUrl: 'https://creativecommons.org/publicdomain/zero/1.0/'
|
|
91
|
+
},
|
|
92
|
+
rings: {
|
|
93
|
+
title: 'Rings',
|
|
94
|
+
creator: 'DiceBear',
|
|
95
|
+
source: 'https://www.dicebear.com',
|
|
96
|
+
homepage: 'https://www.dicebear.com',
|
|
97
|
+
license: 'CC0 1.0',
|
|
98
|
+
licenseUrl: 'https://creativecommons.org/publicdomain/zero/1.0/'
|
|
99
|
+
},
|
|
100
|
+
shapes: {
|
|
101
|
+
title: 'Shapes',
|
|
102
|
+
creator: 'DiceBear',
|
|
103
|
+
source: 'https://www.dicebear.com',
|
|
104
|
+
homepage: 'https://www.dicebear.com',
|
|
105
|
+
license: 'CC0 1.0',
|
|
106
|
+
licenseUrl: 'https://creativecommons.org/publicdomain/zero/1.0/'
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// Explicit per-style loader map — rollup can only analyze static import()
|
|
111
|
+
// targets, so each vendored style module becomes its own lazy chunk.
|
|
112
|
+
const STYLE_LOADERS = {
|
|
113
|
+
identicon: () => import('./identicon-BUdMd-yf.mjs').then(function (n) { return n.j; }),
|
|
114
|
+
lorelei: () => import('./index-_sf_14Zu.mjs').then(function (n) { return n.m; }),
|
|
115
|
+
'lorelei-neutral': () => import('./index-BtH8Lbra.mjs').then(function (n) { return n.m; }),
|
|
116
|
+
notionists: () => import('./index-BchW1tWE.mjs').then(function (n) { return n.m; }),
|
|
117
|
+
'notionists-neutral': () => import('./index-C02dvGLD.mjs').then(function (n) { return n.m; }),
|
|
118
|
+
'open-peeps': () => import('./index-w5Pzdwhv.mjs').then(function (n) { return n.m; }),
|
|
119
|
+
rings: () => import('./index-CFybYBA1.mjs').then(function (n) { return n.m; }),
|
|
120
|
+
shapes: () => import('./index-DgI872-7.mjs').then(function (n) { return n.m; })
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const loadedStyles = new Map();
|
|
124
|
+
const loadingStyles = new Map();
|
|
125
|
+
|
|
126
|
+
// 默认画风静态打进主包:createAvatarSvg() 默认路径无需任何异步等待。
|
|
127
|
+
loadedStyles.set('identicon', identicon);
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Load (and cache) a dicebear style rendering module. Concurrent calls for
|
|
131
|
+
* the same style share a single import; different styles stay independent.
|
|
132
|
+
*/
|
|
133
|
+
function loadDicebearStyle(styleId) {
|
|
134
|
+
const loader = STYLE_LOADERS[styleId];
|
|
135
|
+
if (!loader) return Promise.reject(new Error(`dlc-avatar-creator: unknown dicebear style "${styleId}"`));
|
|
136
|
+
if (loadedStyles.has(styleId)) return Promise.resolve(loadedStyles.get(styleId));
|
|
137
|
+
if (!loadingStyles.has(styleId)) {
|
|
138
|
+
loadingStyles.set(styleId, loader().then((module) => {
|
|
139
|
+
const meta = STYLE_META[styleId];
|
|
140
|
+
const title = EN_TITLES[styleId] || meta.title || styleId;
|
|
141
|
+
loadedStyles.set(styleId, defineDicebearStyle({
|
|
142
|
+
id: styleId,
|
|
143
|
+
module,
|
|
144
|
+
schema: STYLE_SCHEMAS[styleId],
|
|
145
|
+
meta,
|
|
146
|
+
label: (ZH_LABELS[styleId] || title) + '(' + title + ')',
|
|
147
|
+
title
|
|
148
|
+
}));
|
|
149
|
+
loadingStyles.delete(styleId);
|
|
150
|
+
return loadedStyles.get(styleId);
|
|
151
|
+
}).catch((error) => {
|
|
152
|
+
loadingStyles.delete(styleId);
|
|
153
|
+
throw error;
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
156
|
+
return loadingStyles.get(styleId);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** 同步判断某个 dicebear 画风模块是否已经就绪(组件字符串路径占位用)。 */
|
|
160
|
+
function isDicebearStyleLoaded(styleId) {
|
|
161
|
+
return loadedStyles.has(styleId);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const ZH_LABELS = {
|
|
165
|
+
identicon: '几何标识',
|
|
166
|
+
lorelei: '洛蕾莱',
|
|
167
|
+
'lorelei-neutral': '洛蕾莱 · 中性',
|
|
168
|
+
notionists: '概念派',
|
|
169
|
+
'notionists-neutral': '概念派 · 中性',
|
|
170
|
+
'open-peeps': '开放小人',
|
|
171
|
+
rings: '同心圆环',
|
|
172
|
+
shapes: '几何形状'
|
|
173
|
+
};
|
|
174
|
+
// 上游 vendored meta.title 有个别样板标题不准确(notionists-neutral 写成了
|
|
175
|
+
// 'Notionists' 而非官方 'Notionists Neutral'),这里统一补上官方英文名,保证
|
|
176
|
+
// getStyleDefinitions() 的 label / title 展示正确。
|
|
177
|
+
const EN_TITLES = {
|
|
178
|
+
'notionists-neutral': 'Notionists Neutral'
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
function buildStyleDefinitions() {
|
|
182
|
+
const definitions = [];
|
|
183
|
+
for (const id of Object.keys(STYLE_META)) {
|
|
184
|
+
const meta = STYLE_META[id];
|
|
185
|
+
const schema = mergedSchema(id);
|
|
186
|
+
const title = EN_TITLES[id] || meta.title || id;
|
|
187
|
+
definitions.push({
|
|
188
|
+
id,
|
|
189
|
+
label: (ZH_LABELS[id] || title) + '(' + title + ')',
|
|
190
|
+
provider: 'dicebear',
|
|
191
|
+
title,
|
|
192
|
+
designer: meta.creator || '',
|
|
193
|
+
homepage: meta.homepage || '',
|
|
194
|
+
source: meta.source || '',
|
|
195
|
+
license: meta.license || 'MIT',
|
|
196
|
+
licenseUrl: meta.licenseUrl || '',
|
|
197
|
+
schema,
|
|
198
|
+
controls: buildControlsFromSchema(schema)
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
return definitions;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function mergedSchema(styleId) {
|
|
205
|
+
const schema = STYLE_SCHEMAS[styleId];
|
|
206
|
+
if (!schema) return null;
|
|
207
|
+
return mergedDicebearSchema(schema);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const dicebearProvider = {
|
|
211
|
+
id: 'dicebear',
|
|
212
|
+
label: 'DiceBear',
|
|
213
|
+
styles: buildStyleDefinitions(),
|
|
214
|
+
|
|
215
|
+
getStyleDefinition(styleId) {
|
|
216
|
+
return this.styles.find((style) => style.id === styleId);
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Validate & normalize providerOptions against the merged upstream schema.
|
|
221
|
+
* Returns { normalized, warnings } — unknown keys / invalid values are
|
|
222
|
+
* never silently dropped.
|
|
223
|
+
*
|
|
224
|
+
* Color arrays accept any CSS color: hex (#fff / #ffffff / #ffffffff,
|
|
225
|
+
* with or without the hash), rgb() / rgba() / hsl() / hsla() (comma or
|
|
226
|
+
* modern syntax), CSS named colors ('red', 'green', …) and 'transparent'.
|
|
227
|
+
* Everything is normalized to a 6/8-digit hex (or 'transparent') before
|
|
228
|
+
* schema validation, so hex colors behave the same as the common-layer
|
|
229
|
+
* `background` option.
|
|
230
|
+
*/
|
|
231
|
+
normalizeOptions(styleId, options, { strict = false } = {}) {
|
|
232
|
+
const schema = mergedSchema(styleId);
|
|
233
|
+
if (!schema) {
|
|
234
|
+
throw new Error(`dlc-avatar-creator: unknown dicebear style "${styleId}"`);
|
|
235
|
+
}
|
|
236
|
+
return normalizeDicebearOptions(schema, options, { strict });
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Render a style from seed + common options + normalized native options.
|
|
241
|
+
* Synchronous — throws a clear error if the style module is not loaded yet
|
|
242
|
+
* (call loadDicebearStyle() / loadAvatarStyle() first).
|
|
243
|
+
*/
|
|
244
|
+
render({ style: styleId, seed, size, background, options, strict = false }) {
|
|
245
|
+
const styleObj = loadedStyles.get(styleId);
|
|
246
|
+
if (!styleObj) {
|
|
247
|
+
if (!STYLE_LOADERS[styleId]) {
|
|
248
|
+
throw new Error(`dlc-avatar-creator: unknown dicebear style "${styleId}"`);
|
|
249
|
+
}
|
|
250
|
+
throw new Error(
|
|
251
|
+
`dlc-avatar-creator: dicebear style "${styleId}" is not loaded yet — ` +
|
|
252
|
+
`import { ${styleId} } from '@5even7/dlc-ui/avatar-creator/styles' and pass it to createAvatarSvg/createAvatarCreator`
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
return styleObj.render({ seed, size, background, options, strict });
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Provider registry — the only place that knows about providers.
|
|
261
|
+
* dlc-avatar-creator resolves `provider` + `style` through here and never
|
|
262
|
+
* hardcodes generator internals anywhere else.
|
|
263
|
+
*
|
|
264
|
+
* Load-on-demand model:
|
|
265
|
+
* - dicebear: definitions are static (schemas bundled); each style's heavy
|
|
266
|
+
* rendering module loads via loadDicebearStyle() / loadAvatarStyle().
|
|
267
|
+
* - humation: the whole module (~0.7 MB manifest) is one lazy chunk; a stub
|
|
268
|
+
* keeps the registry / getProviders() working before it is loaded.
|
|
269
|
+
*/
|
|
270
|
+
|
|
271
|
+
const humationStub = {
|
|
272
|
+
id: 'humation',
|
|
273
|
+
label: 'Humation',
|
|
274
|
+
loaded: false,
|
|
275
|
+
// Placeholder so getProviders() lists humation-1 before the chunk loads;
|
|
276
|
+
// replaced by the real definition once humation is loaded.
|
|
277
|
+
styles: [{ id: 'humation-1' }],
|
|
278
|
+
getStyleDefinition() { return undefined; },
|
|
279
|
+
normalizeOptions() {
|
|
280
|
+
throw new Error('dlc-avatar-creator: humation is not loaded yet — `import { humation } from \'@5even7/dlc-ui/avatar-creator/styles/humation\'` and pass it to createAvatarSvg/createAvatarCreator');
|
|
281
|
+
},
|
|
282
|
+
render() {
|
|
283
|
+
throw new Error('dlc-avatar-creator: humation is not loaded yet — `import { humation } from \'@5even7/dlc-ui/avatar-creator/styles/humation\'` and pass it to createAvatarSvg/createAvatarCreator');
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
let humationLoading = null;
|
|
288
|
+
|
|
289
|
+
function loadHumation() {
|
|
290
|
+
if (!humationLoading) {
|
|
291
|
+
humationLoading = import('./humation-B9OPUGL7.mjs').then((module) => {
|
|
292
|
+
const provider = module.humationProvider || module.default;
|
|
293
|
+
humationStub.styles = provider.styles;
|
|
294
|
+
humationStub.getStyleDefinition = provider.getStyleDefinition.bind(provider);
|
|
295
|
+
humationStub.normalizeOptions = provider.normalizeOptions.bind(provider);
|
|
296
|
+
humationStub.render = provider.render.bind(provider);
|
|
297
|
+
humationStub.loaded = true;
|
|
298
|
+
return provider;
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
return humationLoading;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const PROVIDERS = [dicebearProvider, humationStub];
|
|
305
|
+
|
|
306
|
+
function getProviders() {
|
|
307
|
+
return PROVIDERS.map((provider) => ({
|
|
308
|
+
id: provider.id,
|
|
309
|
+
label: provider.label,
|
|
310
|
+
styles: provider.styles.map((style) => style.id)
|
|
311
|
+
}));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function getProvider(providerId) {
|
|
315
|
+
if (typeof providerId !== 'string') return undefined;
|
|
316
|
+
const id = providerId.includes(':') ? providerId.split(':')[0] : providerId;
|
|
317
|
+
return PROVIDERS.find((provider) => provider.id === id);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function getStyleDefinition(providerId, styleId) {
|
|
321
|
+
const provider = getProvider(providerId);
|
|
322
|
+
if (!provider) return undefined;
|
|
323
|
+
return provider.getStyleDefinition(styleId);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function getStyleDefinitions() {
|
|
327
|
+
const definitions = [];
|
|
328
|
+
for (const provider of PROVIDERS) definitions.push(...provider.styles);
|
|
329
|
+
return definitions;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function resolveStyle(providerId, styleId) {
|
|
333
|
+
if (typeof styleId === 'string' && styleId.includes(':') && !providerId) {
|
|
334
|
+
const [resolvedProvider, resolvedStyle] = styleId.split(':');
|
|
335
|
+
return { provider: resolvedProvider, style: resolvedStyle };
|
|
336
|
+
}
|
|
337
|
+
return { provider: providerId, style: styleId };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Load one avatar style's rendering module (cached; safe to call repeatedly
|
|
342
|
+
* and concurrently — the same style shares a single import). Resolves to the
|
|
343
|
+
* loaded provider/style module so callers can inspect it; rejects for
|
|
344
|
+
* unknown providers or styles.
|
|
345
|
+
*/
|
|
346
|
+
function loadAvatarStyle(providerId, styleId) {
|
|
347
|
+
if (typeof providerId !== 'string' || providerId === '') {
|
|
348
|
+
return Promise.reject(new Error('dlc-avatar-creator: provider is required'));
|
|
349
|
+
}
|
|
350
|
+
const provider = getProvider(providerId);
|
|
351
|
+
if (!provider) {
|
|
352
|
+
return Promise.reject(new Error(`dlc-avatar-creator: unknown provider "${providerId}"`));
|
|
353
|
+
}
|
|
354
|
+
if (providerId === 'humation') {
|
|
355
|
+
return loadHumation();
|
|
356
|
+
}
|
|
357
|
+
if (providerId === 'dicebear') {
|
|
358
|
+
if (typeof styleId !== 'string' || styleId === '') {
|
|
359
|
+
return Promise.reject(new Error('dlc-avatar-creator: style is required for dicebear'));
|
|
360
|
+
}
|
|
361
|
+
return loadDicebearStyle(styleId);
|
|
362
|
+
}
|
|
363
|
+
return Promise.reject(new Error(`dlc-avatar-creator: provider "${providerId}" has no style loader`));
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/** 同步判断某个画风是否已加载(组件字符串路径首次渲染时决定要不要显示占位)。 */
|
|
367
|
+
function isAvatarStyleLoaded(providerId, styleId) {
|
|
368
|
+
if (providerId === 'dicebear') return isDicebearStyleLoaded(styleId);
|
|
369
|
+
if (providerId === 'humation') return humationStub.loaded === true;
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* dlc-avatar-creator facade.
|
|
375
|
+
*
|
|
376
|
+
* Pure SVG generation (createAvatarSvg) + DOM controller (createAvatarCreator).
|
|
377
|
+
*
|
|
378
|
+
* Two option layers, exactly as designed:
|
|
379
|
+
*
|
|
380
|
+
* createAvatarCreator(el, {
|
|
381
|
+
* provider: 'dicebear', // 'dicebear' | 'humation'
|
|
382
|
+
* style: 'lorelei',
|
|
383
|
+
* seed: 'user-123',
|
|
384
|
+
* size: 160, // common layer
|
|
385
|
+
* background: '#fff', // common layer
|
|
386
|
+
* providerOptions: { // native upstream params, validated vs schema
|
|
387
|
+
* hair: ['variant01'],
|
|
388
|
+
* glasses: ['variant02'],
|
|
389
|
+
* flip: true,
|
|
390
|
+
* rotate: 10,
|
|
391
|
+
* scale: 90
|
|
392
|
+
* },
|
|
393
|
+
* strict: false // true -> throw on unrecognized/invalid params
|
|
394
|
+
* });
|
|
395
|
+
*
|
|
396
|
+
* Only renders an SVG into the container — no forms, no downloads, no npm
|
|
397
|
+
* packages. UI widgets are the caller's job, driven by getStyleDefinitions().
|
|
398
|
+
*/
|
|
399
|
+
|
|
400
|
+
function defaultSeed() {
|
|
401
|
+
return Math.random().toString(36).slice(2, 10);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function normalizeSize(value) {
|
|
405
|
+
if (value == null || value === '') return undefined;
|
|
406
|
+
const num = Number(value);
|
|
407
|
+
if (Number.isFinite(num) && num > 0) return num;
|
|
408
|
+
throw new Error(`dlc-avatar-creator: invalid size "${value}"`);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function normalizeString(value, fallback) {
|
|
412
|
+
if (typeof value !== 'string' || value.trim() === '') return fallback;
|
|
413
|
+
return value.trim();
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function deepClone(value) {
|
|
417
|
+
if (typeof structuredClone === 'function') return structuredClone(value);
|
|
418
|
+
return JSON.parse(JSON.stringify(value));
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Pure renderer(Element / DiceBear 式):
|
|
423
|
+
* import { lorelei } from '@5even7/dlc-ui/avatar-creator/styles'
|
|
424
|
+
* createAvatarSvg(lorelei, { seed: 'x', size: 160, providerOptions: { hair: [...] } })
|
|
425
|
+
* 画风是 import 进来的模块:同步渲染、可 tree-shaking、无需注册表/预加载。
|
|
426
|
+
* Returns { svg, width, height, warnings, metadata }. No DOM involved.
|
|
427
|
+
*/
|
|
428
|
+
function createAvatarSvg(style, options = {}) {
|
|
429
|
+
if (!style || typeof style.render !== 'function') {
|
|
430
|
+
throw new Error(
|
|
431
|
+
"dlc-avatar-creator: a style module is required — import one from '@5even7/dlc-ui/avatar-creator/styles', " +
|
|
432
|
+
"e.g. `import { lorelei } from '@5even7/dlc-ui/avatar-creator/styles'`"
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
const seed = normalizeString(options.seed, defaultSeed());
|
|
436
|
+
const size = normalizeSize(options.size);
|
|
437
|
+
const background = options.background;
|
|
438
|
+
const strict = options.strict === true;
|
|
439
|
+
|
|
440
|
+
const result = style.render({
|
|
441
|
+
seed,
|
|
442
|
+
size,
|
|
443
|
+
background,
|
|
444
|
+
options: options.providerOptions || {},
|
|
445
|
+
strict
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
let svg = sanitizeSvg(result.svg);
|
|
449
|
+
if (size != null) svg = applySize(svg, size);
|
|
450
|
+
return {
|
|
451
|
+
svg,
|
|
452
|
+
width: result.width,
|
|
453
|
+
height: result.height,
|
|
454
|
+
warnings: result.warnings,
|
|
455
|
+
metadata: { ...result.metadata, size, background: background !== undefined ? String(background) : undefined }
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
const AVATAR_POSTER_HTML = '<div class="dlc-avatar-poster" aria-hidden="true"></div>';
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* DOM controller — mounts the avatar SVG into a container and keeps it in
|
|
462
|
+
* sync with setter calls.
|
|
463
|
+
*/
|
|
464
|
+
function createAvatarCreator(container, options = {}) {
|
|
465
|
+
if (!container || typeof container.appendChild !== 'function') {
|
|
466
|
+
throw new Error('dlc-avatar-creator: container element is required');
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const emitter = createEmitter();
|
|
470
|
+
const state = {
|
|
471
|
+
provider: options.provider || 'dicebear',
|
|
472
|
+
style: options.style,
|
|
473
|
+
seed: options.seed,
|
|
474
|
+
size: options.size,
|
|
475
|
+
background: options.background,
|
|
476
|
+
providerOptions: options.providerOptions ? deepClone(options.providerOptions) : {},
|
|
477
|
+
strict: options.strict === true
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
const element = document.createElement('div');
|
|
481
|
+
element.className = 'dlc-avatar-creator';
|
|
482
|
+
container.appendChild(element);
|
|
483
|
+
|
|
484
|
+
function warnAll(warnings) {
|
|
485
|
+
for (const warning of warnings) emitter.emit('warning', warning);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* 用候选状态渲染并提交:createAvatarSvg 抛错时 state / DOM 都保持原样
|
|
490
|
+
* (原子化),避免 setSize(-1) 之类的失败调用把控制器留在脏状态。
|
|
491
|
+
* 提交是异步的(首次会按需加载样式模块,之后命中缓存直接渲染),
|
|
492
|
+
* 失败时发出 'error' 事件并 reject,state / DOM 不变。
|
|
493
|
+
*/
|
|
494
|
+
let commitSeq = 0;
|
|
495
|
+
let lastApplied = null;
|
|
496
|
+
let pendingCommit = null;
|
|
497
|
+
let readyFired = false;
|
|
498
|
+
|
|
499
|
+
async function commit(next = {}) {
|
|
500
|
+
const seq = ++commitSeq;
|
|
501
|
+
const candidate = {
|
|
502
|
+
provider: next.provider !== undefined ? next.provider : state.provider,
|
|
503
|
+
style: next.style !== undefined ? next.style : state.style,
|
|
504
|
+
seed: next.seed !== undefined ? next.seed : state.seed,
|
|
505
|
+
size: next.size !== undefined ? next.size : state.size,
|
|
506
|
+
background: next.background !== undefined ? next.background : state.background,
|
|
507
|
+
providerOptions: next.providerOptions !== undefined
|
|
508
|
+
? deepClone(next.providerOptions || {})
|
|
509
|
+
: deepClone(state.providerOptions || {}),
|
|
510
|
+
strict: next.strict !== undefined ? next.strict === true : state.strict
|
|
511
|
+
};
|
|
512
|
+
const promise = (async () => {
|
|
513
|
+
try {
|
|
514
|
+
let styleObj = candidate.style;
|
|
515
|
+
const applyResult = (result) => {
|
|
516
|
+
Object.assign(state, candidate);
|
|
517
|
+
element.innerHTML = result.svg;
|
|
518
|
+
element.style.width = '';
|
|
519
|
+
element.style.height = '';
|
|
520
|
+
element.setAttribute('data-provider', escapeXml(result.metadata.provider));
|
|
521
|
+
element.setAttribute('data-style', escapeXml(result.metadata.style));
|
|
522
|
+
warnAll(result.warnings);
|
|
523
|
+
lastApplied = result;
|
|
524
|
+
// 'ready' 表示首次真正上屏的提交完成;被后续提交取代的初始提交不算。
|
|
525
|
+
if (!readyFired) {
|
|
526
|
+
readyFired = true;
|
|
527
|
+
queueMicrotask(() => emitter.emit('ready', { ...result, state: controller.state }));
|
|
528
|
+
}
|
|
529
|
+
return result;
|
|
530
|
+
};
|
|
531
|
+
const renderCurrent = (style) => createAvatarSvg(style, {
|
|
532
|
+
seed: candidate.seed,
|
|
533
|
+
size: candidate.size,
|
|
534
|
+
background: candidate.background,
|
|
535
|
+
providerOptions: candidate.providerOptions,
|
|
536
|
+
strict: candidate.strict
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
// 字符串路径(模板/CDN 用):走 provider 注册表,内部按需加载画风模块;
|
|
540
|
+
// 模块路径(script 用):直接使用 import 进来的画风对象,同步渲染。
|
|
541
|
+
if (typeof styleObj !== 'object' || styleObj === null || typeof styleObj.render !== 'function') {
|
|
542
|
+
const { provider: providerInput, style: styleInput } = resolveStyle(candidate.provider, candidate.style);
|
|
543
|
+
const provider = getProvider(providerInput || 'dicebear');
|
|
544
|
+
if (!provider) {
|
|
545
|
+
throw new Error(`dlc-avatar-creator: unknown provider "${providerInput}" (available: dicebear, humation)`);
|
|
546
|
+
}
|
|
547
|
+
let styleId = normalizeString(styleInput, provider.styles[0] ? provider.styles[0].id : undefined);
|
|
548
|
+
// 显式传入的非法 style 仍然报错;从 state 继承的旧 style 在换 provider
|
|
549
|
+
// 后不属于新 provider 时回退到新 provider 的默认样式(与 emo setType 一致)。
|
|
550
|
+
if (next.style === undefined && styleId && !getStyleDefinition(provider.id, styleId)) {
|
|
551
|
+
styleId = provider.styles[0] ? provider.styles[0].id : styleId;
|
|
552
|
+
}
|
|
553
|
+
// 画风模块还没就绪时先显示占位,避免白屏;失败时恢复原样。
|
|
554
|
+
let restore = null;
|
|
555
|
+
if (!isAvatarStyleLoaded(provider.id, styleId)) {
|
|
556
|
+
const previousHtml = element.innerHTML;
|
|
557
|
+
element.innerHTML = AVATAR_POSTER_HTML;
|
|
558
|
+
if (typeof candidate.size === 'number' && candidate.size > 0) {
|
|
559
|
+
element.style.width = `${candidate.size}px`;
|
|
560
|
+
element.style.height = `${candidate.size}px`;
|
|
561
|
+
}
|
|
562
|
+
restore = () => {
|
|
563
|
+
element.innerHTML = previousHtml;
|
|
564
|
+
element.style.width = '';
|
|
565
|
+
element.style.height = '';
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
try {
|
|
569
|
+
const loaded = await loadAvatarStyle(provider.id, styleId);
|
|
570
|
+
if (!getStyleDefinition(provider.id, styleId)) {
|
|
571
|
+
throw new Error(`dlc-avatar-creator: unknown style "${styleId}" for provider "${provider.id}"`);
|
|
572
|
+
}
|
|
573
|
+
styleObj = provider.id === 'humation'
|
|
574
|
+
? {
|
|
575
|
+
...provider.styles[0],
|
|
576
|
+
render: (renderOptions) => provider.render({ style: provider.styles[0].id, ...renderOptions })
|
|
577
|
+
}
|
|
578
|
+
: loaded;
|
|
579
|
+
} catch (error) {
|
|
580
|
+
if (restore) restore();
|
|
581
|
+
throw error;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
if (seq !== commitSeq) {
|
|
585
|
+
// 已被更新的提交取代:state / DOM 交给新提交,返回当前已提交的结果。
|
|
586
|
+
return { superseded: true, ...(lastApplied ? { result: lastApplied } : {}) };
|
|
587
|
+
}
|
|
588
|
+
return applyResult(renderCurrent(styleObj));
|
|
589
|
+
} catch (error) {
|
|
590
|
+
if (seq !== commitSeq) return { superseded: true };
|
|
591
|
+
emitter.emit('error', error);
|
|
592
|
+
throw error;
|
|
593
|
+
}
|
|
594
|
+
})();
|
|
595
|
+
pendingCommit = promise;
|
|
596
|
+
return promise;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function render() {
|
|
600
|
+
return commit();
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const settlePending = () => (pendingCommit ? pendingCommit.catch(() => null) : Promise.resolve());
|
|
604
|
+
|
|
605
|
+
const controller = {
|
|
606
|
+
element,
|
|
607
|
+
|
|
608
|
+
get state() {
|
|
609
|
+
return { ...state, providerOptions: deepClone(state.providerOptions) };
|
|
610
|
+
},
|
|
611
|
+
|
|
612
|
+
async getSvg() {
|
|
613
|
+
await settlePending();
|
|
614
|
+
return (await render()).svg;
|
|
615
|
+
},
|
|
616
|
+
|
|
617
|
+
async getResult() {
|
|
618
|
+
await settlePending();
|
|
619
|
+
return render();
|
|
620
|
+
},
|
|
621
|
+
|
|
622
|
+
async toDataUri() {
|
|
623
|
+
await settlePending();
|
|
624
|
+
return svgToDataUri((await render()).svg);
|
|
625
|
+
},
|
|
626
|
+
|
|
627
|
+
setOptions(next = {}) {
|
|
628
|
+
const promise = commit(next);
|
|
629
|
+
promise.then((result) => {
|
|
630
|
+
if (result && result.superseded) return;
|
|
631
|
+
emitter.emit('change', { ...result, state: this.state });
|
|
632
|
+
}).catch(() => {});
|
|
633
|
+
return promise;
|
|
634
|
+
},
|
|
635
|
+
|
|
636
|
+
setProvider(provider) {
|
|
637
|
+
return this.setOptions({ provider });
|
|
638
|
+
},
|
|
639
|
+
|
|
640
|
+
setStyle(style) {
|
|
641
|
+
return this.setOptions({ style });
|
|
642
|
+
},
|
|
643
|
+
|
|
644
|
+
setSeed(seed) {
|
|
645
|
+
return this.setOptions({ seed });
|
|
646
|
+
},
|
|
647
|
+
|
|
648
|
+
setSize(size) {
|
|
649
|
+
return this.setOptions({ size });
|
|
650
|
+
},
|
|
651
|
+
|
|
652
|
+
setBackground(background) {
|
|
653
|
+
return this.setOptions({ background });
|
|
654
|
+
},
|
|
655
|
+
|
|
656
|
+
setProviderOptions(providerOptions) {
|
|
657
|
+
return this.setOptions({ providerOptions });
|
|
658
|
+
},
|
|
659
|
+
|
|
660
|
+
randomize() {
|
|
661
|
+
return this.setOptions({ seed: defaultSeed() });
|
|
662
|
+
},
|
|
663
|
+
|
|
664
|
+
on(event, fn) {
|
|
665
|
+
return emitter.on(event, fn);
|
|
666
|
+
},
|
|
667
|
+
|
|
668
|
+
off(event, fn) {
|
|
669
|
+
return emitter.off(event, fn);
|
|
670
|
+
},
|
|
671
|
+
|
|
672
|
+
destroy() {
|
|
673
|
+
element.innerHTML = '';
|
|
674
|
+
if (element.parentNode) element.parentNode.removeChild(element);
|
|
675
|
+
},
|
|
676
|
+
|
|
677
|
+
dispose() {
|
|
678
|
+
return this.destroy();
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
// 首次渲染按需加载样式;加载失败已通过 'error' 事件上报,'ready' 不触发。
|
|
683
|
+
render().catch(() => {});
|
|
684
|
+
return controller;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
export { createAvatarSvg as a, getProviders as b, createAvatarCreator as c, getStyleDefinition as d, getStyleDefinitions as e, dicebearProvider as f, getProvider as g };
|