@5even7/dlc-ui 0.2.11 → 0.2.12

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',
@@ -420,13 +429,22 @@ var DEFAULTS = {
420
429
  height: 104,
421
430
  min: 0,
422
431
  max: 100,
432
+ step: 'any',
423
433
  draggable: true,
434
+ keyboard: true,
424
435
  disabled: false,
425
436
  readonly: false,
437
+ direction: 'ltr',
438
+ precision: 0,
426
439
  valueSuffix: '%',
427
440
  edgeStyle: 'flow',
428
441
  quality: 'auto',
429
442
  renderer: 'auto',
443
+ renderScale: 1,
444
+ powerPreference: 'high-performance',
445
+ fps: 60,
446
+ paused: false,
447
+ static: false,
430
448
  textRatio: 54,
431
449
  showValue: true,
432
450
  respectReducedMotion: true
@@ -660,10 +678,10 @@ function hexToRgba(color) {
660
678
  return "rgba(".concat(red, ", ").concat(green, ", ").concat(blue, ", ").concat(alpha, ")");
661
679
  }
662
680
 
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.
681
+ /**
682
+ * Document-level shared rAF scheduler. Every component instance subscribes
683
+ * its own frame callback; the whole page runs ONE animation loop (like the
684
+ * original demo), which avoids jank from many competing rAF loops.
667
685
  */
668
686
  var subscribers = [];
669
687
  var running = false;
@@ -671,11 +689,7 @@ var rafId = 0;
671
689
  var last = 0;
672
690
  function tick(now) {
673
691
  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);
692
+ var activeItems = [];
679
693
  {
680
694
  var _iterator = _createForOfIteratorHelper(subscribers.slice()),
681
695
  _step;
@@ -683,10 +697,8 @@ function tick(now) {
683
697
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
684
698
  var item = _step.value;
685
699
  try {
686
- if (!item.isPaused()) item.onFrame(delta, now);
687
- } catch (error) {
688
- console.warn('[dlc-ui] frame error:', error);
689
- }
700
+ if (!item.isPaused()) activeItems.push(item);
701
+ } catch (_unused) {}
690
702
  }
691
703
  } catch (err) {
692
704
  _iterator.e(err);
@@ -694,6 +706,25 @@ function tick(now) {
694
706
  _iterator.f();
695
707
  }
696
708
  }
