@stacksjs/defaults 0.70.162 → 0.70.163

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
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/defaults",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.70.162",
5
+ "version": "0.70.163",
6
6
  "description": "Default Stacks scaffold resources (views, layouts, components, preloader) — shipped so apps consuming the framework from node_modules get the same fallback resources a vendored checkout provides. Source of truth: storage/framework/defaults.",
7
7
  "author": "Chris Breuer",
8
8
  "license": "MIT",
@@ -0,0 +1,92 @@
1
+ <script server>
2
+ interface AudioSource { src: string, type?: string }
3
+ interface AudioDrm {
4
+ widevine?: { url: string, headers?: Record<string, string> }
5
+ playready?: { url: string, headers?: Record<string, string> }
6
+ fairplay?: { url: string, certificateUrl: string, headers?: Record<string, string> }
7
+ }
8
+ interface AudioTrack { src: string, kind: 'subtitles' | 'captions' | 'descriptions' | 'chapters' | 'metadata', srclang?: string, label?: string, default?: boolean }
9
+ interface AudioProps {
10
+ src: string
11
+ sources?: AudioSource[]
12
+ drm?: AudioDrm
13
+ tracks?: AudioTrack[]
14
+ title?: string
15
+ artist?: string
16
+ artwork?: string
17
+ waveform?: string
18
+ controls?: boolean
19
+ autoplay?: boolean
20
+ loop?: boolean
21
+ muted?: boolean
22
+ preload?: 'none' | 'metadata' | 'auto'
23
+ crossorigin?: 'anonymous' | 'use-credentials'
24
+ class?: string
25
+ }
26
+ const props = defineProps<AudioProps>()
27
+ const src = props.src || ''
28
+ const sources = props.sources || []
29
+ const tracks = props.tracks || []
30
+ const title = props.title || 'Audio player'
31
+ const artist = props.artist || ''
32
+ const artwork = props.artwork || ''
33
+ const waveform = props.waveform || ''
34
+ const controls = props.controls !== false
35
+ const preload = props.preload || 'metadata'
36
+ const crossorigin = props.crossorigin || ''
37
+ const className = props.class || ''
38
+ const playerSources = (sources.length ? sources : src ? [{ src }] : []).map(source => props.drm && (source.type === 'application/dash+xml' || source.src.split(/[?#]/, 1)[0].toLowerCase().endsWith('.mpd')) ? { ...source, type: 'application/dash+xml', drm: props.drm } : source)
39
+ const playerTracks = tracks.map(track => ({ ...track, language: track.srclang }))
40
+ const playerConfig = JSON.stringify({ sources: playerSources, tracks: playerTracks, ...(props.drm ? { drm: props.drm } : {}) }).replaceAll('<', '\\u003c').replaceAll('>', '\\u003e').replaceAll('&', '\\u0026')
41
+ </script>
42
+
43
+ <script client>
44
+ import { registerElements } from 'ts-video-player/elements'
45
+ registerElements()
46
+ </script>
47
+
48
+ <figure class="m-0 overflow-hidden rounded-2xl bg-white shadow-sm ring-1 ring-stone-200 dark:bg-stone-950 dark:ring-stone-800 {{ className }}">
49
+ <div class="flex gap-4 items-center p-4 sm:p-5">
50
+ @if(artwork)
51
+ <img src="{{ artwork }}" alt="" width="72" height="72" loading="lazy" decoding="async" class="object-cover shrink-0 h-18 w-18 ring-1 ring-black/5 rounded-xl dark:ring-white/10">
52
+ @else
53
+ <div class="flex items-center justify-center shrink-0 h-18 w-18 text-stone-500 dark:text-stone-400 bg-stone-100 dark:bg-stone-900 rounded-xl" aria-hidden="true"><span class="h-7 w-7 i-hugeicons-music-note-01"></span></div>
54
+ @endif
55
+ <div class="flex-1 min-w-0">
56
+ <figcaption class="font-semibold text-sm text-stone-950 truncate dark:text-white">{{ title }}</figcaption>
57
+ @if(artist)<p class="mt-0.5 text-sm text-stone-500 truncate dark:text-stone-400">{{ artist }}</p>@endif
58
+ @if(waveform)<img src="{{ waveform }}" alt="" loading="lazy" decoding="async" class="object-cover mt-3 h-10 w-full opacity-70 dark:opacity-80">@endif
59
+ </div>
60
+ </div>
61
+ <div class="p-3 bg-stone-950 border-stone-100 border-t dark:border-stone-800">
62
+ <video-player src="{{ src }}" title="{{ title }}" data-media-config="{{ playerConfig }}" @if(controls) controls @endif @if(props.autoplay) autoplay @endif @if(props.loop) loop @endif @if(props.muted) muted @endif preload="{{ preload }}" @if(crossorigin) crossorigin="{{ crossorigin }}" keep-alive aria-label="{{ title }}" class="block overflow-hidden relative h-16 w-full text-white rounded-xl focus-within:ring-2 focus-within:ring-sky-500">
63
+ <media-error-display></media-error-display>
64
+ <audio data-native-fallback src="{{ src }}" @if(controls) controls @endif @if(props.autoplay) autoplay @endif @if(props.loop) loop @endif @if(props.muted) muted @endif preload="{{ preload }}" @if(crossorigin) crossorigin="{{ crossorigin }}" @endif aria-label="{{ title }}" class="block h-16 w-full accent-sky-500">
65
+ @foreach (sources as source)<source src="{{ source.src }}" @if(source.type) type="{{ source.type }}" @endif>@endforeach
66
+ @foreach (tracks as track)<track src="{{ track.src }}" kind="{{ track.kind }}" @if(track.srclang) srclang="{{ track.srclang }}" @endif @if(track.label) label="{{ track.label }}" @endif @if(track.default) default @endif>@endforeach
67
+ Your browser does not support audio playback.
68
+ </audio>
69
+ @if(controls)
70
+ <video-skin>
71
+ <media-seek-button slot="left" seconds="-10" aria-label="Back 10 seconds"></media-seek-button>
72
+ <media-play-button slot="left" aria-label="Play or pause"></media-play-button>
73
+ <media-seek-button slot="left" seconds="10" aria-label="Forward 10 seconds"></media-seek-button>
74
+ <media-time-group slot="center" class="font-mono tabular-nums text-xs"></media-time-group>
75
+ <media-progress-bar slot="center" @if(waveform) waveform="{{ waveform }}" @endif aria-label="Audio progress"></media-progress-bar>
76
+ <media-mute-button slot="right" aria-label="Mute or unmute"></media-mute-button>
77
+ <media-volume-slider slot="right" aria-label="Volume"></media-volume-slider>
78
+ <media-playback-rate-button slot="right" rates="0.5,0.75,1,1.25,1.5,2" aria-label="Playback speed"></media-playback-rate-button>
79
+ <media-settings-menu slot="right" aria-label="Audio settings"></media-settings-menu>
80
+ <media-airplay-button slot="right" aria-label="Play on AirPlay"></media-airplay-button>
81
+ <media-remote-playback-button slot="right" aria-label="Play on another device"></media-remote-playback-button>
82
+ </video-skin>
83
+ @endif
84
+ </video-player>
85
+ </div>
86
+ </figure>
87
+
88
+ <style>
89
+ video-player:not(:defined) video-skin { display: none; }
90
+ video-player:defined > [data-native-fallback] { display: none; }
91
+ @media (prefers-reduced-motion: reduce) { video-player, video-player * { transition-duration: 0.01ms !important; } }
92
+ </style>
@@ -0,0 +1,59 @@
1
+ <script server>
2
+ interface ImageSource { type: string, srcset: string, media?: string }
3
+ interface ImageProps {
4
+ src: string
5
+ alt: string
6
+ width?: number
7
+ height?: number
8
+ aspectRatio?: string
9
+ srcset?: string
10
+ sizes?: string
11
+ sources?: ImageSource[]
12
+ loading?: 'lazy' | 'eager'
13
+ decoding?: 'async' | 'sync' | 'auto'
14
+ fetchpriority?: 'high' | 'low' | 'auto'
15
+ priority?: boolean
16
+ decorative?: boolean
17
+ fit?: 'cover' | 'contain' | 'fill' | 'none' | 'scale-down'
18
+ position?: string
19
+ placeholder?: string
20
+ class?: string
21
+ }
22
+ const props = defineProps<ImageProps>()
23
+ if (!props.decorative && !props.alt?.trim()) throw new TypeError('Image alt text is required unless decorative is true')
24
+ const src = props.src || ''
25
+ const alt = props.decorative ? '' : props.alt || ''
26
+ const width = props.width || ''
27
+ const height = props.height || ''
28
+ const ratio = props.aspectRatio || (width && height ? `${width} / ${height}` : '')
29
+ const srcset = props.srcset || ''
30
+ const sizes = props.sizes || (srcset ? '100vw' : '')
31
+ const sources = props.sources || []
32
+ const loading = props.priority ? 'eager' : props.loading || 'lazy'
33
+ const decoding = props.decoding || 'async'
34
+ const priority = props.priority ? 'high' : props.fetchpriority || 'auto'
35
+ const fit = props.fit || 'cover'
36
+ const position = props.position || 'center'
37
+ const placeholder = props.placeholder || ''
38
+ const className = props.class || ''
39
+ </script>
40
+
41
+ <picture class="relative block overflow-hidden bg-stone-100 dark:bg-stone-900 {{ className }}" @if(ratio) style="aspect-ratio: {{ ratio }};" @endif>
42
+ @foreach (sources as source)
43
+ <source type="{{ source.type }}" srcset="{{ source.srcset }}" @if(source.media) media="{{ source.media }}" @endif @if(sizes) sizes="{{ sizes }}" @endif>
44
+ @endforeach
45
+ <img
46
+ src="{{ src }}"
47
+ alt="{{ alt }}"
48
+ @if(width) width="{{ width }}" @endif
49
+ @if(height) height="{{ height }}" @endif
50
+ @if(srcset) srcset="{{ srcset }}" @endif
51
+ @if(sizes) sizes="{{ sizes }}" @endif
52
+ loading="{{ loading }}"
53
+ decoding="{{ decoding }}"
54
+ fetchpriority="{{ priority }}"
55
+ @if(props.decorative) aria-hidden="true" @endif
56
+ class="h-full w-full"
57
+ style="object-fit: {{ fit }}; object-position: {{ position }}; @if(placeholder) background: {{ placeholder }} center / cover no-repeat; @endif"
58
+ >
59
+ </picture>
@@ -0,0 +1,108 @@
1
+ <script server>
2
+ interface VideoSource { src: string, type?: string }
3
+ interface VideoDrm {
4
+ widevine?: { url: string, headers?: Record<string, string> }
5
+ playready?: { url: string, headers?: Record<string, string> }
6
+ fairplay?: { url: string, certificateUrl: string, headers?: Record<string, string> }
7
+ }
8
+ interface VideoTrack { src: string, kind: 'subtitles' | 'captions' | 'descriptions' | 'chapters' | 'metadata', srclang?: string, label?: string, default?: boolean }
9
+ interface VideoProps {
10
+ src: string
11
+ sources?: VideoSource[]
12
+ drm?: VideoDrm
13
+ tracks?: VideoTrack[]
14
+ poster?: string
15
+ title?: string
16
+ width?: number
17
+ height?: number
18
+ aspectRatio?: string
19
+ controls?: boolean
20
+ autoplay?: boolean
21
+ loop?: boolean
22
+ muted?: boolean
23
+ playsinline?: boolean
24
+ preload?: 'none' | 'metadata' | 'auto'
25
+ crossorigin?: 'anonymous' | 'use-credentials'
26
+ controlslist?: string
27
+ disablePictureInPicture?: boolean
28
+ class?: string
29
+ }
30
+ const props = defineProps<VideoProps>()
31
+ const src = props.src || ''
32
+ const sources = props.sources || []
33
+ const tracks = props.tracks || []
34
+ const title = props.title || 'Video player'
35
+ const poster = props.poster || ''
36
+ const width = props.width || ''
37
+ const height = props.height || ''
38
+ const ratio = props.aspectRatio || (width && height ? `${width} / ${height}` : '16 / 9')
39
+ const controls = props.controls !== false
40
+ const inline = props.playsinline !== false
41
+ const preload = props.preload || 'metadata'
42
+ const crossorigin = props.crossorigin || ''
43
+ const controlslist = props.controlslist || 'nodownload'
44
+ const className = props.class || ''
45
+ const playerSources = (sources.length ? sources : src ? [{ src }] : []).map(source => props.drm && (source.type === 'application/dash+xml' || source.src.split(/[?#]/, 1)[0].toLowerCase().endsWith('.mpd')) ? { ...source, type: 'application/dash+xml', drm: props.drm } : source)
46
+ const playerTracks = tracks.map(track => ({ ...track, language: track.srclang }))
47
+ const playerConfig = JSON.stringify({ sources: playerSources, tracks: playerTracks, ...(props.drm ? { drm: props.drm } : {}) }).replaceAll('<', '\\u003c').replaceAll('>', '\\u003e').replaceAll('&', '\\u0026')
48
+ </script>
49
+
50
+ <script client>
51
+ import { registerElements } from 'ts-video-player/elements'
52
+ registerElements()
53
+ </script>
54
+
55
+ <figure class="m-0 {{ className }}">
56
+ <video-player
57
+ src="{{ src }}"
58
+ data-media-config="{{ playerConfig }}"
59
+ @if(poster) poster="{{ poster }}" @endif
60
+ @if(controls) controls @endif
61
+ @if(props.autoplay) autoplay @endif
62
+ @if(props.loop) loop @endif
63
+ @if(props.muted) muted @endif
64
+ @if(inline) playsinline @endif
65
+ preload="{{ preload }}"
66
+ @if(crossorigin) crossorigin="{{ crossorigin }}" @endif
67
+ controlslist="{{ controlslist }}"
68
+ @if(props.disablePictureInPicture) disable-picture-in-picture @endif
69
+ keep-alive
70
+ aria-label="{{ title }}"
71
+ class="block overflow-hidden relative w-full bg-black ring-1 ring-black/10 rounded-2xl dark:ring-white/10 focus-within:ring-2 focus-within:ring-sky-500 shadow-sm group"
72
+ style="aspect-ratio: {{ ratio }};"
73
+ >
74
+ <media-error-display></media-error-display>
75
+ <video data-native-fallback src="{{ src }}" @if(poster) poster="{{ poster }}" @endif @if(width) width="{{ width }}" @endif @if(height) height="{{ height }}" @endif @if(controls) controls @endif @if(props.autoplay) autoplay @endif @if(props.loop) loop @endif @if(props.muted) muted @endif @if(inline) playsinline @endif preload="{{ preload }}" @if(crossorigin) crossorigin="{{ crossorigin }}" @endif controlslist="{{ controlslist }}" @if(props.disablePictureInPicture) disablepictureinpicture @endif aria-label="{{ title }}" class="object-contain h-full w-full">
76
+ @foreach (sources as source)
77
+ <source src="{{ source.src }}" @if(source.type) type="{{ source.type }}" @endif>
78
+ @endforeach
79
+ @foreach (tracks as track)
80
+ <track src="{{ track.src }}" kind="{{ track.kind }}" @if(track.srclang) srclang="{{ track.srclang }}" @endif @if(track.label) label="{{ track.label }}" @endif @if(track.default) default @endif>
81
+ @endforeach
82
+ Your browser does not support video playback.
83
+ </video>
84
+ <video-skin>
85
+ <media-seek-button slot="left" seconds="-10" aria-label="Back 10 seconds"></media-seek-button>
86
+ <media-play-button slot="left" aria-label="Play or pause"></media-play-button>
87
+ <media-seek-button slot="left" seconds="10" aria-label="Forward 10 seconds"></media-seek-button>
88
+ <media-live-button slot="left" aria-label="Go to live edge"></media-live-button>
89
+ <media-time-group slot="center" class="font-mono tabular-nums text-xs"></media-time-group>
90
+ <media-progress-bar slot="center" previews="metadata" aria-label="Video progress"></media-progress-bar>
91
+ <media-mute-button slot="right" aria-label="Mute or unmute"></media-mute-button>
92
+ <media-volume-slider slot="right" aria-label="Volume"></media-volume-slider>
93
+ <media-playback-rate-button slot="right" rates="0.5,0.75,1,1.25,1.5,2" aria-label="Playback speed"></media-playback-rate-button>
94
+ <media-settings-menu slot="right" aria-label="Video settings"></media-settings-menu>
95
+ <media-airplay-button slot="right" aria-label="Play on AirPlay"></media-airplay-button>
96
+ <media-remote-playback-button slot="right" aria-label="Play on another device"></media-remote-playback-button>
97
+ <media-pip-button slot="right" aria-label="Picture in Picture"></media-pip-button>
98
+ <media-fullscreen-button slot="right" aria-label="Fullscreen"></media-fullscreen-button>
99
+ </video-skin>
100
+ </video-player>
101
+ <figcaption class="sr-only">{{ title }}</figcaption>
102
+ </figure>
103
+
104
+ <style>
105
+ video-player:not(:defined) video-skin { display: none; }
106
+ video-player:defined > [data-native-fallback] { display: none; }
107
+ @media (prefers-reduced-motion: reduce) { video-player, video-player * { transition-duration: 0.01ms !important; } }
108
+ </style>
@@ -11,3 +11,6 @@ export { default as SidebarModern } from './Dashboard/SidebarModern.stx'
11
11
 
12
12
  // Dashboard and UI components
13
13
  export * from './Dashboard'
14
+ export { default as Audio } from './Audio.stx'
15
+ export { default as Image } from './Image.stx'
16
+ export { default as Video } from './Video.stx'