@5even7/dlc-ui 0.1.0 → 0.2.1
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 +33 -3
- package/README.md +277 -248
- package/dist/capsule.cjs +421 -103
- package/dist/capsule.mjs +481 -197
- package/dist/color.cjs +342 -37
- package/dist/color.mjs +390 -105
- package/dist/index.cjs +745 -203
- package/dist/index.mjs +923 -457
- package/dist/index.umd.js +745 -203
- package/dist/index.umd.min.js +1 -1
- package/dist/progress.cjs +487 -116
- package/dist/progress.mjs +568 -245
- package/dist/vue2.cjs +1092 -208
- package/dist/vue2.mjs +1280 -501
- package/dist/vue3.cjs +1092 -208
- package/dist/vue3.mjs +1280 -501
- 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 +97 -81
- package/types/vue3.d.ts +59 -20
package/dist/index.mjs
CHANGED
|
@@ -112,50 +112,50 @@ const PALETTES$1 = {
|
|
|
112
112
|
* Capsule 预置:仅星云材质 NC-01~NC-06(NC-07~09 aurora 已移除)。
|
|
113
113
|
* 颜色引用公共色板,seed/speed 决定形态与流速。
|
|
114
114
|
*/
|
|
115
|
-
const CAPSULE_PRESETS = [
|
|
116
|
-
{ id: 'original', code: 'NC-01', name: '
|
|
117
|
-
{ id: 'ocean', code: 'NC-02', name: '
|
|
118
|
-
{ id: 'klein', code: 'NC-03', name: '
|
|
119
|
-
{ id: 'ultraviolet', code: 'NC-04', name: '
|
|
120
|
-
{ id: 'chrome', code: 'NC-05', name: '
|
|
121
|
-
{ id: 'plus', code: 'NC-06', name: '
|
|
115
|
+
const CAPSULE_PRESETS = [
|
|
116
|
+
{ id: 'original', code: 'NC-01', name: '初光', group: 'warm', seed: 1.7, speed: 0.5, colors: [...PALETTES$1.original] },
|
|
117
|
+
{ id: 'ocean', code: 'NC-02', name: '沧溟', group: 'cold', seed: 8.2, speed: 0.48, colors: [...PALETTES$1.ocean] },
|
|
118
|
+
{ id: 'klein', code: 'NC-03', name: '玄夜', group: 'cold', seed: 14.1, speed: 0.49, colors: [...PALETTES$1.klein] },
|
|
119
|
+
{ id: 'ultraviolet', code: 'NC-04', name: '紫霄', group: 'cold', seed: 23.4, speed: 0.47, colors: [...PALETTES$1.ultraviolet] },
|
|
120
|
+
{ id: 'chrome', code: 'NC-05', name: '银汉', group: 'cold', seed: 37.8, speed: 0.42, colors: [...PALETTES$1.chrome] },
|
|
121
|
+
{ id: 'plus', code: 'NC-06', name: '熔金', group: 'warm', seed: 51.3, speed: 0.5, colors: [...PALETTES$1.plus] }
|
|
122
122
|
];
|
|
123
123
|
|
|
124
124
|
const PROGRESS_PRESETS = [
|
|
125
|
-
{
|
|
126
|
-
id: 'model-training',
|
|
127
|
-
code: 'NC-10',
|
|
128
|
-
name: '
|
|
125
|
+
{
|
|
126
|
+
id: 'model-training',
|
|
127
|
+
code: 'NC-10',
|
|
128
|
+
name: '星火',
|
|
129
129
|
subtitle: 'SHA 4.5 + 100 2026 TKN',
|
|
130
130
|
group: 'progress',
|
|
131
131
|
initialProgress: 43,
|
|
132
132
|
edgeStyle: 'flow',
|
|
133
133
|
colors: ['#2B1025', '#FF3F94', '#FF8A3D', '#FFF06A']
|
|
134
134
|
},
|
|
135
|
-
{
|
|
136
|
-
id: 'agent-migration',
|
|
137
|
-
code: 'NC-11',
|
|
138
|
-
name: '
|
|
135
|
+
{
|
|
136
|
+
id: 'agent-migration',
|
|
137
|
+
code: 'NC-11',
|
|
138
|
+
name: '迁流',
|
|
139
139
|
subtitle: 'TRANSFERRING PROTOCOL',
|
|
140
140
|
group: 'progress',
|
|
141
141
|
initialProgress: 33,
|
|
142
142
|
edgeStyle: 'flow',
|
|
143
143
|
colors: ['#101C37', '#245BFF', '#00CFFF', '#5DFFE6']
|
|
144
144
|
},
|
|
145
|
-
{
|
|
146
|
-
id: 'visual-training',
|
|
147
|
-
code: 'NC-12',
|
|
148
|
-
name: '
|
|
145
|
+
{
|
|
146
|
+
id: 'visual-training',
|
|
147
|
+
code: 'NC-12',
|
|
148
|
+
name: '幻境',
|
|
149
149
|
subtitle: 'GENERATING POWER ++',
|
|
150
150
|
group: 'progress',
|
|
151
151
|
initialProgress: 58,
|
|
152
152
|
edgeStyle: 'flow',
|
|
153
153
|
colors: ['#21142D', '#7042FF', '#42F58D', '#C4FF8A']
|
|
154
154
|
},
|
|
155
|
-
{
|
|
156
|
-
id: 'tide',
|
|
157
|
-
code: 'NC-13',
|
|
158
|
-
name: '
|
|
155
|
+
{
|
|
156
|
+
id: 'tide',
|
|
157
|
+
code: 'NC-13',
|
|
158
|
+
name: '汐潮',
|
|
159
159
|
subtitle: 'MOON PULL / COAST',
|
|
160
160
|
group: 'progress',
|
|
161
161
|
initialProgress: 30,
|
|
@@ -164,114 +164,291 @@ const PROGRESS_PRESETS = [
|
|
|
164
164
|
}
|
|
165
165
|
];
|
|
166
166
|
|
|
167
|
-
const PRESETS = CAPSULE_PRESETS;
|
|
168
|
-
const ALL_PRESETS = [...CAPSULE_PRESETS, ...PROGRESS_PRESETS];
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
return Boolean(
|
|
185
|
-
preset &&
|
|
186
|
-
typeof preset.id === 'string' &&
|
|
187
|
-
typeof preset.code === 'string' &&
|
|
188
|
-
typeof preset.name === 'string' &&
|
|
189
|
-
preset.group === 'progress' &&
|
|
190
|
-
Number.isFinite(preset.initialProgress) &&
|
|
191
|
-
['flow', 'tide'].indexOf(preset.edgeStyle || 'flow') !== -1 &&
|
|
192
|
-
Array.isArray(preset.colors) &&
|
|
193
|
-
preset.colors.length === 4
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function getPreset(kind, ref) {
|
|
198
|
-
const list = kind === 'capsule' ? CAPSULE_PRESETS : kind === 'progress' ? PROGRESS_PRESETS : null;
|
|
199
|
-
if (!list) throw new Error(`Unknown preset kind: ${kind} (use "capsule" or "progress")`);
|
|
200
|
-
if (ref && typeof ref === 'object') {
|
|
201
|
-
const valid = kind === 'capsule' ? validatePreset(ref) : validateProgressPreset(ref);
|
|
202
|
-
if (!valid) throw new Error(`Invalid ${kind} preset object`);
|
|
203
|
-
return ref;
|
|
204
|
-
}
|
|
205
|
-
const key = String(ref).trim().toLowerCase();
|
|
206
|
-
const found = list.find(
|
|
207
|
-
(preset) =>
|
|
208
|
-
preset.id.toLowerCase() === key ||
|
|
209
|
-
preset.code.toLowerCase() === key ||
|
|
210
|
-
preset.name.toLowerCase() === key
|
|
211
|
-
);
|
|
212
|
-
if (!found) throw new Error(`Unknown ${kind} preset: ${ref}`);
|
|
213
|
-
return found;
|
|
167
|
+
const PRESETS = CAPSULE_PRESETS;
|
|
168
|
+
const ALL_PRESETS = [...CAPSULE_PRESETS, ...PROGRESS_PRESETS];
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Resolve a preset by its literary name (e.g. '沧溟').
|
|
172
|
+
* Matching is case-insensitive and ignores surrounding whitespace,
|
|
173
|
+
* so '沧溟' and ' 沧溟 ' both work.
|
|
174
|
+
*/
|
|
175
|
+
function getPreset(kind, ref) {
|
|
176
|
+
const list = kind === 'capsule' ? CAPSULE_PRESETS : kind === 'progress' ? PROGRESS_PRESETS : null;
|
|
177
|
+
if (!list) throw new Error(`Unknown preset kind: ${kind} (use "capsule" or "progress")`);
|
|
178
|
+
const key = String(ref).trim().toLowerCase();
|
|
179
|
+
const found = list.find(
|
|
180
|
+
(preset) => preset.name.toLowerCase() === key
|
|
181
|
+
);
|
|
182
|
+
if (!found) throw new Error(`Unknown ${kind} preset: ${ref}`);
|
|
183
|
+
return found;
|
|
214
184
|
}
|
|
215
185
|
|
|
216
|
-
const DEFAULTS = {
|
|
217
|
-
capsule: {
|
|
218
|
-
width: '100%',
|
|
219
|
-
height: 160,
|
|
220
|
-
quality: 'auto',
|
|
221
|
-
renderer: 'auto',
|
|
222
|
-
respectReducedMotion: true,
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
min: 0,
|
|
186
|
+
const DEFAULTS = {
|
|
187
|
+
capsule: {
|
|
188
|
+
width: '100%',
|
|
189
|
+
height: 160,
|
|
190
|
+
quality: 'auto',
|
|
191
|
+
renderer: 'auto',
|
|
192
|
+
respectReducedMotion: true,
|
|
193
|
+
mouseColor: true,
|
|
194
|
+
textRatio: 39
|
|
195
|
+
},
|
|
196
|
+
progress: {
|
|
197
|
+
width: 454,
|
|
198
|
+
height: 104,
|
|
199
|
+
min: 0,
|
|
231
200
|
max: 100,
|
|
232
|
-
draggable: true,
|
|
233
|
-
keyboard: true,
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
201
|
+
draggable: true,
|
|
202
|
+
keyboard: true,
|
|
203
|
+
disabled: false,
|
|
204
|
+
valueSuffix: '%',
|
|
205
|
+
edgeStyle: 'flow',
|
|
206
|
+
quality: 'auto',
|
|
207
|
+
renderer: 'auto',
|
|
208
|
+
textRatio: 54,
|
|
209
|
+
showValue: true,
|
|
210
|
+
respectReducedMotion: true
|
|
211
|
+
}
|
|
240
212
|
};
|
|
241
213
|
|
|
242
|
-
/**
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
valueSuffix: '%',
|
|
214
|
+
/**
|
|
215
|
+
* Internal accessibility labels and small UI text. Since the copy API was
|
|
216
|
+
* removed (slots own the text area), these are no longer user-overridable.
|
|
217
|
+
* Templates use {brand} {code} {name} placeholders.
|
|
218
|
+
*/
|
|
219
|
+
const COPY = {
|
|
220
|
+
brandName: '画境观屿',
|
|
221
|
+
valueSuffix: '%',
|
|
250
222
|
progressAria: '{brand} {code} 加载进度',
|
|
251
223
|
capsuleAria: '打开 {name} 沉浸预览'
|
|
252
224
|
};
|
|
253
225
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
226
|
+
/**
|
|
227
|
+
* Color helpers. `normalizeColor` converts any supported CSS color
|
|
228
|
+
* (hex / named / rgb() / rgba()) into a 6-digit hex string, so renderers can
|
|
229
|
+
* keep working with #rrggbb only. rgba() alpha is intentionally dropped:
|
|
230
|
+
* the WebGL shader has no per-color alpha channel, and the component is
|
|
231
|
+
* opaque by design — use component-level `opacity` for transparency.
|
|
232
|
+
*/
|
|
233
|
+
|
|
234
|
+
const NAMED_COLORS = {
|
|
235
|
+
aliceblue: '#f0f8ff',
|
|
236
|
+
antiquewhite: '#faebd7',
|
|
237
|
+
aqua: '#00ffff',
|
|
238
|
+
aquamarine: '#7fffd4',
|
|
239
|
+
azure: '#f0ffff',
|
|
240
|
+
beige: '#f5f5dc',
|
|
241
|
+
bisque: '#ffe4c4',
|
|
242
|
+
black: '#000000',
|
|
243
|
+
blanchedalmond: '#ffebcd',
|
|
244
|
+
blue: '#0000ff',
|
|
245
|
+
blueviolet: '#8a2be2',
|
|
246
|
+
brown: '#a52a2a',
|
|
247
|
+
burlywood: '#deb887',
|
|
248
|
+
cadetblue: '#5f9ea0',
|
|
249
|
+
chartreuse: '#7fff00',
|
|
250
|
+
chocolate: '#d2691e',
|
|
251
|
+
coral: '#ff7f50',
|
|
252
|
+
cornflowerblue: '#6495ed',
|
|
253
|
+
cornsilk: '#fff8dc',
|
|
254
|
+
crimson: '#dc143c',
|
|
255
|
+
cyan: '#00ffff',
|
|
256
|
+
darkblue: '#00008b',
|
|
257
|
+
darkcyan: '#008b8b',
|
|
258
|
+
darkgoldenrod: '#b8860b',
|
|
259
|
+
darkgray: '#a9a9a9',
|
|
260
|
+
darkgreen: '#006400',
|
|
261
|
+
darkgrey: '#a9a9a9',
|
|
262
|
+
darkkhaki: '#bdb76b',
|
|
263
|
+
darkmagenta: '#8b008b',
|
|
264
|
+
darkolivegreen: '#556b2f',
|
|
265
|
+
darkorange: '#ff8c00',
|
|
266
|
+
darkorchid: '#9932cc',
|
|
267
|
+
darkred: '#8b0000',
|
|
268
|
+
darksalmon: '#e9967a',
|
|
269
|
+
darkseagreen: '#8fbc8f',
|
|
270
|
+
darkslateblue: '#483d8b',
|
|
271
|
+
darkslategray: '#2f4f4f',
|
|
272
|
+
darkslategrey: '#2f4f4f',
|
|
273
|
+
darkturquoise: '#00ced1',
|
|
274
|
+
darkviolet: '#9400d3',
|
|
275
|
+
deeppink: '#ff1493',
|
|
276
|
+
deepskyblue: '#00bfff',
|
|
277
|
+
dimgray: '#696969',
|
|
278
|
+
dimgrey: '#696969',
|
|
279
|
+
dodgerblue: '#1e90ff',
|
|
280
|
+
firebrick: '#b22222',
|
|
281
|
+
floralwhite: '#fffaf0',
|
|
282
|
+
forestgreen: '#228b22',
|
|
283
|
+
fuchsia: '#ff00ff',
|
|
284
|
+
gainsboro: '#dcdcdc',
|
|
285
|
+
ghostwhite: '#f8f8ff',
|
|
286
|
+
gold: '#ffd700',
|
|
287
|
+
goldenrod: '#daa520',
|
|
288
|
+
gray: '#808080',
|
|
289
|
+
green: '#008000',
|
|
290
|
+
greenyellow: '#adff2f',
|
|
291
|
+
grey: '#808080',
|
|
292
|
+
honeydew: '#f0fff0',
|
|
293
|
+
hotpink: '#ff69b4',
|
|
294
|
+
indianred: '#cd5c5c',
|
|
295
|
+
indigo: '#4b0082',
|
|
296
|
+
ivory: '#fffff0',
|
|
297
|
+
khaki: '#f0e68c',
|
|
298
|
+
lavender: '#e6e6fa',
|
|
299
|
+
lavenderblush: '#fff0f5',
|
|
300
|
+
lawngreen: '#7cfc00',
|
|
301
|
+
lemonchiffon: '#fffacd',
|
|
302
|
+
lightblue: '#add8e6',
|
|
303
|
+
lightcoral: '#f08080',
|
|
304
|
+
lightcyan: '#e0ffff',
|
|
305
|
+
lightgoldenrodyellow: '#fafad2',
|
|
306
|
+
lightgray: '#d3d3d3',
|
|
307
|
+
lightgreen: '#90ee90',
|
|
308
|
+
lightgrey: '#d3d3d3',
|
|
309
|
+
lightpink: '#ffb6c1',
|
|
310
|
+
lightsalmon: '#ffa07a',
|
|
311
|
+
lightseagreen: '#20b2aa',
|
|
312
|
+
lightskyblue: '#87cefa',
|
|
313
|
+
lightslategray: '#778899',
|
|
314
|
+
lightslategrey: '#778899',
|
|
315
|
+
lightsteelblue: '#b0c4de',
|
|
316
|
+
lightyellow: '#ffffe0',
|
|
317
|
+
lime: '#00ff00',
|
|
318
|
+
limegreen: '#32cd32',
|
|
319
|
+
linen: '#faf0e6',
|
|
320
|
+
magenta: '#ff00ff',
|
|
321
|
+
maroon: '#800000',
|
|
322
|
+
mediumaquamarine: '#66cdaa',
|
|
323
|
+
mediumblue: '#0000cd',
|
|
324
|
+
mediumorchid: '#ba55d3',
|
|
325
|
+
mediumpurple: '#9370db',
|
|
326
|
+
mediumseagreen: '#3cb371',
|
|
327
|
+
mediumslateblue: '#7b68ee',
|
|
328
|
+
mediumspringgreen: '#00fa9a',
|
|
329
|
+
mediumturquoise: '#48d1cc',
|
|
330
|
+
mediumvioletred: '#c71585',
|
|
331
|
+
midnightblue: '#191970',
|
|
332
|
+
mintcream: '#f5fffa',
|
|
333
|
+
mistyrose: '#ffe4e1',
|
|
334
|
+
moccasin: '#ffe4b5',
|
|
335
|
+
navajowhite: '#ffdead',
|
|
336
|
+
navy: '#000080',
|
|
337
|
+
oldlace: '#fdf5e6',
|
|
338
|
+
olive: '#808000',
|
|
339
|
+
olivedrab: '#6b8e23',
|
|
340
|
+
orange: '#ffa500',
|
|
341
|
+
orangered: '#ff4500',
|
|
342
|
+
orchid: '#da70d6',
|
|
343
|
+
palegoldenrod: '#eee8aa',
|
|
344
|
+
palegreen: '#98fb98',
|
|
345
|
+
paleturquoise: '#afeeee',
|
|
346
|
+
palevioletred: '#db7093',
|
|
347
|
+
papayawhip: '#ffefd5',
|
|
348
|
+
peachpuff: '#ffdab9',
|
|
349
|
+
peru: '#cd853f',
|
|
350
|
+
pink: '#ffc0cb',
|
|
351
|
+
plum: '#dda0dd',
|
|
352
|
+
powderblue: '#b0e0e6',
|
|
353
|
+
purple: '#800080',
|
|
354
|
+
rebeccapurple: '#663399',
|
|
355
|
+
red: '#ff0000',
|
|
356
|
+
rosybrown: '#bc8f8f',
|
|
357
|
+
royalblue: '#4169e1',
|
|
358
|
+
saddlebrown: '#8b4513',
|
|
359
|
+
salmon: '#fa8072',
|
|
360
|
+
sandybrown: '#f4a460',
|
|
361
|
+
seagreen: '#2e8b57',
|
|
362
|
+
seashell: '#fff5ee',
|
|
363
|
+
sienna: '#a0522d',
|
|
364
|
+
silver: '#c0c0c0',
|
|
365
|
+
skyblue: '#87ceeb',
|
|
366
|
+
slateblue: '#6a5acd',
|
|
367
|
+
slategray: '#708090',
|
|
368
|
+
slategrey: '#708090',
|
|
369
|
+
snow: '#fffafa',
|
|
370
|
+
springgreen: '#00ff7f',
|
|
371
|
+
steelblue: '#4682b4',
|
|
372
|
+
tan: '#d2b48c',
|
|
373
|
+
teal: '#008080',
|
|
374
|
+
thistle: '#d8bfd8',
|
|
375
|
+
tomato: '#ff6347',
|
|
376
|
+
turquoise: '#40e0d0',
|
|
377
|
+
violet: '#ee82ee',
|
|
378
|
+
wheat: '#f5deb3',
|
|
379
|
+
white: '#ffffff',
|
|
380
|
+
whitesmoke: '#f5f5f5',
|
|
381
|
+
yellow: '#ffff00',
|
|
382
|
+
yellowgreen: '#9acd32'
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
function clampChannel(value) {
|
|
386
|
+
return Math.min(255, Math.max(0, Math.round(value)));
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function normalizeRgbArgs(args) {
|
|
390
|
+
const parts = args.split(/[,\s/]+/).filter(Boolean);
|
|
391
|
+
if (parts.length < 3) return null;
|
|
392
|
+
const to255 = (value) => {
|
|
393
|
+
if (typeof value !== 'string' || !value) return null;
|
|
394
|
+
if (value.endsWith('%')) return clampChannel((parseFloat(value) / 100) * 255);
|
|
395
|
+
const number = Number(value);
|
|
396
|
+
return Number.isFinite(number) ? clampChannel(number) : null;
|
|
397
|
+
};
|
|
398
|
+
const red = to255(parts[0]);
|
|
399
|
+
const green = to255(parts[1]);
|
|
400
|
+
const blue = to255(parts[2]);
|
|
401
|
+
if (red === null || green === null || blue === null) return null;
|
|
402
|
+
return `#${[red, green, blue].map((n) => n.toString(16).padStart(2, '0')).join('')}`;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Convert any supported CSS color into `#rrggbb`. Returns null when the
|
|
407
|
+
* value cannot be parsed. Supports: #rgb / #rrggbb (case-insensitive),
|
|
408
|
+
* CSS named colors, rgb() / rgba() with comma or modern space syntax,
|
|
409
|
+
* including percentages. Alpha in rgba() is ignored.
|
|
410
|
+
*/
|
|
411
|
+
function normalizeColor(value) {
|
|
412
|
+
if (typeof value !== 'string') return null;
|
|
413
|
+
const input = value.trim();
|
|
414
|
+
if (!input) return null;
|
|
415
|
+
const lower = input.toLowerCase();
|
|
416
|
+
if (NAMED_COLORS[lower]) return NAMED_COLORS[lower];
|
|
417
|
+
if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(input)) {
|
|
418
|
+
const hex = input.slice(1).toLowerCase();
|
|
419
|
+
if (hex.length === 3) return `#${hex.split('').map((c) => c + c).join('')}`;
|
|
420
|
+
return `#${hex}`;
|
|
421
|
+
}
|
|
422
|
+
const match = input.match(/^rgba?\((.*)\)$/i);
|
|
423
|
+
if (match) return normalizeRgbArgs(match[1]);
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function hexToRgb01$1(color) {
|
|
428
|
+
const hex = normalizeColor(color);
|
|
429
|
+
if (!hex) return [0, 0, 0];
|
|
430
|
+
const value = Number.parseInt(hex.slice(1), 16);
|
|
431
|
+
return [
|
|
432
|
+
((value >> 16) & 255) / 255,
|
|
433
|
+
((value >> 8) & 255) / 255,
|
|
434
|
+
(value & 255) / 255
|
|
435
|
+
];
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function hexToRgba(color, alpha = 1) {
|
|
439
|
+
const hex = normalizeColor(color);
|
|
440
|
+
if (!hex) return 'rgba(0, 0, 0, 0)';
|
|
441
|
+
const value = Number.parseInt(hex.slice(1), 16);
|
|
442
|
+
const red = (value >> 16) & 255;
|
|
443
|
+
const green = (value >> 8) & 255;
|
|
444
|
+
const blue = value & 255;
|
|
445
|
+
return `rgba(${red}, ${green}, ${blue}, ${alpha})`;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function validateColors(colors) {
|
|
449
|
+
return Array.isArray(colors)
|
|
450
|
+
&& colors.length === 4
|
|
451
|
+
&& colors.every((color) => normalizeColor(color) !== null);
|
|
275
452
|
}
|
|
276
453
|
|
|
277
454
|
const VERTEX_SHADER$1 = `#version 300 es
|
|
@@ -499,12 +676,31 @@ class CosmicRenderer {
|
|
|
499
676
|
this.timeOffset = preset.seed * 0.73;
|
|
500
677
|
}
|
|
501
678
|
|
|
502
|
-
setDprCap(cap) {
|
|
503
|
-
this.options.dprCap = cap;
|
|
504
|
-
this.resize();
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
|
|
679
|
+
setDprCap(cap) {
|
|
680
|
+
this.options.dprCap = cap;
|
|
681
|
+
this.resize();
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
setMouseColor(enabled) {
|
|
685
|
+
this.options.mouseColor = enabled !== false;
|
|
686
|
+
if (this.options.mouseColor) {
|
|
687
|
+
if (this.onPointerMove) return this;
|
|
688
|
+
this.#bindEvents();
|
|
689
|
+
return this;
|
|
690
|
+
}
|
|
691
|
+
if (this.onPointerMove) {
|
|
692
|
+
const target = this.eventTarget;
|
|
693
|
+
target.removeEventListener('pointermove', this.onPointerMove);
|
|
694
|
+
target.removeEventListener('pointerdown', this.onPointerMove);
|
|
695
|
+
target.removeEventListener('pointerleave', this.onPointerLeave);
|
|
696
|
+
this.onPointerMove = null;
|
|
697
|
+
this.onPointerLeave = null;
|
|
698
|
+
}
|
|
699
|
+
this.motionTarget = 0;
|
|
700
|
+
return this;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
randomize() {
|
|
508
704
|
this.preset.seed = Math.random() * 100;
|
|
509
705
|
this.timeOffset = Math.random() * 40;
|
|
510
706
|
}
|
|
@@ -584,10 +780,12 @@ class FallbackRenderer {
|
|
|
584
780
|
}
|
|
585
781
|
}
|
|
586
782
|
|
|
587
|
-
drawNebula(time) {
|
|
588
|
-
const ctx = this.context;
|
|
589
|
-
const { width, height } = this.canvas;
|
|
590
|
-
const
|
|
783
|
+
drawNebula(time) {
|
|
784
|
+
const ctx = this.context;
|
|
785
|
+
const { width, height } = this.canvas;
|
|
786
|
+
const t = time * (this.preset.speed || 1);
|
|
787
|
+
const phase = (this.preset.seed || 0) * 0.7;
|
|
788
|
+
const gradient = ctx.createLinearGradient(0, 0, width, height);
|
|
591
789
|
gradient.addColorStop(0, this.preset.colors[0]);
|
|
592
790
|
gradient.addColorStop(0.38, this.preset.colors[1]);
|
|
593
791
|
gradient.addColorStop(0.72, this.preset.colors[2]);
|
|
@@ -595,10 +793,10 @@ class FallbackRenderer {
|
|
|
595
793
|
ctx.fillStyle = gradient;
|
|
596
794
|
ctx.fillRect(0, 0, width, height);
|
|
597
795
|
|
|
598
|
-
ctx.globalCompositeOperation = 'screen';
|
|
599
|
-
for (let index = 0; index < 6; index += 1) {
|
|
600
|
-
const x = (0.5 + 0.45 * Math.sin(
|
|
601
|
-
const y = (0.5 + 0.4 * Math.cos(
|
|
796
|
+
ctx.globalCompositeOperation = 'screen';
|
|
797
|
+
for (let index = 0; index < 6; index += 1) {
|
|
798
|
+
const x = (0.5 + 0.45 * Math.sin(t * 0.32 + index * 1.7 + phase)) * width;
|
|
799
|
+
const y = (0.5 + 0.4 * Math.cos(t * 0.25 + index + phase)) * height;
|
|
602
800
|
const radius = Math.max(width, height) * (0.08 + (index % 3) * 0.04);
|
|
603
801
|
const glow = ctx.createRadialGradient(x, y, 0, x, y, radius);
|
|
604
802
|
glow.addColorStop(0, rgb(this.preset.colors[(index + 1) % 4], 0.24));
|
|
@@ -619,8 +817,11 @@ class FallbackRenderer {
|
|
|
619
817
|
this.preset = preset;
|
|
620
818
|
}
|
|
621
819
|
|
|
622
|
-
randomize() {
|
|
623
|
-
|
|
820
|
+
randomize() {
|
|
821
|
+
if (this.preset) this.preset.seed = Math.random() * 100;
|
|
822
|
+
}
|
|
823
|
+
setMouseColor() {}
|
|
824
|
+
dispose() {}
|
|
624
825
|
}
|
|
625
826
|
|
|
626
827
|
/**
|
|
@@ -729,78 +930,105 @@ function createVisibilityGuard(element) {
|
|
|
729
930
|
};
|
|
730
931
|
}
|
|
731
932
|
|
|
933
|
+
/**
|
|
934
|
+
* Run a callback asynchronously, right after the current task and before the
|
|
935
|
+
* next paint. Used for mount-time events (ready / error) so listeners that
|
|
936
|
+
* are attached after create() still receive them. Falls back for legacy
|
|
937
|
+
* browsers that lack queueMicrotask / Promise.
|
|
938
|
+
*/
|
|
939
|
+
function nextTick(fn) {
|
|
940
|
+
if (typeof queueMicrotask === 'function') {
|
|
941
|
+
queueMicrotask(fn);
|
|
942
|
+
} else if (typeof Promise !== 'undefined' && typeof Promise.resolve === 'function') {
|
|
943
|
+
Promise.resolve().then(fn);
|
|
944
|
+
} else {
|
|
945
|
+
setTimeout(fn, 0);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
732
949
|
function prefersReducedMotion$2() {
|
|
733
950
|
return typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
734
951
|
}
|
|
735
952
|
|
|
736
953
|
/**
|
|
737
|
-
* Mount a cosmic (nebula) capsule into `container`.
|
|
738
|
-
*
|
|
739
|
-
* Options: preset (
|
|
740
|
-
*
|
|
741
|
-
*
|
|
742
|
-
|
|
743
|
-
|
|
954
|
+
* Mount a cosmic (nebula) capsule into `container`.
|
|
955
|
+
*
|
|
956
|
+
* Options: preset (literary name), width, height (number=px or string with
|
|
957
|
+
* px/%/vw/vh/em/rem), colors, seed, speed, textRatio (0-100, text region
|
|
958
|
+
* width in percent), text (HTML string or DOM nodes for the text slot),
|
|
959
|
+
* colorContent (HTML string or DOM nodes for the color slot), quality,
|
|
960
|
+
* renderer, respectReducedMotion, mouseColor, cssVars.
|
|
961
|
+
*/
|
|
962
|
+
function createCapsule(container, options = {}) {
|
|
744
963
|
if (!container || typeof container.appendChild !== 'function') {
|
|
745
964
|
throw new Error('createCapsule: container element is required');
|
|
746
965
|
}
|
|
747
966
|
|
|
748
|
-
const preset = { ...getPreset('capsule', options.preset ?? '
|
|
749
|
-
const merged = normalizeOptions(DEFAULTS.capsule, preset, options);
|
|
750
|
-
const
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
root
|
|
757
|
-
root.
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
};
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
if (
|
|
774
|
-
if (
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
canvas.
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
root.appendChild(canvas);
|
|
800
|
-
container.appendChild(root);
|
|
801
|
-
|
|
802
|
-
const emitter = createEmitter();
|
|
803
|
-
let paused = merged.respectReducedMotion && prefersReducedMotion$2();
|
|
967
|
+
const preset = { ...getPreset('capsule', options.preset ?? '初光') };
|
|
968
|
+
const merged = normalizeOptions(DEFAULTS.capsule, preset, options);
|
|
969
|
+
const normalizedColors = merged.colors.map(normalizeColor);
|
|
970
|
+
if (normalizedColors.every(Boolean)) preset.colors = normalizedColors;
|
|
971
|
+
preset.seed = merged.seed;
|
|
972
|
+
preset.speed = merged.speed;
|
|
973
|
+
let customLabel = typeof options.label === 'string' && options.label ? options.label : null;
|
|
974
|
+
|
|
975
|
+
const root = document.createElement('div');
|
|
976
|
+
root.className = 'hj-capsule-root hj-capsule-cosmic';
|
|
977
|
+
root.dataset.group = preset.group;
|
|
978
|
+
root.dataset.mode = 'nebula';
|
|
979
|
+
root.dataset.theme = preset.theme || 'light';
|
|
980
|
+
root.setAttribute('role', 'img');
|
|
981
|
+
const updateAria = () => {
|
|
982
|
+
root.setAttribute('aria-label', customLabel || COPY.capsuleAria.replace('{name}', preset.name));
|
|
983
|
+
};
|
|
984
|
+
updateAria();
|
|
985
|
+
|
|
986
|
+
// Slot containers are always present but transparent by default: without
|
|
987
|
+
// content they are invisible, so "no slot" behaves like the old
|
|
988
|
+
// showCopy=false. They only provide geometry — no typography, padding or
|
|
989
|
+
// background is imposed on user content (see docs: 插槽 CSS 约定).
|
|
990
|
+
const fillContent = (layer, content) => {
|
|
991
|
+
layer.innerHTML = '';
|
|
992
|
+
if (content == null) return;
|
|
993
|
+
if (typeof content === 'string') {
|
|
994
|
+
layer.innerHTML = content;
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
const nodes = Array.isArray(content) ? content : [content];
|
|
998
|
+
for (const node of nodes) {
|
|
999
|
+
if (node && typeof node.nodeType === 'number') layer.appendChild(node);
|
|
1000
|
+
}
|
|
1001
|
+
};
|
|
1002
|
+
|
|
1003
|
+
const textLayer = document.createElement('div');
|
|
1004
|
+
textLayer.className = 'hj-capsule-text hj-capsule-copy';
|
|
1005
|
+
|
|
1006
|
+
const visualLayer = document.createElement('div');
|
|
1007
|
+
visualLayer.className = 'hj-capsule-visual';
|
|
1008
|
+
|
|
1009
|
+
fillContent(textLayer, options.text);
|
|
1010
|
+
fillContent(visualLayer, options.colorContent);
|
|
1011
|
+
|
|
1012
|
+
const canvas = document.createElement('canvas');
|
|
1013
|
+
canvas.className = 'hj-capsule-canvas';
|
|
1014
|
+
canvas.setAttribute('aria-hidden', 'true');
|
|
1015
|
+
|
|
1016
|
+
root.appendChild(textLayer);
|
|
1017
|
+
root.appendChild(visualLayer);
|
|
1018
|
+
root.appendChild(canvas);
|
|
1019
|
+
container.appendChild(root);
|
|
1020
|
+
|
|
1021
|
+
const emitter = createEmitter();
|
|
1022
|
+
let paused = merged.respectReducedMotion && prefersReducedMotion$2();
|
|
1023
|
+
let disposed = false;
|
|
1024
|
+
const dirty = {
|
|
1025
|
+
preset: false,
|
|
1026
|
+
seed: false,
|
|
1027
|
+
speed: false,
|
|
1028
|
+
colors: false,
|
|
1029
|
+
textRatio: false,
|
|
1030
|
+
cssVars: false
|
|
1031
|
+
};
|
|
804
1032
|
|
|
805
1033
|
let renderer;
|
|
806
1034
|
const useWebgl = merged.renderer !== 'canvas2d';
|
|
@@ -809,19 +1037,25 @@ function createCapsule(container, options = {}) {
|
|
|
809
1037
|
renderer = new CosmicRenderer(canvas, merged, { dprCap: dprCapFor(merged.quality) });
|
|
810
1038
|
} catch (error) {
|
|
811
1039
|
renderer = new FallbackRenderer(canvas, merged);
|
|
812
|
-
|
|
1040
|
+
nextTick(() => {
|
|
1041
|
+
if (disposed) return;
|
|
1042
|
+
emitter.emit('error', { message: String(error && error.message ? error.message : error) });
|
|
1043
|
+
});
|
|
813
1044
|
}
|
|
814
1045
|
} else {
|
|
815
1046
|
renderer = new FallbackRenderer(canvas, merged);
|
|
816
1047
|
}
|
|
817
1048
|
|
|
818
|
-
const applySize = () => {
|
|
819
|
-
root.style.width = parseSize(merged.width);
|
|
820
|
-
root.style.height = parseSize(merged.height);
|
|
821
|
-
const vars = merged.cssVars || {};
|
|
822
|
-
for (const name of Object.keys(vars)) root.style.setProperty(name, vars[name]);
|
|
823
|
-
|
|
824
|
-
|
|
1049
|
+
const applySize = () => {
|
|
1050
|
+
root.style.width = parseSize(merged.width);
|
|
1051
|
+
root.style.height = parseSize(merged.height);
|
|
1052
|
+
const vars = merged.cssVars || {};
|
|
1053
|
+
for (const name of Object.keys(vars)) root.style.setProperty(name, vars[name]);
|
|
1054
|
+
if (merged.textRatio !== undefined) {
|
|
1055
|
+
root.style.setProperty('--hj-text-width', `${merged.textRatio}%`);
|
|
1056
|
+
}
|
|
1057
|
+
renderer.resize();
|
|
1058
|
+
};
|
|
825
1059
|
applySize();
|
|
826
1060
|
|
|
827
1061
|
const resizeObserver = typeof ResizeObserver !== 'undefined'
|
|
@@ -832,10 +1066,8 @@ function createCapsule(container, options = {}) {
|
|
|
832
1066
|
|
|
833
1067
|
const visibility = createVisibilityGuard(root);
|
|
834
1068
|
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
root.addEventListener('pointerleave', () => emitter.emit('pointerleave', { preset: { ...preset } }));
|
|
838
|
-
}
|
|
1069
|
+
root.addEventListener('pointerenter', () => emitter.emit('pointerenter', { preset: { ...preset } }));
|
|
1070
|
+
root.addEventListener('pointerleave', () => emitter.emit('pointerleave', { preset: { ...preset } }));
|
|
839
1071
|
root.addEventListener('click', (event) => {
|
|
840
1072
|
emitter.emit('click', { event, preset: { ...preset } });
|
|
841
1073
|
});
|
|
@@ -852,45 +1084,97 @@ function createCapsule(container, options = {}) {
|
|
|
852
1084
|
() => paused
|
|
853
1085
|
);
|
|
854
1086
|
|
|
855
|
-
|
|
1087
|
+
nextTick(() => {
|
|
1088
|
+
if (disposed) return;
|
|
1089
|
+
emitter.emit('ready', { preset: { ...preset } });
|
|
1090
|
+
});
|
|
856
1091
|
|
|
857
|
-
const
|
|
858
|
-
root.dataset.mode = 'nebula';
|
|
859
|
-
root.dataset.theme = preset.theme || 'light';
|
|
860
|
-
|
|
861
|
-
};
|
|
862
|
-
|
|
863
|
-
return {
|
|
1092
|
+
const syncTheme = () => {
|
|
1093
|
+
root.dataset.mode = 'nebula';
|
|
1094
|
+
root.dataset.theme = preset.theme || 'light';
|
|
1095
|
+
updateAria();
|
|
1096
|
+
};
|
|
1097
|
+
|
|
1098
|
+
return {
|
|
864
1099
|
element: root,
|
|
865
1100
|
canvas,
|
|
866
1101
|
preset,
|
|
867
1102
|
on: emitter.on,
|
|
868
1103
|
off: emitter.off,
|
|
869
|
-
setPreset(ref) {
|
|
870
|
-
const next = getPreset('capsule', ref);
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
1104
|
+
setPreset(ref) {
|
|
1105
|
+
const next = getPreset('capsule', ref);
|
|
1106
|
+
dirty.preset = true;
|
|
1107
|
+
Object.assign(preset, next);
|
|
1108
|
+
const nextColors = preset.colors.map(normalizeColor);
|
|
1109
|
+
if (nextColors.every(Boolean)) preset.colors = nextColors;
|
|
1110
|
+
renderer.setPreset({ ...preset });
|
|
1111
|
+
syncTheme();
|
|
1112
|
+
renderer.resize();
|
|
1113
|
+
emitter.emit('presetchange', { preset: { ...next } });
|
|
1114
|
+
return this;
|
|
877
1115
|
},
|
|
878
|
-
setColors(colors) {
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
1116
|
+
setColors(colors) {
|
|
1117
|
+
const next = Array.isArray(colors) ? colors.map(normalizeColor) : [];
|
|
1118
|
+
if (next.length !== 4 || next.some((color) => !color)) return this;
|
|
1119
|
+
dirty.colors = true;
|
|
1120
|
+
preset.colors = next;
|
|
1121
|
+
renderer.setPreset({ ...preset, colors: next });
|
|
1122
|
+
return this;
|
|
1123
|
+
},
|
|
1124
|
+
setSeed(seed) {
|
|
1125
|
+
const value = Number(seed);
|
|
1126
|
+
if (!Number.isFinite(value)) return this;
|
|
1127
|
+
dirty.seed = true;
|
|
1128
|
+
preset.seed = value;
|
|
1129
|
+
renderer.setPreset({ ...preset });
|
|
1130
|
+
return this;
|
|
1131
|
+
},
|
|
1132
|
+
setSpeed(speed) {
|
|
1133
|
+
const value = Number(speed);
|
|
1134
|
+
if (!Number.isFinite(value)) return this;
|
|
1135
|
+
dirty.speed = true;
|
|
1136
|
+
preset.speed = value;
|
|
1137
|
+
renderer.setPreset({ ...preset });
|
|
1138
|
+
return this;
|
|
1139
|
+
},
|
|
1140
|
+
setText(content) {
|
|
1141
|
+
fillContent(textLayer, content);
|
|
1142
|
+
return this;
|
|
1143
|
+
},
|
|
1144
|
+
setColorContent(content) {
|
|
1145
|
+
fillContent(visualLayer, content);
|
|
1146
|
+
return this;
|
|
1147
|
+
},
|
|
1148
|
+
setTextRatio(ratio) {
|
|
1149
|
+
const value = Number(ratio);
|
|
1150
|
+
if (!Number.isFinite(value) || value < 0 || value > 100) return this;
|
|
1151
|
+
dirty.textRatio = true;
|
|
1152
|
+
merged.textRatio = value;
|
|
1153
|
+
root.style.setProperty('--hj-text-width', `${value}%`);
|
|
1154
|
+
return this;
|
|
1155
|
+
},
|
|
1156
|
+
setCssVars(vars) {
|
|
1157
|
+
if (!vars || typeof vars !== 'object') return this;
|
|
1158
|
+
dirty.cssVars = true;
|
|
1159
|
+
merged.cssVars = { ...(merged.cssVars || {}), ...vars };
|
|
1160
|
+
for (const name of Object.keys(vars)) root.style.setProperty(name, vars[name]);
|
|
1161
|
+
return this;
|
|
1162
|
+
},
|
|
1163
|
+
setLabel(label) {
|
|
1164
|
+
customLabel = typeof label === 'string' && label ? label : null;
|
|
1165
|
+
updateAria();
|
|
1166
|
+
return this;
|
|
1167
|
+
},
|
|
1168
|
+
setSize(width, height) {
|
|
885
1169
|
if (width !== undefined) merged.width = width;
|
|
886
1170
|
if (height !== undefined) merged.height = height;
|
|
887
1171
|
applySize();
|
|
888
1172
|
return this;
|
|
889
1173
|
},
|
|
890
|
-
randomize() {
|
|
891
|
-
|
|
892
|
-
return this;
|
|
893
|
-
},
|
|
1174
|
+
randomize() {
|
|
1175
|
+
this.setSeed(Math.random() * 100);
|
|
1176
|
+
return this;
|
|
1177
|
+
},
|
|
894
1178
|
pause() {
|
|
895
1179
|
paused = true;
|
|
896
1180
|
return this;
|
|
@@ -904,15 +1188,19 @@ function createCapsule(container, options = {}) {
|
|
|
904
1188
|
if (typeof renderer.setDprCap === 'function') renderer.setDprCap(dprCapFor(quality));
|
|
905
1189
|
return this;
|
|
906
1190
|
},
|
|
907
|
-
dispose() {
|
|
908
|
-
|
|
1191
|
+
dispose() {
|
|
1192
|
+
disposed = true;
|
|
1193
|
+
unsubscribe();
|
|
909
1194
|
visibility.dispose();
|
|
910
1195
|
if (resizeObserver) resizeObserver.disconnect();
|
|
911
1196
|
else window.removeEventListener('resize', renderer.resize);
|
|
912
|
-
renderer.dispose();
|
|
913
|
-
root.remove();
|
|
914
|
-
}
|
|
915
|
-
|
|
1197
|
+
renderer.dispose();
|
|
1198
|
+
root.remove();
|
|
1199
|
+
},
|
|
1200
|
+
textRatio: merged.textRatio,
|
|
1201
|
+
cssVars: merged.cssVars,
|
|
1202
|
+
dirty
|
|
1203
|
+
};
|
|
916
1204
|
}
|
|
917
1205
|
|
|
918
1206
|
const PROGRESS_MOTION_WIDTH = 240;
|
|
@@ -1600,14 +1888,16 @@ const FLOW_PROFILES = {
|
|
|
1600
1888
|
};
|
|
1601
1889
|
|
|
1602
1890
|
class ProgressCapsuleController {
|
|
1603
|
-
constructor({ root, canvas, valueElement, preset, emitter, options, copy }) {
|
|
1891
|
+
constructor({ root, canvas, valueElement, preset, emitter, options, copy, dirty }) {
|
|
1604
1892
|
this.root = root;
|
|
1605
1893
|
this.canvas = canvas;
|
|
1606
1894
|
this.valueElement = valueElement;
|
|
1607
1895
|
this.preset = preset;
|
|
1608
1896
|
this.emitter = emitter;
|
|
1609
|
-
this.options = options;
|
|
1610
|
-
this.copy = copy;
|
|
1897
|
+
this.options = options;
|
|
1898
|
+
this.copy = copy;
|
|
1899
|
+
this.dirty = dirty;
|
|
1900
|
+
this.valueSuffix = options.valueSuffix ?? copy.valueSuffix;
|
|
1611
1901
|
this.profile = preset.edgeStyle === 'tide'
|
|
1612
1902
|
? FLOW_PROFILES.tide
|
|
1613
1903
|
: (FLOW_PROFILES[preset.id] || FLOW_PROFILES['visual-training']);
|
|
@@ -1648,11 +1938,22 @@ class ProgressCapsuleController {
|
|
|
1648
1938
|
this.value = clamp(nextValue, this.min, this.max);
|
|
1649
1939
|
const rounded = Math.round(this.value);
|
|
1650
1940
|
this.root.style.setProperty('--progress', this.value.toFixed(2));
|
|
1651
|
-
this.root.setAttribute('aria-valuenow', String(rounded));
|
|
1652
|
-
this.root.setAttribute('aria-valuetext', `${rounded}${this.
|
|
1653
|
-
this.valueElement.textContent = `${rounded}${this.
|
|
1654
|
-
if (!this.suppressEvents)
|
|
1655
|
-
|
|
1941
|
+
this.root.setAttribute('aria-valuenow', String(rounded));
|
|
1942
|
+
this.root.setAttribute('aria-valuetext', `${rounded}${this.valueSuffix}`);
|
|
1943
|
+
if (this.valueElement) this.valueElement.textContent = `${rounded}${this.valueSuffix}`;
|
|
1944
|
+
if (!this.suppressEvents) {
|
|
1945
|
+
if (this.dirty) this.dirty.value = true;
|
|
1946
|
+
this.emitter.emit('change', { value: this.value, source });
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
setValueSuffix(suffix) {
|
|
1951
|
+
this.valueSuffix = String(suffix == null ? '' : suffix);
|
|
1952
|
+
const rounded = Math.round(this.value);
|
|
1953
|
+
if (this.valueElement) this.valueElement.textContent = `${rounded}${this.valueSuffix}`;
|
|
1954
|
+
this.root.setAttribute('aria-valuetext', `${rounded}${this.valueSuffix}`);
|
|
1955
|
+
return this;
|
|
1956
|
+
}
|
|
1656
1957
|
|
|
1657
1958
|
resizeCanvas() {
|
|
1658
1959
|
const bounds = this.root.getBoundingClientRect();
|
|
@@ -1825,13 +2126,14 @@ class ProgressCapsuleController {
|
|
|
1825
2126
|
if (clamped !== this.value) this.setProgress(clamped, 'prop');
|
|
1826
2127
|
}
|
|
1827
2128
|
|
|
1828
|
-
drawReferenceFlow() {
|
|
2129
|
+
drawReferenceFlow() {
|
|
1829
2130
|
const ctx = this.ctx;
|
|
1830
2131
|
const width = this.width;
|
|
1831
2132
|
const height = this.height;
|
|
1832
2133
|
if (!ctx || width <= 0 || height <= 0) return;
|
|
1833
2134
|
|
|
1834
|
-
const
|
|
2135
|
+
const range = Math.max(this.max - this.min, 1);
|
|
2136
|
+
const shoreline = width * Math.min(Math.max((this.value - this.min) / range, 0), 1);
|
|
1835
2137
|
const time = this.flowTime;
|
|
1836
2138
|
const [dark, accentA, accentB, glow] = this.preset.colors;
|
|
1837
2139
|
|
|
@@ -1949,32 +2251,49 @@ class ProgressCapsuleController {
|
|
|
1949
2251
|
}
|
|
1950
2252
|
}
|
|
1951
2253
|
|
|
1952
|
-
updateFromPointer(event) {
|
|
1953
|
-
const bounds = this.root.getBoundingClientRect();
|
|
1954
|
-
const ratio = bounds.width > 0 ? (event.clientX - bounds.left) / bounds.width : 0;
|
|
1955
|
-
this.setProgress(ratio *
|
|
1956
|
-
}
|
|
1957
|
-
|
|
1958
|
-
beginDrag(event) {
|
|
1959
|
-
if (event.button !== undefined && event.button !== 0) return;
|
|
1960
|
-
event.preventDefault();
|
|
1961
|
-
this.dragging = true;
|
|
1962
|
-
this.
|
|
1963
|
-
|
|
1964
|
-
this.
|
|
1965
|
-
this.
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
this.
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
if (
|
|
1976
|
-
this.
|
|
1977
|
-
|
|
2254
|
+
updateFromPointer(event) {
|
|
2255
|
+
const bounds = this.root.getBoundingClientRect();
|
|
2256
|
+
const ratio = bounds.width > 0 ? (event.clientX - bounds.left) / bounds.width : 0;
|
|
2257
|
+
this.setProgress(this.min + ratio * (this.max - this.min), 'drag');
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
beginDrag(event) {
|
|
2261
|
+
if (event.button !== undefined && event.button !== 0) return;
|
|
2262
|
+
event.preventDefault();
|
|
2263
|
+
this.dragging = true;
|
|
2264
|
+
this.pendingPointer = null;
|
|
2265
|
+
this._dragRaf = 0;
|
|
2266
|
+
this.root.classList.add('is-dragging');
|
|
2267
|
+
try { this.root.setPointerCapture?.(event.pointerId); } catch {}
|
|
2268
|
+
this.emitter.emit('dragstart', { value: this.value });
|
|
2269
|
+
this.updateFromPointer(event);
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
moveDrag(event) {
|
|
2273
|
+
if (!this.dragging) return;
|
|
2274
|
+
event.preventDefault();
|
|
2275
|
+
// rAF 合并:一帧最多一次 setProgress/change,视觉仍每帧更新。
|
|
2276
|
+
this.pendingPointer = event;
|
|
2277
|
+
if (this._dragRaf) return;
|
|
2278
|
+
this._dragRaf = requestAnimationFrame(() => {
|
|
2279
|
+
this._dragRaf = 0;
|
|
2280
|
+
const pending = this.pendingPointer;
|
|
2281
|
+
this.pendingPointer = null;
|
|
2282
|
+
if (pending) this.updateFromPointer(pending);
|
|
2283
|
+
});
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
endDrag(event) {
|
|
2287
|
+
if (!this.dragging) return;
|
|
2288
|
+
this.dragging = false;
|
|
2289
|
+
if (this._dragRaf) {
|
|
2290
|
+
cancelAnimationFrame(this._dragRaf);
|
|
2291
|
+
this._dragRaf = 0;
|
|
2292
|
+
}
|
|
2293
|
+
const pending = this.pendingPointer;
|
|
2294
|
+
this.pendingPointer = null;
|
|
2295
|
+
if (pending) this.updateFromPointer(pending);
|
|
2296
|
+
this.root.classList.remove('is-dragging');
|
|
1978
2297
|
try {
|
|
1979
2298
|
if (event?.pointerId !== undefined && this.root.hasPointerCapture?.(event.pointerId)) {
|
|
1980
2299
|
this.root.releasePointerCapture(event.pointerId);
|
|
@@ -1996,15 +2315,16 @@ class ProgressCapsuleController {
|
|
|
1996
2315
|
}
|
|
1997
2316
|
|
|
1998
2317
|
if (this.options.keyboard !== false) {
|
|
1999
|
-
this.handlers.keydown = (event) => {
|
|
2000
|
-
const
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2318
|
+
this.handlers.keydown = (event) => {
|
|
2319
|
+
const step = (this.max - this.min) * 0.02;
|
|
2320
|
+
const actions = {
|
|
2321
|
+
ArrowLeft: -step,
|
|
2322
|
+
ArrowDown: -step,
|
|
2323
|
+
ArrowRight: step,
|
|
2324
|
+
ArrowUp: step,
|
|
2325
|
+
PageDown: -step * 5,
|
|
2326
|
+
PageUp: step * 5
|
|
2327
|
+
};
|
|
2008
2328
|
if (event.key === 'Home') {
|
|
2009
2329
|
event.preventDefault();
|
|
2010
2330
|
this.setProgress(this.min, 'keyboard');
|
|
@@ -2042,8 +2362,9 @@ class ProgressCapsuleController {
|
|
|
2042
2362
|
this.preset = { ...this.preset, colors };
|
|
2043
2363
|
}
|
|
2044
2364
|
|
|
2045
|
-
dispose() {
|
|
2046
|
-
if (this.
|
|
2365
|
+
dispose() {
|
|
2366
|
+
if (this._dragRaf) cancelAnimationFrame(this._dragRaf);
|
|
2367
|
+
if (this.resizeObserver) this.resizeObserver.disconnect();
|
|
2047
2368
|
else window.removeEventListener('resize', this.resizeCanvas);
|
|
2048
2369
|
for (const name of Object.keys(this.handlers)) {
|
|
2049
2370
|
const handler = this.handlers[name];
|
|
@@ -2054,103 +2375,127 @@ class ProgressCapsuleController {
|
|
|
2054
2375
|
}
|
|
2055
2376
|
|
|
2056
2377
|
/**
|
|
2057
|
-
* Mount a fluid progress capsule into `container`.
|
|
2058
|
-
*
|
|
2059
|
-
* Options: preset (id/code/name or object), width, height, value, min, max,
|
|
2060
|
-
* draggable, keyboard, colors, edgeStyle,
|
|
2061
|
-
*
|
|
2062
|
-
|
|
2063
|
-
|
|
2378
|
+
* Mount a fluid progress capsule into `container`.
|
|
2379
|
+
*
|
|
2380
|
+
* Options: preset (id/code/name or object), width, height, value, min, max,
|
|
2381
|
+
* draggable, keyboard, colors, edgeStyle, textRatio (0-100, text region
|
|
2382
|
+
* width in percent), text (HTML string or DOM nodes for the text slot),
|
|
2383
|
+
* colorContent (HTML string or DOM nodes for the color slot),
|
|
2384
|
+
* showValue (show/hide the right-side percentage), quality,
|
|
2385
|
+
* respectReducedMotion, cssVars.
|
|
2386
|
+
*/
|
|
2387
|
+
function createProgressCapsule(container, options = {}) {
|
|
2064
2388
|
if (!container || typeof container.appendChild !== 'function') {
|
|
2065
2389
|
throw new Error('createProgressCapsule: container element is required');
|
|
2066
2390
|
}
|
|
2067
2391
|
|
|
2068
|
-
const preset = { ...getPreset('progress', options.preset ?? '
|
|
2069
|
-
const merged = normalizeOptions(DEFAULTS.progress, preset, options);
|
|
2070
|
-
const
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2392
|
+
const preset = { ...getPreset('progress', options.preset ?? '星火') };
|
|
2393
|
+
const merged = normalizeOptions(DEFAULTS.progress, preset, options);
|
|
2394
|
+
const normalizedColors = merged.colors.map(normalizeColor);
|
|
2395
|
+
if (normalizedColors.every(Boolean)) preset.colors = normalizedColors;
|
|
2396
|
+
preset.edgeStyle = merged.edgeStyle;
|
|
2397
|
+
const copy = COPY;
|
|
2398
|
+
const customLabel = typeof options.label === 'string' && options.label ? options.label : null;
|
|
2399
|
+
// Vue 的裸布尔属性(<ProgressCapsule disabled />)会传成空字符串,
|
|
2400
|
+
// 这里统一按 true 处理。
|
|
2401
|
+
const disabled = merged.disabled === true || merged.disabled === '' || merged.disabled === 'true';
|
|
2402
|
+
const effectiveDraggable = disabled ? false : merged.draggable !== false;
|
|
2403
|
+
const effectiveKeyboard = disabled ? false : merged.keyboard !== false;
|
|
2404
|
+
const dirty = {
|
|
2405
|
+
preset: false,
|
|
2406
|
+
colors: false,
|
|
2407
|
+
textRatio: false,
|
|
2408
|
+
cssVars: false,
|
|
2409
|
+
edgeStyle: false,
|
|
2410
|
+
value: false
|
|
2411
|
+
};
|
|
2412
|
+
|
|
2413
|
+
const root = document.createElement('div');
|
|
2414
|
+
root.className = 'hj-capsule-root hj-progress-root';
|
|
2415
|
+
root.setAttribute('role', 'slider');
|
|
2416
|
+
root.setAttribute('tabindex', '0');
|
|
2417
|
+
root.setAttribute('data-draggable', String(effectiveDraggable));
|
|
2418
|
+
if (disabled) {
|
|
2419
|
+
root.setAttribute('aria-disabled', 'true');
|
|
2420
|
+
root.classList.add('is-disabled');
|
|
2421
|
+
}
|
|
2077
2422
|
root.setAttribute('aria-valuemin', String(merged.min ?? 0));
|
|
2078
2423
|
root.setAttribute('aria-valuemax', String(merged.max ?? 100));
|
|
2079
2424
|
root.setAttribute('aria-valuenow', String(preset.initialProgress));
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2425
|
+
const updateAria = () => {
|
|
2426
|
+
root.setAttribute(
|
|
2427
|
+
'aria-label',
|
|
2428
|
+
customLabel || interpolate(copy.progressAria, { brand: copy.brandName, code: preset.code, name: preset.name })
|
|
2429
|
+
);
|
|
2430
|
+
};
|
|
2431
|
+
updateAria();
|
|
2432
|
+
if (!effectiveKeyboard) root.setAttribute('tabindex', '-1');
|
|
2085
2433
|
|
|
2086
2434
|
const canvas = document.createElement('canvas');
|
|
2087
2435
|
canvas.className = 'hj-progress-canvas';
|
|
2088
2436
|
canvas.setAttribute('aria-hidden', 'true');
|
|
2089
2437
|
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
const
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
const
|
|
2100
|
-
const
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
const
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
root.style.height = parseSize(merged.height);
|
|
2140
|
-
const vars = merged.cssVars || {};
|
|
2141
|
-
for (const name of Object.keys(vars)) root.style.setProperty(name, vars[name]);
|
|
2142
|
-
};
|
|
2438
|
+
// Slot containers are always present but transparent by default; they only
|
|
2439
|
+
// provide geometry, never typography/padding/background (docs: 插槽 CSS 约定).
|
|
2440
|
+
const fillContent = (layer, content) => {
|
|
2441
|
+
layer.innerHTML = '';
|
|
2442
|
+
if (content == null) return;
|
|
2443
|
+
if (typeof content === 'string') {
|
|
2444
|
+
layer.innerHTML = content;
|
|
2445
|
+
return;
|
|
2446
|
+
}
|
|
2447
|
+
const nodes = Array.isArray(content) ? content : [content];
|
|
2448
|
+
for (const node of nodes) {
|
|
2449
|
+
if (node && typeof node.nodeType === 'number') layer.appendChild(node);
|
|
2450
|
+
}
|
|
2451
|
+
};
|
|
2452
|
+
|
|
2453
|
+
const textLayer = document.createElement('div');
|
|
2454
|
+
textLayer.className = 'hj-progress-text hj-progress-copy';
|
|
2455
|
+
|
|
2456
|
+
const visualLayer = document.createElement('div');
|
|
2457
|
+
visualLayer.className = 'hj-progress-visual';
|
|
2458
|
+
|
|
2459
|
+
fillContent(textLayer, options.text);
|
|
2460
|
+
fillContent(visualLayer, options.colorContent);
|
|
2461
|
+
|
|
2462
|
+
const valueElement = merged.showValue === false ? null : document.createElement('div');
|
|
2463
|
+
if (valueElement) {
|
|
2464
|
+
valueElement.className = 'hj-progress-value';
|
|
2465
|
+
valueElement.setAttribute('aria-hidden', 'true');
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
root.appendChild(canvas);
|
|
2469
|
+
root.appendChild(textLayer);
|
|
2470
|
+
root.appendChild(visualLayer);
|
|
2471
|
+
if (valueElement) root.appendChild(valueElement);
|
|
2472
|
+
container.appendChild(root);
|
|
2473
|
+
|
|
2474
|
+
const emitter = createEmitter();
|
|
2475
|
+
let paused = merged.respectReducedMotion && prefersReducedMotion$1();
|
|
2476
|
+
let disposed = false;
|
|
2477
|
+
|
|
2478
|
+
const applySize = () => {
|
|
2479
|
+
root.style.width = parseSize(merged.width);
|
|
2480
|
+
root.style.height = parseSize(merged.height);
|
|
2481
|
+
const vars = merged.cssVars || {};
|
|
2482
|
+
for (const name of Object.keys(vars)) root.style.setProperty(name, vars[name]);
|
|
2483
|
+
if (merged.textRatio !== undefined) {
|
|
2484
|
+
root.style.setProperty('--hj-text-width', `${merged.textRatio}%`);
|
|
2485
|
+
}
|
|
2486
|
+
};
|
|
2143
2487
|
applySize();
|
|
2144
2488
|
|
|
2145
|
-
const controller = new ProgressCapsuleController({
|
|
2146
|
-
root,
|
|
2147
|
-
canvas,
|
|
2148
|
-
valueElement,
|
|
2149
|
-
preset,
|
|
2150
|
-
emitter,
|
|
2151
|
-
options: merged,
|
|
2152
|
-
copy
|
|
2153
|
-
|
|
2489
|
+
const controller = new ProgressCapsuleController({
|
|
2490
|
+
root,
|
|
2491
|
+
canvas,
|
|
2492
|
+
valueElement,
|
|
2493
|
+
preset,
|
|
2494
|
+
emitter,
|
|
2495
|
+
options: { ...merged, draggable: effectiveDraggable, keyboard: effectiveKeyboard },
|
|
2496
|
+
copy,
|
|
2497
|
+
dirty
|
|
2498
|
+
});
|
|
2154
2499
|
|
|
2155
2500
|
let overlay = null;
|
|
2156
2501
|
if (merged.renderer !== 'canvas2d') {
|
|
@@ -2161,7 +2506,13 @@ function createProgressCapsule(container, options = {}) {
|
|
|
2161
2506
|
getProgress: () => controller.value
|
|
2162
2507
|
});
|
|
2163
2508
|
}
|
|
2164
|
-
if (overlay) controller.webglActive = true;
|
|
2509
|
+
if (overlay) controller.webglActive = true;
|
|
2510
|
+
else if (merged.renderer !== 'canvas2d') {
|
|
2511
|
+
nextTick(() => {
|
|
2512
|
+
if (disposed) return;
|
|
2513
|
+
emitter.emit('error', { message: 'WebGL2 unavailable, using Canvas2D fallback' });
|
|
2514
|
+
});
|
|
2515
|
+
}
|
|
2165
2516
|
|
|
2166
2517
|
const visibility = createVisibilityGuard(root);
|
|
2167
2518
|
let flowTime = 0;
|
|
@@ -2177,15 +2528,14 @@ function createProgressCapsule(container, options = {}) {
|
|
|
2177
2528
|
() => paused
|
|
2178
2529
|
);
|
|
2179
2530
|
|
|
2180
|
-
|
|
2531
|
+
nextTick(() => {
|
|
2532
|
+
if (disposed) return;
|
|
2533
|
+
emitter.emit('ready', { preset: { ...preset } });
|
|
2534
|
+
});
|
|
2181
2535
|
|
|
2182
|
-
const syncDom = () => {
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
interpolate(copy.progressAria, { brand: copy.brandName, code: preset.code, name: preset.name })
|
|
2186
|
-
);
|
|
2187
|
-
renderCopy();
|
|
2188
|
-
};
|
|
2536
|
+
const syncDom = () => {
|
|
2537
|
+
updateAria();
|
|
2538
|
+
};
|
|
2189
2539
|
|
|
2190
2540
|
return {
|
|
2191
2541
|
element: root,
|
|
@@ -2204,31 +2554,84 @@ function createProgressCapsule(container, options = {}) {
|
|
|
2204
2554
|
controller.setRange(min, max);
|
|
2205
2555
|
return this;
|
|
2206
2556
|
},
|
|
2207
|
-
setPreset(ref) {
|
|
2208
|
-
const next = getPreset('progress', ref);
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
controller.
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
return this;
|
|
2557
|
+
setPreset(ref) {
|
|
2558
|
+
const next = getPreset('progress', ref);
|
|
2559
|
+
dirty.preset = true;
|
|
2560
|
+
Object.assign(preset, next);
|
|
2561
|
+
const nextColors = preset.colors.map(normalizeColor);
|
|
2562
|
+
if (nextColors.every(Boolean)) preset.colors = nextColors;
|
|
2563
|
+
controller.preset = preset;
|
|
2564
|
+
controller.profile = next.edgeStyle === 'tide'
|
|
2565
|
+
? FLOW_PROFILES.tide
|
|
2566
|
+
: (FLOW_PROFILES[next.id] || FLOW_PROFILES['visual-training']);
|
|
2567
|
+
controller.flowTime = stringSeed(next.id) * 31;
|
|
2568
|
+
controller.seed = stringSeed(`${next.id}-reference`) * Math.PI * 2;
|
|
2569
|
+
syncDom();
|
|
2570
|
+
if (overlay) {
|
|
2571
|
+
overlay.dispose();
|
|
2572
|
+
overlay = attachProgressFlowOverlay({ root, canvas, preset, getProgress: () => controller.value });
|
|
2573
|
+
}
|
|
2574
|
+
controller.webglActive = Boolean(overlay);
|
|
2575
|
+
controller.resizeCanvas();
|
|
2576
|
+
emitter.emit('presetchange', { preset: { ...preset } });
|
|
2577
|
+
return this;
|
|
2578
|
+
},
|
|
2579
|
+
setEdgeStyle(edgeStyle) {
|
|
2580
|
+
const value = String(edgeStyle || '').toLowerCase();
|
|
2581
|
+
if (value !== 'flow' && value !== 'tide') return this;
|
|
2582
|
+
dirty.edgeStyle = true;
|
|
2583
|
+
preset.edgeStyle = value;
|
|
2584
|
+
controller.profile = value === 'tide'
|
|
2585
|
+
? FLOW_PROFILES.tide
|
|
2586
|
+
: (FLOW_PROFILES[preset.id] || FLOW_PROFILES['visual-training']);
|
|
2587
|
+
if (overlay) {
|
|
2588
|
+
overlay.dispose();
|
|
2589
|
+
overlay = attachProgressFlowOverlay({ root, canvas, preset, getProgress: () => controller.value });
|
|
2590
|
+
}
|
|
2591
|
+
controller.webglActive = Boolean(overlay);
|
|
2592
|
+
controller.resizeCanvas();
|
|
2593
|
+
return this;
|
|
2594
|
+
},
|
|
2595
|
+
setText(content) {
|
|
2596
|
+
fillContent(textLayer, content);
|
|
2597
|
+
return this;
|
|
2598
|
+
},
|
|
2599
|
+
setColorContent(content) {
|
|
2600
|
+
fillContent(visualLayer, content);
|
|
2601
|
+
return this;
|
|
2602
|
+
},
|
|
2603
|
+
setTextRatio(ratio) {
|
|
2604
|
+
const value = Number(ratio);
|
|
2605
|
+
if (!Number.isFinite(value) || value < 0 || value > 100) return this;
|
|
2606
|
+
dirty.textRatio = true;
|
|
2607
|
+
merged.textRatio = value;
|
|
2608
|
+
root.style.setProperty('--hj-text-width', `${value}%`);
|
|
2609
|
+
return this;
|
|
2610
|
+
},
|
|
2611
|
+
setCssVars(vars) {
|
|
2612
|
+
if (!vars || typeof vars !== 'object') return this;
|
|
2613
|
+
dirty.cssVars = true;
|
|
2614
|
+
merged.cssVars = { ...(merged.cssVars || {}), ...vars };
|
|
2615
|
+
for (const name of Object.keys(vars)) root.style.setProperty(name, vars[name]);
|
|
2616
|
+
return this;
|
|
2617
|
+
},
|
|
2618
|
+
setLabel(label) {
|
|
2619
|
+
customLabel = typeof label === 'string' && label ? label : null;
|
|
2620
|
+
updateAria();
|
|
2621
|
+
return this;
|
|
2622
|
+
},
|
|
2623
|
+
setValueSuffix(suffix) {
|
|
2624
|
+
controller.setValueSuffix(suffix);
|
|
2625
|
+
return this;
|
|
2626
|
+
},
|
|
2627
|
+
setColors(colors) {
|
|
2628
|
+
const next = Array.isArray(colors) ? colors.map(normalizeColor) : [];
|
|
2629
|
+
if (next.length !== 4 || next.some((color) => !color)) return this;
|
|
2630
|
+
dirty.colors = true;
|
|
2631
|
+
controller.setColors(next);
|
|
2632
|
+
if (overlay) overlay.setColors(next);
|
|
2633
|
+
controller.resizeCanvas();
|
|
2634
|
+
return this;
|
|
2232
2635
|
},
|
|
2233
2636
|
setSize(width, height) {
|
|
2234
2637
|
if (width !== undefined) merged.width = width;
|
|
@@ -2255,14 +2658,18 @@ function createProgressCapsule(container, options = {}) {
|
|
|
2255
2658
|
controller.resizeCanvas();
|
|
2256
2659
|
return this;
|
|
2257
2660
|
},
|
|
2258
|
-
dispose() {
|
|
2259
|
-
|
|
2661
|
+
dispose() {
|
|
2662
|
+
disposed = true;
|
|
2663
|
+
unsubscribe();
|
|
2260
2664
|
visibility.dispose();
|
|
2261
2665
|
if (overlay) overlay.dispose();
|
|
2262
|
-
controller.dispose();
|
|
2263
|
-
root.remove();
|
|
2264
|
-
}
|
|
2265
|
-
|
|
2666
|
+
controller.dispose();
|
|
2667
|
+
root.remove();
|
|
2668
|
+
},
|
|
2669
|
+
textRatio: merged.textRatio,
|
|
2670
|
+
cssVars: merged.cssVars,
|
|
2671
|
+
dirty
|
|
2672
|
+
};
|
|
2266
2673
|
}
|
|
2267
2674
|
|
|
2268
2675
|
function prefersReducedMotion() {
|
|
@@ -2287,8 +2694,8 @@ function createColorBackground(host, options = {}) {
|
|
|
2287
2694
|
throw new Error('createColorBackground: host element is required');
|
|
2288
2695
|
}
|
|
2289
2696
|
|
|
2290
|
-
const preset = { ...getPreset('capsule', options.preset ?? '
|
|
2291
|
-
const merged = normalizeOptions(
|
|
2697
|
+
const preset = { ...getPreset('capsule', options.preset ?? '初光') };
|
|
2698
|
+
const merged = normalizeOptions(
|
|
2292
2699
|
{
|
|
2293
2700
|
quality: DEFAULTS.capsule.quality,
|
|
2294
2701
|
renderer: DEFAULTS.capsule.renderer,
|
|
@@ -2298,8 +2705,12 @@ function createColorBackground(host, options = {}) {
|
|
|
2298
2705
|
fallbackColor: true
|
|
2299
2706
|
},
|
|
2300
2707
|
preset,
|
|
2301
|
-
options
|
|
2302
|
-
);
|
|
2708
|
+
options
|
|
2709
|
+
);
|
|
2710
|
+
const normalizedColors = merged.colors.map(normalizeColor);
|
|
2711
|
+
if (normalizedColors.every(Boolean)) preset.colors = normalizedColors;
|
|
2712
|
+
preset.seed = merged.seed;
|
|
2713
|
+
preset.speed = merged.speed;
|
|
2303
2714
|
|
|
2304
2715
|
const computed = getComputedStyle(host);
|
|
2305
2716
|
if (computed.position === 'static' || computed.position === '') {
|
|
@@ -2328,8 +2739,18 @@ function createColorBackground(host, options = {}) {
|
|
|
2328
2739
|
layer.appendChild(canvas);
|
|
2329
2740
|
host.appendChild(layer);
|
|
2330
2741
|
|
|
2331
|
-
const emitter = createEmitter();
|
|
2332
|
-
let paused = merged.respectReducedMotion && prefersReducedMotion();
|
|
2742
|
+
const emitter = createEmitter();
|
|
2743
|
+
let paused = merged.respectReducedMotion && prefersReducedMotion();
|
|
2744
|
+
let disposed = false;
|
|
2745
|
+
const dirty = {
|
|
2746
|
+
preset: false,
|
|
2747
|
+
seed: false,
|
|
2748
|
+
speed: false,
|
|
2749
|
+
colors: false,
|
|
2750
|
+
opacity: false,
|
|
2751
|
+
fallbackColor: false,
|
|
2752
|
+
mouseColor: false
|
|
2753
|
+
};
|
|
2333
2754
|
|
|
2334
2755
|
let renderer;
|
|
2335
2756
|
const useWebgl = merged.renderer !== 'canvas2d';
|
|
@@ -2342,9 +2763,12 @@ function createColorBackground(host, options = {}) {
|
|
|
2342
2763
|
});
|
|
2343
2764
|
} catch (error) {
|
|
2344
2765
|
renderer = new FallbackRenderer(canvas, merged);
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2766
|
+
nextTick(() => {
|
|
2767
|
+
if (disposed) return;
|
|
2768
|
+
emitter.emit('error', {
|
|
2769
|
+
message: String(error && error.message ? error.message : error)
|
|
2770
|
+
});
|
|
2771
|
+
});
|
|
2348
2772
|
}
|
|
2349
2773
|
} else {
|
|
2350
2774
|
renderer = new FallbackRenderer(canvas, merged);
|
|
@@ -2368,7 +2792,10 @@ function createColorBackground(host, options = {}) {
|
|
|
2368
2792
|
() => paused
|
|
2369
2793
|
);
|
|
2370
2794
|
|
|
2371
|
-
|
|
2795
|
+
nextTick(() => {
|
|
2796
|
+
if (disposed) return;
|
|
2797
|
+
emitter.emit('ready', { preset: { ...preset } });
|
|
2798
|
+
});
|
|
2372
2799
|
|
|
2373
2800
|
return {
|
|
2374
2801
|
element: host,
|
|
@@ -2377,38 +2804,72 @@ function createColorBackground(host, options = {}) {
|
|
|
2377
2804
|
preset,
|
|
2378
2805
|
on: emitter.on,
|
|
2379
2806
|
off: emitter.off,
|
|
2380
|
-
setPreset(ref) {
|
|
2381
|
-
const next = getPreset('capsule', ref);
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
return this;
|
|
2395
|
-
|
|
2807
|
+
setPreset(ref) {
|
|
2808
|
+
const next = getPreset('capsule', ref);
|
|
2809
|
+
dirty.preset = true;
|
|
2810
|
+
Object.assign(preset, next);
|
|
2811
|
+
const nextColors = preset.colors.map(normalizeColor);
|
|
2812
|
+
if (nextColors.every(Boolean)) preset.colors = nextColors;
|
|
2813
|
+
renderer.setPreset({ ...preset });
|
|
2814
|
+
renderer.resize();
|
|
2815
|
+
syncLayerVars();
|
|
2816
|
+
emitter.emit('presetchange', { preset: { ...preset } });
|
|
2817
|
+
return this;
|
|
2818
|
+
},
|
|
2819
|
+
setColors(colors) {
|
|
2820
|
+
const next = Array.isArray(colors) ? colors.map(normalizeColor) : [];
|
|
2821
|
+
if (next.length !== 4 || next.some((color) => !color)) return this;
|
|
2822
|
+
dirty.colors = true;
|
|
2823
|
+
preset.colors = next;
|
|
2824
|
+
renderer.setPreset({ ...preset, colors: next });
|
|
2825
|
+
syncLayerVars();
|
|
2826
|
+
return this;
|
|
2827
|
+
},
|
|
2828
|
+
setSeed(seed) {
|
|
2829
|
+
const value = Number(seed);
|
|
2830
|
+
if (!Number.isFinite(value)) return this;
|
|
2831
|
+
dirty.seed = true;
|
|
2832
|
+
preset.seed = value;
|
|
2833
|
+
renderer.setPreset({ ...preset });
|
|
2834
|
+
return this;
|
|
2835
|
+
},
|
|
2836
|
+
setSpeed(speed) {
|
|
2837
|
+
const value = Number(speed);
|
|
2838
|
+
if (!Number.isFinite(value)) return this;
|
|
2839
|
+
dirty.speed = true;
|
|
2840
|
+
preset.speed = value;
|
|
2841
|
+
renderer.setPreset({ ...preset });
|
|
2842
|
+
return this;
|
|
2843
|
+
},
|
|
2844
|
+
setFallbackColor(value) {
|
|
2845
|
+
dirty.fallbackColor = true;
|
|
2846
|
+
merged.fallbackColor = value;
|
|
2847
|
+
syncLayerVars();
|
|
2848
|
+
return this;
|
|
2849
|
+
},
|
|
2850
|
+
setMouseColor(value) {
|
|
2851
|
+
dirty.mouseColor = true;
|
|
2852
|
+
merged.mouseColor = value !== false;
|
|
2853
|
+
if (typeof renderer.setMouseColor === 'function') renderer.setMouseColor(merged.mouseColor);
|
|
2854
|
+
return this;
|
|
2855
|
+
},
|
|
2396
2856
|
setQuality(quality) {
|
|
2397
2857
|
merged.quality = quality;
|
|
2398
2858
|
if (typeof renderer.setDprCap === 'function') renderer.setDprCap(dprCapFor(quality));
|
|
2399
2859
|
return this;
|
|
2400
2860
|
},
|
|
2401
|
-
setOpacity(value) {
|
|
2402
|
-
const opacity = Number(value);
|
|
2403
|
-
if (!Number.isFinite(opacity)) return this;
|
|
2404
|
-
|
|
2861
|
+
setOpacity(value) {
|
|
2862
|
+
const opacity = Number(value);
|
|
2863
|
+
if (!Number.isFinite(opacity)) return this;
|
|
2864
|
+
dirty.opacity = true;
|
|
2865
|
+
merged.opacity = Math.min(1, Math.max(0, opacity));
|
|
2405
2866
|
layer.style.setProperty('--dlc-color-opacity', String(merged.opacity));
|
|
2406
2867
|
return this;
|
|
2407
2868
|
},
|
|
2408
|
-
randomize() {
|
|
2409
|
-
|
|
2410
|
-
return this;
|
|
2411
|
-
},
|
|
2869
|
+
randomize() {
|
|
2870
|
+
this.setSeed(Math.random() * 100);
|
|
2871
|
+
return this;
|
|
2872
|
+
},
|
|
2412
2873
|
pause() {
|
|
2413
2874
|
paused = true;
|
|
2414
2875
|
return this;
|
|
@@ -2417,15 +2878,20 @@ function createColorBackground(host, options = {}) {
|
|
|
2417
2878
|
paused = false;
|
|
2418
2879
|
return this;
|
|
2419
2880
|
},
|
|
2420
|
-
dispose() {
|
|
2421
|
-
|
|
2881
|
+
dispose() {
|
|
2882
|
+
disposed = true;
|
|
2883
|
+
unsubscribe();
|
|
2422
2884
|
visibility.dispose();
|
|
2423
2885
|
if (resizeObserver) resizeObserver.disconnect();
|
|
2424
2886
|
else window.removeEventListener('resize', renderer.resize);
|
|
2425
|
-
renderer.dispose();
|
|
2426
|
-
layer.remove();
|
|
2427
|
-
}
|
|
2428
|
-
|
|
2887
|
+
renderer.dispose();
|
|
2888
|
+
layer.remove();
|
|
2889
|
+
},
|
|
2890
|
+
opacity: merged.opacity,
|
|
2891
|
+
fallbackColor: merged.fallbackColor,
|
|
2892
|
+
mouseColor: merged.mouseColor,
|
|
2893
|
+
dirty
|
|
2894
|
+
};
|
|
2429
2895
|
}
|
|
2430
2896
|
|
|
2431
|
-
export { ALL_PRESETS, COPY, CosmicRenderer, DEFAULTS, FallbackRenderer, PRESETS, PROGRESS_PRESETS, ProgressCapsuleController, ProgressFlowRenderer, QUALITY_TIERS, createCapsule, createColorBackground, createProgressCapsule, dprCapFor, getPreset, hexToRgb01$1 as hexToRgb01, hexToRgba, parseSize, pauseAll, resumeAll, validateColors
|
|
2897
|
+
export { ALL_PRESETS, COPY, CosmicRenderer, DEFAULTS, FallbackRenderer, PRESETS, PROGRESS_PRESETS, ProgressCapsuleController, ProgressFlowRenderer, QUALITY_TIERS, createCapsule, createColorBackground, createProgressCapsule, dprCapFor, getPreset, hexToRgb01$1 as hexToRgb01, hexToRgba, normalizeColor, parseSize, pauseAll, resumeAll, validateColors };
|