@stacksjs/defaults 0.75.179 → 0.75.181

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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: stacks-dashboard
3
- description: Use when building or customizing the Stacks admin dashboard, including dashboard pages, model management views, analytics widgets, commerce dashboards, content management, settings panels, deployment monitoring, job/queue management, or the 414 built-in dashboard components. Covers the dashboard system at storage/framework/defaults/.
3
+ description: Use when building or customizing the Stacks admin dashboard, including dashboard pages, model management views, analytics widgets, commerce dashboards, content management, settings panels, deployment monitoring, job/queue management, or the 413 built-in dashboard components. Covers the dashboard system at storage/framework/defaults/.
4
4
  license: MIT
5
5
  compatibility: Bun >= 1.3.0, TypeScript
6
6
  allowed-tools: Read Edit Write Bash Grep Glob
@@ -8,7 +8,7 @@ allowed-tools: Read Edit Write Bash Grep Glob
8
8
 
9
9
  # Stacks Dashboard
10
10
 
11
- The Stacks admin dashboard provides a full-featured admin panel with 100+ route views, 414 components, and a multi-section layout.
11
+ The Stacks admin dashboard provides a full-featured admin panel with 100+ route views, 413 components, and a multi-section layout.
12
12
 
13
13
  ## Key Paths
14
14
  - Dashboard components: `storage/framework/defaults/resources/components/Dashboard/`
@@ -346,7 +346,7 @@ simulator's Music app has nothing to play; launch a simulator build with
346
346
 
347
347
  ## Crash reporting (bughq)
348
348
 
349
- `crashReporting` in `config/mobile.ts` turns on Craft's native reporter (craft-native >= 0.0.176; the build refuses an older one). It sends crashes, hangs (MetricKit), ANRs and native crashes (ApplicationExitInfo) and uncaught JavaScript errors in native screens and the app script to bughq from an on-disk outbox, with app version and build, OS, device model, session and breadcrumbs (screens shown, failed requests).
349
+ `crashReporting` in `config/mobile.ts` turns on Craft's native reporter (craft-native >= 0.0.179; the build refuses an older one). It sends crashes, hangs (MetricKit), ANRs and native crashes (ApplicationExitInfo) and uncaught JavaScript errors in native screens and the app script to bughq from an on-disk outbox, with app version and build, OS, device model, session and breadcrumbs (screens shown, failed requests).
350
350
 
