@outbook/webcomponents-player 1.3.0 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,8 +13,8 @@ npm install @outbook/webcomponents-player
13
13
  ### As a Lit Element
14
14
 
15
15
  ```javascript
16
- import { html } from 'lit';
17
- import { Player } from '@outbook/webcomponents-player';
16
+ import {html} from 'lit';
17
+ import {Player} from '@outbook/webcomponents-player';
18
18
 
19
19
  const myPlaylist = [
20
20
  {
@@ -42,9 +42,9 @@ const myPlaylist = [
42
42
  function render() {
43
43
  return html`
44
44
  ${Player({
45
- playlist: myPlaylist,
46
- lang: 'en'
47
- })}
45
+ playlist: myPlaylist,
46
+ lang: 'en'
47
+ })}
48
48
  `;
49
49
  }
50
50
  ```
@@ -1,12 +1,17 @@
1
- import { virtual } from 'haunted';
2
1
  import { html } from 'lit';
3
2
  import { TypeIcon } from '@outbook/webcomponents-type-icon/shadow';
4
- import { skip_next, skip_previous, play_arrow, pause, stop } from '@outbook/icons';
3
+ import {
4
+ skip_next,
5
+ skip_previous,
6
+ play_arrow,
7
+ pause,
8
+ stop
9
+ } from '@outbook/icons';
5
10
  import { ifDefined } from 'lit/directives/if-defined.js';
6
11
  import { isEventClick, isKeyEnterOrKeySpace } from 'a11y-key-conjurer';
7
12
  import { classMap } from 'lit/directives/class-map.js';
8
13
 
