@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/progress.mjs
CHANGED
|
@@ -154,41 +154,246 @@ function getPreset(kind, ref) {
|
|
|
154
154
|
return found;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
const DEFAULTS = {
|
|
158
|
-
progress: {
|
|
159
|
-
width: 454,
|
|
160
|
-
height: 104,
|
|
161
|
-
min: 0,
|
|
157
|
+
const DEFAULTS = {
|
|
158
|
+
progress: {
|
|
159
|
+
width: 454,
|
|
160
|
+
height: 104,
|
|
161
|
+
min: 0,
|
|
162
162
|
max: 100,
|
|
163
|
-
draggable: true,
|
|
164
|
-
keyboard: true,
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
163
|
+
draggable: true,
|
|
164
|
+
keyboard: true,
|
|
165
|
+
disabled: false,
|
|
166
|
+
valueSuffix: '%',
|
|
167
|
+
edgeStyle: 'flow',
|
|
168
|
+
quality: 'auto',
|
|
169
|
+
renderer: 'auto',
|
|
170
|
+
textRatio: 54,
|
|
171
|
+
showValue: true,
|
|
172
|
+
respectReducedMotion: true
|
|
173
|
+
}
|
|
171
174
|
};
|
|
172
175
|
|
|
173
|
-
/**
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
valueSuffix: '%',
|
|
176
|
+
/**
|
|
177
|
+
* Internal accessibility labels and small UI text. Since the copy API was
|
|
178
|
+
* removed (slots own the text area), these are no longer user-overridable.
|
|
179
|
+
* Templates use {brand} {code} {name} placeholders.
|
|
180
|
+
*/
|
|
181
|
+
const COPY = {
|
|
182
|
+
brandName: '画境观屿',
|
|
183
|
+
valueSuffix: '%',
|
|
181
184
|
progressAria: '{brand} {code} 加载进度',
|
|
182
185
|
capsuleAria: '打开 {name} 沉浸预览'
|
|
183
186
|
};
|
|
184
187
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
188
|
+
/**
|
|
189
|
+
* Color helpers. `normalizeColor` converts any supported CSS color
|
|
190
|
+
* (hex / named / rgb() / rgba()) into a 6-digit hex string, so renderers can
|
|
191
|
+
* keep working with #rrggbb only. rgba() alpha is intentionally dropped:
|
|
192
|
+
* the WebGL shader has no per-color alpha channel, and the component is
|
|
193
|
+
* opaque by design — use component-level `opacity` for transparency.
|
|
194
|
+
*/
|
|
195
|
+
|
|
196
|
+
const NAMED_COLORS = {
|
|
197
|
+
aliceblue: '#f0f8ff',
|
|
198
|
+
antiquewhite: '#faebd7',
|
|
199
|
+
aqua: '#00ffff',
|
|
200
|
+
aquamarine: '#7fffd4',
|
|
201
|
+
azure: '#f0ffff',
|
|
202
|
+
beige: '#f5f5dc',
|
|
203
|
+
bisque: '#ffe4c4',
|
|
204
|
+
black: '#000000',
|
|
205
|
+
blanchedalmond: '#ffebcd',
|
|
206
|
+
blue: '#0000ff',
|
|
207
|
+
blueviolet: '#8a2be2',
|
|
208
|
+
brown: '#a52a2a',
|
|
209
|
+
burlywood: '#deb887',
|
|
210
|
+
cadetblue: '#5f9ea0',
|
|
211
|
+
chartreuse: '#7fff00',
|
|
212
|
+
chocolate: '#d2691e',
|
|
213
|
+
coral: '#ff7f50',
|
|
214
|
+
cornflowerblue: '#6495ed',
|
|
215
|
+
cornsilk: '#fff8dc',
|
|
216
|
+
crimson: '#dc143c',
|
|
217
|
+
cyan: '#00ffff',
|
|
218
|
+
darkblue: '#00008b',
|
|
219
|
+
darkcyan: '#008b8b',
|
|
220
|
+
darkgoldenrod: '#b8860b',
|
|
221
|
+
darkgray: '#a9a9a9',
|
|
222
|
+
darkgreen: '#006400',
|
|
223
|
+
darkgrey: '#a9a9a9',
|
|
224
|
+
darkkhaki: '#bdb76b',
|
|
225
|
+
darkmagenta: '#8b008b',
|
|
226
|
+
darkolivegreen: '#556b2f',
|
|
227
|
+
darkorange: '#ff8c00',
|
|
228
|
+
darkorchid: '#9932cc',
|
|
229
|
+
darkred: '#8b0000',
|
|
230
|
+
darksalmon: '#e9967a',
|
|
231
|
+
darkseagreen: '#8fbc8f',
|
|
232
|
+
darkslateblue: '#483d8b',
|
|
233
|
+
darkslategray: '#2f4f4f',
|
|
234
|
+
darkslategrey: '#2f4f4f',
|
|
235
|
+
darkturquoise: '#00ced1',
|
|
236
|
+
darkviolet: '#9400d3',
|
|
237
|
+
deeppink: '#ff1493',
|
|
238
|
+
deepskyblue: '#00bfff',
|
|
239
|
+
dimgray: '#696969',
|
|
240
|
+
dimgrey: '#696969',
|
|
241
|
+
dodgerblue: '#1e90ff',
|
|
242
|
+
firebrick: '#b22222',
|
|
243
|
+
floralwhite: '#fffaf0',
|
|
244
|
+
forestgreen: '#228b22',
|
|
245
|
+
fuchsia: '#ff00ff',
|
|
246
|
+
gainsboro: '#dcdcdc',
|
|
247
|
+
ghostwhite: '#f8f8ff',
|
|
248
|
+
gold: '#ffd700',
|
|
249
|
+
goldenrod: '#daa520',
|
|
250
|
+
gray: '#808080',
|
|
251
|
+
green: '#008000',
|
|
252
|
+
greenyellow: '#adff2f',
|
|
253
|
+
grey: '#808080',
|
|
254
|
+
honeydew: '#f0fff0',
|
|
255
|
+
hotpink: '#ff69b4',
|
|
256
|
+
indianred: '#cd5c5c',
|
|
257
|
+
indigo: '#4b0082',
|
|
258
|
+
ivory: '#fffff0',
|
|
259
|
+
khaki: '#f0e68c',
|
|
260
|
+
lavender: '#e6e6fa',
|
|
261
|
+
lavenderblush: '#fff0f5',
|
|
262
|
+
lawngreen: '#7cfc00',
|
|
263
|
+
lemonchiffon: '#fffacd',
|
|
264
|
+
lightblue: '#add8e6',
|
|
265
|
+
lightcoral: '#f08080',
|
|
266
|
+
lightcyan: '#e0ffff',
|
|
267
|
+
lightgoldenrodyellow: '#fafad2',
|
|
268
|
+
lightgray: '#d3d3d3',
|
|
269
|
+
lightgreen: '#90ee90',
|
|
270
|
+
lightgrey: '#d3d3d3',
|
|
271
|
+
lightpink: '#ffb6c1',
|
|
272
|
+
lightsalmon: '#ffa07a',
|
|
273
|
+
lightseagreen: '#20b2aa',
|
|
274
|
+
lightskyblue: '#87cefa',
|
|
275
|
+
lightslategray: '#778899',
|
|
276
|
+
lightslategrey: '#778899',
|
|
277
|
+
lightsteelblue: '#b0c4de',
|
|
278
|
+
lightyellow: '#ffffe0',
|
|
279
|
+
lime: '#00ff00',
|
|
280
|
+
limegreen: '#32cd32',
|
|
281
|
+
linen: '#faf0e6',
|
|
282
|
+
magenta: '#ff00ff',
|
|
283
|
+
maroon: '#800000',
|
|
284
|
+
mediumaquamarine: '#66cdaa',
|
|
285
|
+
mediumblue: '#0000cd',
|
|
286
|
+
mediumorchid: '#ba55d3',
|
|
287
|
+
mediumpurple: '#9370db',
|
|
288
|
+
mediumseagreen: '#3cb371',
|
|
289
|
+
mediumslateblue: '#7b68ee',
|
|
290
|
+
mediumspringgreen: '#00fa9a',
|
|
291
|
+
mediumturquoise: '#48d1cc',
|
|
292
|
+
mediumvioletred: '#c71585',
|
|
293
|
+
midnightblue: '#191970',
|
|
294
|
+
mintcream: '#f5fffa',
|
|
295
|
+
mistyrose: '#ffe4e1',
|
|
296
|
+
moccasin: '#ffe4b5',
|
|
297
|
+
navajowhite: '#ffdead',
|
|
298
|
+
navy: '#000080',
|
|
299
|
+
oldlace: '#fdf5e6',
|
|
300
|
+
olive: '#808000',
|
|
301
|
+
olivedrab: '#6b8e23',
|
|
302
|
+
orange: '#ffa500',
|
|
303
|
+
orangered: '#ff4500',
|
|
304
|
+
orchid: '#da70d6',
|
|
305
|
+
palegoldenrod: '#eee8aa',
|
|
306
|
+
palegreen: '#98fb98',
|
|
307
|
+
paleturquoise: '#afeeee',
|
|
308
|
+
palevioletred: '#db7093',
|
|
309
|
+
papayawhip: '#ffefd5',
|
|
310
|
+
peachpuff: '#ffdab9',
|
|
311
|
+
peru: '#cd853f',
|
|
312
|
+
pink: '#ffc0cb',
|
|
313
|
+
plum: '#dda0dd',
|
|
314
|
+
powderblue: '#b0e0e6',
|
|
315
|
+
purple: '#800080',
|
|
316
|
+
rebeccapurple: '#663399',
|
|
317
|
+
red: '#ff0000',
|
|
318
|
+
rosybrown: '#bc8f8f',
|
|
319
|
+
royalblue: '#4169e1',
|
|
320
|
+
saddlebrown: '#8b4513',
|
|
321
|
+
salmon: '#fa8072',
|
|
322
|
+
sandybrown: '#f4a460',
|
|
323
|
+
seagreen: '#2e8b57',
|
|
324
|
+
seashell: '#fff5ee',
|
|
325
|
+
sienna: '#a0522d',
|
|
326
|
+
silver: '#c0c0c0',
|
|
327
|
+
skyblue: '#87ceeb',
|
|
328
|
+
slateblue: '#6a5acd',
|
|
329
|
+
slategray: '#708090',
|
|
330
|
+
slategrey: '#708090',
|
|
331
|
+
snow: '#fffafa',
|
|
332
|
+
springgreen: '#00ff7f',
|
|
333
|
+
steelblue: '#4682b4',
|
|
334
|
+
tan: '#d2b48c',
|
|
335
|
+
teal: '#008080',
|
|
336
|
+
thistle: '#d8bfd8',
|
|
337
|
+
tomato: '#ff6347',
|
|
338
|
+
turquoise: '#40e0d0',
|
|
339
|
+
violet: '#ee82ee',
|
|
340
|
+
wheat: '#f5deb3',
|
|
341
|
+
white: '#ffffff',
|
|
342
|
+
whitesmoke: '#f5f5f5',
|
|
343
|
+
yellow: '#ffff00',
|
|
344
|
+
yellowgreen: '#9acd32'
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
function clampChannel(value) {
|
|
348
|
+
return Math.min(255, Math.max(0, Math.round(value)));
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function normalizeRgbArgs(args) {
|
|
352
|
+
const parts = args.split(/[,\s/]+/).filter(Boolean);
|
|
353
|
+
if (parts.length < 3) return null;
|
|
354
|
+
const to255 = (value) => {
|
|
355
|
+
if (typeof value !== 'string' || !value) return null;
|
|
356
|
+
if (value.endsWith('%')) return clampChannel((parseFloat(value) / 100) * 255);
|
|
357
|
+
const number = Number(value);
|
|
358
|
+
return Number.isFinite(number) ? clampChannel(number) : null;
|
|
359
|
+
};
|
|
360
|
+
const red = to255(parts[0]);
|
|
361
|
+
const green = to255(parts[1]);
|
|
362
|
+
const blue = to255(parts[2]);
|
|
363
|
+
if (red === null || green === null || blue === null) return null;
|
|
364
|
+
return `#${[red, green, blue].map((n) => n.toString(16).padStart(2, '0')).join('')}`;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Convert any supported CSS color into `#rrggbb`. Returns null when the
|
|
369
|
+
* value cannot be parsed. Supports: #rgb / #rrggbb (case-insensitive),
|
|
370
|
+
* CSS named colors, rgb() / rgba() with comma or modern space syntax,
|
|
371
|
+
* including percentages. Alpha in rgba() is ignored.
|
|
372
|
+
*/
|
|
373
|
+
function normalizeColor(value) {
|
|
374
|
+
if (typeof value !== 'string') return null;
|
|
375
|
+
const input = value.trim();
|
|
376
|
+
if (!input) return null;
|
|
377
|
+
const lower = input.toLowerCase();
|
|
378
|
+
if (NAMED_COLORS[lower]) return NAMED_COLORS[lower];
|
|
379
|
+
if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(input)) {
|
|
380
|
+
const hex = input.slice(1).toLowerCase();
|
|
381
|
+
if (hex.length === 3) return `#${hex.split('').map((c) => c + c).join('')}`;
|
|
382
|
+
return `#${hex}`;
|
|
383
|
+
}
|
|
384
|
+
const match = input.match(/^rgba?\((.*)\)$/i);
|
|
385
|
+
if (match) return normalizeRgbArgs(match[1]);
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function hexToRgba(color, alpha = 1) {
|
|
390
|
+
const hex = normalizeColor(color);
|
|
391
|
+
if (!hex) return 'rgba(0, 0, 0, 0)';
|
|
392
|
+
const value = Number.parseInt(hex.slice(1), 16);
|
|
393
|
+
const red = (value >> 16) & 255;
|
|
394
|
+
const green = (value >> 8) & 255;
|
|
395
|
+
const blue = value & 255;
|
|
396
|
+
return `rgba(${red}, ${green}, ${blue}, ${alpha})`;
|
|
192
397
|
}
|
|
193
398
|
|
|
194
399
|
/**
|
|
@@ -894,6 +1099,22 @@ function attachProgressFlowOverlay({ root, canvas, preset, getProgress }) {
|
|
|
894
1099
|
};
|
|
895
1100
|
}
|
|
896
1101
|
|
|
1102
|
+
/**
|
|
1103
|
+
* Run a callback asynchronously, right after the current task and before the
|
|
1104
|
+
* next paint. Used for mount-time events (ready / error) so listeners that
|
|
1105
|
+
* are attached after create() still receive them. Falls back for legacy
|
|
1106
|
+
* browsers that lack queueMicrotask / Promise.
|
|
1107
|
+
*/
|
|
1108
|
+
function nextTick(fn) {
|
|
1109
|
+
if (typeof queueMicrotask === 'function') {
|
|
1110
|
+
queueMicrotask(fn);
|
|
1111
|
+
} else if (typeof Promise !== 'undefined' && typeof Promise.resolve === 'function') {
|
|
1112
|
+
Promise.resolve().then(fn);
|
|
1113
|
+
} else {
|
|
1114
|
+
setTimeout(fn, 0);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
|
|
897
1118
|
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
|
|
898
1119
|
const SUPPORTS_CTX_FILTER = typeof CanvasRenderingContext2D !== 'undefined' && 'filter' in CanvasRenderingContext2D.prototype;
|
|
899
1120
|
|
|
@@ -973,14 +1194,16 @@ const FLOW_PROFILES = {
|
|
|
973
1194
|
};
|
|
974
1195
|
|
|
975
1196
|
class ProgressCapsuleController {
|
|
976
|
-
constructor({ root, canvas, valueElement, preset, emitter, options, copy }) {
|
|
1197
|
+
constructor({ root, canvas, valueElement, preset, emitter, options, copy, dirty }) {
|
|
977
1198
|
this.root = root;
|
|
978
1199
|
this.canvas = canvas;
|
|
979
1200
|
this.valueElement = valueElement;
|
|
980
1201
|
this.preset = preset;
|
|
981
1202
|
this.emitter = emitter;
|
|
982
|
-
this.options = options;
|
|
983
|
-
this.copy = copy;
|
|
1203
|
+
this.options = options;
|
|
1204
|
+
this.copy = copy;
|
|
1205
|
+
this.dirty = dirty;
|
|
1206
|
+
this.valueSuffix = options.valueSuffix ?? copy.valueSuffix;
|
|
984
1207
|
this.profile = preset.edgeStyle === 'tide'
|
|
985
1208
|
? FLOW_PROFILES.tide
|
|
986
1209
|
: (FLOW_PROFILES[preset.id] || FLOW_PROFILES['visual-training']);
|
|
@@ -1021,11 +1244,22 @@ class ProgressCapsuleController {
|
|
|
1021
1244
|
this.value = clamp(nextValue, this.min, this.max);
|
|
1022
1245
|
const rounded = Math.round(this.value);
|
|
1023
1246
|
this.root.style.setProperty('--progress', this.value.toFixed(2));
|
|
1024
|
-
this.root.setAttribute('aria-valuenow', String(rounded));
|
|
1025
|
-
this.root.setAttribute('aria-valuetext', `${rounded}${this.
|
|
1026
|
-
this.valueElement.textContent = `${rounded}${this.
|
|
1027
|
-
if (!this.suppressEvents)
|
|
1028
|
-
|
|
1247
|
+
this.root.setAttribute('aria-valuenow', String(rounded));
|
|
1248
|
+
this.root.setAttribute('aria-valuetext', `${rounded}${this.valueSuffix}`);
|
|
1249
|
+
if (this.valueElement) this.valueElement.textContent = `${rounded}${this.valueSuffix}`;
|
|
1250
|
+
if (!this.suppressEvents) {
|
|
1251
|
+
if (this.dirty) this.dirty.value = true;
|
|
1252
|
+
this.emitter.emit('change', { value: this.value, source });
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
setValueSuffix(suffix) {
|
|
1257
|
+
this.valueSuffix = String(suffix == null ? '' : suffix);
|
|
1258
|
+
const rounded = Math.round(this.value);
|
|
1259
|
+
if (this.valueElement) this.valueElement.textContent = `${rounded}${this.valueSuffix}`;
|
|
1260
|
+
this.root.setAttribute('aria-valuetext', `${rounded}${this.valueSuffix}`);
|
|
1261
|
+
return this;
|
|
1262
|
+
}
|
|
1029
1263
|
|
|
1030
1264
|
resizeCanvas() {
|
|
1031
1265
|
const bounds = this.root.getBoundingClientRect();
|
|
@@ -1198,13 +1432,14 @@ class ProgressCapsuleController {
|
|
|
1198
1432
|
if (clamped !== this.value) this.setProgress(clamped, 'prop');
|
|
1199
1433
|
}
|
|
1200
1434
|
|
|
1201
|
-
drawReferenceFlow() {
|
|
1435
|
+
drawReferenceFlow() {
|
|
1202
1436
|
const ctx = this.ctx;
|
|
1203
1437
|
const width = this.width;
|
|
1204
1438
|
const height = this.height;
|
|
1205
1439
|
if (!ctx || width <= 0 || height <= 0) return;
|
|
1206
1440
|
|
|
1207
|
-
const
|
|
1441
|
+
const range = Math.max(this.max - this.min, 1);
|
|
1442
|
+
const shoreline = width * Math.min(Math.max((this.value - this.min) / range, 0), 1);
|
|
1208
1443
|
const time = this.flowTime;
|
|
1209
1444
|
const [dark, accentA, accentB, glow] = this.preset.colors;
|
|
1210
1445
|
|
|
@@ -1322,32 +1557,49 @@ class ProgressCapsuleController {
|
|
|
1322
1557
|
}
|
|
1323
1558
|
}
|
|
1324
1559
|
|
|
1325
|
-
updateFromPointer(event) {
|
|
1326
|
-
const bounds = this.root.getBoundingClientRect();
|
|
1327
|
-
const ratio = bounds.width > 0 ? (event.clientX - bounds.left) / bounds.width : 0;
|
|
1328
|
-
this.setProgress(ratio *
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
beginDrag(event) {
|
|
1332
|
-
if (event.button !== undefined && event.button !== 0) return;
|
|
1333
|
-
event.preventDefault();
|
|
1334
|
-
this.dragging = true;
|
|
1335
|
-
this.
|
|
1336
|
-
|
|
1337
|
-
this.
|
|
1338
|
-
this.
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
this.
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
if (
|
|
1349
|
-
this.
|
|
1350
|
-
|
|
1560
|
+
updateFromPointer(event) {
|
|
1561
|
+
const bounds = this.root.getBoundingClientRect();
|
|
1562
|
+
const ratio = bounds.width > 0 ? (event.clientX - bounds.left) / bounds.width : 0;
|
|
1563
|
+
this.setProgress(this.min + ratio * (this.max - this.min), 'drag');
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
beginDrag(event) {
|
|
1567
|
+
if (event.button !== undefined && event.button !== 0) return;
|
|
1568
|
+
event.preventDefault();
|
|
1569
|
+
this.dragging = true;
|
|
1570
|
+
this.pendingPointer = null;
|
|
1571
|
+
this._dragRaf = 0;
|
|
1572
|
+
this.root.classList.add('is-dragging');
|
|
1573
|
+
try { this.root.setPointerCapture?.(event.pointerId); } catch {}
|
|
1574
|
+
this.emitter.emit('dragstart', { value: this.value });
|
|
1575
|
+
this.updateFromPointer(event);
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
moveDrag(event) {
|
|
1579
|
+
if (!this.dragging) return;
|
|
1580
|
+
event.preventDefault();
|
|
1581
|
+
// rAF 合并:一帧最多一次 setProgress/change,视觉仍每帧更新。
|
|
1582
|
+
this.pendingPointer = event;
|
|
1583
|
+
if (this._dragRaf) return;
|
|
1584
|
+
this._dragRaf = requestAnimationFrame(() => {
|
|
1585
|
+
this._dragRaf = 0;
|
|
1586
|
+
const pending = this.pendingPointer;
|
|
1587
|
+
this.pendingPointer = null;
|
|
1588
|
+
if (pending) this.updateFromPointer(pending);
|
|
1589
|
+
});
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
endDrag(event) {
|
|
1593
|
+
if (!this.dragging) return;
|
|
1594
|
+
this.dragging = false;
|
|
1595
|
+
if (this._dragRaf) {
|
|
1596
|
+
cancelAnimationFrame(this._dragRaf);
|
|
1597
|
+
this._dragRaf = 0;
|
|
1598
|
+
}
|
|
1599
|
+
const pending = this.pendingPointer;
|
|
1600
|
+
this.pendingPointer = null;
|
|
1601
|
+
if (pending) this.updateFromPointer(pending);
|
|
1602
|
+
this.root.classList.remove('is-dragging');
|
|
1351
1603
|
try {
|
|
1352
1604
|
if (event?.pointerId !== undefined && this.root.hasPointerCapture?.(event.pointerId)) {
|
|
1353
1605
|
this.root.releasePointerCapture(event.pointerId);
|
|
@@ -1369,15 +1621,16 @@ class ProgressCapsuleController {
|
|
|
1369
1621
|
}
|
|
1370
1622
|
|
|
1371
1623
|
if (this.options.keyboard !== false) {
|
|
1372
|
-
this.handlers.keydown = (event) => {
|
|
1373
|
-
const
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1624
|
+
this.handlers.keydown = (event) => {
|
|
1625
|
+
const step = (this.max - this.min) * 0.02;
|
|
1626
|
+
const actions = {
|
|
1627
|
+
ArrowLeft: -step,
|
|
1628
|
+
ArrowDown: -step,
|
|
1629
|
+
ArrowRight: step,
|
|
1630
|
+
ArrowUp: step,
|
|
1631
|
+
PageDown: -step * 5,
|
|
1632
|
+
PageUp: step * 5
|
|
1633
|
+
};
|
|
1381
1634
|
if (event.key === 'Home') {
|
|
1382
1635
|
event.preventDefault();
|
|
1383
1636
|
this.setProgress(this.min, 'keyboard');
|
|
@@ -1415,8 +1668,9 @@ class ProgressCapsuleController {
|
|
|
1415
1668
|
this.preset = { ...this.preset, colors };
|
|
1416
1669
|
}
|
|
1417
1670
|
|
|
1418
|
-
dispose() {
|
|
1419
|
-
if (this.
|
|
1671
|
+
dispose() {
|
|
1672
|
+
if (this._dragRaf) cancelAnimationFrame(this._dragRaf);
|
|
1673
|
+
if (this.resizeObserver) this.resizeObserver.disconnect();
|
|
1420
1674
|
else window.removeEventListener('resize', this.resizeCanvas);
|
|
1421
1675
|
for (const name of Object.keys(this.handlers)) {
|
|
1422
1676
|
const handler = this.handlers[name];
|
|
@@ -1427,103 +1681,127 @@ class ProgressCapsuleController {
|
|
|
1427
1681
|
}
|
|
1428
1682
|
|
|
1429
1683
|
/**
|
|
1430
|
-
* Mount a fluid progress capsule into `container`.
|
|
1431
|
-
*
|
|
1432
|
-
* Options: preset (id/code/name or object), width, height, value, min, max,
|
|
1433
|
-
* draggable, keyboard, colors, edgeStyle,
|
|
1434
|
-
*
|
|
1435
|
-
|
|
1436
|
-
|
|
1684
|
+
* Mount a fluid progress capsule into `container`.
|
|
1685
|
+
*
|
|
1686
|
+
* Options: preset (id/code/name or object), width, height, value, min, max,
|
|
1687
|
+
* draggable, keyboard, colors, edgeStyle, textRatio (0-100, text region
|
|
1688
|
+
* width in percent), text (HTML string or DOM nodes for the text slot),
|
|
1689
|
+
* colorContent (HTML string or DOM nodes for the color slot),
|
|
1690
|
+
* showValue (show/hide the right-side percentage), quality,
|
|
1691
|
+
* respectReducedMotion, cssVars.
|
|
1692
|
+
*/
|
|
1693
|
+
function createProgressCapsule(container, options = {}) {
|
|
1437
1694
|
if (!container || typeof container.appendChild !== 'function') {
|
|
1438
1695
|
throw new Error('createProgressCapsule: container element is required');
|
|
1439
1696
|
}
|
|
1440
1697
|
|
|
1441
1698
|
const preset = { ...getPreset('progress', options.preset ?? '星火') };
|
|
1442
|
-
const merged = normalizeOptions(DEFAULTS.progress, preset, options);
|
|
1443
|
-
const
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1699
|
+
const merged = normalizeOptions(DEFAULTS.progress, preset, options);
|
|
1700
|
+
const normalizedColors = merged.colors.map(normalizeColor);
|
|
1701
|
+
if (normalizedColors.every(Boolean)) preset.colors = normalizedColors;
|
|
1702
|
+
preset.edgeStyle = merged.edgeStyle;
|
|
1703
|
+
const copy = COPY;
|
|
1704
|
+
let customLabel = typeof options.label === 'string' && options.label ? options.label : null;
|
|
1705
|
+
// Vue 的裸布尔属性(<ProgressCapsule disabled />)会传成空字符串,
|
|
1706
|
+
// 这里统一按 true 处理。
|
|
1707
|
+
const disabled = merged.disabled === true || merged.disabled === '' || merged.disabled === 'true';
|
|
1708
|
+
const effectiveDraggable = disabled ? false : merged.draggable !== false;
|
|
1709
|
+
const effectiveKeyboard = disabled ? false : merged.keyboard !== false;
|
|
1710
|
+
const dirty = {
|
|
1711
|
+
preset: false,
|
|
1712
|
+
colors: false,
|
|
1713
|
+
textRatio: false,
|
|
1714
|
+
cssVars: false,
|
|
1715
|
+
edgeStyle: false,
|
|
1716
|
+
value: false
|
|
1717
|
+
};
|
|
1718
|
+
|
|
1719
|
+
const root = document.createElement('div');
|
|
1720
|
+
root.className = 'hj-capsule-root hj-progress-root';
|
|
1721
|
+
root.setAttribute('role', 'slider');
|
|
1722
|
+
root.setAttribute('tabindex', '0');
|
|
1723
|
+
root.setAttribute('data-draggable', String(effectiveDraggable));
|
|
1724
|
+
if (disabled) {
|
|
1725
|
+
root.setAttribute('aria-disabled', 'true');
|
|
1726
|
+
root.classList.add('is-disabled');
|
|
1727
|
+
}
|
|
1450
1728
|
root.setAttribute('aria-valuemin', String(merged.min ?? 0));
|
|
1451
1729
|
root.setAttribute('aria-valuemax', String(merged.max ?? 100));
|
|
1452
1730
|
root.setAttribute('aria-valuenow', String(preset.initialProgress));
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1731
|
+
const updateAria = () => {
|
|
1732
|
+
root.setAttribute(
|
|
1733
|
+
'aria-label',
|
|
1734
|
+
customLabel || interpolate(copy.progressAria, { brand: copy.brandName, code: preset.code, name: preset.name })
|
|
1735
|
+
);
|
|
1736
|
+
};
|
|
1737
|
+
updateAria();
|
|
1738
|
+
if (!effectiveKeyboard) root.setAttribute('tabindex', '-1');
|
|
1458
1739
|
|
|
1459
1740
|
const canvas = document.createElement('canvas');
|
|
1460
1741
|
canvas.className = 'hj-progress-canvas';
|
|
1461
1742
|
canvas.setAttribute('aria-hidden', 'true');
|
|
1462
1743
|
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
const
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
const
|
|
1473
|
-
const
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
const
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
root.style.height = parseSize(merged.height);
|
|
1513
|
-
const vars = merged.cssVars || {};
|
|
1514
|
-
for (const name of Object.keys(vars)) root.style.setProperty(name, vars[name]);
|
|
1515
|
-
};
|
|
1744
|
+
// Slot containers are always present but transparent by default; they only
|
|
1745
|
+
// provide geometry, never typography/padding/background (docs: 插槽 CSS 约定).
|
|
1746
|
+
const fillContent = (layer, content) => {
|
|
1747
|
+
layer.innerHTML = '';
|
|
1748
|
+
if (content == null) return;
|
|
1749
|
+
if (typeof content === 'string') {
|
|
1750
|
+
layer.innerHTML = content;
|
|
1751
|
+
return;
|
|
1752
|
+
}
|
|
1753
|
+
const nodes = Array.isArray(content) ? content : [content];
|
|
1754
|
+
for (const node of nodes) {
|
|
1755
|
+
if (node && typeof node.nodeType === 'number') layer.appendChild(node);
|
|
1756
|
+
}
|
|
1757
|
+
};
|
|
1758
|
+
|
|
1759
|
+
const textLayer = document.createElement('div');
|
|
1760
|
+
textLayer.className = 'hj-progress-text hj-progress-copy';
|
|
1761
|
+
|
|
1762
|
+
const visualLayer = document.createElement('div');
|
|
1763
|
+
visualLayer.className = 'hj-progress-visual';
|
|
1764
|
+
|
|
1765
|
+
fillContent(textLayer, options.text);
|
|
1766
|
+
fillContent(visualLayer, options.colorContent);
|
|
1767
|
+
|
|
1768
|
+
const valueElement = merged.showValue === false ? null : document.createElement('div');
|
|
1769
|
+
if (valueElement) {
|
|
1770
|
+
valueElement.className = 'hj-progress-value';
|
|
1771
|
+
valueElement.setAttribute('aria-hidden', 'true');
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
root.appendChild(canvas);
|
|
1775
|
+
root.appendChild(textLayer);
|
|
1776
|
+
root.appendChild(visualLayer);
|
|
1777
|
+
if (valueElement) root.appendChild(valueElement);
|
|
1778
|
+
container.appendChild(root);
|
|
1779
|
+
|
|
1780
|
+
const emitter = createEmitter();
|
|
1781
|
+
let paused = merged.respectReducedMotion && prefersReducedMotion();
|
|
1782
|
+
let disposed = false;
|
|
1783
|
+
|
|
1784
|
+
const applySize = () => {
|
|
1785
|
+
root.style.width = parseSize(merged.width);
|
|
1786
|
+
root.style.height = parseSize(merged.height);
|
|
1787
|
+
const vars = merged.cssVars || {};
|
|
1788
|
+
for (const name of Object.keys(vars)) root.style.setProperty(name, vars[name]);
|
|
1789
|
+
if (merged.textRatio !== undefined) {
|
|
1790
|
+
root.style.setProperty('--hj-text-width', `${merged.textRatio}%`);
|
|
1791
|
+
}
|
|
1792
|
+
};
|
|
1516
1793
|
applySize();
|
|
1517
1794
|
|
|
1518
|
-
const controller = new ProgressCapsuleController({
|
|
1519
|
-
root,
|
|
1520
|
-
canvas,
|
|
1521
|
-
valueElement,
|
|
1522
|
-
preset,
|
|
1523
|
-
emitter,
|
|
1524
|
-
options: merged,
|
|
1525
|
-
copy
|
|
1526
|
-
|
|
1795
|
+
const controller = new ProgressCapsuleController({
|
|
1796
|
+
root,
|
|
1797
|
+
canvas,
|
|
1798
|
+
valueElement,
|
|
1799
|
+
preset,
|
|
1800
|
+
emitter,
|
|
1801
|
+
options: { ...merged, draggable: effectiveDraggable, keyboard: effectiveKeyboard },
|
|
1802
|
+
copy,
|
|
1803
|
+
dirty
|
|
1804
|
+
});
|
|
1527
1805
|
|
|
1528
1806
|
let overlay = null;
|
|
1529
1807
|
if (merged.renderer !== 'canvas2d') {
|
|
@@ -1534,7 +1812,13 @@ function createProgressCapsule(container, options = {}) {
|
|
|
1534
1812
|
getProgress: () => controller.value
|
|
1535
1813
|
});
|
|
1536
1814
|
}
|
|
1537
|
-
if (overlay) controller.webglActive = true;
|
|
1815
|
+
if (overlay) controller.webglActive = true;
|
|
1816
|
+
else if (merged.renderer !== 'canvas2d') {
|
|
1817
|
+
nextTick(() => {
|
|
1818
|
+
if (disposed) return;
|
|
1819
|
+
emitter.emit('error', { message: 'WebGL2 unavailable, using Canvas2D fallback' });
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1538
1822
|
|
|
1539
1823
|
const visibility = createVisibilityGuard(root);
|
|
1540
1824
|
let flowTime = 0;
|
|
@@ -1550,15 +1834,14 @@ function createProgressCapsule(container, options = {}) {
|
|
|
1550
1834
|
() => paused
|
|
1551
1835
|
);
|
|
1552
1836
|
|
|
1553
|
-
|
|
1837
|
+
nextTick(() => {
|
|
1838
|
+
if (disposed) return;
|
|
1839
|
+
emitter.emit('ready', { preset: { ...preset } });
|
|
1840
|
+
});
|
|
1554
1841
|
|
|
1555
|
-
const syncDom = () => {
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
interpolate(copy.progressAria, { brand: copy.brandName, code: preset.code, name: preset.name })
|
|
1559
|
-
);
|
|
1560
|
-
renderCopy();
|
|
1561
|
-
};
|
|
1842
|
+
const syncDom = () => {
|
|
1843
|
+
updateAria();
|
|
1844
|
+
};
|
|
1562
1845
|
|
|
1563
1846
|
return {
|
|
1564
1847
|
element: root,
|
|
@@ -1579,26 +1862,30 @@ function createProgressCapsule(container, options = {}) {
|
|
|
1579
1862
|
},
|
|
1580
1863
|
setPreset(ref) {
|
|
1581
1864
|
const next = getPreset('progress', ref);
|
|
1865
|
+
dirty.preset = true;
|
|
1582
1866
|
Object.assign(preset, next);
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1867
|
+
const nextColors = preset.colors.map(normalizeColor);
|
|
1868
|
+
if (nextColors.every(Boolean)) preset.colors = nextColors;
|
|
1869
|
+
controller.preset = preset;
|
|
1870
|
+
controller.profile = next.edgeStyle === 'tide'
|
|
1871
|
+
? FLOW_PROFILES.tide
|
|
1872
|
+
: (FLOW_PROFILES[next.id] || FLOW_PROFILES['visual-training']);
|
|
1873
|
+
controller.flowTime = stringSeed(next.id) * 31;
|
|
1874
|
+
controller.seed = stringSeed(`${next.id}-reference`) * Math.PI * 2;
|
|
1875
|
+
syncDom();
|
|
1876
|
+
if (overlay) {
|
|
1877
|
+
overlay.dispose();
|
|
1878
|
+
overlay = attachProgressFlowOverlay({ root, canvas, preset, getProgress: () => controller.value });
|
|
1879
|
+
}
|
|
1880
|
+
controller.webglActive = Boolean(overlay);
|
|
1881
|
+
controller.resizeCanvas();
|
|
1882
|
+
emitter.emit('presetchange', { preset: { ...preset } });
|
|
1597
1883
|
return this;
|
|
1598
1884
|
},
|
|
1599
1885
|
setEdgeStyle(edgeStyle) {
|
|
1600
1886
|
const value = String(edgeStyle || '').toLowerCase();
|
|
1601
1887
|
if (value !== 'flow' && value !== 'tide') return this;
|
|
1888
|
+
dirty.edgeStyle = true;
|
|
1602
1889
|
preset.edgeStyle = value;
|
|
1603
1890
|
controller.profile = value === 'tide'
|
|
1604
1891
|
? FLOW_PROFILES.tide
|
|
@@ -1611,12 +1898,46 @@ function createProgressCapsule(container, options = {}) {
|
|
|
1611
1898
|
controller.resizeCanvas();
|
|
1612
1899
|
return this;
|
|
1613
1900
|
},
|
|
1901
|
+
setText(content) {
|
|
1902
|
+
fillContent(textLayer, content);
|
|
1903
|
+
return this;
|
|
1904
|
+
},
|
|
1905
|
+
setColorContent(content) {
|
|
1906
|
+
fillContent(visualLayer, content);
|
|
1907
|
+
return this;
|
|
1908
|
+
},
|
|
1909
|
+
setTextRatio(ratio) {
|
|
1910
|
+
const value = Number(ratio);
|
|
1911
|
+
if (!Number.isFinite(value) || value < 0 || value > 100) return this;
|
|
1912
|
+
dirty.textRatio = true;
|
|
1913
|
+
merged.textRatio = value;
|
|
1914
|
+
root.style.setProperty('--hj-text-width', `${value}%`);
|
|
1915
|
+
return this;
|
|
1916
|
+
},
|
|
1917
|
+
setCssVars(vars) {
|
|
1918
|
+
if (!vars || typeof vars !== 'object') return this;
|
|
1919
|
+
dirty.cssVars = true;
|
|
1920
|
+
merged.cssVars = { ...(merged.cssVars || {}), ...vars };
|
|
1921
|
+
for (const name of Object.keys(vars)) root.style.setProperty(name, vars[name]);
|
|
1922
|
+
return this;
|
|
1923
|
+
},
|
|
1924
|
+
setLabel(label) {
|
|
1925
|
+
customLabel = typeof label === 'string' && label ? label : null;
|
|
1926
|
+
updateAria();
|
|
1927
|
+
return this;
|
|
1928
|
+
},
|
|
1929
|
+
setValueSuffix(suffix) {
|
|
1930
|
+
controller.setValueSuffix(suffix);
|
|
1931
|
+
return this;
|
|
1932
|
+
},
|
|
1614
1933
|
setColors(colors) {
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
controller.
|
|
1619
|
-
|
|
1934
|
+
const next = Array.isArray(colors) ? colors.map(normalizeColor) : [];
|
|
1935
|
+
if (next.length !== 4 || next.some((color) => !color)) return this;
|
|
1936
|
+
dirty.colors = true;
|
|
1937
|
+
controller.setColors(next);
|
|
1938
|
+
if (overlay) overlay.setColors(next);
|
|
1939
|
+
controller.resizeCanvas();
|
|
1940
|
+
return this;
|
|
1620
1941
|
},
|
|
1621
1942
|
setSize(width, height) {
|
|
1622
1943
|
if (width !== undefined) merged.width = width;
|
|
@@ -1643,14 +1964,18 @@ function createProgressCapsule(container, options = {}) {
|
|
|
1643
1964
|
controller.resizeCanvas();
|
|
1644
1965
|
return this;
|
|
1645
1966
|
},
|
|
1646
|
-
dispose() {
|
|
1647
|
-
|
|
1967
|
+
dispose() {
|
|
1968
|
+
disposed = true;
|
|
1969
|
+
unsubscribe();
|
|
1648
1970
|
visibility.dispose();
|
|
1649
1971
|
if (overlay) overlay.dispose();
|
|
1650
|
-
controller.dispose();
|
|
1651
|
-
root.remove();
|
|
1652
|
-
}
|
|
1653
|
-
|
|
1972
|
+
controller.dispose();
|
|
1973
|
+
root.remove();
|
|
1974
|
+
},
|
|
1975
|
+
textRatio: merged.textRatio,
|
|
1976
|
+
cssVars: merged.cssVars,
|
|
1977
|
+
dirty
|
|
1978
|
+
};
|
|
1654
1979
|
}
|
|
1655
1980
|
|
|
1656
1981
|
export { ProgressCapsuleController, createProgressCapsule };
|