709
+ if (activeItems.length === 0) {
710
+ running = false;
711
+ rafId = 0;
712
+ last = 0;
713
+ return;
714
+ }
715
+ var delta = last ? Math.min((now - last) / 1000, 0.05) : 0;
716
+ last = now;
717
+ // Schedule the next frame BEFORE running callbacks so one throwing
718
+ // subscriber can never kill the whole animation loop.
719
+ rafId = requestAnimationFrame(tick);
720
+ for (var _i = 0, _activeItems = activeItems; _i < _activeItems.length; _i++) {
721
+ var _item = _activeItems[_i];
722
+ try {
723
+ _item.onFrame(delta, now);
724
+ } catch (error) {
725
+ console.warn('[dlc-ui] frame error:', error);
726
+ }
727
+ }
697
728
  if (subscribers.length === 0) {
698
729
  cancelAnimationFrame(rafId);
699
730
  running = false;
@@ -706,6 +737,9 @@ function start() {
706
737
  last = 0;
707
738
  rafId = requestAnimationFrame(tick);
708
739
  }
740
+ function wakeScheduler() {
741
+ start();
742
+ }
709
743
  function subscribeScheduler(onFrame, isPaused) {
710
744
  var item = {
711
745
  onFrame: onFrame,
@@ -724,11 +758,12 @@ function subscribeScheduler(onFrame, isPaused) {
724
758
  };
725
759
  }
726
760
 
727
- /**
728
- * Gates drawing on "element intersects viewport AND the page tab is visible".
729
- * Falls back to always-visible when IntersectionObserver is unavailable.
761
+ /**
762
+ * Gates drawing on "element intersects viewport AND the page tab is visible".
763
+ * Falls back to always-visible when IntersectionObserver is unavailable.
730
764
  */
731
765
  function createVisibilityGuard(element) {
766
+ var onChange = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
732
767
  var intersecting = true;
733
768
  var pageVisible = typeof document === 'undefined' || !document.hidden;
734
769
  var disposed = false;
@@ -738,6 +773,7 @@ function createVisibilityGuard(element) {
738
773
  intersecting = entries.some(function (entry) {
739
774
  return entry.isIntersecting;
740
775
  });
776
+ if (typeof onChange === 'function') onChange();
741
777
  }, {
742
778
  rootMargin: '180px'
743
779
  });
@@ -745,6 +781,7 @@ function createVisibilityGuard(element) {
745
781
  }
746
782
  var onVisibilityChange = function onVisibilityChange() {
747
783
  pageVisible = typeof document !== 'undefined' && !document.hidden;
784
+ if (typeof onChange === 'function') onChange();
748
785
  };
749
786
  if (typeof document !== 'undefined') {
750
787
  document.addEventListener('visibilitychange', onVisibilityChange);
@@ -900,64 +937,95 @@ function createProgram(gl) {
900
937
  var ProgressFlowRenderer = /*#__PURE__*/function () {
901
938
  function ProgressFlowRenderer(canvas, preset) {
902
939
  var _PROFILE_INDEX$preset;
940
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
903
941
  _classCallCheck(this, ProgressFlowRenderer);
904
942
  var gl = canvas.getContext('webgl2', {
905
943
  alpha: false,
906
944
  antialias: true,
907
945
  premultipliedAlpha: false,
908
- powerPreference: 'high-performance'
946
+ powerPreference: options.powerPreference || 'high-performance'
909
947
  });
910
948
  if (!gl) throw new Error('WebGL2 unavailable');
911
949
  this.canvas = canvas;
950
+ this.options = options;
912
951
  this.gl = gl;
913
- this.program = createProgram(gl);
914
952
  this.profile = preset.edgeStyle === 'tide' ? 3 : (_PROFILE_INDEX$preset = PROFILE_INDEX[preset.id]) !== null && _PROFILE_INDEX$preset !== void 0 ? _PROFILE_INDEX$preset : 2;
915
953
  this.seed = stringSeed$1("".concat(preset.id, "-shader")) * 13.7 + 1.0;
916
954
  this.colors = preset.colors.map(hexToRgb01);
917
955
  this.motionData = getProgressMotionData(preset.id, preset.edgeStyle);
918
956
  var motionFactor = preset.edgeStyle === 'tide' ? MOTION_SCALE_FACTORS.tide : MOTION_SCALE_FACTORS[preset.id] || 1.04;
919
957
  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;
958
+ this.disposed = false;
959
+ this.contextLost = false;
960
+ this.setupResources();
961
+ this.bindContextEvents();
959
962
  }
960
963
  return _createClass(ProgressFlowRenderer, [{
964
+ key: "setupResources",
965
+ value: function setupResources() {
966
+ var gl = this.gl;
967
+ this.program = createProgram(gl);
968
+ this.position = gl.getAttribLocation(this.program, 'a_position');
969
+ this.uniforms = {
970
+ resolution: gl.getUniformLocation(this.program, 'u_resolution'),
971
+ time: gl.getUniformLocation(this.program, 'u_time'),
972
+ progress: gl.getUniformLocation(this.program, 'u_progress'),
973
+ seed: gl.getUniformLocation(this.program, 'u_seed'),
974
+ profile: gl.getUniformLocation(this.program, 'u_profile'),
975
+ motion: gl.getUniformLocation(this.program, 'u_motion'),
976
+ effect: gl.getUniformLocation(this.program, 'u_effect'),
977
+ hasEffect: gl.getUniformLocation(this.program, 'u_hasEffect'),
978
+ effectFrames: gl.getUniformLocation(this.program, 'u_effectFrames'),
979
+ motionDuration: gl.getUniformLocation(this.program, 'u_motionDuration'),
980
+ motionScale: gl.getUniformLocation(this.program, 'u_motionScale'),
981
+ dark: gl.getUniformLocation(this.program, 'u_dark'),
982
+ accentA: gl.getUniformLocation(this.program, 'u_accentA'),
983
+ accentB: gl.getUniformLocation(this.program, 'u_accentB'),
984
+ glow: gl.getUniformLocation(this.program, 'u_glow')
985
+ };
986
+ this.buffer = gl.createBuffer();
987
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
988
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]), gl.STATIC_DRAW);
989
+ this.motionTexture = gl.createTexture();
990
+ gl.activeTexture(gl.TEXTURE0);
991
+ gl.bindTexture(gl.TEXTURE_2D, this.motionTexture);
992
+ gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
993
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8, PROGRESS_MOTION_WIDTH, PROGRESS_MOTION_HEIGHT, 0, gl.RED, gl.UNSIGNED_BYTE, this.motionData);
994
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
995
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
996
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
997
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
998
+ this.effectTexture = gl.createTexture();
999
+ gl.activeTexture(gl.TEXTURE1);
1000
+ gl.bindTexture(gl.TEXTURE_2D, this.effectTexture);
1001
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, 1, 1, 0, gl.RGB, gl.UNSIGNED_BYTE, new Uint8Array([32, 33, 38]));
1002
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
1003
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
1004
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
1005
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
1006
+ this.effectUploaded = false;
1007
+ }
1008
+ }, {
1009
+ key: "bindContextEvents",
1010
+ value: function bindContextEvents() {
1011
+ var _this = this;
1012
+ this.onContextLost = function (event) {
1013
+ event.preventDefault();
1014
+ if (_this.disposed) return;
1015
+ _this.contextLost = true;
1016
+ if (typeof _this.options.onContextLost === 'function') _this.options.onContextLost(event);
1017
+ };
1018
+ this.onContextRestored = function () {
1019
+ if (_this.disposed) return;
1020
+ _this.setupResources();
1021
+ _this.contextLost = false;
1022
+ _this.gl.viewport(0, 0, _this.canvas.width, _this.canvas.height);
1023
+ if (typeof _this.options.onContextRestored === 'function') _this.options.onContextRestored();
1024
+ };
1025
+ this.canvas.addEventListener('webglcontextlost', this.onContextLost, false);
1026
+ this.canvas.addEventListener('webglcontextrestored', this.onContextRestored, false);
1027
+ }
1028
+ }, {
961
1029
  key: "resize",
962
1030
  value: function resize(width, height, dpr) {
963
1031
  var pixelWidth = Math.max(1, Math.round(width * dpr));
@@ -972,6 +1040,7 @@ var ProgressFlowRenderer = /*#__PURE__*/function () {
972
1040
  key: "draw",
973
1041
  value: function draw(time, progress) {
974
1042
  var effectImage = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
1043
+ if (this.disposed || this.contextLost) return;
975
1044
  var gl = this.gl;
976
1045
  gl.useProgram(this.program);
977
1046
  gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
@@ -988,7 +1057,8 @@ var ProgressFlowRenderer = /*#__PURE__*/function () {
988
1057
  gl.uniform1f(this.uniforms.motionDuration, PROGRESS_MOTION_DURATION);
989
1058
  gl.uniform1f(this.uniforms.motionScale, this.motionScale);
990
1059
  var hasEffect = this.effectUploaded ? 1 : 0;
991
- if (!this.effectUploaded && effectImage && effectImage.complete && effectImage.naturalWidth > 0) {
1060
+ var effectWidth = effectImage && (effectImage.naturalWidth || effectImage.width || 0);
1061
+ if (!this.effectUploaded && effectImage && effectWidth > 0) {
992
1062
  gl.activeTexture(gl.TEXTURE1);
993
1063
  gl.bindTexture(gl.TEXTURE_2D, this.effectTexture);
994
1064
  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
@@ -1020,7 +1090,10 @@ var ProgressFlowRenderer = /*#__PURE__*/function () {
1020
1090
  }, {
1021
1091
  key: "dispose",
1022
1092
  value: function dispose() {
1093
+ this.disposed = true;
1023
1094
  var gl = this.gl;
1095
+ this.canvas.removeEventListener('webglcontextlost', this.onContextLost, false);
1096
+ this.canvas.removeEventListener('webglcontextrestored', this.onContextRestored, false);
1024
1097
  gl.deleteBuffer(this.buffer);
1025
1098
  gl.deleteTexture(this.motionTexture);
1026
1099
  gl.deleteTexture(this.effectTexture);
@@ -1031,8 +1104,9 @@ var ProgressFlowRenderer = /*#__PURE__*/function () {
1031
1104
  }]);
1032
1105
  }();
1033
1106
  function createProgressFlowRenderer(canvas, preset) {
1107
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
1034
1108
  try {
1035
- return new ProgressFlowRenderer(canvas, preset);
1109
+ return new ProgressFlowRenderer(canvas, preset, options);
1036
1110
  } catch (error) {
1037
1111
  console.warn('[画境观屿] 进度流体 WebGL2 不可用,使用 Canvas 2D 降级。', error);
1038
1112
  return null;
@@ -1092,50 +1166,49 @@ function createAtlas(id) {
1092
1166
  context.fillRect(20, 0, 44, FRAME_HEIGHT);
1093
1167
  context.restore();
1094
1168
  }
1095
- var image = new Image();
1096
- image.decoding = 'async';
1097
- var state = {
1098
- image: image,
1099
- ready: false
1169
+
1170
+ // A canvas is a valid TexImageSource. Keeping it directly avoids a PNG
1171
+ // encode -> base64 allocation -> Image decode round trip at startup.
1172
+ return {
1173
+ image: canvas,
1174
+ ready: true
1100
1175
  };
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
1176
  }
1114
1177
  function getProgressReferenceAtlas(id) {
1115
1178
  if (!CACHE[id]) CACHE[id] = createAtlas(id);
1116
1179
  return CACHE[id];
1117
1180
  }
1118
1181
 
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}
1182
+ /**
1183
+ * Attach a WebGL2 fluid overlay to a progress capsule root.
1184
+ *
1185
+ * @param {object} params
1186
+ * @param {HTMLElement} params.root progress capsule root element
1187
+ * @param {HTMLCanvasElement} params.canvas 2D fallback canvas (kept beneath)
1188
+ * @param {object} params.preset progress preset
1189
+ * @param {() => number} params.getProgress reads the current progress value
1190
+ * @returns {{ update(flowTime: number): void, setDprCap(cap: number): void, dispose(): void } | null}
1128
1191
  */
1129
1192
  function attachProgressFlowOverlay(_ref) {
1130
1193
  var root = _ref.root,
1131
1194
  canvas = _ref.canvas,
1132
1195
  preset = _ref.preset,
1133
- getProgress = _ref.getProgress;
1196
+ getProgress = _ref.getProgress,
1197
+ _ref$dprCap = _ref.dprCap,
1198
+ dprCap = _ref$dprCap === void 0 ? 2 : _ref$dprCap,
1199
+ _ref$powerPreference = _ref.powerPreference,
1200
+ powerPreference = _ref$powerPreference === void 0 ? 'high-performance' : _ref$powerPreference,
1201
+ onContextLost = _ref.onContextLost,
1202
+ onContextRestored = _ref.onContextRestored;
1134
1203
  var overlay = document.createElement('canvas');
1135
1204
  overlay.className = 'hj-progress-canvas hj-progress-overlay';
1136
1205
  overlay.setAttribute('aria-hidden', 'true');
1137
1206
  canvas.insertAdjacentElement('afterend', overlay);
1138
- var renderer = createProgressFlowRenderer(overlay, preset);
1207
+ var renderer = createProgressFlowRenderer(overlay, preset, {
1208
+ powerPreference: powerPreference,
1209
+ onContextLost: onContextLost,
1210
+ onContextRestored: onContextRestored
1211
+ });
1139
1212
  if (!renderer) {
1140
1213
  overlay.remove();
1141
1214
  return null;
@@ -1146,7 +1219,7 @@ function attachProgressFlowOverlay(_ref) {
1146
1219
  var bounds = root.getBoundingClientRect();
1147
1220
  var width = Math.max(1, bounds.width);
1148
1221
  var height = Math.max(1, bounds.height);
1149
- var dpr = Math.min(window.devicePixelRatio || 1, 2);
1222
+ var dpr = Math.min(window.devicePixelRatio || 1, dprCap);
1150
1223
  overlay.style.width = "".concat(width, "px");
1151
1224
  overlay.style.height = "".concat(height, "px");
1152
1225
  renderer.resize(width, height, dpr);
@@ -1170,6 +1243,10 @@ function attachProgressFlowOverlay(_ref) {
1170
1243
  setColors: function setColors(colors) {
1171
1244
  renderer.setColors(colors);
1172
1245
  },
1246
+ setDprCap: function setDprCap(cap) {
1247
+ dprCap = cap;
1248
+ resize();
1249
+ },
1173
1250
  dispose: function dispose() {
1174
1251
  if (resizeObserver) resizeObserver.disconnect();
1175
1252
  if (onWindowResize) window.removeEventListener('resize', onWindowResize);
@@ -1196,6 +1273,101 @@ function nextTick(fn) {
1196
1273
  }
1197
1274
  }
1198
1275
 
1276
+ function toFiniteNumber(value) {
1277
+ if (value == null || typeof value === 'boolean') return null;
1278
+ if (typeof value === 'string' && value.trim() === '') return null;
1279
+ var number = Number(value);
1280
+ return Number.isFinite(number) ? number : null;
1281
+ }
1282
+
1283
+ function normalizeRange(min, max) {
1284
+ var fallbackMin = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
1285
+ var fallbackMax = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 100;
1286
+ var nextMin = toFiniteNumber(min);
1287
+ var nextMax = toFiniteNumber(max);
1288
+ if (nextMin === null) nextMin = fallbackMin;
1289
+ if (nextMax === null) nextMax = fallbackMax;
1290
+ if (nextMin > nextMax) {
1291
+ var swap = nextMin;
1292
+ nextMin = nextMax;
1293
+ nextMax = swap;
1294
+ }
1295
+ return [nextMin, nextMax];
1296
+ }
1297
+ function clampProgressValue(value, min, max) {
1298
+ var number = toFiniteNumber(value);
1299
+ if (number === null) return null;
1300
+ return Math.min(Math.max(number, min), max);
1301
+ }
1302
+ function normalizeProgressStep(step) {
1303
+ var fallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'any';
1304
+ if (step === 'any' || step == null) return 'any';
1305
+ var increment = toFiniteNumber(step);
1306
+ return increment !== null && increment > 0 ? increment : fallback;
1307
+ }
1308
+ function snapProgressValue(value, min, max) {
1309
+ var step = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'any';
1310
+ var clamped = clampProgressValue(value, min, max);
1311
+ if (clamped === null) return null;
1312
+ if (step === 'any' || step == null) return clamped;
1313
+ var increment = normalizeProgressStep(step);
1314
+ if (increment === 'any') return clamped;
1315
+ var snapped = min + Math.round((clamped - min) / increment) * increment;
1316
+ return Math.min(Math.max(Number(snapped.toFixed(12)), min), max);
1317
+ }
1318
+ function progressRatio(value, min, max) {
1319
+ var range = max - min;
1320
+ if (!Number.isFinite(range) || range <= 0) return 0;
1321
+ return Math.min(Math.max((value - min) / range, 0), 1);
1322
+ }
1323
+
1324
+ function normalizeFps(value) {
1325
+ var fallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 60;
1326
+ var fps = Number(value);
1327
+ if (!Number.isFinite(fps)) return fallback;
1328
+ return Math.min(60, Math.max(1, fps));
1329
+ }
1330
+ function createFrameGate() {
1331
+ var initialFps = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 60;
1332
+ var fps = normalizeFps(initialFps);
1333
+ var elapsed = 0;
1334
+ return {
1335
+ shouldDraw: function shouldDraw(delta) {
1336
+ elapsed += delta;
1337
+ var interval = 1 / fps;
1338
+ if (elapsed + 0.0001 < interval) return false;
1339
+ elapsed %= interval;
1340
+ return true;
1341
+ },
1342
+ setFps: function setFps(value) {
1343
+ fps = normalizeFps(value, fps);
1344
+ elapsed = 0;
1345
+ return fps;
1346
+ },
1347
+ getFps: function getFps() {
1348
+ return fps;
1349
+ }
1350
+ };
1351
+ }
1352
+ function createReducedMotionPreference(enabled, onChange) {
1353
+ var query = enabled && typeof matchMedia !== 'undefined' ? matchMedia('(prefers-reduced-motion: reduce)') : null;
1354
+ var notify = function notify() {
1355
+ if (typeof onChange === 'function') onChange(Boolean(query && query.matches));
1356
+ };
1357
+ if (query) {
1358
+ if (typeof query.addEventListener === 'function') query.addEventListener('change', notify);else if (typeof query.addListener === 'function') query.addListener(notify);
1359
+ }
1360
+ return {
1361
+ matches: function matches() {
1362
+ return Boolean(query && query.matches);
1363
+ },
1364
+ dispose: function dispose() {
1365
+ if (!query) return;
1366
+ if (typeof query.removeEventListener === 'function') query.removeEventListener('change', notify);else if (typeof query.removeListener === 'function') query.removeListener(notify);
1367
+ }
1368
+ };
1369
+ }
1370
+
1199
1371
  var clamp = function clamp(value, min, max) {
1200
1372
  return Math.min(Math.max(value, min), max);
1201
1373
  };
@@ -1222,9 +1394,6 @@ function interpolate(template, values) {
1222
1394
  return values[key] !== undefined ? values[key] : '';
1223
1395
  });
1224
1396
  }
1225
- function prefersReducedMotion() {
1226
- return typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches;
1227
- }
1228
1397
  var FLOW_PROFILES = {
1229
1398
  'model-training': {
1230
1399
  cycles: [0.98, 3.05, 5.45],
@@ -1285,8 +1454,6 @@ var FLOW_PROFILES = {
1285
1454
  var ProgressCapsuleController = /*#__PURE__*/function () {
1286
1455
  function ProgressCapsuleController(_ref) {
1287
1456
  var _options$valueSuffix,
1288
- _options$min,
1289
- _options$max,
1290
1457
  _options$value,
1291
1458
  _this = this;
1292
1459
  var root = _ref.root,
@@ -1308,11 +1475,18 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1308
1475
  this.dirty = dirty;
1309
1476
  this.valueSuffix = (_options$valueSuffix = options.valueSuffix) !== null && _options$valueSuffix !== void 0 ? _options$valueSuffix : copy.valueSuffix;
1310
1477
  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);
1478
+ var _normalizeRange = normalizeRange(options.min, options.max);
1479
+ var _normalizeRange2 = _slicedToArray(_normalizeRange, 2);
1480
+ this.min = _normalizeRange2[0];
1481
+ this.max = _normalizeRange2[1];
1482
+ this.step = normalizeProgressStep(options.step);
1483
+ this.precision = Number.isInteger(Number(options.precision)) ? clamp(Number(options.precision), 0, 20) : 0;
1484
+ this.formatValue = typeof options.formatValue === 'function' ? options.formatValue : null;
1485
+ this.direction = options.direction === 'rtl' ? 'rtl' : 'ltr';
1486
+ this.dprCap = effectiveDprCap(options.quality, options.renderScale);
1314
1487
  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);
1488
+ this.value = snapProgressValue((_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : preset.initialProgress, this.min, this.max, this.step);
1489
+ if (this.value === null) this.value = clamp(preset.initialProgress, this.min, this.max);
1316
1490
  this.dragging = false;
1317
1491
  this.webglActive = false;
1318
1492
  this.flowTime = stringSeed(preset.id) * 31;
@@ -1322,10 +1496,12 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1322
1496
  this.width = 0;
1323
1497
  this.height = 0;
1324
1498
  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);
1499
+ this.onResize = function () {
1500
+ _this.resizeCanvas();
1501
+ if (typeof _this.options.onResize === 'function') _this.options.onResize();
1502
+ };
1503
+ this.resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(this.onResize) : null;
1504
+ if (this.resizeObserver) this.resizeObserver.observe(this.root);else window.addEventListener('resize', this.onResize);
1329
1505
  this.suppressEvents = true;
1330
1506
  this.setProgress(this.value, 'init');
1331
1507
  this.suppressEvents = false;
@@ -1338,16 +1514,37 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1338
1514
  this.randomState = Math.imul(this.randomState, 1664525) + 1013904223 >>> 0;
1339
1515
  return this.randomState / 4294967296;
1340
1516
  }
1517
+ }, {
1518
+ key: "displayText",
1519
+ value: function displayText() {
1520
+ if (this.formatValue) {
1521
+ return String(this.formatValue(this.value, {
1522
+ min: this.min,
1523
+ max: this.max,
1524
+ suffix: this.valueSuffix
1525
+ }));
1526
+ }
1527
+ var number = this.precision > 0 ? this.value.toFixed(this.precision) : String(Math.round(this.value));
1528
+ return "".concat(number).concat(this.valueSuffix);
1529
+ }
1530
+ }, {
1531
+ key: "syncValueDom",
1532
+ value: function syncValueDom() {
1533
+ var text = this.displayText();
1534
+ this.root.style.setProperty('--progress', this.value.toFixed(2));
1535
+ this.root.style.setProperty('--progress-ratio', progressRatio(this.value, this.min, this.max).toFixed(4));
1536
+ this.root.setAttribute('aria-valuenow', String(this.value));
1537
+ this.root.setAttribute('aria-valuetext', text);
1538
+ if (this.valueElement) this.valueElement.textContent = text;
1539
+ }
1341
1540
  }, {
1342
1541
  key: "setProgress",
1343
1542
  value: function setProgress(nextValue) {
1344
1543
  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);
1544
+ var next = snapProgressValue(nextValue, this.min, this.max, this.step);
1545
+ if (next === null) return false;
1546
+ this.value = next;
1547
+ this.syncValueDom();
1351
1548
  if (!this.suppressEvents) {
1352
1549
  if (this.dirty) this.dirty.value = true;
1353
1550
  this.emitter.emit('change', {
@@ -1355,19 +1552,58 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1355
1552
  source: source
1356
1553
  });
1357
1554
  }
1555
+ return true;
1358
1556
  }
1359
1557
  }, {
1360
1558
  key: "setValueSuffix",
1361
1559
  value: function setValueSuffix(suffix) {
1560
+ if (this.dirty) this.dirty.valueSuffix = true;
1362
1561
  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));
1562
+ this.syncValueDom();
1563
+ return this;
1564
+ }
1565
+ }, {
1566
+ key: "setStep",
1567
+ value: function setStep(step) {
1568
+ var nextStep = normalizeProgressStep(step, this.step);
1569
+ if (nextStep === this.step) return this;
1570
+ if (this.dirty) this.dirty.step = true;
1571
+ this.step = nextStep;
1572
+ var next = snapProgressValue(this.value, this.min, this.max, this.step);
1573
+ if (next !== null && next !== this.value) this.setProgress(next, 'prop');
1574
+ return this;
1575
+ }
1576
+ }, {
1577
+ key: "setPrecision",
1578
+ value: function setPrecision(precision) {
1579
+ var value = Number(precision);
1580
+ if (!Number.isInteger(value) || value < 0 || value > 20) return this;
1581
+ if (this.dirty) this.dirty.precision = true;
1582
+ this.precision = value;
1583
+ this.syncValueDom();
1584
+ return this;
1585
+ }
1586
+ }, {
1587
+ key: "setFormatValue",
1588
+ value: function setFormatValue(formatter) {
1589
+ if (formatter != null && typeof formatter !== 'function') return this;
1590
+ if (this.dirty) this.dirty.formatValue = true;
1591
+ this.formatValue = formatter || null;
1592
+ this.syncValueDom();
1593
+ return this;
1594
+ }
1595
+ }, {
1596
+ key: "setDirection",
1597
+ value: function setDirection(direction) {
1598
+ if (this.dirty) this.dirty.direction = true;
1599
+ this.direction = direction === 'rtl' ? 'rtl' : 'ltr';
1600
+ this.root.dataset.direction = this.direction;
1366
1601
  return this;
1367
1602
  }
1368
1603
  }, {
1369
1604
  key: "setShowValue",
1370
1605
  value: function setShowValue(show) {
1606
+ if (this.dirty) this.dirty.showValue = true;
1371
1607
  if (this.valueElement) this.valueElement.hidden = show === false;
1372
1608
  return this;
1373
1609
  }
@@ -1532,15 +1768,14 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1532
1768
  }, {
1533
1769
  key: "setRange",
1534
1770
  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');
1771
+ var nextRange = normalizeRange(min, max, this.min, this.max);
1772
+ this.min = nextRange[0];
1773
+ this.max = nextRange[1];
1774
+ this.root.setAttribute('aria-valuemin', String(this.min));
1775
+ this.root.setAttribute('aria-valuemax', String(this.max));
1776
+ var next = snapProgressValue(this.value, this.min, this.max, this.step);
1777
+ if (next !== this.value) this.setProgress(next, 'prop');else this.syncValueDom();
1778
+ return this;
1544
1779
  }
1545
1780
  }, {
1546
1781
  key: "drawReferenceFlow",
@@ -1665,8 +1900,39 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1665
1900
  value: function updateFromPointer(event) {
1666
1901
  var bounds = this.root.getBoundingClientRect();
1667
1902
  var ratio = bounds.width > 0 ? (event.clientX - bounds.left) / bounds.width : 0;
1903
+ if (this.direction === 'rtl') ratio = 1 - ratio;
1668
1904
  this.setProgress(this.min + ratio * (this.max - this.min), 'drag');
1669
1905
  }
1906
+ }, {
1907
+ key: "onKeyDown",
1908
+ value: function onKeyDown(event) {
1909
+ var range = this.max - this.min;
1910
+ var configuredStep = Number(this.step);
1911
+ var step = Number.isFinite(configuredStep) && configuredStep > 0 ? configuredStep : Math.max(range / 100, 1e-7);
1912
+ var direction = this.direction === 'rtl' ? -1 : 1;
1913
+ var aliases = {
1914
+ Left: 'ArrowLeft',
1915
+ Right: 'ArrowRight',
1916
+ Up: 'ArrowUp',
1917
+ Down: 'ArrowDown'
1918
+ };
1919
+ var keyCodes = {
1920
+ 35: 'End',
1921
+ 36: 'Home',
1922
+ 33: 'PageUp',
1923
+ 34: 'PageDown',
1924
+ 37: 'ArrowLeft',
1925
+ 38: 'ArrowUp',
1926
+ 39: 'ArrowRight',
1927
+ 40: 'ArrowDown'
1928
+ };
1929
+ var key = aliases[event.key] || event.key || keyCodes[event.keyCode];
1930
+ var next = null;
1931
+ 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;
1932
+ if (next === null) return;
1933
+ event.preventDefault();
1934
+ this.setProgress(next, 'keyboard');
1935
+ }
1670
1936
  }, {
1671
1937
  key: "beginDrag",
1672
1938
  value: function beginDrag(event) {
@@ -1754,6 +2020,12 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1754
2020
  this.root.addEventListener('pointerup', this.handlers.pointerup);
1755
2021
  this.root.addEventListener('pointercancel', this.handlers.pointercancel);
1756
2022
  }
2023
+ if (this.options.keyboard !== false) {
2024
+ this.handlers.keydown = function (event) {
2025
+ return _this3.onKeyDown(event);
2026
+ };
2027
+ this.root.addEventListener('keydown', this.handlers.keydown);
2028
+ }
1757
2029
  }
1758
2030
  }, {
1759
2031
  key: "update",
@@ -1772,20 +2044,19 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1772
2044
  key: "randomize",
1773
2045
  value: function randomize() {
1774
2046
  this.flowTime = this.random() * 40;
2047
+ return this.flowTime;
1775
2048
  }
1776
2049
  }, {
1777
2050
  key: "setColors",
1778
2051
  value: function setColors(colors) {
1779
2052
  if (!Array.isArray(colors) || colors.length !== 4) return;
1780
- this.preset = _objectSpread2(_objectSpread2({}, this.preset), {}, {
1781
- colors: colors
1782
- });
2053
+ this.preset.colors = _toConsumableArray(colors);
1783
2054
  }
1784
2055
  }, {
1785
2056
  key: "dispose",
1786
2057
  value: function dispose() {
1787
2058
  if (this._dragRaf) cancelAnimationFrame(this._dragRaf);
1788
- if (this.resizeObserver) this.resizeObserver.disconnect();else window.removeEventListener('resize', this.resizeCanvas);
2059
+ if (this.resizeObserver) this.resizeObserver.disconnect();else window.removeEventListener('resize', this.onResize);
1789
2060
  for (var _i = 0, _Object$keys = Object.keys(this.handlers); _i < _Object$keys.length; _i++) {
1790
2061
  var name = _Object$keys[_i];
1791
2062
  var handler = this.handlers[name];
@@ -1796,15 +2067,16 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1796
2067
  }]);
1797
2068
  }();
1798
2069
 
1799
- /**
2070
+ /**
1800
2071
  * Mount a fluid progress capsule into `container`.
1801
2072
  *
1802
- * Options: preset (literary name), width, height, value, min, max,
1803
- * draggable, colors, edgeStyle, textRatio (0-100, text region
2073
+ * Options: preset (literary name), width, height, value/modelValue, min, max,
2074
+ * step, draggable, keyboard, direction, precision/formatValue, colors,
2075
+ * edgeStyle, textRatio (0-100, text region
1804
2076
  * width in percent), text (HTML string or DOM nodes for the text slot),
1805
2077
  * colorContent (HTML string or DOM nodes for the color slot),
1806
- * showValue (show/hide the right-side percentage), quality,
1807
- * respectReducedMotion, cssVars.
2078
+ * showValue (show/hide the right-side percentage), quality, renderScale,
2079
+ * powerPreference, fps, paused/static, respectReducedMotion, cssVars.
1808
2080
  */
1809
2081
  function createProgressCapsule(container) {
1810
2082
  var _options$preset, _merged$min, _merged$max;
@@ -1814,14 +2086,20 @@ function createProgressCapsule(container) {
1814
2086
  }
1815
2087
  var preset = _objectSpread2({}, getPreset('progress', (_options$preset = options.preset) !== null && _options$preset !== void 0 ? _options$preset : '星火'));
1816
2088
  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;
2089
+ var _normalizeRange3 = normalizeRange(merged.min, merged.max);
2090
+ var _normalizeRange4 = _slicedToArray(_normalizeRange3, 2);
2091
+ merged.min = _normalizeRange4[0];
2092
+ merged.max = _normalizeRange4[1];
2093
+ if (merged.modelValue !== undefined) merged.value = merged.modelValue;
2094
+ var normalizedColors = Array.isArray(merged.colors) ? merged.colors.map(normalizeColor) : [];
2095
+ if (normalizedColors.length === 4 && normalizedColors.every(Boolean)) preset.colors = normalizedColors;
2096
+ var initialEdgeStyle = merged.edgeStyle === 'tide' || merged.edgeStyle === 'flow' ? merged.edgeStyle : preset.edgeStyle;
2097
+ preset.edgeStyle = initialEdgeStyle;
2098
+ merged.edgeStyle = initialEdgeStyle;
2099
+ merged.colors = _toConsumableArray(preset.colors);
2100
+ var optionColors = Array.isArray(options.colors) ? options.colors.map(normalizeColor) : [];
2101
+ var colorOverride = optionColors.length === 4 && optionColors.every(Boolean) ? _toConsumableArray(optionColors) : null;
2102
+ var edgeStyleOverride = options.edgeStyle === 'flow' || options.edgeStyle === 'tide' ? merged.edgeStyle : null;
1825
2103
  var copy = COPY;
1826
2104
  var customLabel = typeof options.label === 'string' && options.label ? options.label : null;
1827
2105
  // Vue 的裸布尔属性(<ProgressCapsule disabled />)会传成空字符串,
@@ -1830,18 +2108,31 @@ function createProgressCapsule(container) {
1830
2108
  var readonly = merged.readonly === true || merged.readonly === '' || merged.readonly === 'true';
1831
2109
  var locked = disabled || readonly;
1832
2110
  var effectiveDraggable = locked ? false : merged.draggable !== false;
2111
+ var effectiveKeyboard = locked ? false : merged.keyboard !== false;
1833
2112
  var dirty = {
1834
2113
  preset: false,
1835
2114
  colors: false,
1836
2115
  textRatio: false,
1837
2116
  cssVars: false,
1838
2117
  edgeStyle: false,
1839
- value: false
2118
+ value: false,
2119
+ step: false,
2120
+ precision: false,
2121
+ formatValue: false,
2122
+ direction: false,
2123
+ showValue: false,
2124
+ valueSuffix: false,
2125
+ quality: false,
2126
+ renderScale: false,
2127
+ paused: false,
2128
+ static: false,
2129
+ fps: false
1840
2130
  };
1841
2131
  var root = document.createElement('div');
1842
2132
  root.className = 'hj-capsule-root hj-progress-root';
1843
2133
  root.setAttribute('role', 'slider');
1844
2134
  root.setAttribute('data-draggable', String(effectiveDraggable));
2135
+ root.dataset.direction = merged.direction === 'rtl' ? 'rtl' : 'ltr';
1845
2136
  if (disabled) {
1846
2137
  root.setAttribute('aria-disabled', 'true');
1847
2138
  root.classList.add('is-disabled');
@@ -1850,7 +2141,8 @@ function createProgressCapsule(container) {
1850
2141
  root.setAttribute('aria-readonly', 'true');
1851
2142
  root.classList.add('is-readonly');
1852
2143
  }
1853
- if (locked) root.setAttribute('tabindex', '-1');
2144
+ root.setAttribute('tabindex', locked ? '-1' : effectiveKeyboard ? '0' : '-1');
2145
+ root.setAttribute('aria-orientation', 'horizontal');
1854
2146
  root.setAttribute('aria-valuemin', String((_merged$min = merged.min) !== null && _merged$min !== void 0 ? _merged$min : 0));
1855
2147
  root.setAttribute('aria-valuemax', String((_merged$max = merged.max) !== null && _merged$max !== void 0 ? _merged$max : 100));
1856
2148
  root.setAttribute('aria-valuenow', String(preset.initialProgress));
@@ -1905,8 +2197,22 @@ function createProgressCapsule(container) {
1905
2197
  root.appendChild(valueElement);
1906
2198
  container.appendChild(root);
1907
2199
  var emitter = createEmitter();
1908
- var paused = merged.respectReducedMotion && prefersReducedMotion();
2200
+ var manuallyPaused = merged.paused === true;
2201
+ var reducedPaused = false;
2202
+ var staticMode = merged.static === true;
2203
+ var contextLost = false;
1909
2204
  var disposed = false;
2205
+ var renderOnce = function renderOnce() {};
2206
+ var onContextLost = function onContextLost() {
2207
+ contextLost = true;
2208
+ emitter.emit('contextlost', {});
2209
+ };
2210
+ var onContextRestored = function onContextRestored() {
2211
+ contextLost = false;
2212
+ emitter.emit('contextrestored', {});
2213
+ renderOnce();
2214
+ wakeScheduler();
2215
+ };
1910
2216
  var applySize = function applySize() {
1911
2217
  root.style.width = parseSize(merged.width);
1912
2218
  root.style.height = parseSize(merged.height);
@@ -1927,7 +2233,11 @@ function createProgressCapsule(container) {
1927
2233
  preset: preset,
1928
2234
  emitter: emitter,
1929
2235
  options: _objectSpread2(_objectSpread2({}, merged), {}, {
1930
- draggable: effectiveDraggable
2236
+ draggable: effectiveDraggable,
2237
+ keyboard: effectiveKeyboard,
2238
+ onResize: function onResize() {
2239
+ if (manuallyPaused || reducedPaused || staticMode) renderOnce();
2240
+ }
1931
2241
  }),
1932
2242
  copy: copy,
1933
2243
  dirty: dirty
@@ -1939,8 +2249,12 @@ function createProgressCapsule(container) {
1939
2249
  canvas: canvas,
1940
2250
  preset: preset,
1941
2251
  getProgress: function getProgress() {
1942
- return controller.value;
1943
- }
2252
+ return progressRatio(controller.value, controller.min, controller.max) * 100;
2253
+ },
2254
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
2255
+ powerPreference: merged.powerPreference,
2256
+ onContextLost: onContextLost,
2257
+ onContextRestored: onContextRestored
1944
2258
  });
1945
2259
  }
1946
2260
  if (overlay) controller.webglActive = true;else if (merged.renderer !== 'canvas2d') {
@@ -1951,17 +2265,34 @@ function createProgressCapsule(container) {
1951
2265
  });
1952
2266
  });
1953
2267
  }
1954
- var visibility = createVisibilityGuard(root);
2268
+ var visibility = createVisibilityGuard(root, wakeScheduler);
2269
+ var motionPreference = createReducedMotionPreference(merged.respectReducedMotion, function (matches) {
2270
+ reducedPaused = matches;
2271
+ if (matches) renderOnce();else wakeScheduler();
2272
+ });
2273
+ reducedPaused = motionPreference.matches();
2274
+ var isMotionPaused = function isMotionPaused() {
2275
+ return manuallyPaused || reducedPaused || staticMode || contextLost;
2276
+ };
1955
2277
  var flowTime = 0;
2278
+ var frameGate = createFrameGate(merged.fps);
2279
+ renderOnce = function renderOnce() {
2280
+ controller.draw();
2281
+ if (overlay) overlay.update(flowTime);
2282
+ };
2283
+ renderOnce();
2284
+ var offPausedChange = emitter.on('change', function () {
2285
+ if (isMotionPaused()) renderOnce();
2286
+ });
1956
2287
  var unsubscribe = subscribeScheduler(function (delta, now) {
1957
2288
  flowTime += delta;
1958
2289
  controller.update(delta, false);
1959
- if (visibility.isVisible()) {
2290
+ if (frameGate.shouldDraw(delta)) {
1960
2291
  controller.draw();
1961
2292
  if (overlay) overlay.update(flowTime);
1962
2293
  }
1963
2294
  }, function () {
1964
- return paused;
2295
+ return isMotionPaused() || !visibility.isVisible();
1965
2296
  });
1966
2297
  nextTick(function () {
1967
2298
  if (disposed) return;
@@ -1988,32 +2319,41 @@ function createProgressCapsule(container) {
1988
2319
  },
1989
2320
  setRange: function setRange(min, max) {
1990
2321
  controller.setRange(min, max);
2322
+ if (isMotionPaused()) renderOnce();
1991
2323
  return this;
1992
2324
  },
1993
2325
  setPreset: function setPreset(ref) {
1994
2326
  var next = getPreset('progress', ref);
1995
2327
  dirty.preset = true;
1996
2328
  Object.assign(preset, next);
2329
+ if (colorOverride) preset.colors = _toConsumableArray(colorOverride);
2330
+ if (edgeStyleOverride) preset.edgeStyle = edgeStyleOverride;
1997
2331
  var nextColors = preset.colors.map(normalizeColor);
1998
2332
  if (nextColors.every(Boolean)) preset.colors = nextColors;
1999
2333
  controller.preset = preset;
2000
- controller.profile = next.edgeStyle === 'tide' ? FLOW_PROFILES.tide : FLOW_PROFILES[next.id] || FLOW_PROFILES['visual-training'];
2334
+ controller.profile = preset.edgeStyle === 'tide' ? FLOW_PROFILES.tide : FLOW_PROFILES[preset.id] || FLOW_PROFILES['visual-training'];
2001
2335
  controller.flowTime = stringSeed(next.id) * 31;
2002
2336
  controller.seed = stringSeed("".concat(next.id, "-reference")) * Math.PI * 2;
2003
2337
  syncDom();
2004
2338
  if (overlay) {
2005
2339
  overlay.dispose();
2340
+ contextLost = false;
2006
2341
  overlay = attachProgressFlowOverlay({
2007
2342
  root: root,
2008
2343
  canvas: canvas,
2009
2344
  preset: preset,
2010
2345
  getProgress: function getProgress() {
2011
- return controller.value;
2012
- }
2346
+ return progressRatio(controller.value, controller.min, controller.max) * 100;
2347
+ },
2348
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
2349
+ powerPreference: merged.powerPreference,
2350
+ onContextLost: onContextLost,
2351
+ onContextRestored: onContextRestored
2013
2352
  });
2014
2353
  }
2015
2354
  controller.webglActive = Boolean(overlay);
2016
2355
  controller.resizeCanvas();
2356
+ if (isMotionPaused()) renderOnce();
2017
2357
  emitter.emit('presetchange', {
2018
2358
  preset: _objectSpread2({}, preset)
2019
2359
  });
@@ -2023,21 +2363,28 @@ function createProgressCapsule(container) {
2023
2363
  var value = String(edgeStyle || '').toLowerCase();
2024
2364
  if (value !== 'flow' && value !== 'tide') return this;
2025
2365
  dirty.edgeStyle = true;
2366
+ edgeStyleOverride = value;
2026
2367
  preset.edgeStyle = value;
2027
2368
  controller.profile = value === 'tide' ? FLOW_PROFILES.tide : FLOW_PROFILES[preset.id] || FLOW_PROFILES['visual-training'];
2028
2369
  if (overlay) {
2029
2370
  overlay.dispose();
2371
+ contextLost = false;
2030
2372
  overlay = attachProgressFlowOverlay({
2031
2373
  root: root,
2032
2374
  canvas: canvas,
2033
2375
  preset: preset,
2034
2376
  getProgress: function getProgress() {
2035
- return controller.value;
2036
- }
2377
+ return progressRatio(controller.value, controller.min, controller.max) * 100;
2378
+ },
2379
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
2380
+ powerPreference: merged.powerPreference,
2381
+ onContextLost: onContextLost,
2382
+ onContextRestored: onContextRestored
2037
2383
  });
2038
2384
  }
2039
2385
  controller.webglActive = Boolean(overlay);
2040
2386
  controller.resizeCanvas();
2387
+ if (isMotionPaused()) renderOnce();
2041
2388
  return this;
2042
2389
  },
2043
2390
  setText: function setText(content) {
@@ -2075,6 +2422,22 @@ function createProgressCapsule(container) {
2075
2422
  controller.setValueSuffix(suffix);
2076
2423
  return this;
2077
2424
  },
2425
+ setStep: function setStep(step) {
2426
+ controller.setStep(step);
2427
+ return this;
2428
+ },
2429
+ setPrecision: function setPrecision(precision) {
2430
+ controller.setPrecision(precision);
2431
+ return this;
2432
+ },
2433
+ setFormatValue: function setFormatValue(formatter) {
2434
+ controller.setFormatValue(formatter);
2435
+ return this;
2436
+ },
2437
+ setDirection: function setDirection(direction) {
2438
+ controller.setDirection(direction);
2439
+ return this;
2440
+ },
2078
2441
  setShowValue: function setShowValue(show) {
2079
2442
  controller.setShowValue(show);
2080
2443
  return this;
@@ -2085,9 +2448,12 @@ function createProgressCapsule(container) {
2085
2448
  return !color;
2086
2449
  })) return this;
2087
2450
  dirty.colors = true;
2451
+ colorOverride = _toConsumableArray(next);
2452
+ preset.colors = _toConsumableArray(next);
2088
2453
  controller.setColors(next);
2089
2454
  if (overlay) overlay.setColors(next);
2090
2455
  controller.resizeCanvas();
2456
+ if (isMotionPaused()) renderOnce();
2091
2457
  return this;
2092
2458
  },
2093
2459
  setSize: function setSize(width, height) {
@@ -2101,30 +2467,68 @@ function createProgressCapsule(container) {
2101
2467
  }
2102
2468
  applySize();
2103
2469
  controller.resizeCanvas();
2470
+ if (isMotionPaused()) renderOnce();
2104
2471
  return this;
2105
2472
  },
2106
2473
  randomize: function randomize() {
2107
- controller.randomize();
2474
+ flowTime = controller.randomize();
2475
+ if (isMotionPaused()) renderOnce();
2108
2476
  return this;
2109
2477
  },
2110
2478
  pause: function pause() {
2111
- paused = true;
2479
+ dirty.paused = true;
2480
+ manuallyPaused = true;
2481
+ renderOnce();
2112
2482
  return this;
2113
2483
  },
2114
2484
  resume: function resume() {
2115
- paused = false;
2485
+ dirty.paused = true;
2486
+ manuallyPaused = false;
2487
+ wakeScheduler();
2488
+ return this;
2489
+ },
2490
+ setPaused: function setPaused(value) {
2491
+ return value ? this.pause() : this.resume();
2492
+ },
2493
+ setStatic: function setStatic(value) {
2494
+ dirty.static = true;
2495
+ staticMode = value === true;
2496
+ merged.static = staticMode;
2497
+ if (staticMode) renderOnce();else wakeScheduler();
2498
+ return this;
2499
+ },
2500
+ setFps: function setFps(fps) {
2501
+ dirty.fps = true;
2502
+ merged.fps = frameGate.setFps(fps);
2503
+ wakeScheduler();
2116
2504
  return this;
2117
2505
  },
2118
2506
  setQuality: function setQuality(quality) {
2507
+ dirty.quality = true;
2119
2508
  merged.quality = quality;
2120
- controller.dprCap = dprCapFor(quality);
2509
+ controller.dprCap = effectiveDprCap(quality, merged.renderScale);
2510
+ controller.resizeCanvas();
2511
+ if (overlay) overlay.setDprCap(controller.dprCap);
2512
+ if (isMotionPaused()) renderOnce();
2513
+ return this;
2514
+ },
2515
+ setRenderScale: function setRenderScale(renderScale) {
2516
+ var value = Number(renderScale);
2517
+ if (!Number.isFinite(value)) return this;
2518
+ dirty.renderScale = true;
2519
+ merged.renderScale = Math.min(1, Math.max(0.25, value));
2520
+ controller.dprCap = effectiveDprCap(merged.quality, merged.renderScale);
2121
2521
  controller.resizeCanvas();
2522
+ if (overlay) overlay.setDprCap(controller.dprCap);
2523
+ if (isMotionPaused()) renderOnce();
2122
2524
  return this;
2123
2525
  },
2124
2526
  dispose: function dispose() {
2125
2527
  disposed = true;
2126
2528
  unsubscribe();
2529
+ offPausedChange();
2127
2530
  visibility.dispose();
2531
+ motionPreference.dispose();
2128
2532
  if (overlay) overlay.dispose();
2129
2533
  controller.dispose();
2130
2534
  root.remove();
@@ -2135,6 +2539,45 @@ function createProgressCapsule(container) {
2135
2539
  get cssVars() {
2136
2540
  return merged.cssVars;
2137
2541
  },
2542
+ get min() {
2543
+ return controller.min;
2544
+ },
2545
+ get max() {
2546
+ return controller.max;
2547
+ },
2548
+ get step() {
2549
+ return controller.step;
2550
+ },
2551
+ get precision() {
2552
+ return controller.precision;
2553
+ },
2554
+ get formatValue() {
2555
+ return controller.formatValue;
2556
+ },
2557
+ get direction() {
2558
+ return controller.direction;
2559
+ },
2560
+ get showValue() {
2561
+ return !valueElement.hidden;
2562
+ },
2563
+ get valueSuffix() {
2564
+ return controller.valueSuffix;
2565
+ },
2566
+ get quality() {
2567
+ return merged.quality;
2568
+ },
2569
+ get renderScale() {
2570
+ return merged.renderScale;
2571
+ },
2572
+ get paused() {
2573
+ return manuallyPaused;
2574
+ },
2575
+ get static() {
2576
+ return staticMode;
2577
+ },
2578
+ get fps() {
2579
+ return frameGate.getFps();
2580
+ },
2138
2581
  dirty: dirty
2139
2582
  };
2140
2583
  }