@5even7/dlc-ui 0.2.11 → 0.2.13

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/dist/progress.cjs CHANGED
@@ -95,7 +95,10 @@ function _iterableToArrayLimit(r, l) {
95
95
  f = true,
96
96
  o = false;
97
97
  try {
98
- if (i = (t = t.call(r)).next, 0 === l) ; else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
98
+ if (i = (t = t.call(r)).next, 0 === l) {
99
+ if (Object(t) !== t) return;
100
+ f = !1;
101
+ } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
99
102
  } catch (r) {
100
103
  o = true, n = r;
101
104
  } finally {
@@ -172,9 +175,9 @@ function _unsupportedIterableToArray(r, a) {
172
175
  }
173
176
  }
174
177
 
175
- /**
176
- * Tiny event emitter. Accepts any event name so the API stays open for
177
- * future events (hover, click, custom) without breaking changes.
178
+ /**
179
+ * Tiny event emitter. Accepts any event name so the API stays open for
180
+ * future events (hover, click, custom) without breaking changes.
178
181
  */
179
182
  function createEmitter() {
180
183
  // Plain object storage (no Map/Set) so the legacy build has no API
@@ -224,9 +227,9 @@ function createEmitter() {
224
227
 
225
228
  var UNIT_PATTERN = /^[\d.]+(?:px|%|vw|vh|vmin|vmax|em|rem)$/;
226
229
 
227
- /**
228
- * Normalize a size option to a CSS length string.
229
- * Accepts numbers (treated as px) or strings with px/%, vw/vh/vmin/vmax, em/rem.
230
+ /**
231
+ * Normalize a size option to a CSS length string.
232
+ * Accepts numbers (treated as px) or strings with px/%, vw/vh/vmin/vmax, em/rem.
230
233
  */
231
234
  function parseSize(value) {
232
235
  if (typeof value === 'number') {
@@ -265,11 +268,17 @@ function dprCapFor(quality) {
265
268
  var tier = QUALITY_TIERS[quality] || QUALITY_TIERS.medium;
266
269
  return tier.dpr;
267
270
  }
271
+ function effectiveDprCap(quality) {
272
+ var renderScale = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
273
+ var scale = Number(renderScale);
274
+ var normalizedScale = Number.isFinite(scale) ? Math.min(1, Math.max(0.25, scale)) : 1;
275
+ return Math.max(0.5, dprCapFor(quality) * normalizedScale);
276
+ }
268
277
 
269
- /**
270
- * Merge defaults < preset < user. Unknown user keys are preserved so the
271
- * component API can grow (new props, cssVars, callbacks) without a breaking
272
- * change.
278
+ /**
279
+ * Merge defaults < preset < user. Unknown user keys are preserved so the
280
+ * component API can grow (new props, cssVars, callbacks) without a breaking
281
+ * change.
273
282
  */
274
283
  function normalizeOptions(defaults, preset, user) {
275
284
  // undefined means "not provided" (e.g. Vue $props with unset props):
@@ -287,9 +296,9 @@ function normalizeOptions(defaults, preset, user) {
287
296
  return _objectSpread2(_objectSpread2(_objectSpread2({}, defaults), presetOptions), userOptions);
288
297
  }
289
298
 
290
- /**
291
- * 公共色板:NC-01~NC-06 的四色组(底色 / 主色 / 辅色 / 高光色)。
292
- * Capsule 预置与 dlc-color 等组件共用,新增色板只改这里。
299
+ /**
300
+ * 公共色板:NC-01~NC-06 的四色组(底色 / 主色 / 辅色 / 高光色)。
301
+ * Capsule 预置与 dlc-color 等组件共用,新增色板只改这里。
293
302
  */
294
303
  var PALETTES$1 = {
295
304
  original: ['#FFF3EA', '#F5B27A', '#F67BC6', '#A978E8'],
@@ -300,9 +309,9 @@ var PALETTES$1 = {
300
309
  plus: ['#FFF0E6', '#F6C26B', '#F98A64', '#E86D74']
301
310
  };
302
311
 
303
- /**
304
- * Capsule 预置:仅星云材质 NC-01~NC-06(NC-07~09 aurora 已移除)。
305
- * 颜色引用公共色板,seed/speed 决定形态与流速。
312
+ /**
313
+ * Capsule 预置:仅星云材质 NC-01~NC-06(NC-07~09 aurora 已移除)。
314
+ * 颜色引用公共色板,seed/speed 决定形态与流速。
306
315
  */
307
316
  var CAPSULE_PRESETS = [{
308
317
  id: 'original',
@@ -390,6 +399,15 @@ var PROGRESS_PRESETS = [{
390
399
  initialProgress: 30,
391
400
  edgeStyle: 'tide',
392
401
  colors: ['#0A2239', '#2E9BFF', '#7FE3FF', '#EAF9FF']
402
+ }, {
403
+ id: 'botanic-lab',
404
+ code: 'NC-14',
405
+ name: '森息',
406
+ subtitle: 'MOSS CULTURE / ROOTING',
407
+ group: 'progress',
408
+ initialProgress: 46,
409
+ edgeStyle: 'flow',
410
+ colors: ['#102A23', '#287A58', '#9BD65B', '#F2FFC4']
393
411
  }];
394
412
 
395
413
  [].concat(_toConsumableArray(CAPSULE_PRESETS), _toConsumableArray(PROGRESS_PRESETS));
@@ -420,13 +438,22 @@ var DEFAULTS = {
420
438
  height: 104,
421
439
  min: 0,
422
440
  max: 100,
441
+ step: 'any',
423
442
  draggable: true,
443
+ keyboard: true,
424
444
  disabled: false,
425
445
  readonly: false,
446
+ direction: 'ltr',
447
+ precision: 0,
426
448
  valueSuffix: '%',
427
449
  edgeStyle: 'flow',
428
450
  quality: 'auto',
429
451
  renderer: 'auto',
452
+ renderScale: 1,
453
+ powerPreference: 'high-performance',
454
+ fps: 60,
455
+ paused: false,
456
+ static: false,
430
457
  textRatio: 54,
431
458
  showValue: true,
432
459
  respectReducedMotion: true
@@ -660,10 +687,10 @@ function hexToRgba(color) {
660
687
  return "rgba(".concat(red, ", ").concat(green, ", ").concat(blue, ", ").concat(alpha, ")");
661
688
  }
662
689
 
663
- /**
664
- * Document-level shared rAF scheduler. Every component instance subscribes
665
- * its own frame callback; the whole page runs ONE animation loop (like the
666
- * original demo), which avoids jank from many competing rAF loops.
690
+ /**
691
+ * Document-level shared rAF scheduler. Every component instance subscribes
692
+ * its own frame callback; the whole page runs ONE animation loop (like the
693
+ * original demo), which avoids jank from many competing rAF loops.
667
694
  */
668
695
  var subscribers = [];
669
696
  var running = false;
@@ -671,11 +698,7 @@ var rafId = 0;
671
698
  var last = 0;
672
699
  function tick(now) {
673
700
  if (!running) return;
674
- var delta = last ? Math.min((now - last) / 1000, 0.05) : 0;
675
- last = now;
676
- // Schedule the next frame BEFORE running callbacks so one throwing
677
- // subscriber can never kill the whole animation loop.
678
- rafId = requestAnimationFrame(tick);
701
+ var activeItems = [];
679
702
  {
680
703
  var _iterator = _createForOfIteratorHelper(subscribers.slice()),
681
704
  _step;
@@ -683,10 +706,8 @@ function tick(now) {
683
706
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
684
707
  var item = _step.value;
685
708
  try {
686
- if (!item.isPaused()) item.onFrame(delta, now);
687
- } catch (error) {
688
- console.warn('[dlc-ui] frame error:', error);
689
- }
709
+ if (!item.isPaused()) activeItems.push(item);
710
+ } catch (_unused) {}
690
711
  }
691
712
  } catch (err) {
692
713
  _iterator.e(err);
@@ -694,6 +715,25 @@ function tick(now) {
694
715
  _iterator.f();
695
716
  }
696
717
  }
718
+ if (activeItems.length === 0) {
719
+ running = false;
720
+ rafId = 0;
721
+ last = 0;
722
+ return;
723
+ }
724
+ var delta = last ? Math.min((now - last) / 1000, 0.05) : 0;
725
+ last = now;
726
+ // Schedule the next frame BEFORE running callbacks so one throwing
727
+ // subscriber can never kill the whole animation loop.
728
+ rafId = requestAnimationFrame(tick);
729
+ for (var _i = 0, _activeItems = activeItems; _i < _activeItems.length; _i++) {
730
+ var _item = _activeItems[_i];
731
+ try {
732
+ _item.onFrame(delta, now);
733
+ } catch (error) {
734
+ console.warn('[dlc-ui] frame error:', error);
735
+ }
736
+ }
697
737
  if (subscribers.length === 0) {
698
738
  cancelAnimationFrame(rafId);
699
739
  running = false;
@@ -706,6 +746,9 @@ function start() {
706
746
  last = 0;
707
747
  rafId = requestAnimationFrame(tick);
708
748
  }
749
+ function wakeScheduler() {
750
+ start();
751
+ }
709
752
  function subscribeScheduler(onFrame, isPaused) {
710
753
  var item = {
711
754
  onFrame: onFrame,
@@ -724,11 +767,12 @@ function subscribeScheduler(onFrame, isPaused) {
724
767
  };
725
768
  }
726
769
 
727
- /**
728
- * Gates drawing on "element intersects viewport AND the page tab is visible".
729
- * Falls back to always-visible when IntersectionObserver is unavailable.
770
+ /**
771
+ * Gates drawing on "element intersects viewport AND the page tab is visible".
772
+ * Falls back to always-visible when IntersectionObserver is unavailable.
730
773
  */
731
774
  function createVisibilityGuard(element) {
775
+ var onChange = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
732
776
  var intersecting = true;
733
777
  var pageVisible = typeof document === 'undefined' || !document.hidden;
734
778
  var disposed = false;
@@ -738,6 +782,7 @@ function createVisibilityGuard(element) {
738
782
  intersecting = entries.some(function (entry) {
739
783
  return entry.isIntersecting;
740
784
  });
785
+ if (typeof onChange === 'function') onChange();
741
786
  }, {
742
787
  rootMargin: '180px'
743
788
  });
@@ -745,6 +790,7 @@ function createVisibilityGuard(element) {
745
790
  }
746
791
  var onVisibilityChange = function onVisibilityChange() {
747
792
  pageVisible = typeof document !== 'undefined' && !document.hidden;
793
+ if (typeof onChange === 'function') onChange();
748
794
  };
749
795
  if (typeof document !== 'undefined') {
750
796
  document.addEventListener('visibilitychange', onVisibilityChange);
@@ -790,6 +836,13 @@ var PROFILE_CONFIG = {
790
836
  detail: 0.06,
791
837
  lobe: 0.23
792
838
  },
839
+ 'botanic-lab': {
840
+ seed: 3.46,
841
+ broad: 0.54,
842
+ middle: 0.18,
843
+ detail: 0.05,
844
+ lobe: 0.28
845
+ },
793
846
  // ponytail: first-pass tide = asymmetric time warp on the same motion pipeline.
794
847
  // Refine (foam line / wash streaks) in the shader after visual QA.
795
848
  'tide': {
@@ -900,64 +953,95 @@ function createProgram(gl) {
900
953
  var ProgressFlowRenderer = /*#__PURE__*/function () {
901
954
  function ProgressFlowRenderer(canvas, preset) {
902
955
  var _PROFILE_INDEX$preset;
956
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
903
957
  _classCallCheck(this, ProgressFlowRenderer);
904
958
  var gl = canvas.getContext('webgl2', {
905
959
  alpha: false,
906
960
  antialias: true,
907
961
  premultipliedAlpha: false,
908
- powerPreference: 'high-performance'
962
+ powerPreference: options.powerPreference || 'high-performance'
909
963
  });
910
964
  if (!gl) throw new Error('WebGL2 unavailable');
911
965
  this.canvas = canvas;
966
+ this.options = options;
912
967
  this.gl = gl;
913
- this.program = createProgram(gl);
914
968
  this.profile = preset.edgeStyle === 'tide' ? 3 : (_PROFILE_INDEX$preset = PROFILE_INDEX[preset.id]) !== null && _PROFILE_INDEX$preset !== void 0 ? _PROFILE_INDEX$preset : 2;
915
969
  this.seed = stringSeed$1("".concat(preset.id, "-shader")) * 13.7 + 1.0;
916
970
  this.colors = preset.colors.map(hexToRgb01);
917
971
  this.motionData = getProgressMotionData(preset.id, preset.edgeStyle);
918
972
  var motionFactor = preset.edgeStyle === 'tide' ? MOTION_SCALE_FACTORS.tide : MOTION_SCALE_FACTORS[preset.id] || 1.04;
919
973
  this.motionScale = PROGRESS_MOTION_MAX_PX * motionFactor / 1257;
920
- this.position = gl.getAttribLocation(this.program, 'a_position');
921
- this.uniforms = {
922
- resolution: gl.getUniformLocation(this.program, 'u_resolution'),
923
- time: gl.getUniformLocation(this.program, 'u_time'),
924
- progress: gl.getUniformLocation(this.program, 'u_progress'),
925
- seed: gl.getUniformLocation(this.program, 'u_seed'),
926
- profile: gl.getUniformLocation(this.program, 'u_profile'),
927
- motion: gl.getUniformLocation(this.program, 'u_motion'),
928
- effect: gl.getUniformLocation(this.program, 'u_effect'),
929
- hasEffect: gl.getUniformLocation(this.program, 'u_hasEffect'),
930
- effectFrames: gl.getUniformLocation(this.program, 'u_effectFrames'),
931
- motionDuration: gl.getUniformLocation(this.program, 'u_motionDuration'),
932
- motionScale: gl.getUniformLocation(this.program, 'u_motionScale'),
933
- dark: gl.getUniformLocation(this.program, 'u_dark'),
934
- accentA: gl.getUniformLocation(this.program, 'u_accentA'),
935
- accentB: gl.getUniformLocation(this.program, 'u_accentB'),
936
- glow: gl.getUniformLocation(this.program, 'u_glow')
937
- };
938
- this.buffer = gl.createBuffer();
939
- gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
940
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]), gl.STATIC_DRAW);
941
- this.motionTexture = gl.createTexture();
942
- gl.activeTexture(gl.TEXTURE0);
943
- gl.bindTexture(gl.TEXTURE_2D, this.motionTexture);
944
- gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
945
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8, PROGRESS_MOTION_WIDTH, PROGRESS_MOTION_HEIGHT, 0, gl.RED, gl.UNSIGNED_BYTE, this.motionData);
946
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
947
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
948
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
949
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
950
- this.effectTexture = gl.createTexture();
951
- gl.activeTexture(gl.TEXTURE1);
952
- gl.bindTexture(gl.TEXTURE_2D, this.effectTexture);
953
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, 1, 1, 0, gl.RGB, gl.UNSIGNED_BYTE, new Uint8Array([32, 33, 38]));
954
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
955
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
956
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
957
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
958
- this.effectUploaded = false;
974
+ this.disposed = false;
975
+ this.contextLost = false;
976
+ this.setupResources();
977
+ this.bindContextEvents();
959
978
  }
960
979
  return _createClass(ProgressFlowRenderer, [{
980
+ key: "setupResources",
981
+ value: function setupResources() {
982
+ var gl = this.gl;
983
+ this.program = createProgram(gl);
984
+ this.position = gl.getAttribLocation(this.program, 'a_position');
985
+ this.uniforms = {
986
+ resolution: gl.getUniformLocation(this.program, 'u_resolution'),
987
+ time: gl.getUniformLocation(this.program, 'u_time'),
988
+ progress: gl.getUniformLocation(this.program, 'u_progress'),
989
+ seed: gl.getUniformLocation(this.program, 'u_seed'),
990
+ profile: gl.getUniformLocation(this.program, 'u_profile'),
991
+ motion: gl.getUniformLocation(this.program, 'u_motion'),
992
+ effect: gl.getUniformLocation(this.program, 'u_effect'),
993
+ hasEffect: gl.getUniformLocation(this.program, 'u_hasEffect'),
994
+ effectFrames: gl.getUniformLocation(this.program, 'u_effectFrames'),
995
+ motionDuration: gl.getUniformLocation(this.program, 'u_motionDuration'),
996
+ motionScale: gl.getUniformLocation(this.program, 'u_motionScale'),
997
+ dark: gl.getUniformLocation(this.program, 'u_dark'),
998
+ accentA: gl.getUniformLocation(this.program, 'u_accentA'),
999
+ accentB: gl.getUniformLocation(this.program, 'u_accentB'),
1000
+ glow: gl.getUniformLocation(this.program, 'u_glow')
1001
+ };
1002
+ this.buffer = gl.createBuffer();
1003
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
1004
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]), gl.STATIC_DRAW);
1005
+ this.motionTexture = gl.createTexture();
1006
+ gl.activeTexture(gl.TEXTURE0);
1007
+ gl.bindTexture(gl.TEXTURE_2D, this.motionTexture);
1008
+ gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
1009
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8, PROGRESS_MOTION_WIDTH, PROGRESS_MOTION_HEIGHT, 0, gl.RED, gl.UNSIGNED_BYTE, this.motionData);
1010
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
1011
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
1012
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
1013
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
1014
+ this.effectTexture = gl.createTexture();
1015
+ gl.activeTexture(gl.TEXTURE1);
1016
+ gl.bindTexture(gl.TEXTURE_2D, this.effectTexture);
1017
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, 1, 1, 0, gl.RGB, gl.UNSIGNED_BYTE, new Uint8Array([32, 33, 38]));
1018
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
1019
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
1020
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
1021
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
1022
+ this.effectUploaded = false;
1023
+ }
1024
+ }, {
1025
+ key: "bindContextEvents",
1026
+ value: function bindContextEvents() {
1027
+ var _this = this;
1028
+ this.onContextLost = function (event) {
1029
+ event.preventDefault();
1030
+ if (_this.disposed) return;
1031
+ _this.contextLost = true;
1032
+ if (typeof _this.options.onContextLost === 'function') _this.options.onContextLost(event);
1033
+ };
1034
+ this.onContextRestored = function () {
1035
+ if (_this.disposed) return;
1036
+ _this.setupResources();
1037
+ _this.contextLost = false;
1038
+ _this.gl.viewport(0, 0, _this.canvas.width, _this.canvas.height);
1039
+ if (typeof _this.options.onContextRestored === 'function') _this.options.onContextRestored();
1040
+ };
1041
+ this.canvas.addEventListener('webglcontextlost', this.onContextLost, false);
1042
+ this.canvas.addEventListener('webglcontextrestored', this.onContextRestored, false);
1043
+ }
1044
+ }, {
961
1045
  key: "resize",
962
1046
  value: function resize(width, height, dpr) {
963
1047
  var pixelWidth = Math.max(1, Math.round(width * dpr));
@@ -972,6 +1056,7 @@ var ProgressFlowRenderer = /*#__PURE__*/function () {
972
1056
  key: "draw",
973
1057
  value: function draw(time, progress) {
974
1058
  var effectImage = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
1059
+ if (this.disposed || this.contextLost) return;
975
1060
  var gl = this.gl;
976
1061
  gl.useProgram(this.program);
977
1062
  gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
@@ -988,7 +1073,8 @@ var ProgressFlowRenderer = /*#__PURE__*/function () {
988
1073
  gl.uniform1f(this.uniforms.motionDuration, PROGRESS_MOTION_DURATION);
989
1074
  gl.uniform1f(this.uniforms.motionScale, this.motionScale);
990
1075
  var hasEffect = this.effectUploaded ? 1 : 0;
991
- if (!this.effectUploaded && effectImage && effectImage.complete && effectImage.naturalWidth > 0) {
1076
+ var effectWidth = effectImage && (effectImage.naturalWidth || effectImage.width || 0);
1077
+ if (!this.effectUploaded && effectImage && effectWidth > 0) {
992
1078
  gl.activeTexture(gl.TEXTURE1);
993
1079
  gl.bindTexture(gl.TEXTURE_2D, this.effectTexture);
994
1080
  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
@@ -1020,7 +1106,10 @@ var ProgressFlowRenderer = /*#__PURE__*/function () {
1020
1106
  }, {
1021
1107
  key: "dispose",
1022
1108
  value: function dispose() {
1109
+ this.disposed = true;
1023
1110
  var gl = this.gl;
1111
+ this.canvas.removeEventListener('webglcontextlost', this.onContextLost, false);
1112
+ this.canvas.removeEventListener('webglcontextrestored', this.onContextRestored, false);
1024
1113
  gl.deleteBuffer(this.buffer);
1025
1114
  gl.deleteTexture(this.motionTexture);
1026
1115
  gl.deleteTexture(this.effectTexture);
@@ -1031,8 +1120,9 @@ var ProgressFlowRenderer = /*#__PURE__*/function () {
1031
1120
  }]);
1032
1121
  }();
1033
1122
  function createProgressFlowRenderer(canvas, preset) {
1123
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
1034
1124
  try {
1035
- return new ProgressFlowRenderer(canvas, preset);
1125
+ return new ProgressFlowRenderer(canvas, preset, options);
1036
1126
  } catch (error) {
1037
1127
  console.warn('[画境观屿] 进度流体 WebGL2 不可用,使用 Canvas 2D 降级。', error);
1038
1128
  return null;
@@ -1048,7 +1138,8 @@ var PALETTES = {
1048
1138
  'model-training': ['#20131f', '#ff3f94', '#ff8a3d', '#fff06a'],
1049
1139
  'agent-migration': ['#111a31', '#245bff', '#00cfff', '#5dffe6'],
1050
1140
  'visual-training': ['#1f172b', '#7042ff', '#42f58d', '#c4ff8a'],
1051
- 'tide': ['#0a2239', '#2e9bff', '#7fe3ff', '#eaf9ff']
1141
+ 'tide': ['#0a2239', '#2e9bff', '#7fe3ff', '#eaf9ff'],
1142
+ 'botanic-lab': ['#102a23', '#287a58', '#9bd65b', '#f2ffc4']
1052
1143
  };
1053
1144
  function drawCloud(context, x, y, radiusX, radiusY, color, alpha) {
1054
1145
  context.save();
@@ -1092,50 +1183,49 @@ function createAtlas(id) {
1092
1183
  context.fillRect(20, 0, 44, FRAME_HEIGHT);
1093
1184
  context.restore();
1094
1185
  }
1095
- var image = new Image();
1096
- image.decoding = 'async';
1097
- var state = {
1098
- image: image,
1099
- ready: false
1186
+
1187
+ // A canvas is a valid TexImageSource. Keeping it directly avoids a PNG
1188
+ // encode -> base64 allocation -> Image decode round trip at startup.
1189
+ return {
1190
+ image: canvas,
1191
+ ready: true
1100
1192
  };
1101
- image.addEventListener('load', function () {
1102
- state.ready = true;
1103
- }, {
1104
- once: true
1105
- });
1106
- image.addEventListener('error', function () {
1107
- state.ready = false;
1108
- }, {
1109
- once: true
1110
- });
1111
- image.src = canvas.toDataURL('image/png');
1112
- return state;
1113
1193
  }
1114
1194
  function getProgressReferenceAtlas(id) {
1115
1195
  if (!CACHE[id]) CACHE[id] = createAtlas(id);
1116
1196
  return CACHE[id];
1117
1197
  }
1118
1198
 
1119
- /**
1120
- * Attach a WebGL2 fluid overlay to a progress capsule root.
1121
- *
1122
- * @param {object} params
1123
- * @param {HTMLElement} params.root progress capsule root element
1124
- * @param {HTMLCanvasElement} params.canvas 2D fallback canvas (kept beneath)
1125
- * @param {object} params.preset progress preset
1126
- * @param {() => number} params.getProgress reads the current progress value
1127
- * @returns {{ update(flowTime: number): void, dispose(): void } | null}
1199
+ /**
1200
+ * Attach a WebGL2 fluid overlay to a progress capsule root.
1201
+ *
1202
+ * @param {object} params
1203
+ * @param {HTMLElement} params.root progress capsule root element
1204
+ * @param {HTMLCanvasElement} params.canvas 2D fallback canvas (kept beneath)
1205
+ * @param {object} params.preset progress preset
1206
+ * @param {() => number} params.getProgress reads the current progress value
1207
+ * @returns {{ update(flowTime: number): void, setDprCap(cap: number): void, dispose(): void } | null}
1128
1208
  */
1129
1209
  function attachProgressFlowOverlay(_ref) {
1130
1210
  var root = _ref.root,
1131
1211
  canvas = _ref.canvas,
1132
1212
  preset = _ref.preset,
1133
- getProgress = _ref.getProgress;
1213
+ getProgress = _ref.getProgress,
1214
+ _ref$dprCap = _ref.dprCap,
1215
+ dprCap = _ref$dprCap === void 0 ? 2 : _ref$dprCap,
1216
+ _ref$powerPreference = _ref.powerPreference,
1217
+ powerPreference = _ref$powerPreference === void 0 ? 'high-performance' : _ref$powerPreference,
1218
+ onContextLost = _ref.onContextLost,
1219
+ onContextRestored = _ref.onContextRestored;
1134
1220
  var overlay = document.createElement('canvas');
1135
1221
  overlay.className = 'hj-progress-canvas hj-progress-overlay';
1136
1222
  overlay.setAttribute('aria-hidden', 'true');
1137
1223
  canvas.insertAdjacentElement('afterend', overlay);
1138
- var renderer = createProgressFlowRenderer(overlay, preset);
1224
+ var renderer = createProgressFlowRenderer(overlay, preset, {
1225
+ powerPreference: powerPreference,
1226
+ onContextLost: onContextLost,
1227
+ onContextRestored: onContextRestored
1228
+ });
1139
1229
  if (!renderer) {
1140
1230
  overlay.remove();
1141
1231
  return null;
@@ -1146,7 +1236,7 @@ function attachProgressFlowOverlay(_ref) {
1146
1236
  var bounds = root.getBoundingClientRect();
1147
1237
  var width = Math.max(1, bounds.width);
1148
1238
  var height = Math.max(1, bounds.height);
1149
- var dpr = Math.min(window.devicePixelRatio || 1, 2);
1239
+ var dpr = Math.min(window.devicePixelRatio || 1, dprCap);
1150
1240
  overlay.style.width = "".concat(width, "px");
1151
1241
  overlay.style.height = "".concat(height, "px");
1152
1242
  renderer.resize(width, height, dpr);
@@ -1170,6 +1260,10 @@ function attachProgressFlowOverlay(_ref) {
1170
1260
  setColors: function setColors(colors) {
1171
1261
  renderer.setColors(colors);
1172
1262
  },
1263
+ setDprCap: function setDprCap(cap) {
1264
+ dprCap = cap;
1265
+ resize();
1266
+ },
1173
1267
  dispose: function dispose() {
1174
1268
  if (resizeObserver) resizeObserver.disconnect();
1175
1269
  if (onWindowResize) window.removeEventListener('resize', onWindowResize);
@@ -1196,6 +1290,101 @@ function nextTick(fn) {
1196
1290
  }
1197
1291
  }
1198
1292
 
1293
+ function toFiniteNumber(value) {
1294
+ if (value == null || typeof value === 'boolean') return null;
1295
+ if (typeof value === 'string' && value.trim() === '') return null;
1296
+ var number = Number(value);
1297
+ return Number.isFinite(number) ? number : null;
1298
+ }
1299
+
1300
+ function normalizeRange(min, max) {
1301
+ var fallbackMin = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
1302
+ var fallbackMax = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 100;
1303
+ var nextMin = toFiniteNumber(min);
1304
+ var nextMax = toFiniteNumber(max);
1305
+ if (nextMin === null) nextMin = fallbackMin;
1306
+ if (nextMax === null) nextMax = fallbackMax;
1307
+ if (nextMin > nextMax) {
1308
+ var swap = nextMin;
1309
+ nextMin = nextMax;
1310
+ nextMax = swap;
1311
+ }
1312
+ return [nextMin, nextMax];
1313
+ }
1314
+ function clampProgressValue(value, min, max) {
1315
+ var number = toFiniteNumber(value);
1316
+ if (number === null) return null;
1317
+ return Math.min(Math.max(number, min), max);
1318
+ }
1319
+ function normalizeProgressStep(step) {
1320
+ var fallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'any';
1321
+ if (step === 'any' || step == null) return 'any';
1322
+ var increment = toFiniteNumber(step);
1323
+ return increment !== null && increment > 0 ? increment : fallback;
1324
+ }
1325
+ function snapProgressValue(value, min, max) {
1326
+ var step = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'any';
1327
+ var clamped = clampProgressValue(value, min, max);
1328
+ if (clamped === null) return null;
1329
+ if (step === 'any' || step == null) return clamped;
1330
+ var increment = normalizeProgressStep(step);
1331
+ if (increment === 'any') return clamped;
1332
+ var snapped = min + Math.round((clamped - min) / increment) * increment;
1333
+ return Math.min(Math.max(Number(snapped.toFixed(12)), min), max);
1334
+ }
1335
+ function progressRatio(value, min, max) {
1336
+ var range = max - min;
1337
+ if (!Number.isFinite(range) || range <= 0) return 0;
1338
+ return Math.min(Math.max((value - min) / range, 0), 1);
1339
+ }
1340
+
1341
+ function normalizeFps(value) {
1342
+ var fallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 60;
1343
+ var fps = Number(value);
1344
+ if (!Number.isFinite(fps)) return fallback;
1345
+ return Math.min(60, Math.max(1, fps));
1346
+ }
1347
+ function createFrameGate() {
1348
+ var initialFps = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 60;
1349
+ var fps = normalizeFps(initialFps);
1350
+ var elapsed = 0;
1351
+ return {
1352
+ shouldDraw: function shouldDraw(delta) {
1353
+ elapsed += delta;
1354
+ var interval = 1 / fps;
1355
+ if (elapsed + 0.0001 < interval) return false;
1356
+ elapsed %= interval;
1357
+ return true;
1358
+ },
1359
+ setFps: function setFps(value) {
1360
+ fps = normalizeFps(value, fps);
1361
+ elapsed = 0;
1362
+ return fps;
1363
+ },
1364
+ getFps: function getFps() {
1365
+ return fps;
1366
+ }
1367
+ };
1368
+ }
1369
+ function createReducedMotionPreference(enabled, onChange) {
1370
+ var query = enabled && typeof matchMedia !== 'undefined' ? matchMedia('(prefers-reduced-motion: reduce)') : null;
1371
+ var notify = function notify() {
1372
+ if (typeof onChange === 'function') onChange(Boolean(query && query.matches));
1373
+ };
1374
+ if (query) {
1375
+ if (typeof query.addEventListener === 'function') query.addEventListener('change', notify);else if (typeof query.addListener === 'function') query.addListener(notify);
1376
+ }
1377
+ return {
1378
+ matches: function matches() {
1379
+ return Boolean(query && query.matches);
1380
+ },
1381
+ dispose: function dispose() {
1382
+ if (!query) return;
1383
+ if (typeof query.removeEventListener === 'function') query.removeEventListener('change', notify);else if (typeof query.removeListener === 'function') query.removeListener(notify);
1384
+ }
1385
+ };
1386
+ }
1387
+
1199
1388
  var clamp = function clamp(value, min, max) {
1200
1389
  return Math.min(Math.max(value, min), max);
1201
1390
  };
@@ -1222,9 +1411,6 @@ function interpolate(template, values) {
1222
1411
  return values[key] !== undefined ? values[key] : '';
1223
1412
  });
1224
1413
  }
1225
- function prefersReducedMotion() {
1226
- return typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches;
1227
- }
1228
1414
  var FLOW_PROFILES = {
1229
1415
  'model-training': {
1230
1416
  cycles: [0.98, 3.05, 5.45],
@@ -1285,8 +1471,6 @@ var FLOW_PROFILES = {
1285
1471
  var ProgressCapsuleController = /*#__PURE__*/function () {
1286
1472
  function ProgressCapsuleController(_ref) {
1287
1473
  var _options$valueSuffix,
1288
- _options$min,
1289
- _options$max,
1290
1474
  _options$value,
1291
1475
  _this = this;
1292
1476
  var root = _ref.root,
@@ -1308,11 +1492,18 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1308
1492
  this.dirty = dirty;
1309
1493
  this.valueSuffix = (_options$valueSuffix = options.valueSuffix) !== null && _options$valueSuffix !== void 0 ? _options$valueSuffix : copy.valueSuffix;
1310
1494
  this.profile = preset.edgeStyle === 'tide' ? FLOW_PROFILES.tide : FLOW_PROFILES[preset.id] || FLOW_PROFILES['visual-training'];
1311
- this.min = (_options$min = options.min) !== null && _options$min !== void 0 ? _options$min : 0;
1312
- this.max = (_options$max = options.max) !== null && _options$max !== void 0 ? _options$max : 100;
1313
- this.dprCap = dprCapFor(options.quality);
1495
+ var _normalizeRange = normalizeRange(options.min, options.max);
1496
+ var _normalizeRange2 = _slicedToArray(_normalizeRange, 2);
1497
+ this.min = _normalizeRange2[0];
1498
+ this.max = _normalizeRange2[1];
1499
+ this.step = normalizeProgressStep(options.step);
1500
+ this.precision = Number.isInteger(Number(options.precision)) ? clamp(Number(options.precision), 0, 20) : 0;
1501
+ this.formatValue = typeof options.formatValue === 'function' ? options.formatValue : null;
1502
+ this.direction = options.direction === 'rtl' ? 'rtl' : 'ltr';
1503
+ this.dprCap = effectiveDprCap(options.quality, options.renderScale);
1314
1504
  this.ctx = canvas.getContext('2d');
1315
- this.value = clamp((_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : preset.initialProgress, this.min, this.max);
1505
+ this.value = snapProgressValue((_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : preset.initialProgress, this.min, this.max, this.step);
1506
+ if (this.value === null) this.value = clamp(preset.initialProgress, this.min, this.max);
1316
1507
  this.dragging = false;
1317
1508
  this.webglActive = false;
1318
1509
  this.flowTime = stringSeed(preset.id) * 31;
@@ -1322,10 +1513,12 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1322
1513
  this.width = 0;
1323
1514
  this.height = 0;
1324
1515
  this.handlers = {};
1325
- this.resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(function () {
1326
- return _this.resizeCanvas();
1327
- }) : null;
1328
- if (this.resizeObserver) this.resizeObserver.observe(this.root);else window.addEventListener('resize', this.resizeCanvas);
1516
+ this.onResize = function () {
1517
+ _this.resizeCanvas();
1518
+ if (typeof _this.options.onResize === 'function') _this.options.onResize();
1519
+ };
1520
+ this.resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(this.onResize) : null;
1521
+ if (this.resizeObserver) this.resizeObserver.observe(this.root);else window.addEventListener('resize', this.onResize);
1329
1522
  this.suppressEvents = true;
1330
1523
  this.setProgress(this.value, 'init');
1331
1524
  this.suppressEvents = false;
@@ -1338,16 +1531,37 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1338
1531
  this.randomState = Math.imul(this.randomState, 1664525) + 1013904223 >>> 0;
1339
1532
  return this.randomState / 4294967296;
1340
1533
  }
1534
+ }, {
1535
+ key: "displayText",
1536
+ value: function displayText() {
1537
+ if (this.formatValue) {
1538
+ return String(this.formatValue(this.value, {
1539
+ min: this.min,
1540
+ max: this.max,
1541
+ suffix: this.valueSuffix
1542
+ }));
1543
+ }
1544
+ var number = this.precision > 0 ? this.value.toFixed(this.precision) : String(Math.round(this.value));
1545
+ return "".concat(number).concat(this.valueSuffix);
1546
+ }
1547
+ }, {
1548
+ key: "syncValueDom",
1549
+ value: function syncValueDom() {
1550
+ var text = this.displayText();
1551
+ this.root.style.setProperty('--progress', this.value.toFixed(2));
1552
+ this.root.style.setProperty('--progress-ratio', progressRatio(this.value, this.min, this.max).toFixed(4));
1553
+ this.root.setAttribute('aria-valuenow', String(this.value));
1554
+ this.root.setAttribute('aria-valuetext', text);
1555
+ if (this.valueElement) this.valueElement.textContent = text;
1556
+ }
1341
1557
  }, {
1342
1558
  key: "setProgress",
1343
1559
  value: function setProgress(nextValue) {
1344
1560
  var source = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'auto';
1345
- this.value = clamp(nextValue, this.min, this.max);
1346
- var rounded = Math.round(this.value);
1347
- this.root.style.setProperty('--progress', this.value.toFixed(2));
1348
- this.root.setAttribute('aria-valuenow', String(rounded));
1349
- this.root.setAttribute('aria-valuetext', "".concat(rounded).concat(this.valueSuffix));
1350
- if (this.valueElement) this.valueElement.textContent = "".concat(rounded).concat(this.valueSuffix);
1561
+ var next = snapProgressValue(nextValue, this.min, this.max, this.step);
1562
+ if (next === null) return false;
1563
+ this.value = next;
1564
+ this.syncValueDom();
1351
1565
  if (!this.suppressEvents) {
1352
1566
  if (this.dirty) this.dirty.value = true;
1353
1567
  this.emitter.emit('change', {
@@ -1355,19 +1569,58 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1355
1569
  source: source
1356
1570
  });
1357
1571
  }
1572
+ return true;
1358
1573
  }
1359
1574
  }, {
1360
1575
  key: "setValueSuffix",
1361
1576
  value: function setValueSuffix(suffix) {
1577
+ if (this.dirty) this.dirty.valueSuffix = true;
1362
1578
  this.valueSuffix = String(suffix == null ? '' : suffix);
1363
- var rounded = Math.round(this.value);
1364
- if (this.valueElement) this.valueElement.textContent = "".concat(rounded).concat(this.valueSuffix);
1365
- this.root.setAttribute('aria-valuetext', "".concat(rounded).concat(this.valueSuffix));
1579
+ this.syncValueDom();
1580
+ return this;
1581
+ }
1582
+ }, {
1583
+ key: "setStep",
1584
+ value: function setStep(step) {
1585
+ var nextStep = normalizeProgressStep(step, this.step);
1586
+ if (nextStep === this.step) return this;
1587
+ if (this.dirty) this.dirty.step = true;
1588
+ this.step = nextStep;
1589
+ var next = snapProgressValue(this.value, this.min, this.max, this.step);
1590
+ if (next !== null && next !== this.value) this.setProgress(next, 'prop');
1591
+ return this;
1592
+ }
1593
+ }, {
1594
+ key: "setPrecision",
1595
+ value: function setPrecision(precision) {
1596
+ var value = Number(precision);
1597
+ if (!Number.isInteger(value) || value < 0 || value > 20) return this;
1598
+ if (this.dirty) this.dirty.precision = true;
1599
+ this.precision = value;
1600
+ this.syncValueDom();
1601
+ return this;
1602
+ }
1603
+ }, {
1604
+ key: "setFormatValue",
1605
+ value: function setFormatValue(formatter) {
1606
+ if (formatter != null && typeof formatter !== 'function') return this;
1607
+ if (this.dirty) this.dirty.formatValue = true;
1608
+ this.formatValue = formatter || null;
1609
+ this.syncValueDom();
1610
+ return this;
1611
+ }
1612
+ }, {
1613
+ key: "setDirection",
1614
+ value: function setDirection(direction) {
1615
+ if (this.dirty) this.dirty.direction = true;
1616
+ this.direction = direction === 'rtl' ? 'rtl' : 'ltr';
1617
+ this.root.dataset.direction = this.direction;
1366
1618
  return this;
1367
1619
  }
1368
1620
  }, {
1369
1621
  key: "setShowValue",
1370
1622
  value: function setShowValue(show) {
1623
+ if (this.dirty) this.dirty.showValue = true;
1371
1624
  if (this.valueElement) this.valueElement.hidden = show === false;
1372
1625
  return this;
1373
1626
  }
@@ -1532,15 +1785,14 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1532
1785
  }, {
1533
1786
  key: "setRange",
1534
1787
  value: function setRange(min, max) {
1535
- if (min > max) {
1536
- var swap = min;
1537
- min = max;
1538
- max = swap;
1539
- }
1540
- this.min = min;
1541
- this.max = max;
1542
- var clamped = clamp(this.value, this.min, this.max);
1543
- if (clamped !== this.value) this.setProgress(clamped, 'prop');
1788
+ var nextRange = normalizeRange(min, max, this.min, this.max);
1789
+ this.min = nextRange[0];
1790
+ this.max = nextRange[1];
1791
+ this.root.setAttribute('aria-valuemin', String(this.min));
1792
+ this.root.setAttribute('aria-valuemax', String(this.max));
1793
+ var next = snapProgressValue(this.value, this.min, this.max, this.step);
1794
+ if (next !== this.value) this.setProgress(next, 'prop');else this.syncValueDom();
1795
+ return this;
1544
1796
  }
1545
1797
  }, {
1546
1798
  key: "drawReferenceFlow",
@@ -1665,8 +1917,39 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1665
1917
  value: function updateFromPointer(event) {
1666
1918
  var bounds = this.root.getBoundingClientRect();
1667
1919
  var ratio = bounds.width > 0 ? (event.clientX - bounds.left) / bounds.width : 0;
1920
+ if (this.direction === 'rtl') ratio = 1 - ratio;
1668
1921
  this.setProgress(this.min + ratio * (this.max - this.min), 'drag');
1669
1922
  }
1923
+ }, {
1924
+ key: "onKeyDown",
1925
+ value: function onKeyDown(event) {
1926
+ var range = this.max - this.min;
1927
+ var configuredStep = Number(this.step);
1928
+ var step = Number.isFinite(configuredStep) && configuredStep > 0 ? configuredStep : Math.max(range / 100, 1e-7);
1929
+ var direction = this.direction === 'rtl' ? -1 : 1;
1930
+ var aliases = {
1931
+ Left: 'ArrowLeft',
1932
+ Right: 'ArrowRight',
1933
+ Up: 'ArrowUp',
1934
+ Down: 'ArrowDown'
1935
+ };
1936
+ var keyCodes = {
1937
+ 35: 'End',
1938
+ 36: 'Home',
1939
+ 33: 'PageUp',
1940
+ 34: 'PageDown',
1941
+ 37: 'ArrowLeft',
1942
+ 38: 'ArrowUp',
1943
+ 39: 'ArrowRight',
1944
+ 40: 'ArrowDown'
1945
+ };
1946
+ var key = aliases[event.key] || event.key || keyCodes[event.keyCode];
1947
+ var next = null;
1948
+ if (key === 'ArrowRight') next = this.value + step * direction;else if (key === 'ArrowLeft') next = this.value - step * direction;else if (key === 'ArrowUp') next = this.value + step;else if (key === 'ArrowDown') next = this.value - step;else if (key === 'PageUp') next = this.value + step * 10;else if (key === 'PageDown') next = this.value - step * 10;else if (key === 'Home') next = this.min;else if (key === 'End') next = this.max;
1949
+ if (next === null) return;
1950
+ event.preventDefault();
1951
+ this.setProgress(next, 'keyboard');
1952
+ }
1670
1953
  }, {
1671
1954
  key: "beginDrag",
1672
1955
  value: function beginDrag(event) {
@@ -1754,6 +2037,12 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1754
2037
  this.root.addEventListener('pointerup', this.handlers.pointerup);
1755
2038
  this.root.addEventListener('pointercancel', this.handlers.pointercancel);
1756
2039
  }
2040
+ if (this.options.keyboard !== false) {
2041
+ this.handlers.keydown = function (event) {
2042
+ return _this3.onKeyDown(event);
2043
+ };
2044
+ this.root.addEventListener('keydown', this.handlers.keydown);
2045
+ }
1757
2046
  }
1758
2047
  }, {
1759
2048
  key: "update",
@@ -1772,20 +2061,19 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1772
2061
  key: "randomize",
1773
2062
  value: function randomize() {
1774
2063
  this.flowTime = this.random() * 40;
2064
+ return this.flowTime;
1775
2065
  }
1776
2066
  }, {
1777
2067
  key: "setColors",
1778
2068
  value: function setColors(colors) {
1779
2069
  if (!Array.isArray(colors) || colors.length !== 4) return;
1780
- this.preset = _objectSpread2(_objectSpread2({}, this.preset), {}, {
1781
- colors: colors
1782
- });
2070
+ this.preset.colors = _toConsumableArray(colors);
1783
2071
  }
1784
2072
  }, {
1785
2073
  key: "dispose",
1786
2074
  value: function dispose() {
1787
2075
  if (this._dragRaf) cancelAnimationFrame(this._dragRaf);
1788
- if (this.resizeObserver) this.resizeObserver.disconnect();else window.removeEventListener('resize', this.resizeCanvas);
2076
+ if (this.resizeObserver) this.resizeObserver.disconnect();else window.removeEventListener('resize', this.onResize);
1789
2077
  for (var _i = 0, _Object$keys = Object.keys(this.handlers); _i < _Object$keys.length; _i++) {
1790
2078
  var name = _Object$keys[_i];
1791
2079
  var handler = this.handlers[name];
@@ -1796,15 +2084,16 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1796
2084
  }]);
1797
2085
  }();
1798
2086
 
1799
- /**
2087
+ /**
1800
2088
  * Mount a fluid progress capsule into `container`.
1801
2089
  *
1802
- * Options: preset (literary name), width, height, value, min, max,
1803
- * draggable, colors, edgeStyle, textRatio (0-100, text region
2090
+ * Options: preset (literary name), width, height, value/modelValue, min, max,
2091
+ * step, draggable, keyboard, direction, precision/formatValue, colors,
2092
+ * edgeStyle, textRatio (0-100, text region
1804
2093
  * width in percent), text (HTML string or DOM nodes for the text slot),
1805
2094
  * colorContent (HTML string or DOM nodes for the color slot),
1806
- * showValue (show/hide the right-side percentage), quality,
1807
- * respectReducedMotion, cssVars.
2095
+ * showValue (show/hide the right-side percentage), quality, renderScale,
2096
+ * powerPreference, fps, paused/static, respectReducedMotion, cssVars.
1808
2097
  */
1809
2098
  function createProgressCapsule(container) {
1810
2099
  var _options$preset, _merged$min, _merged$max;
@@ -1814,14 +2103,20 @@ function createProgressCapsule(container) {
1814
2103
  }
1815
2104
  var preset = _objectSpread2({}, getPreset('progress', (_options$preset = options.preset) !== null && _options$preset !== void 0 ? _options$preset : '星火'));
1816
2105
  var merged = normalizeOptions(DEFAULTS.progress, preset, options);
1817
- if (merged.min > merged.max) {
1818
- var swap = merged.min;
1819
- merged.min = merged.max;
1820
- merged.max = swap;
1821
- }
1822
- var normalizedColors = merged.colors.map(normalizeColor);
1823
- if (normalizedColors.every(Boolean)) preset.colors = normalizedColors;
1824
- preset.edgeStyle = merged.edgeStyle;
2106
+ var _normalizeRange3 = normalizeRange(merged.min, merged.max);
2107
+ var _normalizeRange4 = _slicedToArray(_normalizeRange3, 2);
2108
+ merged.min = _normalizeRange4[0];
2109
+ merged.max = _normalizeRange4[1];
2110
+ if (merged.modelValue !== undefined) merged.value = merged.modelValue;
2111
+ var normalizedColors = Array.isArray(merged.colors) ? merged.colors.map(normalizeColor) : [];
2112
+ if (normalizedColors.length === 4 && normalizedColors.every(Boolean)) preset.colors = normalizedColors;
2113
+ var initialEdgeStyle = merged.edgeStyle === 'tide' || merged.edgeStyle === 'flow' ? merged.edgeStyle : preset.edgeStyle;
2114
+ preset.edgeStyle = initialEdgeStyle;
2115
+ merged.edgeStyle = initialEdgeStyle;
2116
+ merged.colors = _toConsumableArray(preset.colors);
2117
+ var optionColors = Array.isArray(options.colors) ? options.colors.map(normalizeColor) : [];
2118
+ var colorOverride = optionColors.length === 4 && optionColors.every(Boolean) ? _toConsumableArray(optionColors) : null;
2119
+ var edgeStyleOverride = options.edgeStyle === 'flow' || options.edgeStyle === 'tide' ? merged.edgeStyle : null;
1825
2120
  var copy = COPY;
1826
2121
  var customLabel = typeof options.label === 'string' && options.label ? options.label : null;
1827
2122
  // Vue 的裸布尔属性(<ProgressCapsule disabled />)会传成空字符串,
@@ -1830,18 +2125,31 @@ function createProgressCapsule(container) {
1830
2125
  var readonly = merged.readonly === true || merged.readonly === '' || merged.readonly === 'true';
1831
2126
  var locked = disabled || readonly;
1832
2127
  var effectiveDraggable = locked ? false : merged.draggable !== false;
2128
+ var effectiveKeyboard = locked ? false : merged.keyboard !== false;
1833
2129
  var dirty = {
1834
2130
  preset: false,
1835
2131
  colors: false,
1836
2132
  textRatio: false,
1837
2133
  cssVars: false,
1838
2134
  edgeStyle: false,
1839
- value: false
2135
+ value: false,
2136
+ step: false,
2137
+ precision: false,
2138
+ formatValue: false,
2139
+ direction: false,
2140
+ showValue: false,
2141
+ valueSuffix: false,
2142
+ quality: false,
2143
+ renderScale: false,
2144
+ paused: false,
2145
+ static: false,
2146
+ fps: false
1840
2147
  };
1841
2148
  var root = document.createElement('div');
1842
2149
  root.className = 'hj-capsule-root hj-progress-root';
1843
2150
  root.setAttribute('role', 'slider');
1844
2151
  root.setAttribute('data-draggable', String(effectiveDraggable));
2152
+ root.dataset.direction = merged.direction === 'rtl' ? 'rtl' : 'ltr';
1845
2153
  if (disabled) {
1846
2154
  root.setAttribute('aria-disabled', 'true');
1847
2155
  root.classList.add('is-disabled');
@@ -1850,7 +2158,8 @@ function createProgressCapsule(container) {
1850
2158
  root.setAttribute('aria-readonly', 'true');
1851
2159
  root.classList.add('is-readonly');
1852
2160
  }
1853
- if (locked) root.setAttribute('tabindex', '-1');
2161
+ root.setAttribute('tabindex', locked ? '-1' : effectiveKeyboard ? '0' : '-1');
2162
+ root.setAttribute('aria-orientation', 'horizontal');
1854
2163
  root.setAttribute('aria-valuemin', String((_merged$min = merged.min) !== null && _merged$min !== void 0 ? _merged$min : 0));
1855
2164
  root.setAttribute('aria-valuemax', String((_merged$max = merged.max) !== null && _merged$max !== void 0 ? _merged$max : 100));
1856
2165
  root.setAttribute('aria-valuenow', String(preset.initialProgress));
@@ -1905,8 +2214,22 @@ function createProgressCapsule(container) {
1905
2214
  root.appendChild(valueElement);
1906
2215
  container.appendChild(root);
1907
2216
  var emitter = createEmitter();
1908
- var paused = merged.respectReducedMotion && prefersReducedMotion();
2217
+ var manuallyPaused = merged.paused === true;
2218
+ var reducedPaused = false;
2219
+ var staticMode = merged.static === true;
2220
+ var contextLost = false;
1909
2221
  var disposed = false;
2222
+ var renderOnce = function renderOnce() {};
2223
+ var onContextLost = function onContextLost() {
2224
+ contextLost = true;
2225
+ emitter.emit('contextlost', {});
2226
+ };
2227
+ var onContextRestored = function onContextRestored() {
2228
+ contextLost = false;
2229
+ emitter.emit('contextrestored', {});
2230
+ renderOnce();
2231
+ wakeScheduler();
2232
+ };
1910
2233
  var applySize = function applySize() {
1911
2234
  root.style.width = parseSize(merged.width);
1912
2235
  root.style.height = parseSize(merged.height);
@@ -1927,7 +2250,11 @@ function createProgressCapsule(container) {
1927
2250
  preset: preset,
1928
2251
  emitter: emitter,
1929
2252
  options: _objectSpread2(_objectSpread2({}, merged), {}, {
1930
- draggable: effectiveDraggable
2253
+ draggable: effectiveDraggable,
2254
+ keyboard: effectiveKeyboard,
2255
+ onResize: function onResize() {
2256
+ if (manuallyPaused || reducedPaused || staticMode) renderOnce();
2257
+ }
1931
2258
  }),
1932
2259
  copy: copy,
1933
2260
  dirty: dirty
@@ -1939,8 +2266,12 @@ function createProgressCapsule(container) {
1939
2266
  canvas: canvas,
1940
2267
  preset: preset,
1941
2268
  getProgress: function getProgress() {
1942
- return controller.value;
1943
- }
2269
+ return progressRatio(controller.value, controller.min, controller.max) * 100;
2270
+ },
2271
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
2272
+ powerPreference: merged.powerPreference,
2273
+ onContextLost: onContextLost,
2274
+ onContextRestored: onContextRestored
1944
2275
  });
1945
2276
  }
1946
2277
  if (overlay) controller.webglActive = true;else if (merged.renderer !== 'canvas2d') {
@@ -1951,17 +2282,34 @@ function createProgressCapsule(container) {
1951
2282
  });
1952
2283
  });
1953
2284
  }
1954
- var visibility = createVisibilityGuard(root);
2285
+ var visibility = createVisibilityGuard(root, wakeScheduler);
2286
+ var motionPreference = createReducedMotionPreference(merged.respectReducedMotion, function (matches) {
2287
+ reducedPaused = matches;
2288
+ if (matches) renderOnce();else wakeScheduler();
2289
+ });
2290
+ reducedPaused = motionPreference.matches();
2291
+ var isMotionPaused = function isMotionPaused() {
2292
+ return manuallyPaused || reducedPaused || staticMode || contextLost;
2293
+ };
1955
2294
  var flowTime = 0;
2295
+ var frameGate = createFrameGate(merged.fps);
2296
+ renderOnce = function renderOnce() {
2297
+ controller.draw();
2298
+ if (overlay) overlay.update(flowTime);
2299
+ };
2300
+ renderOnce();
2301
+ var offPausedChange = emitter.on('change', function () {
2302
+ if (isMotionPaused()) renderOnce();
2303
+ });
1956
2304
  var unsubscribe = subscribeScheduler(function (delta, now) {
1957
2305
  flowTime += delta;
1958
2306
  controller.update(delta, false);
1959
- if (visibility.isVisible()) {
2307
+ if (frameGate.shouldDraw(delta)) {
1960
2308
  controller.draw();
1961
2309
  if (overlay) overlay.update(flowTime);
1962
2310
  }
1963
2311
  }, function () {
1964
- return paused;
2312
+ return isMotionPaused() || !visibility.isVisible();
1965
2313
  });
1966
2314
  nextTick(function () {
1967
2315
  if (disposed) return;
@@ -1988,32 +2336,41 @@ function createProgressCapsule(container) {
1988
2336
  },
1989
2337
  setRange: function setRange(min, max) {
1990
2338
  controller.setRange(min, max);
2339
+ if (isMotionPaused()) renderOnce();
1991
2340
  return this;
1992
2341
  },
1993
2342
  setPreset: function setPreset(ref) {
1994
2343
  var next = getPreset('progress', ref);
1995
2344
  dirty.preset = true;
1996
2345
  Object.assign(preset, next);
2346
+ if (colorOverride) preset.colors = _toConsumableArray(colorOverride);
2347
+ if (edgeStyleOverride) preset.edgeStyle = edgeStyleOverride;
1997
2348
  var nextColors = preset.colors.map(normalizeColor);
1998
2349
  if (nextColors.every(Boolean)) preset.colors = nextColors;
1999
2350
  controller.preset = preset;
2000
- controller.profile = next.edgeStyle === 'tide' ? FLOW_PROFILES.tide : FLOW_PROFILES[next.id] || FLOW_PROFILES['visual-training'];
2351
+ controller.profile = preset.edgeStyle === 'tide' ? FLOW_PROFILES.tide : FLOW_PROFILES[preset.id] || FLOW_PROFILES['visual-training'];
2001
2352
  controller.flowTime = stringSeed(next.id) * 31;
2002
2353
  controller.seed = stringSeed("".concat(next.id, "-reference")) * Math.PI * 2;
2003
2354
  syncDom();
2004
2355
  if (overlay) {
2005
2356
  overlay.dispose();
2357
+ contextLost = false;
2006
2358
  overlay = attachProgressFlowOverlay({
2007
2359
  root: root,
2008
2360
  canvas: canvas,
2009
2361
  preset: preset,
2010
2362
  getProgress: function getProgress() {
2011
- return controller.value;
2012
- }
2363
+ return progressRatio(controller.value, controller.min, controller.max) * 100;
2364
+ },
2365
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
2366
+ powerPreference: merged.powerPreference,
2367
+ onContextLost: onContextLost,
2368
+ onContextRestored: onContextRestored
2013
2369
  });
2014
2370
  }
2015
2371
  controller.webglActive = Boolean(overlay);
2016
2372
  controller.resizeCanvas();
2373
+ if (isMotionPaused()) renderOnce();
2017
2374
  emitter.emit('presetchange', {
2018
2375
  preset: _objectSpread2({}, preset)
2019
2376
  });
@@ -2023,21 +2380,28 @@ function createProgressCapsule(container) {
2023
2380
  var value = String(edgeStyle || '').toLowerCase();
2024
2381
  if (value !== 'flow' && value !== 'tide') return this;
2025
2382
  dirty.edgeStyle = true;
2383
+ edgeStyleOverride = value;
2026
2384
  preset.edgeStyle = value;
2027
2385
  controller.profile = value === 'tide' ? FLOW_PROFILES.tide : FLOW_PROFILES[preset.id] || FLOW_PROFILES['visual-training'];
2028
2386
  if (overlay) {
2029
2387
  overlay.dispose();
2388
+ contextLost = false;
2030
2389
  overlay = attachProgressFlowOverlay({
2031
2390
  root: root,
2032
2391
  canvas: canvas,
2033
2392
  preset: preset,
2034
2393
  getProgress: function getProgress() {
2035
- return controller.value;
2036
- }
2394
+ return progressRatio(controller.value, controller.min, controller.max) * 100;
2395
+ },
2396
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
2397
+ powerPreference: merged.powerPreference,
2398
+ onContextLost: onContextLost,
2399
+ onContextRestored: onContextRestored
2037
2400
  });
2038
2401
  }
2039
2402
  controller.webglActive = Boolean(overlay);
2040
2403
  controller.resizeCanvas();
2404
+ if (isMotionPaused()) renderOnce();
2041
2405
  return this;
2042
2406
  },
2043
2407
  setText: function setText(content) {
@@ -2075,6 +2439,22 @@ function createProgressCapsule(container) {
2075
2439
  controller.setValueSuffix(suffix);
2076
2440
  return this;
2077
2441
  },
2442
+ setStep: function setStep(step) {
2443
+ controller.setStep(step);
2444
+ return this;
2445
+ },
2446
+ setPrecision: function setPrecision(precision) {
2447
+ controller.setPrecision(precision);
2448
+ return this;
2449
+ },
2450
+ setFormatValue: function setFormatValue(formatter) {
2451
+ controller.setFormatValue(formatter);
2452
+ return this;
2453
+ },
2454
+ setDirection: function setDirection(direction) {
2455
+ controller.setDirection(direction);
2456
+ return this;
2457
+ },
2078
2458
  setShowValue: function setShowValue(show) {
2079
2459
  controller.setShowValue(show);
2080
2460
  return this;
@@ -2085,9 +2465,12 @@ function createProgressCapsule(container) {
2085
2465
  return !color;
2086
2466
  })) return this;
2087
2467
  dirty.colors = true;
2468
+ colorOverride = _toConsumableArray(next);
2469
+ preset.colors = _toConsumableArray(next);
2088
2470
  controller.setColors(next);
2089
2471
  if (overlay) overlay.setColors(next);
2090
2472
  controller.resizeCanvas();
2473
+ if (isMotionPaused()) renderOnce();
2091
2474
  return this;
2092
2475
  },
2093
2476
  setSize: function setSize(width, height) {
@@ -2101,30 +2484,68 @@ function createProgressCapsule(container) {
2101
2484
  }
2102
2485
  applySize();
2103
2486
  controller.resizeCanvas();
2487
+ if (isMotionPaused()) renderOnce();
2104
2488
  return this;
2105
2489
  },
2106
2490
  randomize: function randomize() {
2107
- controller.randomize();
2491
+ flowTime = controller.randomize();
2492
+ if (isMotionPaused()) renderOnce();
2108
2493
  return this;
2109
2494
  },
2110
2495
  pause: function pause() {
2111
- paused = true;
2496
+ dirty.paused = true;
2497
+ manuallyPaused = true;
2498
+ renderOnce();
2112
2499
  return this;
2113
2500
  },
2114
2501
  resume: function resume() {
2115
- paused = false;
2502
+ dirty.paused = true;
2503
+ manuallyPaused = false;
2504
+ wakeScheduler();
2505
+ return this;
2506
+ },
2507
+ setPaused: function setPaused(value) {
2508
+ return value ? this.pause() : this.resume();
2509
+ },
2510
+ setStatic: function setStatic(value) {
2511
+ dirty.static = true;
2512
+ staticMode = value === true;
2513
+ merged.static = staticMode;
2514
+ if (staticMode) renderOnce();else wakeScheduler();
2515
+ return this;
2516
+ },
2517
+ setFps: function setFps(fps) {
2518
+ dirty.fps = true;
2519
+ merged.fps = frameGate.setFps(fps);
2520
+ wakeScheduler();
2116
2521
  return this;
2117
2522
  },
2118
2523
  setQuality: function setQuality(quality) {
2524
+ dirty.quality = true;
2119
2525
  merged.quality = quality;
2120
- controller.dprCap = dprCapFor(quality);
2526
+ controller.dprCap = effectiveDprCap(quality, merged.renderScale);
2527
+ controller.resizeCanvas();
2528
+ if (overlay) overlay.setDprCap(controller.dprCap);
2529
+ if (isMotionPaused()) renderOnce();
2530
+ return this;
2531
+ },
2532
+ setRenderScale: function setRenderScale(renderScale) {
2533
+ var value = Number(renderScale);
2534
+ if (!Number.isFinite(value)) return this;
2535
+ dirty.renderScale = true;
2536
+ merged.renderScale = Math.min(1, Math.max(0.25, value));
2537
+ controller.dprCap = effectiveDprCap(merged.quality, merged.renderScale);
2121
2538
  controller.resizeCanvas();
2539
+ if (overlay) overlay.setDprCap(controller.dprCap);
2540
+ if (isMotionPaused()) renderOnce();
2122
2541
  return this;
2123
2542
  },
2124
2543
  dispose: function dispose() {
2125
2544
  disposed = true;
2126
2545
  unsubscribe();
2546
+ offPausedChange();
2127
2547
  visibility.dispose();
2548
+ motionPreference.dispose();
2128
2549
  if (overlay) overlay.dispose();
2129
2550
  controller.dispose();
2130
2551
  root.remove();
@@ -2135,6 +2556,45 @@ function createProgressCapsule(container) {
2135
2556
  get cssVars() {
2136
2557
  return merged.cssVars;
2137
2558
  },
2559
+ get min() {
2560
+ return controller.min;
2561
+ },
2562
+ get max() {
2563
+ return controller.max;
2564
+ },
2565
+ get step() {
2566
+ return controller.step;
2567
+ },
2568
+ get precision() {
2569
+ return controller.precision;
2570
+ },
2571
+ get formatValue() {
2572
+ return controller.formatValue;
2573
+ },
2574
+ get direction() {
2575
+ return controller.direction;
2576
+ },
2577
+ get showValue() {
2578
+ return !valueElement.hidden;
2579
+ },
2580
+ get valueSuffix() {
2581
+ return controller.valueSuffix;
2582
+ },
2583
+ get quality() {
2584
+ return merged.quality;
2585
+ },
2586
+ get renderScale() {
2587
+ return merged.renderScale;
2588
+ },
2589
+ get paused() {
2590
+ return manuallyPaused;
2591
+ },
2592
+ get static() {
2593
+ return staticMode;
2594
+ },
2595
+ get fps() {
2596
+ return frameGate.getFps();
2597
+ },
2138
2598
  dirty: dirty
2139
2599
  };
2140
2600
  }