@5even7/dlc-ui 0.2.0 → 0.2.2
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 +34 -7
- package/README.md +132 -106
- package/dist/capsule.cjs +381 -72
- package/dist/capsule.mjs +436 -147
- package/dist/color.cjs +324 -17
- package/dist/color.mjs +366 -65
- package/dist/index.cjs +680 -165
- package/dist/index.mjs +836 -366
- package/dist/index.umd.js +680 -165
- package/dist/index.umd.min.js +1 -1
- package/dist/progress.cjs +446 -96
- package/dist/progress.mjs +516 -191
- package/dist/vue2.cjs +1026 -162
- package/dist/vue2.mjs +1175 -389
- package/dist/vue3.cjs +1026 -162
- package/dist/vue3.mjs +1175 -389
- package/package.json +1 -1
- package/styles/base.css +12 -18
- package/styles/capsule.css +34 -81
- package/styles/progress.css +37 -57
- package/types/index.d.ts +59 -44
- package/types/vue3.d.ts +59 -20
package/dist/capsule.mjs
CHANGED
|
@@ -137,46 +137,236 @@ function getPreset(kind, ref) {
|
|
|
137
137
|
return found;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
const DEFAULTS = {
|
|
141
|
-
capsule: {
|
|
142
|
-
width: '100%',
|
|
143
|
-
height: 160,
|
|
140
|
+
const DEFAULTS = {
|
|
141
|
+
capsule: {
|
|
142
|
+
width: '100%',
|
|
143
|
+
height: 160,
|
|
144
144
|
quality: 'auto',
|
|
145
145
|
renderer: 'auto',
|
|
146
146
|
respectReducedMotion: true,
|
|
147
147
|
mouseColor: true,
|
|
148
|
-
|
|
148
|
+
textRatio: 39
|
|
149
149
|
}};
|
|
150
150
|
|
|
151
|
-
/**
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
dragLabel: 'DRAG',
|
|
158
|
-
valueSuffix: '%',
|
|
159
|
-
progressAria: '{brand} {code} 加载进度',
|
|
151
|
+
/**
|
|
152
|
+
* Internal accessibility labels and small UI text. Since the copy API was
|
|
153
|
+
* removed (slots own the text area), these are no longer user-overridable.
|
|
154
|
+
* Templates use {brand} {code} {name} placeholders.
|
|
155
|
+
*/
|
|
156
|
+
const COPY = {
|
|
160
157
|
capsuleAria: '打开 {name} 沉浸预览'
|
|
161
158
|
};
|
|
162
159
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
160
|
+
/**
|
|
161
|
+
* Color helpers. `normalizeColor` converts any supported CSS color
|
|
162
|
+
* (hex / named / rgb() / rgba()) into a 6-digit hex string, so renderers can
|
|
163
|
+
* keep working with #rrggbb only. rgba() alpha is intentionally dropped:
|
|
164
|
+
* the WebGL shader has no per-color alpha channel, and the component is
|
|
165
|
+
* opaque by design — use component-level `opacity` for transparency.
|
|
166
|
+
*/
|
|
167
|
+
|
|
168
|
+
const NAMED_COLORS = {
|
|
169
|
+
aliceblue: '#f0f8ff',
|
|
170
|
+
antiquewhite: '#faebd7',
|
|
171
|
+
aqua: '#00ffff',
|
|
172
|
+
aquamarine: '#7fffd4',
|
|
173
|
+
azure: '#f0ffff',
|
|
174
|
+
beige: '#f5f5dc',
|
|
175
|
+
bisque: '#ffe4c4',
|
|
176
|
+
black: '#000000',
|
|
177
|
+
blanchedalmond: '#ffebcd',
|
|
178
|
+
blue: '#0000ff',
|
|
179
|
+
blueviolet: '#8a2be2',
|
|
180
|
+
brown: '#a52a2a',
|
|
181
|
+
burlywood: '#deb887',
|
|
182
|
+
cadetblue: '#5f9ea0',
|
|
183
|
+
chartreuse: '#7fff00',
|
|
184
|
+
chocolate: '#d2691e',
|
|
185
|
+
coral: '#ff7f50',
|
|
186
|
+
cornflowerblue: '#6495ed',
|
|
187
|
+
cornsilk: '#fff8dc',
|
|
188
|
+
crimson: '#dc143c',
|
|
189
|
+
cyan: '#00ffff',
|
|
190
|
+
darkblue: '#00008b',
|
|
191
|
+
darkcyan: '#008b8b',
|
|
192
|
+
darkgoldenrod: '#b8860b',
|
|
193
|
+
darkgray: '#a9a9a9',
|
|
194
|
+
darkgreen: '#006400',
|
|
195
|
+
darkgrey: '#a9a9a9',
|
|
196
|
+
darkkhaki: '#bdb76b',
|
|
197
|
+
darkmagenta: '#8b008b',
|
|
198
|
+
darkolivegreen: '#556b2f',
|
|
199
|
+
darkorange: '#ff8c00',
|
|
200
|
+
darkorchid: '#9932cc',
|
|
201
|
+
darkred: '#8b0000',
|
|
202
|
+
darksalmon: '#e9967a',
|
|
203
|
+
darkseagreen: '#8fbc8f',
|
|
204
|
+
darkslateblue: '#483d8b',
|
|
205
|
+
darkslategray: '#2f4f4f',
|
|
206
|
+
darkslategrey: '#2f4f4f',
|
|
207
|
+
darkturquoise: '#00ced1',
|
|
208
|
+
darkviolet: '#9400d3',
|
|
209
|
+
deeppink: '#ff1493',
|
|
210
|
+
deepskyblue: '#00bfff',
|
|
211
|
+
dimgray: '#696969',
|
|
212
|
+
dimgrey: '#696969',
|
|
213
|
+
dodgerblue: '#1e90ff',
|
|
214
|
+
firebrick: '#b22222',
|
|
215
|
+
floralwhite: '#fffaf0',
|
|
216
|
+
forestgreen: '#228b22',
|
|
217
|
+
fuchsia: '#ff00ff',
|
|
218
|
+
gainsboro: '#dcdcdc',
|
|
219
|
+
ghostwhite: '#f8f8ff',
|
|
220
|
+
gold: '#ffd700',
|
|
221
|
+
goldenrod: '#daa520',
|
|
222
|
+
gray: '#808080',
|
|
223
|
+
green: '#008000',
|
|
224
|
+
greenyellow: '#adff2f',
|
|
225
|
+
grey: '#808080',
|
|
226
|
+
honeydew: '#f0fff0',
|
|
227
|
+
hotpink: '#ff69b4',
|
|
228
|
+
indianred: '#cd5c5c',
|
|
229
|
+
indigo: '#4b0082',
|
|
230
|
+
ivory: '#fffff0',
|
|
231
|
+
khaki: '#f0e68c',
|
|
232
|
+
lavender: '#e6e6fa',
|
|
233
|
+
lavenderblush: '#fff0f5',
|
|
234
|
+
lawngreen: '#7cfc00',
|
|
235
|
+
lemonchiffon: '#fffacd',
|
|
236
|
+
lightblue: '#add8e6',
|
|
237
|
+
lightcoral: '#f08080',
|
|
238
|
+
lightcyan: '#e0ffff',
|
|
239
|
+
lightgoldenrodyellow: '#fafad2',
|
|
240
|
+
lightgray: '#d3d3d3',
|
|
241
|
+
lightgreen: '#90ee90',
|
|
242
|
+
lightgrey: '#d3d3d3',
|
|
243
|
+
lightpink: '#ffb6c1',
|
|
244
|
+
lightsalmon: '#ffa07a',
|
|
245
|
+
lightseagreen: '#20b2aa',
|
|
246
|
+
lightskyblue: '#87cefa',
|
|
247
|
+
lightslategray: '#778899',
|
|
248
|
+
lightslategrey: '#778899',
|
|
249
|
+
lightsteelblue: '#b0c4de',
|
|
250
|
+
lightyellow: '#ffffe0',
|
|
251
|
+
lime: '#00ff00',
|
|
252
|
+
limegreen: '#32cd32',
|
|
253
|
+
linen: '#faf0e6',
|
|
254
|
+
magenta: '#ff00ff',
|
|
255
|
+
maroon: '#800000',
|
|
256
|
+
mediumaquamarine: '#66cdaa',
|
|
257
|
+
mediumblue: '#0000cd',
|
|
258
|
+
mediumorchid: '#ba55d3',
|
|
259
|
+
mediumpurple: '#9370db',
|
|
260
|
+
mediumseagreen: '#3cb371',
|
|
261
|
+
mediumslateblue: '#7b68ee',
|
|
262
|
+
mediumspringgreen: '#00fa9a',
|
|
263
|
+
mediumturquoise: '#48d1cc',
|
|
264
|
+
mediumvioletred: '#c71585',
|
|
265
|
+
midnightblue: '#191970',
|
|
266
|
+
mintcream: '#f5fffa',
|
|
267
|
+
mistyrose: '#ffe4e1',
|
|
268
|
+
moccasin: '#ffe4b5',
|
|
269
|
+
navajowhite: '#ffdead',
|
|
270
|
+
navy: '#000080',
|
|
271
|
+
oldlace: '#fdf5e6',
|
|
272
|
+
olive: '#808000',
|
|
273
|
+
olivedrab: '#6b8e23',
|
|
274
|
+
orange: '#ffa500',
|
|
275
|
+
orangered: '#ff4500',
|
|
276
|
+
orchid: '#da70d6',
|
|
277
|
+
palegoldenrod: '#eee8aa',
|
|
278
|
+
palegreen: '#98fb98',
|
|
279
|
+
paleturquoise: '#afeeee',
|
|
280
|
+
palevioletred: '#db7093',
|
|
281
|
+
papayawhip: '#ffefd5',
|
|
282
|
+
peachpuff: '#ffdab9',
|
|
283
|
+
peru: '#cd853f',
|
|
284
|
+
pink: '#ffc0cb',
|
|
285
|
+
plum: '#dda0dd',
|
|
286
|
+
powderblue: '#b0e0e6',
|
|
287
|
+
purple: '#800080',
|
|
288
|
+
rebeccapurple: '#663399',
|
|
289
|
+
red: '#ff0000',
|
|
290
|
+
rosybrown: '#bc8f8f',
|
|
291
|
+
royalblue: '#4169e1',
|
|
292
|
+
saddlebrown: '#8b4513',
|
|
293
|
+
salmon: '#fa8072',
|
|
294
|
+
sandybrown: '#f4a460',
|
|
295
|
+
seagreen: '#2e8b57',
|
|
296
|
+
seashell: '#fff5ee',
|
|
297
|
+
sienna: '#a0522d',
|
|
298
|
+
silver: '#c0c0c0',
|
|
299
|
+
skyblue: '#87ceeb',
|
|
300
|
+
slateblue: '#6a5acd',
|
|
301
|
+
slategray: '#708090',
|
|
302
|
+
slategrey: '#708090',
|
|
303
|
+
snow: '#fffafa',
|
|
304
|
+
springgreen: '#00ff7f',
|
|
305
|
+
steelblue: '#4682b4',
|
|
306
|
+
tan: '#d2b48c',
|
|
307
|
+
teal: '#008080',
|
|
308
|
+
thistle: '#d8bfd8',
|
|
309
|
+
tomato: '#ff6347',
|
|
310
|
+
turquoise: '#40e0d0',
|
|
311
|
+
violet: '#ee82ee',
|
|
312
|
+
wheat: '#f5deb3',
|
|
313
|
+
white: '#ffffff',
|
|
314
|
+
whitesmoke: '#f5f5f5',
|
|
315
|
+
yellow: '#ffff00',
|
|
316
|
+
yellowgreen: '#9acd32'
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
function clampChannel(value) {
|
|
320
|
+
return Math.min(255, Math.max(0, Math.round(value)));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function normalizeRgbArgs(args) {
|
|
324
|
+
const parts = args.split(/[,\s/]+/).filter(Boolean);
|
|
325
|
+
if (parts.length < 3) return null;
|
|
326
|
+
const to255 = (value) => {
|
|
327
|
+
if (typeof value !== 'string' || !value) return null;
|
|
328
|
+
if (value.endsWith('%')) return clampChannel((parseFloat(value) / 100) * 255);
|
|
329
|
+
const number = Number(value);
|
|
330
|
+
return Number.isFinite(number) ? clampChannel(number) : null;
|
|
331
|
+
};
|
|
332
|
+
const red = to255(parts[0]);
|
|
333
|
+
const green = to255(parts[1]);
|
|
334
|
+
const blue = to255(parts[2]);
|
|
335
|
+
if (red === null || green === null || blue === null) return null;
|
|
336
|
+
return `#${[red, green, blue].map((n) => n.toString(16).padStart(2, '0')).join('')}`;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Convert any supported CSS color into `#rrggbb`. Returns null when the
|
|
341
|
+
* value cannot be parsed. Supports: #rgb / #rrggbb (case-insensitive),
|
|
342
|
+
* CSS named colors, rgb() / rgba() with comma or modern space syntax,
|
|
343
|
+
* including percentages. Alpha in rgba() is ignored.
|
|
344
|
+
*/
|
|
345
|
+
function normalizeColor(value) {
|
|
346
|
+
if (typeof value !== 'string') return null;
|
|
347
|
+
const input = value.trim();
|
|
348
|
+
if (!input) return null;
|
|
349
|
+
const lower = input.toLowerCase();
|
|
350
|
+
if (NAMED_COLORS[lower]) return NAMED_COLORS[lower];
|
|
351
|
+
if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(input)) {
|
|
352
|
+
const hex = input.slice(1).toLowerCase();
|
|
353
|
+
if (hex.length === 3) return `#${hex.split('').map((c) => c + c).join('')}`;
|
|
354
|
+
return `#${hex}`;
|
|
355
|
+
}
|
|
356
|
+
const match = input.match(/^rgba?\((.*)\)$/i);
|
|
357
|
+
if (match) return normalizeRgbArgs(match[1]);
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function hexToRgb01(color) {
|
|
362
|
+
const hex = normalizeColor(color);
|
|
363
|
+
if (!hex) return [0, 0, 0];
|
|
364
|
+
const value = Number.parseInt(hex.slice(1), 16);
|
|
365
|
+
return [
|
|
366
|
+
((value >> 16) & 255) / 255,
|
|
367
|
+
((value >> 8) & 255) / 255,
|
|
368
|
+
(value & 255) / 255
|
|
369
|
+
];
|
|
180
370
|
}
|
|
181
371
|
|
|
182
372
|
const VERTEX_SHADER = `#version 300 es
|
|
@@ -404,12 +594,31 @@ class CosmicRenderer {
|
|
|
404
594
|
this.timeOffset = preset.seed * 0.73;
|
|
405
595
|
}
|
|
406
596
|
|
|
407
|
-
setDprCap(cap) {
|
|
408
|
-
this.options.dprCap = cap;
|
|
409
|
-
this.resize();
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
|
|
597
|
+
setDprCap(cap) {
|
|
598
|
+
this.options.dprCap = cap;
|
|
599
|
+
this.resize();
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
setMouseColor(enabled) {
|
|
603
|
+
this.options.mouseColor = enabled !== false;
|
|
604
|
+
if (this.options.mouseColor) {
|
|
605
|
+
if (this.onPointerMove) return this;
|
|
606
|
+
this.#bindEvents();
|
|
607
|
+
return this;
|
|
608
|
+
}
|
|
609
|
+
if (this.onPointerMove) {
|
|
610
|
+
const target = this.eventTarget;
|
|
611
|
+
target.removeEventListener('pointermove', this.onPointerMove);
|
|
612
|
+
target.removeEventListener('pointerdown', this.onPointerMove);
|
|
613
|
+
target.removeEventListener('pointerleave', this.onPointerLeave);
|
|
614
|
+
this.onPointerMove = null;
|
|
615
|
+
this.onPointerLeave = null;
|
|
616
|
+
}
|
|
617
|
+
this.motionTarget = 0;
|
|
618
|
+
return this;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
randomize() {
|
|
413
622
|
this.preset.seed = Math.random() * 100;
|
|
414
623
|
this.timeOffset = Math.random() * 40;
|
|
415
624
|
}
|
|
@@ -489,10 +698,12 @@ class FallbackRenderer {
|
|
|
489
698
|
}
|
|
490
699
|
}
|
|
491
700
|
|
|
492
|
-
drawNebula(time) {
|
|
493
|
-
const ctx = this.context;
|
|
494
|
-
const { width, height } = this.canvas;
|
|
495
|
-
const
|
|
701
|
+
drawNebula(time) {
|
|
702
|
+
const ctx = this.context;
|
|
703
|
+
const { width, height } = this.canvas;
|
|
704
|
+
const t = time * (this.preset.speed || 1);
|
|
705
|
+
const phase = (this.preset.seed || 0) * 0.7;
|
|
706
|
+
const gradient = ctx.createLinearGradient(0, 0, width, height);
|
|
496
707
|
gradient.addColorStop(0, this.preset.colors[0]);
|
|
497
708
|
gradient.addColorStop(0.38, this.preset.colors[1]);
|
|
498
709
|
gradient.addColorStop(0.72, this.preset.colors[2]);
|
|
@@ -500,10 +711,10 @@ class FallbackRenderer {
|
|
|
500
711
|
ctx.fillStyle = gradient;
|
|
501
712
|
ctx.fillRect(0, 0, width, height);
|
|
502
713
|
|
|
503
|
-
ctx.globalCompositeOperation = 'screen';
|
|
504
|
-
for (let index = 0; index < 6; index += 1) {
|
|
505
|
-
const x = (0.5 + 0.45 * Math.sin(
|
|
506
|
-
const y = (0.5 + 0.4 * Math.cos(
|
|
714
|
+
ctx.globalCompositeOperation = 'screen';
|
|
715
|
+
for (let index = 0; index < 6; index += 1) {
|
|
716
|
+
const x = (0.5 + 0.45 * Math.sin(t * 0.32 + index * 1.7 + phase)) * width;
|
|
717
|
+
const y = (0.5 + 0.4 * Math.cos(t * 0.25 + index + phase)) * height;
|
|
507
718
|
const radius = Math.max(width, height) * (0.08 + (index % 3) * 0.04);
|
|
508
719
|
const glow = ctx.createRadialGradient(x, y, 0, x, y, radius);
|
|
509
720
|
glow.addColorStop(0, rgb(this.preset.colors[(index + 1) % 4], 0.24));
|
|
@@ -524,8 +735,11 @@ class FallbackRenderer {
|
|
|
524
735
|
this.preset = preset;
|
|
525
736
|
}
|
|
526
737
|
|
|
527
|
-
randomize() {
|
|
528
|
-
|
|
738
|
+
randomize() {
|
|
739
|
+
if (this.preset) this.preset.seed = Math.random() * 100;
|
|
740
|
+
}
|
|
741
|
+
setMouseColor() {}
|
|
742
|
+
dispose() {}
|
|
529
743
|
}
|
|
530
744
|
|
|
531
745
|
/**
|
|
@@ -625,78 +839,105 @@ function createVisibilityGuard(element) {
|
|
|
625
839
|
};
|
|
626
840
|
}
|
|
627
841
|
|
|
842
|
+
/**
|
|
843
|
+
* Run a callback asynchronously, right after the current task and before the
|
|
844
|
+
* next paint. Used for mount-time events (ready / error) so listeners that
|
|
845
|
+
* are attached after create() still receive them. Falls back for legacy
|
|
846
|
+
* browsers that lack queueMicrotask / Promise.
|
|
847
|
+
*/
|
|
848
|
+
function nextTick(fn) {
|
|
849
|
+
if (typeof queueMicrotask === 'function') {
|
|
850
|
+
queueMicrotask(fn);
|
|
851
|
+
} else if (typeof Promise !== 'undefined' && typeof Promise.resolve === 'function') {
|
|
852
|
+
Promise.resolve().then(fn);
|
|
853
|
+
} else {
|
|
854
|
+
setTimeout(fn, 0);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
628
858
|
function prefersReducedMotion() {
|
|
629
859
|
return typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
630
860
|
}
|
|
631
861
|
|
|
632
862
|
/**
|
|
633
|
-
* Mount a cosmic (nebula) capsule into `container`.
|
|
634
|
-
*
|
|
635
|
-
* Options: preset (
|
|
636
|
-
*
|
|
637
|
-
*
|
|
638
|
-
|
|
639
|
-
|
|
863
|
+
* Mount a cosmic (nebula) capsule into `container`.
|
|
864
|
+
*
|
|
865
|
+
* Options: preset (literary name), width, height (number=px or string with
|
|
866
|
+
* px/%/vw/vh/em/rem), colors, seed, speed, textRatio (0-100, text region
|
|
867
|
+
* width in percent), text (HTML string or DOM nodes for the text slot),
|
|
868
|
+
* colorContent (HTML string or DOM nodes for the color slot), quality,
|
|
869
|
+
* renderer, respectReducedMotion, mouseColor, cssVars.
|
|
870
|
+
*/
|
|
871
|
+
function createCapsule(container, options = {}) {
|
|
640
872
|
if (!container || typeof container.appendChild !== 'function') {
|
|
641
873
|
throw new Error('createCapsule: container element is required');
|
|
642
874
|
}
|
|
643
875
|
|
|
644
876
|
const preset = { ...getPreset('capsule', options.preset ?? '初光') };
|
|
645
|
-
const merged = normalizeOptions(DEFAULTS.capsule, preset, options);
|
|
646
|
-
const
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
root
|
|
653
|
-
root.
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
};
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
if (
|
|
670
|
-
if (
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
canvas.
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
root.appendChild(canvas);
|
|
696
|
-
container.appendChild(root);
|
|
697
|
-
|
|
698
|
-
const emitter = createEmitter();
|
|
699
|
-
let paused = merged.respectReducedMotion && prefersReducedMotion();
|
|
877
|
+
const merged = normalizeOptions(DEFAULTS.capsule, preset, options);
|
|
878
|
+
const normalizedColors = merged.colors.map(normalizeColor);
|
|
879
|
+
if (normalizedColors.every(Boolean)) preset.colors = normalizedColors;
|
|
880
|
+
preset.seed = merged.seed;
|
|
881
|
+
preset.speed = merged.speed;
|
|
882
|
+
let customLabel = typeof options.label === 'string' && options.label ? options.label : null;
|
|
883
|
+
|
|
884
|
+
const root = document.createElement('div');
|
|
885
|
+
root.className = 'hj-capsule-root hj-capsule-cosmic';
|
|
886
|
+
root.dataset.group = preset.group;
|
|
887
|
+
root.dataset.mode = 'nebula';
|
|
888
|
+
root.dataset.theme = preset.theme || 'light';
|
|
889
|
+
root.setAttribute('role', 'img');
|
|
890
|
+
const updateAria = () => {
|
|
891
|
+
root.setAttribute('aria-label', customLabel || COPY.capsuleAria.replace('{name}', preset.name));
|
|
892
|
+
};
|
|
893
|
+
updateAria();
|
|
894
|
+
|
|
895
|
+
// Slot containers are always present but transparent by default: without
|
|
896
|
+
// content they are invisible, so "no slot" behaves like the old
|
|
897
|
+
// showCopy=false. They only provide geometry — no typography, padding or
|
|
898
|
+
// background is imposed on user content (see docs: 插槽 CSS 约定).
|
|
899
|
+
const fillContent = (layer, content) => {
|
|
900
|
+
layer.innerHTML = '';
|
|
901
|
+
if (content == null) return;
|
|
902
|
+
if (typeof content === 'string') {
|
|
903
|
+
layer.innerHTML = content;
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
const nodes = Array.isArray(content) ? content : [content];
|
|
907
|
+
for (const node of nodes) {
|
|
908
|
+
if (node && typeof node.nodeType === 'number') layer.appendChild(node);
|
|
909
|
+
}
|
|
910
|
+
};
|
|
911
|
+
|
|
912
|
+
const textLayer = document.createElement('div');
|
|
913
|
+
textLayer.className = 'hj-capsule-text hj-capsule-copy';
|
|
914
|
+
|
|
915
|
+
const visualLayer = document.createElement('div');
|
|
916
|
+
visualLayer.className = 'hj-capsule-visual';
|
|
917
|
+
|
|
918
|
+
fillContent(textLayer, options.text);
|
|
919
|
+
fillContent(visualLayer, options.colorContent);
|
|
920
|
+
|
|
921
|
+
const canvas = document.createElement('canvas');
|
|
922
|
+
canvas.className = 'hj-capsule-canvas';
|
|
923
|
+
canvas.setAttribute('aria-hidden', 'true');
|
|
924
|
+
|
|
925
|
+
root.appendChild(textLayer);
|
|
926
|
+
root.appendChild(visualLayer);
|
|
927
|
+
root.appendChild(canvas);
|
|
928
|
+
container.appendChild(root);
|
|
929
|
+
|
|
930
|
+
const emitter = createEmitter();
|
|
931
|
+
let paused = merged.respectReducedMotion && prefersReducedMotion();
|
|
932
|
+
let disposed = false;
|
|
933
|
+
const dirty = {
|
|
934
|
+
preset: false,
|
|
935
|
+
seed: false,
|
|
936
|
+
speed: false,
|
|
937
|
+
colors: false,
|
|
938
|
+
textRatio: false,
|
|
939
|
+
cssVars: false
|
|
940
|
+
};
|
|
700
941
|
|
|
701
942
|
let renderer;
|
|
702
943
|
const useWebgl = merged.renderer !== 'canvas2d';
|
|
@@ -705,19 +946,25 @@ function createCapsule(container, options = {}) {
|
|
|
705
946
|
renderer = new CosmicRenderer(canvas, merged, { dprCap: dprCapFor(merged.quality) });
|
|
706
947
|
} catch (error) {
|
|
707
948
|
renderer = new FallbackRenderer(canvas, merged);
|
|
708
|
-
|
|
949
|
+
nextTick(() => {
|
|
950
|
+
if (disposed) return;
|
|
951
|
+
emitter.emit('error', { message: String(error && error.message ? error.message : error) });
|
|
952
|
+
});
|
|
709
953
|
}
|
|
710
954
|
} else {
|
|
711
955
|
renderer = new FallbackRenderer(canvas, merged);
|
|
712
956
|
}
|
|
713
957
|
|
|
714
|
-
const applySize = () => {
|
|
715
|
-
root.style.width = parseSize(merged.width);
|
|
716
|
-
root.style.height = parseSize(merged.height);
|
|
717
|
-
const vars = merged.cssVars || {};
|
|
718
|
-
for (const name of Object.keys(vars)) root.style.setProperty(name, vars[name]);
|
|
719
|
-
|
|
720
|
-
|
|
958
|
+
const applySize = () => {
|
|
959
|
+
root.style.width = parseSize(merged.width);
|
|
960
|
+
root.style.height = parseSize(merged.height);
|
|
961
|
+
const vars = merged.cssVars || {};
|
|
962
|
+
for (const name of Object.keys(vars)) root.style.setProperty(name, vars[name]);
|
|
963
|
+
if (merged.textRatio !== undefined) {
|
|
964
|
+
root.style.setProperty('--hj-text-width', `${merged.textRatio}%`);
|
|
965
|
+
}
|
|
966
|
+
renderer.resize();
|
|
967
|
+
};
|
|
721
968
|
applySize();
|
|
722
969
|
|
|
723
970
|
const resizeObserver = typeof ResizeObserver !== 'undefined'
|
|
@@ -746,38 +993,47 @@ function createCapsule(container, options = {}) {
|
|
|
746
993
|
() => paused
|
|
747
994
|
);
|
|
748
995
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
root.dataset.theme = preset.theme || 'light';
|
|
754
|
-
renderCopy();
|
|
755
|
-
};
|
|
996
|
+
nextTick(() => {
|
|
997
|
+
if (disposed) return;
|
|
998
|
+
emitter.emit('ready', { preset: { ...preset } });
|
|
999
|
+
});
|
|
756
1000
|
|
|
757
|
-
|
|
1001
|
+
const syncTheme = () => {
|
|
1002
|
+
root.dataset.mode = 'nebula';
|
|
1003
|
+
root.dataset.theme = preset.theme || 'light';
|
|
1004
|
+
updateAria();
|
|
1005
|
+
};
|
|
1006
|
+
|
|
1007
|
+
return {
|
|
758
1008
|
element: root,
|
|
759
1009
|
canvas,
|
|
760
1010
|
preset,
|
|
761
1011
|
on: emitter.on,
|
|
762
1012
|
off: emitter.off,
|
|
763
|
-
setPreset(ref) {
|
|
764
|
-
const next = getPreset('capsule', ref);
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
1013
|
+
setPreset(ref) {
|
|
1014
|
+
const next = getPreset('capsule', ref);
|
|
1015
|
+
dirty.preset = true;
|
|
1016
|
+
Object.assign(preset, next);
|
|
1017
|
+
const nextColors = preset.colors.map(normalizeColor);
|
|
1018
|
+
if (nextColors.every(Boolean)) preset.colors = nextColors;
|
|
1019
|
+
renderer.setPreset({ ...preset });
|
|
1020
|
+
syncTheme();
|
|
1021
|
+
renderer.resize();
|
|
1022
|
+
emitter.emit('presetchange', { preset: { ...next } });
|
|
1023
|
+
return this;
|
|
771
1024
|
},
|
|
772
1025
|
setColors(colors) {
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
1026
|
+
const next = Array.isArray(colors) ? colors.map(normalizeColor) : [];
|
|
1027
|
+
if (next.length !== 4 || next.some((color) => !color)) return this;
|
|
1028
|
+
dirty.colors = true;
|
|
1029
|
+
preset.colors = next;
|
|
1030
|
+
renderer.setPreset({ ...preset, colors: next });
|
|
776
1031
|
return this;
|
|
777
1032
|
},
|
|
778
1033
|
setSeed(seed) {
|
|
779
1034
|
const value = Number(seed);
|
|
780
1035
|
if (!Number.isFinite(value)) return this;
|
|
1036
|
+
dirty.seed = true;
|
|
781
1037
|
preset.seed = value;
|
|
782
1038
|
renderer.setPreset({ ...preset });
|
|
783
1039
|
return this;
|
|
@@ -785,20 +1041,49 @@ function createCapsule(container, options = {}) {
|
|
|
785
1041
|
setSpeed(speed) {
|
|
786
1042
|
const value = Number(speed);
|
|
787
1043
|
if (!Number.isFinite(value)) return this;
|
|
1044
|
+
dirty.speed = true;
|
|
788
1045
|
preset.speed = value;
|
|
789
1046
|
renderer.setPreset({ ...preset });
|
|
790
1047
|
return this;
|
|
791
1048
|
},
|
|
792
|
-
|
|
1049
|
+
setText(content) {
|
|
1050
|
+
fillContent(textLayer, content);
|
|
1051
|
+
return this;
|
|
1052
|
+
},
|
|
1053
|
+
setColorContent(content) {
|
|
1054
|
+
fillContent(visualLayer, content);
|
|
1055
|
+
return this;
|
|
1056
|
+
},
|
|
1057
|
+
setTextRatio(ratio) {
|
|
1058
|
+
const value = Number(ratio);
|
|
1059
|
+
if (!Number.isFinite(value) || value < 0 || value > 100) return this;
|
|
1060
|
+
dirty.textRatio = true;
|
|
1061
|
+
merged.textRatio = value;
|
|
1062
|
+
root.style.setProperty('--hj-text-width', `${value}%`);
|
|
1063
|
+
return this;
|
|
1064
|
+
},
|
|
1065
|
+
setCssVars(vars) {
|
|
1066
|
+
if (!vars || typeof vars !== 'object') return this;
|
|
1067
|
+
dirty.cssVars = true;
|
|
1068
|
+
merged.cssVars = { ...(merged.cssVars || {}), ...vars };
|
|
1069
|
+
for (const name of Object.keys(vars)) root.style.setProperty(name, vars[name]);
|
|
1070
|
+
return this;
|
|
1071
|
+
},
|
|
1072
|
+
setLabel(label) {
|
|
1073
|
+
customLabel = typeof label === 'string' && label ? label : null;
|
|
1074
|
+
updateAria();
|
|
1075
|
+
return this;
|
|
1076
|
+
},
|
|
1077
|
+
setSize(width, height) {
|
|
793
1078
|
if (width !== undefined) merged.width = width;
|
|
794
1079
|
if (height !== undefined) merged.height = height;
|
|
795
1080
|
applySize();
|
|
796
1081
|
return this;
|
|
797
1082
|
},
|
|
798
|
-
randomize() {
|
|
799
|
-
|
|
800
|
-
return this;
|
|
801
|
-
},
|
|
1083
|
+
randomize() {
|
|
1084
|
+
this.setSeed(Math.random() * 100);
|
|
1085
|
+
return this;
|
|
1086
|
+
},
|
|
802
1087
|
pause() {
|
|
803
1088
|
paused = true;
|
|
804
1089
|
return this;
|
|
@@ -812,15 +1097,19 @@ function createCapsule(container, options = {}) {
|
|
|
812
1097
|
if (typeof renderer.setDprCap === 'function') renderer.setDprCap(dprCapFor(quality));
|
|
813
1098
|
return this;
|
|
814
1099
|
},
|
|
815
|
-
dispose() {
|
|
816
|
-
|
|
1100
|
+
dispose() {
|
|
1101
|
+
disposed = true;
|
|
1102
|
+
unsubscribe();
|
|
817
1103
|
visibility.dispose();
|
|
818
1104
|
if (resizeObserver) resizeObserver.disconnect();
|
|
819
1105
|
else window.removeEventListener('resize', renderer.resize);
|
|
820
|
-
renderer.dispose();
|
|
821
|
-
root.remove();
|
|
822
|
-
}
|
|
823
|
-
|
|
1106
|
+
renderer.dispose();
|
|
1107
|
+
root.remove();
|
|
1108
|
+
},
|
|
1109
|
+
textRatio: merged.textRatio,
|
|
1110
|
+
cssVars: merged.cssVars,
|
|
1111
|
+
dirty
|
|
1112
|
+
};
|
|
824
1113
|
}
|
|
825
1114
|
|
|
826
1115
|
export { createCapsule };
|