9
- export const Controls = virtual(({ props, handleAudio, literals = {} }) => {
14
+ export function Controls({ props, handleAudio, literals = {} }) {
10
15
  const { isPlaying = false, playlist, indexSelected } = props;
11
16
  const controlsDisabled = !Number.isInteger(indexSelected);
12
17
  const controlItems = [
@@ -57,15 +62,15 @@ export const Controls = virtual(({ props, handleAudio, literals = {} }) => {
57
62
  }
58
63
 
59
64
  return html`
60
- <div class="myth-controls">
61
- <ul class="myth-controls__items">
65
+ <div class="player-controls">
66
+ <ul class="player-controls__items">
62
67
  ${controlItems.map(item => {
63
68
  const buttonClasses = classMap({
64
- 'myth-controls__button': true,
69
+ 'player-controls__button': true,
65
70
  'is-disabled': item.isDisabled
66
71
  });
67
72
  return html`
68
- <li class="myth-controls__item">
73
+ <li class="player-controls__item">
69
74
  <button
70
75
  class="${buttonClasses}"
71
76
  aria-label="${item.label}"
@@ -79,7 +84,7 @@ export const Controls = virtual(({ props, handleAudio, literals = {} }) => {
79
84
  ${TypeIcon({
80
85
  icon: item.icon,
81
86
  icons: { skip_next, skip_previous, play_arrow, pause, stop },
82
- extraClasses: 'myth-controls__icon'
87
+ extraClasses: 'player-controls__icon'
83
88
  })}
84
89
  </button>
85
90
  </li>
@@ -88,4 +93,4 @@ export const Controls = virtual(({ props, handleAudio, literals = {} }) => {
88
93
  </ul>
89
94
  </div>
90
95
  `;
91
- });
96
+ }
@@ -32,18 +32,18 @@ export const Playlist = virtual(
32
32
  };
33
33
  }
34
34
  return html`
35
- <div class="myth-playlist" id="${listId}">
36
- <ul class="myth-playlist__items">
35
+ <div class="player-playlist" id="${listId}">
36
+ <ul class="player-playlist__items">
37
37
  ${_playlist.map((item, index) => {
38
38
  const isPlayingItem = indexSelected === index;
39
39
  const itemClasses = classMap({
40
- 'myth-playlist__item': true,
40
+ 'player-playlist__item': true,
41
41
  'is-playing': isPlayingItem
42
42
  });
43
43
  return html`
44
44
  <li class="${itemClasses}" data-playlist-item="${index}">
45
45
  <div
46
- class="myth-playlist__item-button"
46
+ class="player-playlist__item-button"
47
47
  tabindex="0"
48
48
  role="button"
49
49
  @click="${handleButton(index)}"
@@ -53,13 +53,13 @@ export const Playlist = virtual(
53
53
  ${showIconIsPlaying && isPlayingItem
54
54
  ? html`
55
55
  <div
56
- class="myth-playlist__item-block myth-playlist__item-playing-icon"
56
+ class="player-playlist__item-block player-playlist__item-playing-icon"
57
57
  >
58
58
  ${TypeIcon({
59
59
  icon: isPlaying ? 'play_arrow' : 'pause',
60
60
  icons: { play_arrow, pause },
61
61
  extraClasses:
62
- 'myth-playlist__item-playing-icon-inner'
62
+ 'player-playlist__item-playing-icon-inner'
63
63
  })}
64
64
  </div>
65
65
  `
@@ -67,13 +67,13 @@ export const Playlist = virtual(
67
67
  ${showTrackNumber
68
68
  ? html`
69
69
  <div
70
- class="myth-playlist__item-block myth-item-text size-small"
70
+ class="player-playlist__item-block track-text__item size-small"
71
71
  >
72
72
  ${item.trackNumber || index + 1}
73
73
  </div>
74
74
  `
75
75
  : nothing}
76
- <div class="myth-playlist__item-block">
76
+ <div class="player-playlist__item-block">
77
77
  ${TrackText({
78
78
  title: item.title,
79
79
  artist: item.artist,
@@ -82,11 +82,11 @@ export const Playlist = virtual(
82
82
  </div>
83
83
  ${item.duration || showFileExtension
84
84
  ? html`
85
- <div class="myth-playlist__item-group is-last-right">
85
+ <div class="player-playlist__item-group is-last-right">
86
86
  ${ItemDuration({ duration: item.duration })}
87
87
  ${showFileExtension
88
88
  ? html`
89
- <div class="myth-playlist__item-block">
89
+ <div class="player-playlist__item-block">
90
90
  ${BadgeFileExtension({ text: item.format })}
91
91
  </div>
92
92
  `
@@ -1,17 +1,16 @@
1
- import { virtual } from 'haunted';
2
1
  import { html } from 'lit';
3
2
  import {
4
3
  readable as formatTime,
5
4
  iso as formatTimeDuration
6
5
  } from 'mystic-format-time';
7
6
 
8
- export const ItemDuration = virtual(({ duration }) => {
7
+ export function ItemDuration({ duration }) {
9
8
  return html`
10
9
  <time
11
- class="myth-playlist__item-block myth-item-text size-small"
10
+ class="player-playlist__item-block track-text__item size-small"
12
11
  datetime="${formatTimeDuration(duration)}"
13
12
  >
14
13
  ${formatTime(duration)}
15
14
  </time>
16
15
  `;
17
- });
16
+ }
@@ -1,14 +1,12 @@
1
- import { virtual } from 'haunted';
2
1
  import { html } from 'lit';
3
- import { CoverFallback } from '../../cover-fallback/index.js';
4
- import { getCover } from '../../../_lib/get-cover.js';
2
+ import { getCover } from '../../get-cover.js';
3
+ import { PlayerArtwork } from '@outbook/webcomponents-player-artwork';
5
4
 
6
- export const ShowCover = virtual(({ cover }) => {
5
+ export const ShowCover = ({ cover }) => {
7
6
  return html`
8
- <div class="myth-playlist__item-block myth-playlist__item-image">
9
- ${cover
10
- ? html`<img src="${getCover(cover)}" alt="" aria-hidden="true" />`
11
- : CoverFallback({ isInFileItem: true })}
12
- </div>
7
+ ${PlayerArtwork({
8
+ src1x: cover ? getCover(cover) : undefined,
9
+ extraClasses: 'player-playlist__item-block player-playlist__item-image'
10
+ })}
13
11
  `;
14
- });
12
+ };
@@ -1,36 +1,29 @@
1
- import { virtual } from 'haunted';
2
1
  import { html } from 'lit';
3
- import { CoverFallback } from '../cover-fallback/index.js';
4
2
  import { TrackText } from '../track-text/index.js';
5
- import { getCover } from '../../_lib/get-cover.js';
3
+ import { getCover } from '../get-cover.js';
4
+ import { PlayerArtwork } from '@outbook/webcomponents-player-artwork';
6
5
 
7
- export const TrackInfo = virtual(
8
- ({ track = {}, indexSelected, literals = {} }) => {
9
- const hasTrackInfo = Number.isInteger(indexSelected);
10
- return html`
11
- <div class="myth-track-info">
12
- <div class="myth-track-info__inner">
13
- <div class="myth-track-info__image">
14
- ${hasTrackInfo && track.cover
15
- ? html`<img
16
- src="${getCover(track.cover)}"
17
- alt=""
18
- aria-hidden="true"
19
- />`
20
- : CoverFallback({})}
21
- </div>
22
- <div class="myth-track-info__data">
23
- ${hasTrackInfo
24
- ? TrackText({
25
- title: track.title,
26
- artist: track.artist,
27
- album: track.album,
28
- hasTrackInfo
29
- })
30
- : TrackText({ title: literals.noPlaying, hasTrackInfo })}
31
- </div>
6
+ export function TrackInfo({ track = {}, indexSelected, literals = {} }) {
7
+ const hasTrackInfo = Number.isInteger(indexSelected);
8
+ const cover = hasTrackInfo && track.cover ? getCover(track.cover) : undefined;
9
+ return html`
10
+ <div class="track-info">
11
+ <div class="track-info__inner">
12
+ ${PlayerArtwork({
13
+ src1x: cover,
14
+ extraClasses: 'track-info__image'
15
+ })}
16
+ <div class="track-info__data">
17
+ ${hasTrackInfo
18
+ ? TrackText({
19
+ title: track.title,
20
+ artist: track.artist,
21
+ album: track.album,
22
+ hasTrackInfo
23
+ })
24
+ : TrackText({ title: literals.noPlaying, hasTrackInfo })}
32
25
  </div>
33
26
  </div>
34
- `;
35
- }
36
- );
27
+ </div>
28
+ `;
29
+ }
@@ -1,20 +1,19 @@
1
- import { virtual } from 'haunted';
2
1
  import { html, nothing } from 'lit';
3
2
 
4
- export const TrackText = virtual(({ artist, album, title, hasTrackInfo }) => {
3
+ export function TrackText({ artist, album, title, hasTrackInfo }) {
5
4
  return html`
6
- <div class="myth-track-text">
5
+ <div class="track-text">
7
6
  <div
8
- class="myth-item-text"
7
+ class="track-text__item"
9
8
  data-test-id="player-data-${hasTrackInfo ? 'title' : 'stopped'}"
10
9
  >
11
10
  ${title}
12
11
  </div>
13
- <div class="myth-item-text size-small">
12
+ <div class="track-text__item size-small">
14
13
  ${artist ? html`<span>${artist}</span>` : nothing}
15
14
  ${artist && album ? html`<span> / </span>` : nothing}
16
15
  ${album ? html`<span>${album}</span>` : nothing}
17
16
  </div>
18
17
  </div>
19
18
  `;
20
- });
19
+ }
@@ -3,7 +3,7 @@
3
3
  @use '../_lib/variables';
4
4
 
5
5
  @mixin style() {
6
- .myth-controls {
6
+ .player-controls {
7
7
  --_controls-color: light-dark(
8
8
  var(--_accent-950),
9
9
  var(--_accent-100)
@@ -12,27 +12,27 @@
12
12
  }
13
13
 
14
14
  @container player (width >= #{variables.$player-widht-m}) {
15
- .myth-controls {
15
+ .player-controls {
16
16
  --_controls-size: #{measures.$baseline * 7};
17
17
  }
18
18
  }
19
19
 
20
20
  @container player (width >= #{variables.$player-widht-l}) {
21
- .myth-controls {
21
+ .player-controls {
22
22
  --_controls-size: #{measures.$baseline * 8};
23
23
  }
24
24
  }
25
25
 
26
- .myth-controls,
27
- .myth-controls__items {
26
+ .player-controls,
27
+ .player-controls__items {
28
28
  width: auto;
29
29
  }
30
30
 
31
- .myth-controls__items {
31
+ .player-controls__items {
32
32
  display: flex;
33
33
  }
34
34
 
35
- .myth-controls__button {
35
+ .player-controls__button {
36
36
  background: transparent;
37
37
  border: 0;
38
38
  color: var(--_controls-color);
@@ -43,7 +43,7 @@
43
43
  opacity: 0.4;
44
44
  }
45
45
  }
46
- .myth-controls__icon {
46
+ .player-controls__icon {
47
47
  width: var(--_controls-size);
48
48
  height: var(--_controls-size);
49
49
  }
@@ -4,7 +4,7 @@
4
4
  @use '../_lib/variables';
5
5
 
6
6
  @mixin style() {
7
- .myth-playlist {
7
+ .player-playlist {
8
8
  --_playlist-font-weight: normal;
9
9
  --_playlist-font-size: #{measures.$baseline * 1.5};
10
10
  --_playlist-font-size-small: #{measures.$baseline * 1.25};
@@ -19,16 +19,22 @@
19
19
  }
20
20
  }
21
21
 
22
+ @container player (width >= #{variables.$player-widht-m}) {
23
+ .player-playlist {
24
+ --_playlist-font-size: #{measures.$baseline * 1.75};
25
+ --_playlist-font-size-small: #{measures.$baseline * 1.5};
26
+ }
27
+ }
22
28
  @container player (width >= #{variables.$player-widht-l}) {
23
- .myth-playlist {
29
+ .player-playlist {
24
30
  --_playlist-font-size: #{measures.$baseline * 2};
25
- --_playlist-font-size-small: #{measures.$baseline * 1.5};
31
+ --_playlist-font-size-small: #{measures.$baseline * 1.75};
26
32
  --_playlist-item-image-size: #{measures.$baseline * 8};
27
33
  }
28
34
  }
29
35
 
30
- .myth-playlist {
31
- .myth-item-text {
36
+ .player-playlist {
37
+ .track-text__item {
32
38
  font-weight: var(--_playlist-font-weight);
33
39
  font-size: var(--_playlist-font-size);
34
40
  line-height: 150%;
@@ -38,12 +44,12 @@
38
44
  }
39
45
  }
40
46
 
41
- .myth-playlist__items {
47
+ .player-playlist__items {
42
48
  padding: 0;
43
49
  position: relative;
44
50
  }
45
51
 
46
- .myth-playlist__item {
52
+ .player-playlist__item {
47
53
  position: relative;
48
54
  border-bottom: 1px solid var(--_separator-color);
49
55
  &.is-playing {
@@ -63,7 +69,7 @@
63
69
  }
64
70
  }
65
71
 
66
- .myth-playlist__item-button {
72
+ .player-playlist__item-button {
67
73
  display: flex;
68
74
  padding: #{measures.$baseline};
69
75
  position: relative;
@@ -73,9 +79,12 @@
73
79
  }
74
80
  }
75
81
 
76
- .myth-playlist__item-group,
77
- .myth-playlist__item-block {
82
+ .player-playlist__item-group,
83
+ .player-playlist__item-block {
84
+ display: flex;
85
+ align-items: center;
78
86
  margin-left: #{measures.$baseline * 2};
87
+ height: var(--_playlist-item-image-size);
79
88
  &:first-child {
80
89
  margin-left: 0;
81
90
  }
@@ -85,34 +94,20 @@
85
94
  }
86
95
  }
87
96
 
88
- .myth-playlist__item-group {
89
- display: flex;
90
- align-items: center;
91
- }
92
-
93
- .myth-playlist__item-image {
97
+ .player-playlist__item-image {
94
98
  width: var(--_playlist-item-image-size);
95
99
  height: var(--_playlist-item-image-size);
96
- position: relative;
97
- overflow: hidden;
98
100
  flex-shrink: 0;
99
- img {
100
- height: 100%;
101
- position: absolute;
102
- top: 0;
103
- left: 50%;
104
- transform: translateX(-50%);
105
- }
106
101
  }
107
102
 
108
- .myth-playlist__item-playing-icon {
103
+ .player-playlist__item-playing-icon {
109
104
  width: #{measures.$baseline * 2};
110
105
  height: #{measures.$baseline * 2};
111
106
  display: flex;
112
107
  align-items: center;
113
108
  }
114
109
 
115
- .myth-playlist__item-playing-icon-inner {
110
+ .player-playlist__item-playing-icon-inner {
116
111
  width: 100%;
117
112
  aspect-ratio: 1 / 1;
118
113
  }
@@ -4,7 +4,7 @@
4
4
  @mixin style() {
5
5
  $height-xs: measures.$baseline * 8;
6
6
 
7
- .myth-track-info {
7
+ .track-info {
8
8
  --track-info--flex-direction: row;
9
9
  --track-info--image-width: #{measures.$baseline * 8};
10
10
  --track-info--image-separation-horizontal: #{measures.$baseline * 2};
@@ -12,7 +12,7 @@
12
12
  --track-info--height: #{$height-xs};
13
13
  }
14
14
  @container player (width >= #{variables.$player-widht-m}) {
15
- .myth-track-info {
15
+ .track-info {
16
16
  --track-info--flex-direction: column;
17
17
  --track-info--image-width: 100%;
18
18
  --track-info--image-separation-horizontal: 0;
@@ -22,11 +22,11 @@
22
22
  }
23
23
 
24
24
 
25
- .myth-track-info__inner {
25
+ .track-info__inner {
26
26
  display: flex;
27
27
  flex-direction: var(--track-info--flex-direction);
28
28
  height: var(--track-info--height);
29
- .myth-item-text {
29
+ .track-text__item {
30
30
  font-size: #{measures.$baseline * 2};
31
31
  line-height: 150%;
32
32
  &.size-small {
@@ -35,7 +35,7 @@
35
35
  }
36
36
  }
37
37
 
38
- .myth-track-info__image {
38
+ .track-info__image {
39
39
  width: var(--track-info--image-width);
40
40
  flex-shrink: 0;
41
41
  display: flex;
@@ -47,7 +47,7 @@
47
47
  }
48
48
  }
49
49
 
50
- .myth-track-info__data {
50
+ .track-info__data {
51
51
  display: flex;
52
52
  align-items: center;
53
53
  }
@@ -1,4 +1,4 @@
1
1
  /* eslint-disable */
2
2
  import { css } from 'lit';
3
3
 
4
- export default css`.skeleton-loader{--pulse-bg-start: blue;--pulse-bg-end: red}@keyframes colorPulse{from{background-color:var(--pulse-bg-start)}to{background-color:var(--pulse-bg-end)}}.skeleton-loader{background-color:var(--pulse-bg-start);animation:colorPulse 3.2s ease infinite alternate-reverse;opacity:.3}:host{display:block;color-scheme:inherit;--_accent-0: var(--outbook-player--color-accent-0, oklch(100% 0 0deg));--_accent-50: var(--outbook-player--color-accent-50, oklch(98.5% 0 0deg));--_accent-100: var(--outbook-player--color-accent-100, oklch(97% 0 0deg));--_accent-200: var(--outbook-player--color-accent-200, oklch(92.2% 0 0deg));--_accent-300: var(--outbook-player--color-accent-300, oklch(87% 0 0deg));--_accent-400: var(--outbook-player--color-accent-400, oklch(70.8% 0 0deg));--_accent-500: var(--outbook-player--color-accent-500, oklch(55.6% 0 0deg));--_accent-600: var(--outbook-player--color-accent-600, oklch(43.9% 0 0deg));--_accent-700: var(--outbook-player--color-accent-700, oklch(37.1% 0 0deg));--_accent-800: var(--outbook-player--color-accent-800, oklch(26.9% 0 0deg));--_accent-900: var(--outbook-player--color-accent-900, oklch(20.5% 0 0deg));--_accent-950: var(--outbook-player--color-accent-950, oklch(14.5% 0 0deg));--_accent-1000: var(--outbook-player--color-accent-1000, oklch(0% 0 0deg));--pulse-bg-start: light-dark( var(--_accent-100), var(--_accent-600) );--pulse-bg-end: light-dark( var(--_accent-600), var(--_accent-500) );--outbook-badge-file-extension--border-color: light-dark( oklch(37.1% 0 0deg), oklch(87% 0 0deg) );--outbook-badge-file-extension--text-color: light-dark( oklch(14.5% 0 0deg), oklch(100% 0 0deg) );--outbook-scrollable--indicator-color: light-dark( var(--_accent-900), var(--_accent-200) );--_text-color: light-dark(oklch(20.5% 0 0deg), oklch(100% 0 0deg));--_background-color: light-dark(oklch(98.5% 0 0deg), oklch(20.5% 0 0deg));--_separator-color: light-dark(oklch(92.2% 0 0deg), oklch(43.9% 0 0deg));--_link-color: light-dark(var(--_accent-700), var(--_accent-200));--_link-color-hover: light-dark(var(--_accent-500), var(--_accent-50))}*{box-sizing:border-box;padding:0;margin:0}ul,ol,li{list-style:none}a,button,[role=button],[role=link]{cursor:pointer}a[disabled],button[disabled],[role=button][disabled],[role=link][disabled]{cursor:default}.mythical-player{container-name:player;container-type:inline-size;height:var(--_main-height, 32rem);background-color:var(--_background-color);position:relative}.mythical-player::after,.mythical-player::before{content:"";height:100%;width:100%;position:absolute;left:0;top:0}.mythical-player.has-gradient::after{opacity:.65;background-color:var(--_background-color)}.mythical-player::before{opacity:.45;background:linear-gradient(145deg, var(--_accent-400) 0%, var(--_accent-500) 25%, var(--_accent-600) 50%, var(--_accent-700) 75%, var(--_accent-800) 100%)}.mythical-player.has-gradient::before{opacity:1;background:linear-gradient(145deg, var(--player--main-color-0) 0%, var(--player--main-color-1) 25%, var(--player--main-color-2) 50%, var(--player--main-color-3) 75%, var(--player--main-color-4) 100%)}@container player (width < 768px){.mythical-player__inner{--player--flex-direction: column;--_secondary-block-width: unset;--_secondary-block-min-width: unset}}@container player (width >= 768px){.mythical-player__inner{--player--flex-direction: row;--_secondary-block-width: 25%;--_secondary-block-min-width: 20rem}}.mythical-player__inner{display:flex;flex-direction:column;height:100%;color:var(--_text-color);position:relative;z-index:2}.mythical-player__body{display:flex;flex-direction:var(--player--flex-direction);flex:1 0 0;overflow:hidden;padding:0.5rem 0}.mythical-player__playlist{overflow:hidden;padding:0 0 0 0.5rem;display:flex;flex:1 0 0}.mythical-player__track-info{width:var(--_secondary-block-width);min-width:var(--_secondary-block-min-width);padding:0.5rem}.mythical-player__controls{display:flex;justify-content:center;margin-top:-1rem}.mythical-player__controls,.mythical-player__timeline{position:relative}mythical-cover-fallback{display:block}.cover-fallback{--_cover-fallback-bg-color: light-dark(var(--_accent-200), var(--_accent-700));--_cover-fallback-font-size: 2rem}.cover-fallback.cover-fallback--in-file-item{--_cover-fallback-font-size: 2rem}@container player (width >= 768px){.cover-fallback{--_cover-fallback-font-size: 10rem}.cover-fallback.cover-fallback--in-file-item{--_cover-fallback-font-size: 2rem}}.cover-fallback{width:inherit;padding-top:100%;background-color:var(--_cover-fallback-bg-color);display:flex;align-items:center;justify-content:center;position:relative}.cover-fallback__icon{width:80%;height:80%;position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);color:var(--_text-color);display:flex;justify-content:center;align-items:center}.cover-fallback__icon-inner{width:100%;height:100%}.myth-controls{--_controls-color: light-dark( var(--_accent-950), var(--_accent-100) );--_controls-size: 3rem}@container player (width >= 768px){.myth-controls{--_controls-size: 3.5rem}}@container player (width >= 1024px){.myth-controls{--_controls-size: 4rem}}.myth-controls,.myth-controls__items{width:auto}.myth-controls__items{display:flex}.myth-controls__button{background:rgba(0,0,0,0);border:0;color:var(--_controls-color)}.myth-controls__button:focus-visible{outline:light-dark(oklch(48.8% 0.243 264.376deg), oklch(88.2% 0.059 254.128deg)) solid 2px;outline-offset:-4px}.myth-controls__button.is-disabled{opacity:.4}.myth-controls__icon{width:var(--_controls-size);height:var(--_controls-size)}.myth-playlist{--_playlist-font-weight: normal;--_playlist-font-size: 0.75rem;--_playlist-font-size-small: 0.625rem;--_playlist-item-background-color: transparent;--_playlist-item-image-size: 3rem}.myth-playlist .is-playing{--_playlist-font-weight: 600;--_playlist-item-background-color: var(--_accent-200)}.ambient-dark .myth-playlist .is-playing{--_playlist-item-background-color: var(--_accent-700)}@container player (width >= 1024px){.myth-playlist{--_playlist-font-size: 1rem;--_playlist-font-size-small: 0.75rem;--_playlist-item-image-size: 4rem}}.myth-playlist .myth-item-text{font-weight:var(--_playlist-font-weight);font-size:var(--_playlist-font-size);line-height:150%}.myth-playlist .myth-item-text.size-small{font-size:var(--_playlist-font-size-small)}.myth-playlist__items{padding:0;position:relative}.myth-playlist__item{position:relative;border-bottom:1px solid var(--_separator-color)}.myth-playlist__item.is-playing:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;opacity:.45}@keyframes colorPulse{from{background-color:var(--pulse-bg-start)}to{background-color:var(--pulse-bg-end)}}.myth-playlist__item.is-playing:before{background-color:var(--pulse-bg-start);animation:colorPulse 3.2s ease infinite alternate-reverse;opacity:.3}.myth-playlist__item:last-child{border-bottom:0}.myth-playlist__item-button{display:flex;padding:0.5rem;position:relative;align-items:center}.myth-playlist__item-button:focus-visible{outline:light-dark(oklch(48.8% 0.243 264.376deg), oklch(88.2% 0.059 254.128deg)) solid 2px;outline-offset:-4px}.myth-playlist__item-group,.myth-playlist__item-block{margin-left:1rem}.myth-playlist__item-group:first-child,.myth-playlist__item-block:first-child{margin-left:0}.myth-playlist__item-group.is-last-right,.myth-playlist__item-block.is-last-right{margin-left:auto;padding-left:0.5rem}.myth-playlist__item-group{display:flex;align-items:center}.myth-playlist__item-image{width:var(--_playlist-item-image-size);height:var(--_playlist-item-image-size);position:relative;overflow:hidden;flex-shrink:0}.myth-playlist__item-image img{height:100%;position:absolute;top:0;left:50%;transform:translateX(-50%)}.myth-playlist__item-playing-icon{width:1rem;height:1rem;display:flex;align-items:center}.myth-playlist__item-playing-icon-inner{width:100%;aspect-ratio:1/1}.timeline{--timeline--background: var(--_accent-200);--timeline--completed-background: var(--_accent-800);--timeline--font-size: 0.75rem;--timeline--numbers-separation: 0.125rem;--timeline--chapter-limit-color: oklch(14.5% 0 0deg)}.timeline .timeline__chapter-limit--played{--timeline--chapter-limit-color: oklch(98.5% 0 0deg)}.ambient-dark .timeline{--timeline--background: var(--_accent-800);--timeline--chapter-limit-color: oklch(98.5% 0 0deg);--timeline--completed-background: var(--_accent-200)}.ambient-dark .timeline .timeline__chapter-limit--played{--timeline--chapter-limit-color: oklch(14.5% 0 0deg)}@container player (width >= 768px){.timeline{--timeline--font-size: 0.875rem;--timeline--numbers-separation: 0.25rem}}@container player (width >= 1024px){.timeline{--timeline--font-size: 1rem;--timeline--numbers-separation: 0.5rem}}mythical-player-timeline{display:block}.timeline{position:relative;padding:0 0.5rem}.timeline__track{height:0.5rem;width:100%;background-color:var(--timeline--background);cursor:pointer}.timeline__track:focus-visible{outline:light-dark(oklch(48.8% 0.243 264.376deg), oklch(88.2% 0.059 254.128deg)) solid 2px;outline-offset:2px}.timeline__completed{height:100%;background-color:var(--timeline--completed-background)}.timeline__numbers{display:flex;justify-content:space-between;padding-top:var(--timeline--numbers-separation);font-size:var(--timeline--font-size)}.timeline__tooltip{position:absolute;top:0;transform:translate(-50%, -100%);left:var(--timeline-tooltip-position);padding-bottom:0.5rem;z-index:3}.timeline__tooltip-inner{background-color:oklch(100% 0 0deg);color:oklch(37.1% 0 0deg);padding:0.25rem;position:relative;border-width:1px 1px 0 1px;border-color:oklch(.922 0 0);border-style:solid;max-width:8rem;display:flex;flex-direction:column;align-items:center}.timeline__tooltip-inner:after{content:"";width:0;height:0;border-style:solid;border-width:0 4px 6.9px 4px;border-color:rgba(0,0,0,0) rgba(0,0,0,0) oklch(100% 0 0deg) rgba(0,0,0,0);position:absolute;top:100%;left:50%;transform:translateX(-50%) rotate(180deg)}.timeline__tooltip-time{font-size:1rem}.timeline__tooltip-chapter-title{font-size:0.75rem;margin-bottom:0.25rem}.timeline__chapter-limit{width:1px;height:0.5rem;background-color:var(--timeline--chapter-limit-color);position:absolute;top:0;left:var(--timeline--chapter-limit-position)}.myth-track-info{--track-info--flex-direction: row;--track-info--image-width: 4rem;--track-info--image-separation-horizontal: 1rem;--track-info--image-separation-vertical: 0;--track-info--height: 4rem}@container player (width >= 768px){.myth-track-info{--track-info--flex-direction: column;--track-info--image-width: 100%;--track-info--image-separation-horizontal: 0;--track-info--image-separation-vertical: 1rem;--track-info--height: auto}}.myth-track-info__inner{display:flex;flex-direction:var(--track-info--flex-direction);height:var(--track-info--height)}.myth-track-info__inner .myth-item-text{font-size:1rem;line-height:150%}.myth-track-info__inner .myth-item-text.size-small{font-size:0.75rem}.myth-track-info__image{width:var(--track-info--image-width);flex-shrink:0;display:flex;align-items:center;margin-right:var(--track-info--image-separation-horizontal);margin-bottom:var(--track-info--image-separation-vertical)}.myth-track-info__image>*{width:100%}.myth-track-info__data{display:flex;align-items:center}`;
4
+ export default css`.skeleton-loader{--pulse-bg-start: blue;--pulse-bg-end: red}@keyframes colorPulse{from{background-color:var(--pulse-bg-start)}to{background-color:var(--pulse-bg-end)}}.skeleton-loader{background-color:var(--pulse-bg-start);animation:colorPulse 3.2s ease infinite alternate-reverse;opacity:.3}:host{display:block;color-scheme:inherit;--_accent-0: var(--outbook-player--color-accent-0, oklch(100% 0 0deg));--_accent-50: var(--outbook-player--color-accent-50, oklch(98.5% 0 0deg));--_accent-100: var(--outbook-player--color-accent-100, oklch(97% 0 0deg));--_accent-200: var(--outbook-player--color-accent-200, oklch(92.2% 0 0deg));--_accent-300: var(--outbook-player--color-accent-300, oklch(87% 0 0deg));--_accent-400: var(--outbook-player--color-accent-400, oklch(70.8% 0 0deg));--_accent-500: var(--outbook-player--color-accent-500, oklch(55.6% 0 0deg));--_accent-600: var(--outbook-player--color-accent-600, oklch(43.9% 0 0deg));--_accent-700: var(--outbook-player--color-accent-700, oklch(37.1% 0 0deg));--_accent-800: var(--outbook-player--color-accent-800, oklch(26.9% 0 0deg));--_accent-900: var(--outbook-player--color-accent-900, oklch(20.5% 0 0deg));--_accent-950: var(--outbook-player--color-accent-950, oklch(14.5% 0 0deg));--_accent-1000: var(--outbook-player--color-accent-1000, oklch(0% 0 0deg));--pulse-bg-start: light-dark( var(--_accent-100), var(--_accent-600) );--pulse-bg-end: light-dark( var(--_accent-600), var(--_accent-500) );--outbook-badge-file-extension--border-color: light-dark( oklch(37.1% 0 0deg), oklch(87% 0 0deg) );--outbook-badge-file-extension--text-color: light-dark( oklch(14.5% 0 0deg), oklch(100% 0 0deg) );--outbook-scrollable--indicator-color: light-dark( var(--_accent-900), var(--_accent-200) );--outbook-player-timeline--background: light-dark( var(--_accent-200), var(--_accent-800) );--outbook-player-timeline--background-completed: light-dark( var(--_accent-800), var(--_accent-200) );--outbook-player-timeline--chapter--mark-color: light-dark( oklch(14.5% 0 0deg), oklch(98.5% 0 0deg) );--outbook-player-timeline--chapter-played-mark-color: light-dark( oklch(98.5% 0 0deg), oklch(14.5% 0 0deg) );--_text-color: light-dark(oklch(20.5% 0 0deg), oklch(100% 0 0deg));--_background-color: light-dark(oklch(98.5% 0 0deg), oklch(20.5% 0 0deg));--_separator-color: light-dark(oklch(92.2% 0 0deg), oklch(43.9% 0 0deg));--_link-color: light-dark(var(--_accent-700), var(--_accent-200));--_link-color-hover: light-dark(var(--_accent-500), var(--_accent-50))}*{box-sizing:border-box;padding:0;margin:0}ul,ol,li{list-style:none}a,button,[role=button],[role=link]{cursor:pointer}a[disabled],button[disabled],[role=button][disabled],[role=link][disabled]{cursor:default}.player{container-name:player;container-type:inline-size;height:var(--_main-height, 32rem);background-color:var(--_background-color);position:relative}.player::after,.player::before{content:"";height:100%;width:100%;position:absolute;left:0;top:0}.player.has-gradient::after{opacity:.65;background-color:var(--_background-color)}.player::before{opacity:.45;background:linear-gradient(145deg, var(--_accent-400) 0%, var(--_accent-500) 25%, var(--_accent-600) 50%, var(--_accent-700) 75%, var(--_accent-800) 100%)}.player.has-gradient::before{opacity:1;background:linear-gradient(145deg, var(--player--main-color-0) 0%, var(--player--main-color-1) 25%, var(--player--main-color-2) 50%, var(--player--main-color-3) 75%, var(--player--main-color-4) 100%)}@container player (width < 768px){.player__inner{--player--flex-direction: column;--_secondary-block-width: unset;--_secondary-block-min-width: unset}}@container player (width >= 768px){.player__inner{--player--flex-direction: row;--_secondary-block-width: 25%;--_secondary-block-min-width: 20rem}}.player__inner{display:flex;flex-direction:column;height:100%;color:var(--_text-color);position:relative;z-index:2}.player__body{display:flex;flex-direction:var(--player--flex-direction);flex:1 0 0;overflow:hidden}.player__playlist{overflow:hidden;padding:0 0 0 0.5rem;display:flex;flex:1 0 0}.player__track-info{width:var(--_secondary-block-width);min-width:var(--_secondary-block-min-width);padding:0.5rem}.player__controls{display:flex;justify-content:center;margin-top:-1rem}.player__controls,.player__timeline{position:relative}.player__timeline{padding:0 0.5rem}.player-controls{--_controls-color: light-dark( var(--_accent-950), var(--_accent-100) );--_controls-size: 3rem}@container player (width >= 768px){.player-controls{--_controls-size: 3.5rem}}@container player (width >= 1024px){.player-controls{--_controls-size: 4rem}}.player-controls,.player-controls__items{width:auto}.player-controls__items{display:flex}.player-controls__button{background:rgba(0,0,0,0);border:0;color:var(--_controls-color)}.player-controls__button:focus-visible{outline:var(--outbook-outline--color, light-dark(oklch(48.8% 0.243 264.376deg), oklch(88.2% 0.059 254.128deg))) solid 2px;outline-offset:-4px}.player-controls__button.is-disabled{opacity:.4}.player-controls__icon{width:var(--_controls-size);height:var(--_controls-size)}.player-playlist{--_playlist-font-weight: normal;--_playlist-font-size: 0.75rem;--_playlist-font-size-small: 0.625rem;--_playlist-item-background-color: transparent;--_playlist-item-image-size: 3rem}.player-playlist .is-playing{--_playlist-font-weight: 600;--_playlist-item-background-color: var(--_accent-200)}.ambient-dark .player-playlist .is-playing{--_playlist-item-background-color: var(--_accent-700)}@container player (width >= 768px){.player-playlist{--_playlist-font-size: 0.875rem;--_playlist-font-size-small: 0.75rem}}@container player (width >= 1024px){.player-playlist{--_playlist-font-size: 1rem;--_playlist-font-size-small: 0.875rem;--_playlist-item-image-size: 4rem}}.player-playlist .track-text__item{font-weight:var(--_playlist-font-weight);font-size:var(--_playlist-font-size);line-height:150%}.player-playlist .track-text__item.size-small{font-size:var(--_playlist-font-size-small)}.player-playlist__items{padding:0;position:relative}.player-playlist__item{position:relative;border-bottom:1px solid var(--_separator-color)}.player-playlist__item.is-playing:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;opacity:.45}@keyframes colorPulse{from{background-color:var(--pulse-bg-start)}to{background-color:var(--pulse-bg-end)}}.player-playlist__item.is-playing:before{background-color:var(--pulse-bg-start);animation:colorPulse 3.2s ease infinite alternate-reverse;opacity:.3}.player-playlist__item:last-child{border-bottom:0}.player-playlist__item-button{display:flex;padding:0.5rem;position:relative;align-items:center}.player-playlist__item-button:focus-visible{outline:var(--outbook-outline--color, light-dark(oklch(48.8% 0.243 264.376deg), oklch(88.2% 0.059 254.128deg))) solid 2px;outline-offset:-4px}.player-playlist__item-group,.player-playlist__item-block{display:flex;align-items:center;margin-left:1rem;height:var(--_playlist-item-image-size)}.player-playlist__item-group:first-child,.player-playlist__item-block:first-child{margin-left:0}.player-playlist__item-group.is-last-right,.player-playlist__item-block.is-last-right{margin-left:auto;padding-left:0.5rem}.player-playlist__item-image{width:var(--_playlist-item-image-size);height:var(--_playlist-item-image-size);flex-shrink:0}.player-playlist__item-playing-icon{width:1rem;height:1rem;display:flex;align-items:center}.player-playlist__item-playing-icon-inner{width:100%;aspect-ratio:1/1}.track-info{--track-info--flex-direction: row;--track-info--image-width: 4rem;--track-info--image-separation-horizontal: 1rem;--track-info--image-separation-vertical: 0;--track-info--height: 4rem}@container player (width >= 768px){.track-info{--track-info--flex-direction: column;--track-info--image-width: 100%;--track-info--image-separation-horizontal: 0;--track-info--image-separation-vertical: 1rem;--track-info--height: auto}}.track-info__inner{display:flex;flex-direction:var(--track-info--flex-direction);height:var(--track-info--height)}.track-info__inner .track-text__item{font-size:1rem;line-height:150%}.track-info__inner .track-text__item.size-small{font-size:0.75rem}.track-info__image{width:var(--track-info--image-width);flex-shrink:0;display:flex;align-items:center;margin-right:var(--track-info--image-separation-horizontal);margin-bottom:var(--track-info--image-separation-vertical)}.track-info__image>*{width:100%}.track-info__data{display:flex;align-items:center}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@outbook/webcomponents-player",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "main": "player.js",
5
5
  "type": "module",
6
6
  "private": false,
@@ -14,14 +14,14 @@
14
14
  "bugs": {
15
15
  "url": "https://gitlab.com/arr2019/web-components/issues"
16
16
  },
17
- "homepage": "https://gitlab.com/arr2019/web-components/pkg/player#readme",
17
+ "homepage": "https://gitlab.com/arr2019/web-components/pkg/web-components/player#readme",
18
18
  "repository": {
19
19
  "type": "git",
20
20
  "url": "git+https://gitlab.com/arr2019/web-components.git",
21
- "folder": "pkg/player"
21
+ "folder": "pkg/web-components/player"
22
22
  },
23
23
  "publishConfig": {
24
- "registry": "https://registry.npmjs.org/"
24
+ "registry": "https://registry.npmjs.com/"
25
25
  },
26
26
  "author": "Antonio Rodríguez",
27
27
  "license": "Apache-2.0",
@@ -33,12 +33,15 @@
33
33
  "mystic-format-time": "1.0.2",
34
34
  "@outbook/webcomponents-scrollable": ">=1.0.0",
35
35
  "@outbook/webcomponents-type-icon": ">=1.1.2",
36
- "@outbook/webcomponents-badge-file-extension": ">=1.2.0",
36
+ "@outbook/webcomponents-badge-file-extension": ">=1.2.1",
37
+ "@outbook/webcomponents-player-artwork": ">=1.0.0",
37
38
  "@outbook/icons": ">=1.3.0"
38
39
  },
39
40
  "devDependencies": {
40
41
  "@outbook/colorful": ">=1.1.2",
41
- "@outbook/design-decisions": ">=1.1.5"
42
+ "@outbook/design-decisions": ">=1.1.5",
43
+ "@outbook/webcomponents-player-artwork": ">=1.0.0",
44
+ "@outbook/webcomponents-player-timeline": ">=1.0.0"
42
45
  },
43
46
  "peerDependencies": {
44
47
  "sass": "^1.97.2",
package/player.js CHANGED
@@ -11,8 +11,7 @@ import { Scrollable } from '@outbook/webcomponents-scrollable';
11
11
  import { Playlist as ComponentPlaylist } from './_lib/playlist/index.js';
12
12
  import { TrackInfo } from './_lib/track-info/index.js';
13
13
  import { Controls } from './_lib/controls/index.js';
14
- import { Timeline } from './_lib/timeline/index.js';
15
- import { readable as formatTime } from 'mystic-format-time';
14
+ import { PlayerTimeline } from '@outbook/webcomponents-player-timeline';
16
15
  import { loadLiterals } from './_i18n/i18n.js';
17
16
  import _literals from './_i18n/en.json' with { type: 'json' };
18
17
  import { useLangObserver } from '@outbook/hooks';
@@ -22,7 +21,6 @@ function ComponentPlayer(element) {
22
21
  const mediaId = 'mythical-media-element';
23
22
  const listId = 'mythical-list';
24
23
  const playlistScrollId = 'mythical-playlist-scroll';
25
- const timelineUniqueId = 'mythical-timeline';
26
24
 
27
25
  function getMediaElement() {
28
26
  const el = element.shadowRoot.getElementById(mediaId);
@@ -37,12 +35,13 @@ function ComponentPlayer(element) {
37
35
  const [indexSelected, setIndexSelected] = useState(null);
38
36
  const [isPlaying, setIsPlaying] = useState(false);
39
37
  const [totalTime, setTotalTime] = useState(0);
38
+ const [currentTime, setCurrentTime] = useState(0); // Added state
40
39
  const [prominentColors, setProminentColors] = useState('');
41
40
  const [hasProminentColors, setHasProminentColors] = useState(false);
42
41
  const [coverUrl, setCoverUrl] = useState(null);
43
42
 
44
43
  useEffect(() => {
45
- let isActive = true; // 1. Flag to track if component is alive
44
+ let isActive = true;
46
45
 
47
46
  if (coverUrl) {
48
47
  setHasProminentColors(true);
@@ -144,6 +143,7 @@ function ComponentPlayer(element) {
144
143
  }
145
144
 
146
145
  setTotalTime(0);
146
+ setCurrentTime(0); // Set zero on stop
147
147
  setHasProminentColors(false);
148
148
 
149
149
  if (navigator.mediaSession) {
@@ -194,6 +194,7 @@ function ComponentPlayer(element) {
194
194
  return;
195
195
  }
196
196
  mediaElement.currentTime = 0;
197
+ setCurrentTime(0); // Set zero initial time
197
198
 
198
199
  mediaElement
199
200
  .play()
@@ -219,34 +220,29 @@ function ComponentPlayer(element) {
219
220
 
220
221
  function handleTimeUpdate(ev) {
221
222
  const media = ev.currentTarget;
222
- const currentSecs = media.currentTime;
223
- const duration = media.duration || totalTime; // fallback to state totalTime
224
-
225
- const bar = element.shadowRoot.getElementById(`bar-${timelineUniqueId}`);
226
- const txt = element.shadowRoot.getElementById(`txt-${timelineUniqueId}`);
227
-
228
- if (bar && duration > 0) {
229
- const percent = (currentSecs / duration) * 100;
230
- bar.style.width = `${percent}%`;
231
- }
223
+ setCurrentTime(media.currentTime);
224
+ }
232
225
 
233
- if (txt) {
234
- txt.textContent = formatTime(currentSecs);
226
+ function handleReposition(ev) {
227
+ const newTime = ev.detail.currentTime;
228
+ const media = getMediaElement();
229
+ if (media) {
230
+ media.currentTime = newTime;
235
231
  }
232
+ setCurrentTime(newTime);
236
233
  }
237
234
 
238
235
  const mainClasses = classMap({
239
- 'mythical-player': true,
240
- 'root-container': true,
236
+ player: true,
241
237
  'has-gradient': hasProminentColors
242
238
  });
243
239
 
244
240
  return literals
245
241
  ? html`
246
242
  <div class="${mainClasses}" style="${prominentColors}">
247
- <div class="mythical-player__inner">
248
- <div class="mythical-player__body">
249
- <div class="mythical-player__playlist">
243
+ <div class="player__inner">
244
+ <div class="player__body">
245
+ <div class="player__playlist">
250
246
  ${Scrollable({
251
247
  extraClasses: 'scrollable--default',
252
248
  eventScrollToPlayingItem: true,
@@ -263,7 +259,7 @@ function ComponentPlayer(element) {
263
259
  }
264
260
  })}
265
261
  </div>
266
- <div class="mythical-player__track-info">
262
+ <div class="player__track-info">
267
263
  ${TrackInfo({
268
264
  indexSelected,
269
265
  track: playlist[indexSelected],
@@ -271,15 +267,13 @@ function ComponentPlayer(element) {
271
267
  })}
272
268
  </div>
273
269
  </div>
274
- <div class="mythical-player__timeline">
275
- ${Timeline({
276
- totalTime,
277
- currentTime: 0,
278
- getMediaElement,
279
- id: timelineUniqueId
270
+ <div class="player__timeline" @reposition="${handleReposition}">
271
+ ${PlayerTimeline({
272
+ duration: totalTime,
273
+ currentTime
280
274
  })}
281
275
  </div>
282
- <div class="mythical-player__controls">
276
+ <div class="player__controls">
283
277
  ${Controls({
284
278
  props: { isPlaying, playlist, indexSelected },
285
279
  handleAudio,
@@ -1,48 +0,0 @@
1
- import { html } from 'lit';
2
- import { classMap } from 'lit/directives/class-map.js';
3
- import { ifDefined } from 'lit/directives/if-defined.js';
4
- import { TypeIcon } from '@outbook/webcomponents-type-icon/shadow';
5
- import { audiotrack } from '@outbook/icons';
6
- import { component } from 'haunted';
7
-
8
- function CoverFallbackComponent(element) {
9
- const { props, icon = 'audiotrack' } = element;
10
- const { isInFileItem } = props;
11
- const mainClasses = classMap({
12
- 'cover-fallback': true,
13
- 'cover-fallback--in-file-item': isInFileItem
14
- });
15
- return html`
16
- <div class="${mainClasses}">
17
- <div class="cover-fallback__icon">
18
- ${TypeIcon({
19
- icon,
20
- icons: { audiotrack },
21
- extraClasses: 'cover-fallback__icon-inner'
22
- })}
23
- </div>
24
- </div>
25
- `;
26
- }
27
-
28
- if (!customElements.get('mythical-cover-fallback')) {
29
- customElements.define(
30
- 'mythical-cover-fallback',
31
- component(CoverFallbackComponent, {
32
- observedAttributes: ['icon'],
33
- useShadowDOM: false
34
- })
35
- );
36
- }
37
-
38
- export function CoverFallback(props) {
39
- const { extraClasses, icon = null } = props;
40
-
41
- return html`
42
- <mythical-cover-fallback
43
- class="${ifDefined(extraClasses || undefined)}"
44
- icon="${ifDefined(icon || undefined)}"
45
- .props="${props}"
46
- ></mythical-cover-fallback>
47
- `;
48
- }
package/_lib/hooks.js DELETED
@@ -1,23 +0,0 @@
1
- import { useEffect } from 'haunted';
2
-
3
- export function useDocumentEvent(eventName, handler, condition) {
4
- useEffect(() => {
5
- if (condition) {
6
- document.addEventListener(eventName, handler);
7
- return () => {
8
- document.removeEventListener(eventName, handler);
9
- };
10
- }
11
- }, [eventName, handler, condition]);
12
- }
13
-
14
- export function useWindowEvent(eventName, handler, condition = true) {
15
- useEffect(() => {
16
- if (condition) {
17
- window.addEventListener(eventName, handler);
18
- return () => {
19
- window.removeEventListener(eventName, handler);
20
- };
21
- }
22
- }, [eventName, handler, condition]);
23
- }
@@ -1,42 +0,0 @@
1
- import {
2
- isEventClick,
3
- isKeyArrowLeft,
4
- isKeyArrowRight
5
- } from 'a11y-key-conjurer';
6
-
7
- const SKIP_TIME = 10;
8
-
9
- function handleClick(ev, getMediaElement) {
10
- const audioElement = getMediaElement();
11
- const target = ev.currentTarget;
12
- const rect = target.getBoundingClientRect();
13
- const x = ev.clientX - rect.left;
14
- const percent = (100 * x) / target.offsetWidth;
15
- audioElement.currentTime = (audioElement.duration / 100) * percent;
16
- }
17
-
18
- function handleArrows({ multiplier }, getMediaElement) {
19
- const audioElement = getMediaElement();
20
- const newTime = audioElement.currentTime + SKIP_TIME * multiplier;
21
- const duration = audioElement.duration;
22
- audioElement.currentTime =
23
- newTime > 0 ? (newTime < duration ? newTime : duration) : 0;
24
- }
25
-
26
- function handleArrowLeft(ev, getMediaElement) {
27
- handleArrows({ ev, multiplier: -1 }, getMediaElement);
28
- }
29
-
30
- function handleArrowRight(ev, getMediaElement) {
31
- handleArrows({ ev, multiplier: 1 }, getMediaElement);
32
- }
33
-
34
- export function handleTimeline(ev, getMediaElement) {
35
- if (isEventClick(ev)) {
36
- handleClick(ev, getMediaElement);
37
- } else if (isKeyArrowLeft(ev)) {
38
- handleArrowLeft(ev, getMediaElement);
39
- } else if (isKeyArrowRight(ev)) {
40
- handleArrowRight(ev, getMediaElement);
41
- }
42
- }
@@ -1,121 +0,0 @@
1
- import { html, nothing } from 'lit';
2
- import { component, useState } from 'haunted';
3
- import { classMap } from 'lit/directives/class-map.js';
4
- import { handleTimeline } from './_lib/handle-timeline.js';
5
- import { readable as formatTime } from 'mystic-format-time';
6
-
7
- function getChapter({ targetTime, chapters }) {
8
- const selected = chapters.find(
9
- chapter =>
10
- targetTime >= chapter.start / 1000 && targetTime < chapter.end / 1000
11
- );
12
- return selected?.tags?.title || null;
13
- }
14
-
15
- function TimelineComponent(element) {
16
- const { props } = element;
17
- const {
18
- totalTime,
19
- currentTime = 0, // keep currentTime here for the INITIAL render, but we won't rely on it for updates
20
- currentItem = {},
21
- getMediaElement,
22
- id
23
- } = props;
24
-
25
- const [timePosition, setTimePosition] = useState(null);
26
- const chapters = currentItem?.metadata?.chapters || [];
27
-
28
- function handleTimelineIn(ev) {
29
- const target = ev.currentTarget;
30
- const leftPosition = ev.layerX;
31
- const targetWidth = target.offsetWidth;
32
- const targetTime = (leftPosition * totalTime) / targetWidth;
33
- setTimePosition({
34
- visual: formatTime(targetTime),
35
- chapter:
36
- chapters.length > 0 ? getChapter({ targetTime, chapters }) : null,
37
- left: leftPosition
38
- });
39
- }
40
-
41
- function handleTimelineOut() {
42
- setTimePosition(null);
43
- }
44
-
45
- function fnHandleTimeline(ev) {
46
- handleTimeline(ev, getMediaElement);
47
- }
48
-
49
- return html`
50
- <div class="timeline">
51
- <div
52
- class="timeline__track"
53
- @click="${fnHandleTimeline}"
54
- @keydown="${fnHandleTimeline}"
55
- @mousemove="${handleTimelineIn}"
56
- @mouseout="${handleTimelineOut}"
57
- role="progressbar"
58
- tabindex="0"
59
- >
60
- <div
61
- id="bar-${id}"
62
- class="timeline__completed"
63
- style="width: 0;"
64
- ></div>
65
- </div>
66
- <div class="timeline__numbers">
67
- <div id="txt-${id}">00:00</div>
68
- <div>${formatTime(totalTime)}</div>
69
- </div>
70
-
71
- ${chapters.map(chapter => {
72
- const limitClasses = {
73
- 'timeline__chapter-limit': true,
74
- 'timeline__chapter-limit--played': currentTime > chapter.start / 1000
75
- };
76
- const position = (100 / totalTime) * (chapter.start / 1000);
77
- return html`
78
- <div
79
- class="${classMap(limitClasses)}"
80
- style="--timeline--chapter-limit-position: ${position}%;"
81
- ></div>
82
- `;
83
- })}
84
- ${timePosition === null
85
- ? nothing
86
- : html`
87
- <div
88
- class="timeline__tooltip"
89
- style="--timeline-tooltip-position: ${timePosition.left}px;"
90
- >
91
- <div class="timeline__tooltip-inner">
92
- ${timePosition.chapter
93
- ? html`<span class="timeline__tooltip-chapter-title"
94
- >${timePosition.chapter}</span
95
- >`
96
- : nothing}
97
- <span class="timeline__tooltip-time"
98
- >${timePosition.visual}</span
99
- >
100
- </div>
101
- </div>
102
- `}
103
- </div>
104
- `;
105
- }
106
-
107
- if (!customElements.get('mythical-player-timeline')) {
108
- customElements.define(
109
- 'mythical-player-timeline',
110
- component(TimelineComponent, {
111
- observedAttributes: [],
112
- useShadowDOM: false
113
- })
114
- );
115
- }
116
-
117
- export function Timeline(props) {
118
- return html`<mythical-player-timeline
119
- .props="${props}"
120
- ></mythical-player-timeline>`;
121
- }
@@ -1,52 +0,0 @@
1
- @use '@outbook/design-decisions/measures';
2
- @use '../_lib/variables';
3
-
4
- @mixin style {
5
- mythical-cover-fallback {
6
- display: block;
7
- }
8
-
9
- .cover-fallback {
10
- --_cover-fallback-bg-color: light-dark(var(--_accent-200), var(--_accent-700));
11
- --_cover-fallback-font-size: #{measures.$baseline * 4};
12
-
13
- &.cover-fallback--in-file-item {
14
- --_cover-fallback-font-size: #{measures.$baseline * 4};
15
- }
16
- }
17
- @container player (width >= #{variables.$player-widht-m}) {
18
- .cover-fallback {
19
- --_cover-fallback-font-size: #{measures.$baseline * 20};
20
- &.cover-fallback--in-file-item {
21
- --_cover-fallback-font-size: #{measures.$baseline * 4};
22
- }
23
- }
24
- }
25
-
26
- .cover-fallback {
27
- width: inherit;
28
- padding-top: 100%;
29
- background-color: var(--_cover-fallback-bg-color);
30
- display: flex;
31
- align-items: center;
32
- justify-content: center;
33
- position: relative;
34
- }
35
-
36
- .cover-fallback__icon {
37
- width: 80%;
38
- height: 80%;
39
- position: absolute;
40
- top: 50%;
41
- left: 50%;
42
- transform: translate(-50%, -50%);
43
- color: var(--_text-color);
44
- display: flex;
45
- justify-content: center;
46
- align-items: center;
47
- }
48
- .cover-fallback__icon-inner {
49
- width: 100%;
50
- height: 100%;
51
- }
52
- }
@@ -1,125 +0,0 @@
1
- @use '@outbook/design-decisions/measures';
2
- @use '@outbook/design-decisions/outline';
3
- @use '../_lib/variables';
4
- @use '@outbook/colorful/palettes-oklch' as colors;
5
-
6
-
7
- @mixin style() {
8
-
9
- .timeline {
10
- --timeline--background: var(--_accent-200);
11
- --timeline--completed-background: var(--_accent-800);
12
- --timeline--font-size: #{measures.$baseline * 1.5};
13
- --timeline--numbers-separation: #{measures.$baseline * 0.25};
14
- --timeline--chapter-limit-color: #{colors.$neutral_950};
15
- .timeline__chapter-limit--played {
16
- --timeline--chapter-limit-color: #{colors.$neutral_50};
17
- }
18
- .ambient-dark & {
19
- --timeline--background: var(--_accent-800);
20
- --timeline--chapter-limit-color: #{colors.$neutral_50};
21
- --timeline--completed-background: var(--_accent-200);
22
- .timeline__chapter-limit--played {
23
- --timeline--chapter-limit-color: #{colors.$neutral_950};
24
- }
25
- }
26
- }
27
- @container player (width >= #{variables.$player-widht-m}) {
28
- .timeline {
29
- --timeline--font-size: #{measures.$baseline * 1.75};
30
- --timeline--numbers-separation: #{measures.$baseline * 0.5};
31
- }
32
- }
33
-
34
- @container player (width >= #{variables.$player-widht-l}) {
35
- .timeline {
36
- --timeline--font-size: #{measures.$baseline * 2};
37
- --timeline--numbers-separation: #{measures.$baseline};
38
- }
39
- }
40
-
41
- mythical-player-timeline {
42
- display: block;
43
- }
44
-
45
- .timeline {
46
- position: relative;
47
- padding: 0 #{measures.$baseline};
48
- }
49
-
50
- .timeline__track {
51
- height: #{measures.$baseline};
52
- width: 100%;
53
- background-color: var(--timeline--background);
54
- cursor: pointer;
55
- &:focus-visible {
56
- @include outline.outside();
57
- }
58
-
59
- }
60
-
61
- .timeline__completed {
62
- height: 100%;
63
- background-color: var(--timeline--completed-background);
64
- //transition: width 0.35s;
65
- }
66
-
67
- .timeline__numbers {
68
- display: flex;
69
- justify-content: space-between;
70
- padding-top: var(--timeline--numbers-separation);
71
- font-size: var(--timeline--font-size);
72
- }
73
-
74
- .timeline__tooltip {
75
- position: absolute;
76
- top: 0;
77
- transform: translate(-50%, -100%);
78
- left: var(--timeline-tooltip-position);
79
- padding-bottom: #{measures.$baseline};
80
- z-index: 3;
81
- }
82
-
83
- .timeline__tooltip-inner {
84
- background-color: #{colors.$white};
85
- color: #{colors.$neutral_700};
86
- padding: #{measures.$baseline * 0.5};
87
- position: relative;
88
- border-width: 1px 1px 0 1px;
89
- border-color: colors.$neutral_200;
90
- border-style: solid;
91
- max-width: #{measures.$baseline * 16};
92
- display: flex;
93
- flex-direction: column;
94
- align-items: center;
95
- &:after {
96
- content: "";
97
- width: 0;
98
- height: 0;
99
- border-style: solid;
100
- border-width: 0 4px 6.9px 4px;
101
- border-color: transparent transparent #{colors.$white} transparent;
102
- position: absolute;
103
- top: 100%;
104
- left: 50%;
105
- transform: translateX(-50%) rotate(180deg);
106
- }
107
- }
108
-
109
- .timeline__tooltip-time {
110
- font-size: #{measures.$baseline * 2};
111
- }
112
- .timeline__tooltip-chapter-title {
113
- font-size: #{measures.$baseline * 1.5};
114
- margin-bottom: #{measures.$baseline * 0.5};
115
- }
116
-
117
- .timeline__chapter-limit {
118
- width: 1px;
119
- height: #{measures.$baseline};
120
- background-color: var(--timeline--chapter-limit-color);
121
- position: absolute;
122
- top: 0;
123
- left: var(--timeline--chapter-limit-position);
124
- }
125
- }