@5even7/dlc-ui 0.2.2 → 0.2.4

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/vue2.mjs CHANGED
@@ -175,9 +175,13 @@ function getPreset(kind, ref) {
175
175
  const list = kind === 'capsule' ? CAPSULE_PRESETS : kind === 'progress' ? PROGRESS_PRESETS : null;
176
176
  if (!list) throw new Error(`Unknown preset kind: ${kind} (use "capsule" or "progress")`);
177
177
  const key = String(ref).trim().toLowerCase();
178
- const found = list.find(
179
- (preset) => preset.name.toLowerCase() === key
180
- );
178
+ let found = null;
179
+ for (let index = 0; index < list.length; index += 1) {
180
+ if (list[index].name.toLowerCase() === key) {
181
+ found = list[index];
182
+ break;
183
+ }
184
+ }
181
185
  if (!found) throw new Error(`Unknown ${kind} preset: ${ref}`);
182
186
  return found;
183
187
  }
@@ -196,10 +200,10 @@ const DEFAULTS = {
196
200
  width: 454,
197
201
  height: 104,
198
202
  min: 0,
199
- max: 100,
203
+ max: 100,
200
204
  draggable: true,
201
- keyboard: true,
202
205
  disabled: false,
206
+ readonly: false,
203
207
  valueSuffix: '%',
204
208
  edgeStyle: 'flow',
205
209
  quality: 'auto',
@@ -390,7 +394,7 @@ function normalizeRgbArgs(args) {
390
394
  if (parts.length < 3) return null;
391
395
  const to255 = (value) => {
392
396
  if (typeof value !== 'string' || !value) return null;
393
- if (value.endsWith('%')) return clampChannel((parseFloat(value) / 100) * 255);
397
+ if (value.charAt(value.length - 1) === '%') return clampChannel((parseFloat(value) / 100) * 255);
394
398
  const number = Number(value);
395
399
  return Number.isFinite(number) ? clampChannel(number) : null;
396
400
  };
@@ -398,7 +402,11 @@ function normalizeRgbArgs(args) {
398
402
  const green = to255(parts[1]);
399
403
  const blue = to255(parts[2]);
400
404
  if (red === null || green === null || blue === null) return null;
401
- return `#${[red, green, blue].map((n) => n.toString(16).padStart(2, '0')).join('')}`;
405
+ const hex = (n) => {
406
+ const text = n.toString(16);
407
+ return text.length === 1 ? `0${text}` : text;
408
+ };
409
+ return `#${hex(red)}${hex(green)}${hex(blue)}`;
402
410
  }
403
411
 
404
412
  /**
@@ -755,12 +763,13 @@ function rgb(color, alpha = 1) {
755
763
  }
756
764
 
757
765
  class FallbackRenderer {
758
- constructor(canvas, preset) {
759
- this.canvas = canvas;
760
- this.preset = preset;
761
- this.context = canvas.getContext('2d');
762
- this.visible = true;
763
- }
766
+ constructor(canvas, preset) {
767
+ this.canvas = canvas;
768
+ this.preset = preset;
769
+ this.context = canvas.getContext('2d');
770
+ this.visible = true;
771
+ this.timePhase = 0;
772
+ }
764
773
 
765
774
  resize() {
766
775
  const rect = this.canvas.getBoundingClientRect();
@@ -776,7 +785,7 @@ class FallbackRenderer {
776
785
  drawNebula(time) {
777
786
  const ctx = this.context;
778
787
  const { width, height } = this.canvas;
779
- const t = time * (this.preset.speed || 1);
788
+ const t = (time + this.timePhase) * (this.preset.speed || 1);
780
789
  const phase = (this.preset.seed || 0) * 0.7;
781
790
  const gradient = ctx.createLinearGradient(0, 0, width, height);
782
791
  gradient.addColorStop(0, this.preset.colors[0]);
@@ -811,7 +820,10 @@ class FallbackRenderer {
811
820
  }
812
821
 
813
822
  randomize() {
814
- if (this.preset) this.preset.seed = Math.random() * 100;
823
+ if (this.preset) {
824
+ this.preset.seed = Math.random() * 100;
825
+ this.timePhase = Math.random() * Math.PI * 2;
826
+ }
815
827
  }
816
828
  setMouseColor() {}
817
829
  dispose() {}
@@ -1037,6 +1049,10 @@ function createCapsule(container, options = {}) {
1037
1049
  for (const name of Object.keys(vars)) root.style.setProperty(name, vars[name]);
1038
1050
  if (merged.textRatio !== undefined) {
1039
1051
  root.style.setProperty('--hj-text-width', `${merged.textRatio}%`);
1052
+ const userMinWidth = merged.cssVars && merged.cssVars['--hj-text-min-width'];
1053
+ if (merged.textRatio === 0) root.style.setProperty('--hj-text-min-width', '0');
1054
+ else if (userMinWidth) root.style.setProperty('--hj-text-min-width', userMinWidth);
1055
+ else root.style.removeProperty('--hj-text-min-width');
1040
1056
  }
1041
1057
  renderer.resize();
1042
1058
  };
@@ -1135,6 +1151,10 @@ function createCapsule(container, options = {}) {
1135
1151
  dirty.textRatio = true;
1136
1152
  merged.textRatio = value;
1137
1153
  root.style.setProperty('--hj-text-width', `${value}%`);
1154
+ const userMinWidth = merged.cssVars && merged.cssVars['--hj-text-min-width'];
1155
+ if (value === 0) root.style.setProperty('--hj-text-min-width', '0');
1156
+ else if (userMinWidth) root.style.setProperty('--hj-text-min-width', userMinWidth);
1157
+ else root.style.removeProperty('--hj-text-min-width');
1138
1158
  return this;
1139
1159
  },
1140
1160
  setCssVars(vars) {
@@ -1150,11 +1170,17 @@ function createCapsule(container, options = {}) {
1150
1170
  return this;
1151
1171
  },
1152
1172
  setSize(width, height) {
1153
- if (width !== undefined) merged.width = width;
1154
- if (height !== undefined) merged.height = height;
1155
- applySize();
1156
- return this;
1157
- },
1173
+ if (width !== undefined) {
1174
+ parseSize(width); // 非法尺寸直接抛错前先校验,避免污染内部状态
1175
+ merged.width = width;
1176
+ }
1177
+ if (height !== undefined) {
1178
+ parseSize(height);
1179
+ merged.height = height;
1180
+ }
1181
+ applySize();
1182
+ return this;
1183
+ },
1158
1184
  randomize() {
1159
1185
  this.setSeed(Math.random() * 100);
1160
1186
  return this;
@@ -1181,8 +1207,8 @@ function createCapsule(container, options = {}) {
1181
1207
  renderer.dispose();
1182
1208
  root.remove();
1183
1209
  },
1184
- textRatio: merged.textRatio,
1185
- cssVars: merged.cssVars,
1210
+ get textRatio() { return merged.textRatio; },
1211
+ get cssVars() { return merged.cssVars; },
1186
1212
  dirty
1187
1213
  };
1188
1214
  }
@@ -1667,13 +1693,17 @@ const PALETTES = {
1667
1693
  'tide': ['#0a2239', '#2e9bff', '#7fe3ff', '#eaf9ff']
1668
1694
  };
1669
1695
 
1670
- function drawCloud(context, x, y, radiusX, radiusY, color, alpha) {
1671
- context.save();
1672
- context.translate(x, y);
1673
- context.scale(1, radiusY / radiusX);
1674
- const gradient = context.createRadialGradient(0, 0, 0, 0, 0, radiusX);
1675
- gradient.addColorStop(0, `${color}${Math.round(alpha * 255).toString(16).padStart(2, '0')}`);
1676
- gradient.addColorStop(0.46, `${color}${Math.round(alpha * 0.42 * 255).toString(16).padStart(2, '0')}`);
1696
+ function drawCloud(context, x, y, radiusX, radiusY, color, alpha) {
1697
+ context.save();
1698
+ context.translate(x, y);
1699
+ context.scale(1, radiusY / radiusX);
1700
+ const gradient = context.createRadialGradient(0, 0, 0, 0, 0, radiusX);
1701
+ const alphaHex = (value) => {
1702
+ const text = Math.round(value).toString(16);
1703
+ return text.length === 1 ? `0${text}` : text;
1704
+ };
1705
+ gradient.addColorStop(0, `${color}${alphaHex(alpha * 255)}`);
1706
+ gradient.addColorStop(0.46, `${color}${alphaHex(alpha * 0.42 * 255)}`);
1677
1707
  gradient.addColorStop(1, `${color}00`);
1678
1708
  context.fillStyle = gradient;
1679
1709
  context.fillRect(-radiusX, -radiusX, radiusX * 2, radiusX * 2);
@@ -2103,9 +2133,14 @@ class ProgressCapsuleController {
2103
2133
  ctx.restore();
2104
2134
  }
2105
2135
 
2106
- setRange(min, max) {
2107
- this.min = min;
2108
- this.max = max;
2136
+ setRange(min, max) {
2137
+ if (min > max) {
2138
+ const swap = min;
2139
+ min = max;
2140
+ max = swap;
2141
+ }
2142
+ this.min = min;
2143
+ this.max = max;
2109
2144
  const clamped = clamp(this.value, this.min, this.max);
2110
2145
  if (clamped !== this.value) this.setProgress(clamped, 'prop');
2111
2146
  }
@@ -2244,6 +2279,8 @@ class ProgressCapsuleController {
2244
2279
  beginDrag(event) {
2245
2280
  if (event.button !== undefined && event.button !== 0) return;
2246
2281
  event.preventDefault();
2282
+ // preventDefault 会吞掉点击聚焦,主动聚焦让键盘方向键立即可用。
2283
+ try { this.root.focus({ preventScroll: true }); } catch { this.root.focus(); }
2247
2284
  this.dragging = true;
2248
2285
  this.pendingPointer = null;
2249
2286
  this._dragRaf = 0;
@@ -2298,33 +2335,7 @@ class ProgressCapsuleController {
2298
2335
  this.root.addEventListener('pointercancel', this.handlers.pointercancel);
2299
2336
  }
2300
2337
 
2301
- if (this.options.keyboard !== false) {
2302
- this.handlers.keydown = (event) => {
2303
- const step = (this.max - this.min) * 0.02;
2304
- const actions = {
2305
- ArrowLeft: -step,
2306
- ArrowDown: -step,
2307
- ArrowRight: step,
2308
- ArrowUp: step,
2309
- PageDown: -step * 5,
2310
- PageUp: step * 5
2311
- };
2312
- if (event.key === 'Home') {
2313
- event.preventDefault();
2314
- this.setProgress(this.min, 'keyboard');
2315
- } else if (event.key === 'End') {
2316
- event.preventDefault();
2317
- this.setProgress(this.max, 'keyboard');
2318
- } else if (actions[event.key]) {
2319
- event.preventDefault();
2320
- this.setProgress(this.value + actions[event.key], 'keyboard');
2321
- } else {
2322
- return;
2323
- }
2324
- };
2325
- this.root.addEventListener('keydown', this.handlers.keydown);
2326
- }
2327
- }
2338
+ }
2328
2339
 
2329
2340
  update(delta, paused) {
2330
2341
  if (!paused) this.flowTime += delta;
@@ -2362,7 +2373,7 @@ class ProgressCapsuleController {
2362
2373
  * Mount a fluid progress capsule into `container`.
2363
2374
  *
2364
2375
  * Options: preset (id/code/name or object), width, height, value, min, max,
2365
- * draggable, keyboard, colors, edgeStyle, textRatio (0-100, text region
2376
+ * draggable, colors, edgeStyle, textRatio (0-100, text region
2366
2377
  * width in percent), text (HTML string or DOM nodes for the text slot),
2367
2378
  * colorContent (HTML string or DOM nodes for the color slot),
2368
2379
  * showValue (show/hide the right-side percentage), quality,
@@ -2375,6 +2386,11 @@ function createProgressCapsule(container, options = {}) {
2375
2386
 
2376
2387
  const preset = { ...getPreset('progress', options.preset ?? '星火') };
2377
2388
  const merged = normalizeOptions(DEFAULTS.progress, preset, options);
2389
+ if (merged.min > merged.max) {
2390
+ const swap = merged.min;
2391
+ merged.min = merged.max;
2392
+ merged.max = swap;
2393
+ }
2378
2394
  const normalizedColors = merged.colors.map(normalizeColor);
2379
2395
  if (normalizedColors.every(Boolean)) preset.colors = normalizedColors;
2380
2396
  preset.edgeStyle = merged.edgeStyle;
@@ -2383,8 +2399,9 @@ function createProgressCapsule(container, options = {}) {
2383
2399
  // Vue 的裸布尔属性(<ProgressCapsule disabled />)会传成空字符串,
2384
2400
  // 这里统一按 true 处理。
2385
2401
  const disabled = merged.disabled === true || merged.disabled === '' || merged.disabled === 'true';
2386
- const effectiveDraggable = disabled ? false : merged.draggable !== false;
2387
- const effectiveKeyboard = disabled ? false : merged.keyboard !== false;
2402
+ const readonly = merged.readonly === true || merged.readonly === '' || merged.readonly === 'true';
2403
+ const locked = disabled || readonly;
2404
+ const effectiveDraggable = locked ? false : merged.draggable !== false;
2388
2405
  const dirty = {
2389
2406
  preset: false,
2390
2407
  colors: false,
@@ -2397,12 +2414,16 @@ function createProgressCapsule(container, options = {}) {
2397
2414
  const root = document.createElement('div');
2398
2415
  root.className = 'hj-capsule-root hj-progress-root';
2399
2416
  root.setAttribute('role', 'slider');
2400
- root.setAttribute('tabindex', '0');
2401
2417
  root.setAttribute('data-draggable', String(effectiveDraggable));
2402
2418
  if (disabled) {
2403
2419
  root.setAttribute('aria-disabled', 'true');
2404
2420
  root.classList.add('is-disabled');
2405
2421
  }
2422
+ if (readonly) {
2423
+ root.setAttribute('aria-readonly', 'true');
2424
+ root.classList.add('is-readonly');
2425
+ }
2426
+ if (locked) root.setAttribute('tabindex', '-1');
2406
2427
  root.setAttribute('aria-valuemin', String(merged.min ?? 0));
2407
2428
  root.setAttribute('aria-valuemax', String(merged.max ?? 100));
2408
2429
  root.setAttribute('aria-valuenow', String(preset.initialProgress));
@@ -2413,8 +2434,6 @@ function createProgressCapsule(container, options = {}) {
2413
2434
  );
2414
2435
  };
2415
2436
  updateAria();
2416
- if (!effectiveKeyboard) root.setAttribute('tabindex', '-1');
2417
-
2418
2437
  const canvas = document.createElement('canvas');
2419
2438
  canvas.className = 'hj-progress-canvas';
2420
2439
  canvas.setAttribute('aria-hidden', 'true');
@@ -2476,7 +2495,7 @@ function createProgressCapsule(container, options = {}) {
2476
2495
  valueElement,
2477
2496
  preset,
2478
2497
  emitter,
2479
- options: { ...merged, draggable: effectiveDraggable, keyboard: effectiveKeyboard },
2498
+ options: { ...merged, draggable: effectiveDraggable },
2480
2499
  copy,
2481
2500
  dirty
2482
2501
  });
@@ -2617,10 +2636,16 @@ function createProgressCapsule(container, options = {}) {
2617
2636
  controller.resizeCanvas();
2618
2637
  return this;
2619
2638
  },
2620
- setSize(width, height) {
2621
- if (width !== undefined) merged.width = width;
2622
- if (height !== undefined) merged.height = height;
2623
- applySize();
2639
+ setSize(width, height) {
2640
+ if (width !== undefined) {
2641
+ parseSize(width);
2642
+ merged.width = width;
2643
+ }
2644
+ if (height !== undefined) {
2645
+ parseSize(height);
2646
+ merged.height = height;
2647
+ }
2648
+ applySize();
2624
2649
  controller.resizeCanvas();
2625
2650
  return this;
2626
2651
  },
@@ -2650,8 +2675,8 @@ function createProgressCapsule(container, options = {}) {
2650
2675
  controller.dispose();
2651
2676
  root.remove();
2652
2677
  },
2653
- textRatio: merged.textRatio,
2654
- cssVars: merged.cssVars,
2678
+ get textRatio() { return merged.textRatio; },
2679
+ get cssVars() { return merged.cssVars; },
2655
2680
  dirty
2656
2681
  };
2657
2682
  }
@@ -2871,9 +2896,9 @@ function createColorBackground(host, options = {}) {
2871
2896
  renderer.dispose();
2872
2897
  layer.remove();
2873
2898
  },
2874
- opacity: merged.opacity,
2875
- fallbackColor: merged.fallbackColor,
2876
- mouseColor: merged.mouseColor,
2899
+ get opacity() { return merged.opacity; },
2900
+ get fallbackColor() { return merged.fallbackColor; },
2901
+ get mouseColor() { return merged.mouseColor; },
2877
2902
  dirty
2878
2903
  };
2879
2904
  }
@@ -2909,6 +2934,9 @@ function makeWrapper({ name, props, events, create, render, methods, watch }) {
2909
2934
  const extraState = this.runtimeState();
2910
2935
  if (this.controller) this.controller.dispose();
2911
2936
  this.mountController(extraState);
2937
+ // 结构属性连续变化时,插槽内容可能在重建后才被 Vue 写入容器,
2938
+ // 补一次搬运确保不丢。
2939
+ this.$nextTick(() => this.moveSlots());
2912
2940
  },
2913
2941
  scheduleRecreate() {
2914
2942
  if (this._recreateQueued) return;
@@ -2923,13 +2951,15 @@ function makeWrapper({ name, props, events, create, render, methods, watch }) {
2923
2951
  const root = this.controller.element;
2924
2952
  const textLayer = root.querySelector('.hj-capsule-text, .hj-progress-text');
2925
2953
  const visualLayer = root.querySelector('.hj-capsule-visual, .hj-progress-visual');
2926
- const move = (wrapper, layer) => {
2927
- if (!wrapper || !layer) return;
2928
- for (const node of Array.from(wrapper.childNodes)) layer.appendChild(node);
2929
- };
2930
2954
  const refs = this.$refs || {};
2931
- move(refs.slotText, textLayer);
2932
- move(refs.slotColor, visualLayer);
2955
+ // 把插槽容器整体搬进对应区域,而不是搬子节点:Vue 重渲染/重建组件时
2956
+ // 内容始终留在区域内,不会丢(子节点搬运会在 recreate 时序下丢失)。
2957
+ if (refs.slotText && textLayer && refs.slotText.parentElement !== textLayer) {
2958
+ textLayer.appendChild(refs.slotText);
2959
+ }
2960
+ if (refs.slotColor && visualLayer && refs.slotColor.parentElement !== visualLayer) {
2961
+ visualLayer.appendChild(refs.slotColor);
2962
+ }
2933
2963
  },
2934
2964
  applyAttrs() {
2935
2965
  if (!this.controller || !this.controller.element || !this.$attrs) return;
@@ -3056,7 +3086,7 @@ function makeProgressWrapper(createProgressCapsule, render) {
3056
3086
  name: 'ProgressCapsule',
3057
3087
  props: [
3058
3088
  'preset', 'width', 'height', 'value', 'min', 'max',
3059
- 'draggable', 'keyboard', 'disabled', 'edgeStyle', 'colors', 'textRatio', 'label', 'showValue', 'valueSuffix',
3089
+ 'draggable', 'disabled', 'readonly', 'edgeStyle', 'colors', 'textRatio', 'label', 'showValue', 'valueSuffix',
3060
3090
  'quality', 'renderer', 'respectReducedMotion', 'cssVars'
3061
3091
  ],
3062
3092
  events: PROGRESS_EVENTS,
@@ -3093,12 +3123,12 @@ function makeProgressWrapper(createProgressCapsule, render) {
3093
3123
  disabled() {
3094
3124
  this.scheduleRecreate();
3095
3125
  },
3126
+ readonly() {
3127
+ this.scheduleRecreate();
3128
+ },
3096
3129
  draggable() {
3097
3130
  this.scheduleRecreate();
3098
3131
  },
3099
- keyboard() {
3100
- this.scheduleRecreate();
3101
- },
3102
3132
  renderer() {
3103
3133
  this.scheduleRecreate();
3104
3134
  }