351
351
  ```ts
352
352
  export default {
@@ -466,10 +466,12 @@ option the original metres heuristic remains for unlabelled inputs.
466
466
  - A screen that is a task of its own (a workout player, a composer) marks an
467
467
  element `data-native-hide-tab-bar`, and the tab bar steps away while it is
468
468
  shown, its reserved space with it.
469
- - `<Video :src="current.url">` is the framework's player (ts-video-player):
470
- YouTube, Vimeo, HLS, DASH or a file. `src` is reactive, so one player in a
471
- sheet can show whichever video is chosen; clearing it pauses the player.
472
- Craft lets an https iframe load inside the app, so embeds play inline.
469
+ - `<Video :src="current.url" autoplay>` is the player from
470
+ `@stacksjs/components` (ts-video-player): YouTube, Vimeo, HLS, DASH or a
471
+ file, with controls in the page's language. `src`, `poster`, `aspectRatio`
472
+ and `label` are reactive, so one player in a sheet can show whichever video
473
+ is chosen, `autoplay` starts each one, and clearing `src` pauses it. Craft
474
+ lets an https iframe load inside the app, so embeds play inline.
473
475
 
474
476
  `<NativeTabItem match="/m/workout">` keeps a tab lit on the detail screens
475
477
  opened from it. A tab bar is a `<nav>`, where a link is otherwise current only
@@ -2,7 +2,7 @@
2
2
  "publisher": "Stacks",
3
3
  "name": "vscode-stacks",
4
4
  "displayName": "Stacks",
5
- "version": "0.75.179",
5
+ "version": "0.75.181",
6
6
  "description": "Stacks, built into VS Code: stx templates, pickier linting and formatting, encrypted env files, the dev server preview and every buddy command. No other extensions required.",
7
7
  "license": "MIT",
8
8
  "funding": "https://github.com/sponsors/chrisbbreuer",
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/defaults",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.75.179",
5
+ "version": "0.75.181",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/stacksjs/stacks.git",
@@ -55,7 +55,7 @@
55
55
  "dependencies": {
56
56
  "@iconify-json/f7": "^1.2.2",
57
57
  "@iconify-json/hugeicons": "^1.2.35",
58
- "@stacksjs/mobile": "0.75.179",
58
+ "@stacksjs/mobile": "0.75.181",
59
59
  "@stacksjs/sanitizer": "^0.2.483",
60
60
  "@xterm/addon-fit": "^0.11.0",
61
61
  "@xterm/xterm": "^6.0.0",
@@ -27,4 +27,3 @@ export { default as NativeSheet } from './NativeSheet.stx'
27
27
  export { default as NativeSymbol } from './NativeSymbol.stx'
28
28
  export { default as NativeTabBar } from './NativeTabBar.stx'
29
29
  export { default as NativeTabItem } from './NativeTabItem.stx'
30
- export { default as Video } from './Video.stx'
@@ -1,132 +0,0 @@
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
- // A YouTube or Vimeo link is a web page, not a media file: the native <video>
48
- // fallback cannot play it, so without the player it offers the link instead.
49
- const isEmbed = /^https?:\/\/(?:[\w-]+\.)*(?:youtube\.com|youtube-nocookie\.com|youtu\.be|vimeo\.com)\//i.test(src)
50
- const playerConfig = JSON.stringify({ sources: playerSources, tracks: playerTracks, ...(props.drm ? { drm: props.drm } : {}) }).replaceAll('<', '\\u003c').replaceAll('>', '\\u003e').replaceAll('&', '\\u0026')
51
- </script>
52
-
53
- <script client>
54
- import { registerElements } from 'ts-video-player/elements'
55
- registerElements()
56
-
57
- // `src` and `poster` follow the parent, so one player can show whichever
58
- // video is chosen: `<Video :src="current.url" />` in a sheet. An empty `src`
59
- // pauses it, which is what closing that sheet should do.
60
- const liveSrc = useReactiveProp<string>('src', '')
61
- const livePoster = useReactiveProp<string>('poster', '')
62
- const element = useRef('player')
63
-
64
- effect(() => {
65
- if (!liveSrc())
66
- (element.current as { player?: { pause: () => void } } | null)?.player?.pause()
67
- })
68
- </script>
69
-
70
- {{-- No src attribute until there is a video: an empty one resolves to the
71
- page's own URL, which the browser then downloads as the video. --}}
72
- <figure class="m-0 {{ className }}">
73
- <video-player
74
- ref="player"
75
- @if(src) src="{{ src }}" @endif
76
- :src="liveSrc() || null"
77
- :poster="livePoster() || null"
78
- data-media-config="{{ playerConfig }}"
79
- @if(poster) poster="{{ poster }}" @endif
80
- @if(controls) controls @endif
81
- @if(props.autoplay) autoplay @endif
82
- @if(props.loop) loop @endif
83
- @if(props.muted) muted @endif
84
- @if(inline) playsinline @endif
85
- preload="{{ preload }}"
86
- @if(crossorigin) crossorigin="{{ crossorigin }}" @endif
87
- controlslist="{{ controlslist }}"
88
- @if(props.disablePictureInPicture) disable-picture-in-picture @endif
89
- keep-alive
90
- aria-label="{{ title }}"
91
- 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"
92
- style="aspect-ratio: {{ ratio }};"
93
- >
94
- <media-error-display></media-error-display>
95
- @if(isEmbed)
96
- <a data-native-fallback href="{{ src }}" target="_blank" rel="noopener" class="flex items-center justify-center h-full w-full text-sm text-white underline">Watch {{ title }}</a>
97
- @else
98
- <video data-native-fallback @if(src) src="{{ src }}" @endif @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">
99
- @foreach (sources as source)
100
- <source src="{{ source.src }}" @if(source.type) type="{{ source.type }}" @endif>
101
- @endforeach
102
- @foreach (tracks as track)
103
- <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>
104
- @endforeach
105
- Your browser does not support video playback.
106
- </video>
107
- @endif
108
- <video-skin>
109
- <media-seek-button slot="left" seconds="-10" aria-label="Back 10 seconds"></media-seek-button>
110
- <media-play-button slot="left" aria-label="Play or pause"></media-play-button>
111
- <media-seek-button slot="left" seconds="10" aria-label="Forward 10 seconds"></media-seek-button>
112
- <media-live-button slot="left" aria-label="Go to live edge"></media-live-button>
113
- <media-time-group slot="center" class="font-mono tabular-nums text-xs"></media-time-group>
114
- <media-progress-bar slot="center" previews="metadata" aria-label="Video progress"></media-progress-bar>
115
- <media-mute-button slot="right" aria-label="Mute or unmute"></media-mute-button>
116
- <media-volume-slider slot="right" aria-label="Volume"></media-volume-slider>
117
- <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>
118
- <media-settings-menu slot="right" aria-label="Video settings"></media-settings-menu>
119
- <media-airplay-button slot="right" aria-label="Play on AirPlay"></media-airplay-button>
120
- <media-remote-playback-button slot="right" aria-label="Play on another device"></media-remote-playback-button>
121
- <media-pip-button slot="right" aria-label="Picture in Picture"></media-pip-button>
122
- <media-fullscreen-button slot="right" aria-label="Fullscreen"></media-fullscreen-button>
123
- </video-skin>
124
- </video-player>
125
- <figcaption class="sr-only">{{ title }}</figcaption>
126
- </figure>
127
-
128
- <style>
129
- video-player:not(:defined) video-skin { display: none; }
130
- video-player:defined > [data-native-fallback] { display: none; }
131
- @media (prefers-reduced-motion: reduce) { video-player, video-player * { transition-duration: 0.01ms !important; } }
132
- </style>
@@ -1,36 +0,0 @@
1
- import { describe, expect, it } from 'bun:test'
2
- import { readFileSync } from 'node:fs'
3
- import { join } from 'node:path'
4
-
5
- const video = readFileSync(join(import.meta.dir, 'Video.stx'), 'utf8')
6
-
7
- describe('Video', () => {
8
- it('follows a reactive src and poster, so one player can show whichever video is chosen', () => {
9
- expect(video).toContain(`useReactiveProp<string>('src', '')`)
10
- expect(video).toContain(`:src="liveSrc() || null"`)
11
- expect(video).toContain(`:poster="livePoster() || null"`)
12
- })
13
-
14
- it('renders no src attribute without a video, which would fetch the page itself', () => {
15
- // An empty src resolves to the document's URL: the browser downloads the
16
- // whole page as the video and fails, every time a player with nothing
17
- // chosen yet renders.
18
- expect(video).not.toMatch(/(?<!@if\(src\) )src="\{\{ src \}\}"/)
19
- expect(video.match(/@if\(src\) src="\{\{ src \}\}" @endif/g)?.length).toBe(2)
20
- })
21
-
22
- it('pauses when its src is cleared, as closing a sheet does', () => {
23
- expect(video).toContain(`useRef('player')`)
24
- expect(video).toContain('ref="player"')
25
- expect(video).toMatch(/if \(!liveSrc\(\)\)[\s\S]*?\.pause\(\)/)
26
- })
27
-
28
- it('never hands a YouTube or Vimeo page to the native <video> fallback', () => {
29
- const isEmbed = new RegExp(video.match(/const isEmbed = \/(.+)\/i\.test/)![1]!, 'i')
30
- for (const url of ['https://www.youtube.com/watch?v=aclHkVaku9U', 'https://youtu.be/aclHkVaku9U', 'https://m.youtube.com/shorts/aclHkVaku9U', 'https://vimeo.com/76979871'])
31
- expect(isEmbed.test(url)).toBe(true)
32
- for (const url of ['https://cdn.example.com/clip.mp4', '/media/demo/manifest.mpd', 'https://example.com/youtube.com/fake.mp4'])
33
- expect(isEmbed.test(url)).toBe(false)
34
- expect(video).toContain('@if(isEmbed)')
35
- })
36
- })