@arraypress/waveform-player 1.12.0 → 1.13.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arraypress/waveform-player",
3
- "version": "1.12.0",
3
+ "version": "1.13.0",
4
4
  "description": "Lightweight, customizable audio player with waveform visualization",
5
5
  "type": "module",
6
6
  "types": "./index.d.ts",
@@ -59,9 +59,9 @@
59
59
  nudged 1px) for the pause glyph would otherwise change the button's
60
60
  auto width, shifting the adjacent waveform — and re-sampling its bars —
61
61
  on every play/pause. Fixed width keeps the canvas (and bars) stable. */
62
- width: 1.625rem;
63
- height: 1.625rem;
64
- min-width: 1.625rem;
62
+ width: 2.25rem;
63
+ height: 2.25rem;
64
+ min-width: 2.25rem;
65
65
  border: none;
66
66
  border-radius: 0;
67
67
  opacity: 0.7;
@@ -73,8 +73,8 @@
73
73
  }
74
74
 
75
75
  .waveform-btn-minimal svg {
76
- width: 22px;
77
- height: 22px;
76
+ width: 28px;
77
+ height: 28px;
78
78
  }
79
79
 
80
80
  .waveform-btn:disabled {
package/src/js/core.js CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  escapeHtml
17
17
  } from './utils.js';
18
18
 
19
- import {DEFAULT_OPTIONS, STYLE_DEFAULTS, getColorPreset} from './themes.js';
19
+ import {DEFAULT_OPTIONS, STYLE_DEFAULTS, getColorPreset, COLOR_PRESETS} from './themes.js';
20
20
 
21
21
  // Keyboard seek steps (seconds) for the accessible slider.
22
22
  const SEEK_STEP_SECONDS = 5;
@@ -75,10 +75,17 @@ export class WaveformPlayer {
75
75
  // Apply color preset (auto-detect if not specified)
76
76
  const preset = getColorPreset(this.options.colorPreset);
77
77
 
78
+ // Track auto-theme state so the player can re-detect on a runtime light/
79
+ // dark switch. Only colours the user LEFT UNSET follow the preset; an
80
+ // explicit colorPreset or hand-set colour is never overridden.
81
+ this._autoTheme = this.options.colorPreset == null || !COLOR_PRESETS[this.options.colorPreset];
82
+ this._presetKeys = [];
83
+
78
84
  // Apply preset colors only if individual colors aren't explicitly set
79
85
  for (const [key, value] of Object.entries(preset)) {
80
86
  if (this.options[key] === null || this.options[key] === undefined) {
81
87
  this.options[key] = value;
88
+ this._presetKeys.push(key);
82
89
  }
83
90
  }
84
91
 
@@ -116,6 +123,9 @@ export class WaveformPlayer {
116
123
  // Add to instances
117
124
  WaveformPlayer.instances.set(this.id, this);
118
125
 
126
+ // Re-detect the theme on runtime light/dark switches (shared watcher).
127
+ WaveformPlayer._watchTheme();
128
+
119
129
  // Initialize
120
130
  this.init();
121
131
 
@@ -1388,6 +1398,72 @@ export class WaveformPlayer {
1388
1398
  }
1389
1399
  }
1390
1400
 
1401
+ /**
1402
+ * Re-detect the page theme and re-apply auto colours + redraw, so an
1403
+ * auto-themed player adapts to a runtime light/dark switch (not just the
1404
+ * value present on load). No-op when the player uses an explicit
1405
+ * `colorPreset` or hand-set colours. Driven by the shared theme watcher.
1406
+ * @public
1407
+ */
1408
+ refreshTheme() {
1409
+ if (!this._autoTheme || !this._presetKeys || !this._presetKeys.length) return;
1410
+ const preset = getColorPreset(this.options.colorPreset);
1411
+ let changed = false;
1412
+ for (const key of this._presetKeys) {
1413
+ if (this.options[key] !== preset[key]) {
1414
+ this.options[key] = preset[key];
1415
+ changed = true;
1416
+ }
1417
+ }
1418
+ if (changed) this._applyThemeColors();
1419
+ }
1420
+
1421
+ /**
1422
+ * Push the current resolved colours onto the live DOM (button border/icon,
1423
+ * title, subtitle, time, BPM) and redraw the waveform.
1424
+ * @private
1425
+ */
1426
+ _applyThemeColors() {
1427
+ const o = this.options;
1428
+ if (this.playBtn) {
1429
+ this.playBtn.style.borderColor = o.buttonColor;
1430
+ this.playBtn.style.color = o.buttonColor;
1431
+ }
1432
+ if (this.titleEl) this.titleEl.style.color = o.textColor;
1433
+ if (this.subtitleEl) this.subtitleEl.style.color = o.textSecondaryColor;
1434
+ this.container.querySelectorAll('.waveform-time, .waveform-bpm').forEach((el) => {
1435
+ el.style.color = o.textSecondaryColor;
1436
+ });
1437
+ if (this.canvas) this.drawWaveform();
1438
+ }
1439
+
1440
+ /**
1441
+ * Lazily install ONE shared watcher that re-detects the theme for every
1442
+ * auto-themed instance when the document theme changes — a class/attribute
1443
+ * flip on `<html>`/`<body>` (Tailwind `dark`, `data-theme`,
1444
+ * `data-color-scheme`) or an OS `prefers-color-scheme` change. Event-driven
1445
+ * (MutationObserver + matchMedia), never a timer.
1446
+ * @private
1447
+ */
1448
+ static _watchTheme() {
1449
+ if (WaveformPlayer._themeWatch || typeof document === 'undefined') return;
1450
+ const refresh = () => requestAnimationFrame(() => {
1451
+ WaveformPlayer.instances.forEach((p) => {
1452
+ try { p.refreshTheme(); } catch (e) { /* ignore */ }
1453
+ });
1454
+ });
1455
+ const opts = {attributes: true, attributeFilter: ['class', 'data-theme', 'data-color-scheme', 'style']};
1456
+ const obs = new MutationObserver(refresh);
1457
+ obs.observe(document.documentElement, opts);
1458
+ if (document.body) obs.observe(document.body, opts);
1459
+ let mq = null;
1460
+ try {
1461
+ mq = window.matchMedia('(prefers-color-scheme: dark)');
1462
+ mq.addEventListener('change', refresh);
1463
+ } catch (e) { /* no matchMedia */ }
1464
+ WaveformPlayer._themeWatch = {obs, mq, refresh};
1465
+ }
1466
+
1391
1467
  /**
1392
1468
  * Sync the speed control's label and the menu's active-option highlight to
1393
1469
  * the audio element's current `playbackRate`. No-op in external mode (no