cursedbelt 5.46.2 → 5.48.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.
Files changed (170) hide show
  1. package/dist/react/charts/IntervalTimeline.d.ts.map +1 -1
  2. package/dist/react/charts/IntervalTimeline.js +2 -2
  3. package/dist/react/charts/IntervalTimeline.js.map +1 -1
  4. package/dist/react/chat/DateMarker.d.ts +4 -0
  5. package/dist/react/chat/DateMarker.d.ts.map +1 -1
  6. package/dist/react/chat/DateMarker.js +15 -2
  7. package/dist/react/chat/DateMarker.js.map +1 -1
  8. package/dist/react/components/Button.d.ts.map +1 -1
  9. package/dist/react/components/Button.js +5 -0
  10. package/dist/react/components/Button.js.map +1 -1
  11. package/dist/react/components/DataTable/DataTable.d.ts.map +1 -1
  12. package/dist/react/components/DataTable/DataTable.js +1 -1
  13. package/dist/react/components/DataTable/DataTable.js.map +1 -1
  14. package/dist/react/components/DataTable/SortHeader.d.ts +4 -1
  15. package/dist/react/components/DataTable/SortHeader.d.ts.map +1 -1
  16. package/dist/react/components/DataTable/SortHeader.js +22 -5
  17. package/dist/react/components/DataTable/SortHeader.js.map +1 -1
  18. package/dist/react/components/DataTable/types.d.ts +10 -0
  19. package/dist/react/components/DataTable/types.d.ts.map +1 -1
  20. package/dist/react/components/DataTable/types.js.map +1 -1
  21. package/dist/react/components/DropdownMenu.js +10 -4
  22. package/dist/react/components/DropdownMenu.js.map +1 -1
  23. package/dist/react/components/Field.d.ts +9 -1
  24. package/dist/react/components/Field.d.ts.map +1 -1
  25. package/dist/react/components/Field.js +3 -3
  26. package/dist/react/components/Field.js.map +1 -1
  27. package/dist/react/components/SecretInput.d.ts +4 -2
  28. package/dist/react/components/SecretInput.d.ts.map +1 -1
  29. package/dist/react/components/SecretInput.js +2 -2
  30. package/dist/react/components/SecretInput.js.map +1 -1
  31. package/dist/react/components/auth/AuthCard.d.ts.map +1 -1
  32. package/dist/react/components/auth/AuthCard.js +5 -1
  33. package/dist/react/components/auth/AuthCard.js.map +1 -1
  34. package/dist/react/components/auth/PasswordSetFields.d.ts.map +1 -1
  35. package/dist/react/components/auth/PasswordSetFields.js +2 -2
  36. package/dist/react/components/auth/PasswordSetFields.js.map +1 -1
  37. package/dist/react/components/auth/PasswordStrengthMeter.d.ts.map +1 -1
  38. package/dist/react/components/auth/PasswordStrengthMeter.js +3 -1
  39. package/dist/react/components/auth/PasswordStrengthMeter.js.map +1 -1
  40. package/dist/react/components/auth/RegisterForm.js +1 -1
  41. package/dist/react/components/auth/RegisterForm.js.map +1 -1
  42. package/dist/react/components/auth/SignupForm.d.ts.map +1 -1
  43. package/dist/react/components/auth/SignupForm.js +4 -2
  44. package/dist/react/components/auth/SignupForm.js.map +1 -1
  45. package/dist/react/components/auth/TotpEntry.d.ts.map +1 -1
  46. package/dist/react/components/auth/TotpEntry.js +18 -1
  47. package/dist/react/components/auth/TotpEntry.js.map +1 -1
  48. package/dist/react/components/auth/fields.d.ts +3 -1
  49. package/dist/react/components/auth/fields.d.ts.map +1 -1
  50. package/dist/react/components/auth/fields.js +6 -2
  51. package/dist/react/components/auth/fields.js.map +1 -1
  52. package/dist/react/components/calendar/DayAgenda.d.ts.map +1 -1
  53. package/dist/react/components/calendar/DayAgenda.js +4 -2
  54. package/dist/react/components/calendar/DayAgenda.js.map +1 -1
  55. package/dist/react/components/calendar/EventChip.d.ts +5 -1
  56. package/dist/react/components/calendar/EventChip.d.ts.map +1 -1
  57. package/dist/react/components/calendar/EventChip.js +10 -3
  58. package/dist/react/components/calendar/EventChip.js.map +1 -1
  59. package/dist/react/components/calendar/MonthGrid.js +1 -1
  60. package/dist/react/components/calendar/MonthGrid.js.map +1 -1
  61. package/dist/react/components/calendar/WeekView.d.ts.map +1 -1
  62. package/dist/react/components/calendar/WeekView.js +2 -2
  63. package/dist/react/components/calendar/WeekView.js.map +1 -1
  64. package/dist/react/components/calendar/colorUtils.d.ts.map +1 -1
  65. package/dist/react/components/calendar/colorUtils.js +13 -11
  66. package/dist/react/components/calendar/colorUtils.js.map +1 -1
  67. package/dist/react/components/fieldErrorText.d.ts +12 -0
  68. package/dist/react/components/fieldErrorText.d.ts.map +1 -0
  69. package/dist/react/components/fieldErrorText.js +12 -0
  70. package/dist/react/components/fieldErrorText.js.map +1 -0
  71. package/dist/react/deep-link/resume.d.ts.map +1 -1
  72. package/dist/react/deep-link/resume.js +9 -2
  73. package/dist/react/deep-link/resume.js.map +1 -1
  74. package/dist/react/file-tree/FileTree.d.ts +3 -0
  75. package/dist/react/file-tree/FileTree.d.ts.map +1 -1
  76. package/dist/react/file-tree/FileTree.js +12 -1
  77. package/dist/react/file-tree/FileTree.js.map +1 -1
  78. package/dist/react/file-tree/FolderViewMenus.d.ts +3 -1
  79. package/dist/react/file-tree/FolderViewMenus.d.ts.map +1 -1
  80. package/dist/react/file-tree/FolderViewMenus.js +51 -15
  81. package/dist/react/file-tree/FolderViewMenus.js.map +1 -1
  82. package/dist/react/file-tree/folderView.d.ts +25 -4
  83. package/dist/react/file-tree/folderView.d.ts.map +1 -1
  84. package/dist/react/file-tree/folderView.js +49 -9
  85. package/dist/react/file-tree/folderView.js.map +1 -1
  86. package/dist/react/file-tree/index.d.ts +2 -1
  87. package/dist/react/file-tree/index.d.ts.map +1 -1
  88. package/dist/react/file-tree/index.js +2 -1
  89. package/dist/react/file-tree/index.js.map +1 -1
  90. package/dist/react/file-tree/useFolderView.d.ts +34 -4
  91. package/dist/react/file-tree/useFolderView.d.ts.map +1 -1
  92. package/dist/react/file-tree/useFolderView.js +43 -11
  93. package/dist/react/file-tree/useFolderView.js.map +1 -1
  94. package/dist/react/media/DenseGalleryShell.d.ts +8 -1
  95. package/dist/react/media/DenseGalleryShell.d.ts.map +1 -1
  96. package/dist/react/media/DenseGalleryShell.js +33 -8
  97. package/dist/react/media/DenseGalleryShell.js.map +1 -1
  98. package/dist/react/media/VideoPlayer.d.ts +12 -1
  99. package/dist/react/media/VideoPlayer.d.ts.map +1 -1
  100. package/dist/react/media/VideoPlayer.js +30 -2
  101. package/dist/react/media/VideoPlayer.js.map +1 -1
  102. package/dist/react/media/controlBar.d.ts +3 -2
  103. package/dist/react/media/controlBar.d.ts.map +1 -1
  104. package/dist/react/media/controlBar.js +16 -10
  105. package/dist/react/media/controlBar.js.map +1 -1
  106. package/dist/react/media/denseGallery.css +56 -18
  107. package/dist/react/media/videoPlayer.css +2 -1
  108. package/dist/react/media-gallery/MediaGallery.d.ts.map +1 -1
  109. package/dist/react/media-gallery/MediaGallery.js +8 -4
  110. package/dist/react/media-gallery/MediaGallery.js.map +1 -1
  111. package/dist/styles-areas/analytics.css +26 -25
  112. package/dist/styles-areas/calendar.css +4 -4
  113. package/dist/styles-areas/data-table.css +26 -25
  114. package/dist/styles-utilities.css +24 -9
  115. package/package.json +5 -1
  116. package/scripts/demoCoverage.spec.ts +33 -0
  117. package/scripts/demoCoverage.ts +124 -0
  118. package/src/react/charts/IntervalTimeline.tsx +6 -4
  119. package/src/react/chat/DateMarker.tsx +22 -6
  120. package/src/react/chat/MessageThread.spec.tsx +25 -1
  121. package/src/react/components/Button.spec.tsx +11 -0
  122. package/src/react/components/Button.tsx +5 -0
  123. package/src/react/components/DataTable/DataTable.spec.tsx +43 -0
  124. package/src/react/components/DataTable/DataTable.tsx +1 -0
  125. package/src/react/components/DataTable/SortHeader.tsx +45 -5
  126. package/src/react/components/DataTable/types.ts +10 -0
  127. package/src/react/components/DropdownMenu.spec.tsx +26 -0
  128. package/src/react/components/DropdownMenu.tsx +10 -3
  129. package/src/react/components/Field.spec.tsx +24 -0
  130. package/src/react/components/Field.tsx +15 -5
  131. package/src/react/components/SecretInput.tsx +5 -1
  132. package/src/react/components/auth/AuthCard.spec.tsx +8 -0
  133. package/src/react/components/auth/AuthCard.tsx +4 -1
  134. package/src/react/components/auth/PasswordSetFields.tsx +7 -11
  135. package/src/react/components/auth/PasswordStrengthMeter.tsx +5 -3
  136. package/src/react/components/auth/RegisterForm.tsx +6 -6
  137. package/src/react/components/auth/SignupForm.spec.tsx +17 -0
  138. package/src/react/components/auth/SignupForm.tsx +11 -10
  139. package/src/react/components/auth/TotpEntry.spec.tsx +27 -0
  140. package/src/react/components/auth/TotpEntry.tsx +18 -1
  141. package/src/react/components/auth/fields.tsx +8 -1
  142. package/src/react/components/calendar/DayAgenda.spec.tsx +9 -0
  143. package/src/react/components/calendar/DayAgenda.tsx +5 -2
  144. package/src/react/components/calendar/EventChip.spec.tsx +12 -8
  145. package/src/react/components/calendar/EventChip.tsx +11 -6
  146. package/src/react/components/calendar/MonthGrid.tsx +1 -1
  147. package/src/react/components/calendar/WeekView.tsx +5 -3
  148. package/src/react/components/calendar/colorUtils.ts +13 -11
  149. package/src/react/components/fieldErrorText.tsx +17 -0
  150. package/src/react/deep-link/resume.spec.tsx +16 -0
  151. package/src/react/deep-link/resume.ts +9 -2
  152. package/src/react/file-tree/FileTree.tsx +14 -1
  153. package/src/react/file-tree/FolderViewMenus.tsx +57 -17
  154. package/src/react/file-tree/folderView.spec.ts +63 -1
  155. package/src/react/file-tree/folderView.ts +63 -12
  156. package/src/react/file-tree/folderViewTree.spec.tsx +111 -16
  157. package/src/react/file-tree/index.ts +15 -1
  158. package/src/react/file-tree/useFolderView.ts +73 -12
  159. package/src/react/media/DenseGalleryShell.spec.tsx +9 -7
  160. package/src/react/media/DenseGalleryShell.tsx +53 -11
  161. package/src/react/media/VideoPlayer.tsx +78 -2
  162. package/src/react/media/controlBar.tsx +17 -10
  163. package/src/react/media/denseGallery.css +56 -18
  164. package/src/react/media/videoPlayer.css +2 -1
  165. package/src/react/media-gallery/MediaGallery.spec.tsx +4 -4
  166. package/src/react/media-gallery/MediaGallery.tsx +12 -0
  167. package/src/styles-areas/analytics.css +26 -25
  168. package/src/styles-areas/calendar.css +4 -4
  169. package/src/styles-areas/data-table.css +26 -25
  170. package/src/styles-utilities.css +24 -9
@@ -158,6 +158,17 @@ export interface VideoPlayerProps {
158
158
  * dead bands that change bought.
159
159
  */
160
160
  fullscreenOnDoubleClick?: boolean;
161
+ /**
162
+ * Whether an ancestor is showing this player FILLING THE WINDOW — the gallery's full-window
163
+ * stage, which covers the app around it but is not browser fullscreen. Read only when
164
+ * `onFillWindowChange` is passed.
165
+ */
166
+ fillWindow?: boolean;
167
+ /**
168
+ * Passing this puts a full-window toggle IN THE CONTROL BAR, beside the fullscreen button.
169
+ * See {@link fillWindowSlots} for why it lives there.
170
+ */
171
+ onFillWindowChange?: (next: boolean) => void;
161
172
  }
162
173
 
163
174
  /**
@@ -278,6 +289,64 @@ export function stepSlots(
278
289
  return { ...pair, smallLayout: { ...pair }, largeLayout: { ...pair } };
279
290
  }
280
291
 
292
+ /**
293
+ * The full-window glyph — a wide frame to fill the window, a smaller one to leave it. Drawn
294
+ * here for the reason {@link StepGlyph} is: `@vidstack/react/icons` is not free to import.
295
+ * Same 32×32 grid as Vidstack's own icons, so it sits level with the fullscreen corners.
296
+ */
297
+ function FillWindowGlyph({ active }: { active: boolean }) {
298
+ return (
299
+ <svg
300
+ className='vds-icon'
301
+ viewBox='0 0 32 32'
302
+ fill='none'
303
+ stroke='currentColor'
304
+ strokeWidth='2.5'
305
+ aria-hidden='true'
306
+ focusable='false'
307
+ >
308
+ {active ? (
309
+ <rect x='8' y='11' width='16' height='10' rx='1.5' />
310
+ ) : (
311
+ <rect x='4' y='8' width='24' height='16' rx='2' />
312
+ )}
313
+ </svg>
314
+ );
315
+ }
316
+
317
+ /**
318
+ * The full-window toggle, IN the control bar beside the fullscreen button.
319
+ *
320
+ * Owner, 2026-10-02: *"I often want the video as big as it can be to fill the browser window
321
+ * … The button to leave extended mode should be somewhere accessible but not in the toolstrip
322
+ * if the toolstrip is hidden to make room for the video … a potential spot is near the full
323
+ * screen icon in the video player itself."* Full-window mode hides everything around the
324
+ * picture — the titlebar's toggle included, below the fold — so the way out has to ride on
325
+ * the one chrome that is always over the picture. And it sits beside fullscreen because the
326
+ * two are the same kind of choice (how big), the arrangement every player with a theatre
327
+ * mode uses.
328
+ */
329
+ function fillWindowSlots(
330
+ active: boolean,
331
+ onChange: ((next: boolean) => void) | undefined,
332
+ ): DefaultVideoLayoutSlots | undefined {
333
+ if (!onChange) return undefined;
334
+ const pair = {
335
+ beforeFullscreenButton: (
336
+ <BarButton
337
+ className='cbvp-fill-window'
338
+ aria-label='Full window'
339
+ aria-pressed={active}
340
+ title={active ? 'Leave full window (Esc)' : 'Fill the window'}
341
+ onClick={() => onChange(!active)}
342
+ >
343
+ <FillWindowGlyph active={active} />
344
+ </BarButton>
345
+ ),
346
+ };
347
+ return { ...pair, smallLayout: { ...pair }, largeLayout: { ...pair } };
348
+ }
349
+
281
350
  /**
282
351
  * 🔴 When the player is allowed to drop its control bar for the touch layout.
283
352
  *
@@ -544,6 +613,8 @@ export function VideoPlayer({
544
613
  onStep,
545
614
  canStep = true,
546
615
  fullscreenOnDoubleClick = true,
616
+ fillWindow = false,
617
+ onFillWindowChange,
547
618
  }: VideoPlayerProps) {
548
619
  /*
549
620
  * 🔴 What the SOURCES have answered, and why a player needs to hold that at all.
@@ -612,8 +683,13 @@ export function VideoPlayer({
612
683
  // Vidstack's whole layout, which drops the control bar mid-hover and restarts its
613
684
  // auto-hide timer on every state change the player makes (i.e. constantly, while playing).
614
685
  const slots = useMemo(
615
- () => mergeSlots(stepSlots(onStep, canStep), sectionSlots(video.chapters)),
616
- [onStep, canStep, video.chapters],
686
+ () =>
687
+ mergeSlots(
688
+ stepSlots(onStep, canStep),
689
+ sectionSlots(video.chapters),
690
+ fillWindowSlots(fillWindow, onFillWindowChange),
691
+ ),
692
+ [onStep, canStep, video.chapters, fillWindow, onFillWindowChange],
617
693
  );
618
694
 
619
695
  return (
@@ -22,6 +22,7 @@ export function BarButton({
22
22
  title: string;
23
23
  disabled?: boolean;
24
24
  'aria-label': string;
25
+ 'aria-pressed'?: boolean;
25
26
  onClick: () => void;
26
27
  children: React.ReactNode;
27
28
  }) {
@@ -67,18 +68,24 @@ export function sectionSlots(
67
68
  return { ...pair, smallLayout: { ...pair }, largeLayout: { ...pair } };
68
69
  }
69
70
 
70
- /** Two slot sets as one — each names different slots, so a shallow merge per layout is exact. */
71
+ /** Slot sets as one — each names different slots, so a shallow merge per layout is exact. */
71
72
  export function mergeSlots(
72
- a: DefaultVideoLayoutSlots | undefined,
73
- b: DefaultVideoLayoutSlots | undefined,
73
+ ...sets: (DefaultVideoLayoutSlots | undefined)[]
74
74
  ): DefaultVideoLayoutSlots | undefined {
75
- if (!a || !b) return a ?? b;
76
- return {
77
- ...a,
78
- ...b,
79
- smallLayout: { ...a.smallLayout, ...b.smallLayout },
80
- largeLayout: { ...a.largeLayout, ...b.largeLayout },
81
- };
75
+ let merged: DefaultVideoLayoutSlots | undefined;
76
+ for (const next of sets) {
77
+ if (!merged || !next) {
78
+ merged = merged ?? next;
79
+ continue;
80
+ }
81
+ merged = {
82
+ ...merged,
83
+ ...next,
84
+ smallLayout: { ...merged.smallLayout, ...next.smallLayout },
85
+ largeLayout: { ...merged.largeLayout, ...next.largeLayout },
86
+ };
87
+ }
88
+ return merged;
82
89
  }
83
90
 
84
91
  /**
@@ -622,34 +622,72 @@
622
622
  overflow: hidden;
623
623
  background: var(--background);
624
624
  }
625
- /* Fullscreen-within-the-plugin — hide the rail and related grid, let the stage grow to
626
- fill the whole plugin container instead of a fixed 80vh cap. */
625
+ /*
626
+ * ── 🔴 Full window: the picture as big as the WINDOW, the files still below it ──────────
627
+ *
628
+ * Owner, 2026-10-02: *"I do not want the video size limited by anything other than the size
629
+ * of the window. This may mean I have to scroll to see anything under the video at all …
630
+ * and that is ok."* So the detail view covers the whole window (`fixed`, over the app's own
631
+ * toolstrip, sidebar and activity column — none of which this module could otherwise reach),
632
+ * stays the scroll container the related grid virtualises against, and the frame is exactly
633
+ * one window tall and wide. The stage letterboxes inside it as it always does, which is what
634
+ * keeps the aspect ratio and every edge of the picture on screen.
635
+ *
636
+ * `--z-overlay`, not higher: a menu or tooltip opened from here is `--z-popover`, and a
637
+ * dialog portalled to <body> comes later in the document at the same layer, so both still
638
+ * draw over it.
639
+ *
640
+ * Until 2026-10-02 this filled the plugin's container instead, and hid the related grid.
641
+ */
627
642
  .cbgd-watch-full {
628
- overflow: hidden;
643
+ position: fixed;
644
+ inset: 0;
645
+ z-index: var(--z-overlay, 1000);
646
+ height: auto;
629
647
  }
630
648
  .cbgd-watch-full .cbgd-watch-top {
631
- display: flex;
632
- flex-direction: column;
633
- flex: 1;
634
- min-height: 0;
635
- height: 100%;
636
- }
637
- .cbgd-watch-full .cbgd-watch-main {
638
- flex: 1;
639
- min-height: 0;
649
+ display: block;
650
+ padding: 0;
640
651
  }
641
- .cbgd-watch-full .cbgd-watch-player {
642
- flex: 1;
643
- min-height: 0;
652
+ /* Pinning a band that is the whole window would leave nothing to scroll into view. */
653
+ .cbgd-watch-sticky.cbgd-watch-full .cbgd-watch-top {
654
+ position: static;
655
+ max-height: none;
656
+ overflow: visible;
644
657
  }
645
658
  .cbgd-watch-full .cbgd-watch-player-box {
646
- max-width: 100%;
647
- height: 100%;
659
+ width: 100%;
660
+ max-width: none;
661
+ height: 100dvh;
662
+ }
663
+ .cbgd-watch-full .cbgd-watch-titlebar {
664
+ padding-inline: 10px;
648
665
  }
649
- .cbgd-watch-full .cbgd-watch-rail,
650
666
  .cbgd-watch-full .cbgd-related {
667
+ padding-top: 6px;
668
+ }
669
+ .cbgd-watch-full .cbgd-watch-rail {
651
670
  display: none;
652
671
  }
672
+ /* The way out for a stage with no control bar of its own (a picture, a document, the native
673
+ `<video controls>`): pinned to the window's corner, quiet until it is wanted. A Vidstack
674
+ video carries its toggle in its own bar instead — `stageOffersFullStageToggle`. */
675
+ .cbgd-full-exit {
676
+ position: fixed;
677
+ top: 12px;
678
+ right: 12px;
679
+ opacity: 0.6;
680
+ transition: opacity 120ms ease;
681
+ }
682
+ /* Solid, because what is under it is a picture of any colour. */
683
+ .cbgd-full-exit .cbgd-order-btn {
684
+ background: var(--background);
685
+ color: var(--foreground);
686
+ }
687
+ .cbgd-full-exit:hover,
688
+ .cbgd-full-exit:focus-within {
689
+ opacity: 1;
690
+ }
653
691
  /*
654
692
  * An image stage (photo detail) — a letterbox of CONSTANT height.
655
693
  *
@@ -23,7 +23,8 @@
23
23
  /* Vidstack sizes its icons through `.vds-icon`; the pair only needs to match the seek
24
24
  buttons beside them, which are 80% of the play button. */
25
25
  .cbvp-step .vds-icon,
26
- .cbvp-section .vds-icon {
26
+ .cbvp-section .vds-icon,
27
+ .cbvp-fill-window .vds-icon {
27
28
  width: var(--media-button-icon-size, 80%);
28
29
  height: var(--media-button-icon-size, 80%);
29
30
  }
@@ -358,7 +358,7 @@ describe('MediaGallery detail view', () => {
358
358
  expect(getByRole('button', { name: 'Open beach.jpg' })).toBeDefined();
359
359
  // Download + fullscreen affordances.
360
360
  expect(getByRole('link', { name: /download/i }).getAttribute('href')).toBe('/files/img1');
361
- expect(getByRole('button', { name: /fullscreen/i })).toBeDefined();
361
+ expect(getByRole('button', { name: 'Full window' })).toBeDefined();
362
362
  // Back to grid returns to browse.
363
363
  fireEvent.click(getByRole('button', { name: 'Back to grid' }));
364
364
  expect(container.querySelector('.cbgd-root')).not.toBeNull();
@@ -1770,7 +1770,7 @@ describe('🔴 double-clicking the stage', () => {
1770
1770
  fireEvent.doubleClick(frameOf(view.container));
1771
1771
  expect(isFull(view.container)).toBe(true);
1772
1772
  // The titlebar's own control agrees — one state, two ways into it.
1773
- expect(view.getByRole('button', { name: /Shrink/ }).getAttribute('aria-pressed')).toBe('true');
1773
+ expect(view.getByRole('button', { name: 'Full window' }).getAttribute('aria-pressed')).toBe('true');
1774
1774
 
1775
1775
  fireEvent.doubleClick(frameOf(view.container));
1776
1776
  expect(isFull(view.container)).toBe(false);
@@ -1786,7 +1786,7 @@ describe('🔴 double-clicking the stage', () => {
1786
1786
  test('🔴 …but a video DOES leave the shell fullscreen, because the player stood down', () => {
1787
1787
  const view = render(<MediaGallery items={ITEMS} nativeVideoStage />);
1788
1788
  fireEvent.click(view.getByRole('button', { name: 'Open clip.mp4' }));
1789
- fireEvent.click(view.getByRole('button', { name: /Fullscreen/ }));
1789
+ fireEvent.click(view.getByRole('button', { name: 'Full window' }));
1790
1790
  expect(isFull(view.container)).toBe(true);
1791
1791
  fireEvent.doubleClick(frameOf(view.container));
1792
1792
  expect(isFull(view.container)).toBe(false);
@@ -2386,7 +2386,7 @@ describe('MediaGallery — the toolbar is icons, and the words are its names', (
2386
2386
  'Picture',
2387
2387
  'Delete this item',
2388
2388
  'Back to grid',
2389
- 'Fullscreen',
2389
+ 'Full window',
2390
2390
  ]) {
2391
2391
  expect(view.getByRole('button', { name }), `no control named "${name}"`).toBeDefined();
2392
2392
  }
@@ -1359,6 +1359,8 @@ function GalleryStage({
1359
1359
  onStep,
1360
1360
  canStep,
1361
1361
  fullscreenOnDoubleClick,
1362
+ fillWindow,
1363
+ onFillWindowChange,
1362
1364
  }: {
1363
1365
  item: MediaGalleryItem;
1364
1366
  mediaCache: MediaCache;
@@ -1377,6 +1379,9 @@ function GalleryStage({
1377
1379
  canStep?: boolean;
1378
1380
  /** False while the SHELL is fullscreen — see `VideoPlayerProps.fullscreenOnDoubleClick`. */
1379
1381
  fullscreenOnDoubleClick?: boolean;
1382
+ /** The shell's full-window state, toggled from the player's own bar — see `fillWindowSlots`. */
1383
+ fillWindow?: boolean;
1384
+ onFillWindowChange?: (next: boolean) => void;
1380
1385
  }) {
1381
1386
  const category = galleryItemCategory(item);
1382
1387
  const { media, resolving, refresh } = useItemMedia(item, mediaCache);
@@ -1444,6 +1449,7 @@ function GalleryStage({
1444
1449
  {...(repeat ? {} : { onEnded })}
1445
1450
  {...(onStep ? { onStep, canStep: canStep !== false } : {})}
1446
1451
  {...(fullscreenOnDoubleClick === false ? { fullscreenOnDoubleClick: false } : {})}
1452
+ {...(onFillWindowChange ? { fillWindow: fillWindow === true, onFillWindowChange } : {})}
1447
1453
  />
1448
1454
  </React.Suspense>
1449
1455
  );
@@ -2719,6 +2725,10 @@ export function MediaGallery({
2719
2725
  `⤢ Fullscreen` is what a double-click means on it. Owner, 2026-09-11:
2720
2726
  *"Doubleclicking an image to toggle full screen is not working at all."* */
2721
2727
  stageOwnsDoubleClick={category === 'video'}
2728
+ /* A Vidstack video carries its own full-window toggle in its control bar, so the
2729
+ shell's floating way out would be a second one over the same picture. The native
2730
+ `<video controls>` stage has no bar of ours, so it keeps the shell's. */
2731
+ stageOffersFullStageToggle={category === 'video' && !nativeVideoStage}
2722
2732
  title={selected.name}
2723
2733
  meta={
2724
2734
  // The date joins the line rather than replacing what was there — beside the title is
@@ -2767,6 +2777,8 @@ export function MediaGallery({
2767
2777
  screen video should change back … to non-full screen view") has exactly one
2768
2778
  meaning. `DenseGalleryDetail` handles it from the frame instead. */
2769
2779
  fullscreenOnDoubleClick={!fullStage}
2780
+ fillWindow={fullStage}
2781
+ onFillWindowChange={setFullStage}
2770
2782
  repeat={runRepeatsOne(playbackEnd)}
2771
2783
  // 🔴 `advance`, not `step(1)`. The arrows wrap and the RUN does not: `next`
2772
2784
  // stops after the last item, `all` starts over, `shuffle` draws from the pass.
@@ -18,35 +18,36 @@
18
18
  * after an app's responsive variant (task 2124). An app that imports every area gets exactly
19
19
  * what `cursedbelt/styles-utilities.css` emits — proved rule by rule in
20
20
  * src/stylesAreas.spec.ts, which is also what fails when this file is stale.
21
- * 148 candidate(s).
21
+ * 154 candidate(s).
22
22
  *
23
23
  * Written by scripts/generateAreaStyles.ts (`bun run styles:areas`), which
24
24
  * `bun run build` runs. The map of what is in which area, and why the areas are
25
25
  * import closures rather than a hand-written list, is scripts/styleAreas.ts.
26
26
  */
27
- @source inline("-m-1 -mt-2 -mt-6 -mx-1 -my-2.5 -my-3 @3xl:block @3xl:hidden @max-2xl:[&>*]:hidden");
28
- @source inline("@max-2xl:border-0 @max-2xl:min-w-0! @max-2xl:p-0! @max-2xl:w-0! @max-4xl:[&>*]:hidden");
27
+ @source inline("-m-1 -mt-2 -mt-6 -mx-1 -my-2.5 -my-3 @3xl:block @3xl:hidden @max-2xl @max-2xl:[&>*]:hidden");
28
+ @source inline("@max-2xl:border-0 @max-2xl:inline-flex @max-2xl:justify-center @max-2xl:min-w-0!");
29
+ @source inline("@max-2xl:min-w-11 @max-2xl:p-0! @max-2xl:sr-only @max-2xl:w-0! @max-4xl:[&>*]:hidden");
29
30
  @source inline("@max-4xl:border-0 @max-4xl:min-w-0! @max-4xl:p-0! @max-4xl:w-0! @max-6xl:[&>*]:hidden");
30
31
  @source inline("@max-6xl:border-0 @max-6xl:min-w-0! @max-6xl:p-0! @max-6xl:w-0! @xl:block @xl:hidden");
31
- @source inline("[&>*]:hidden [&>*]:max-w-none [&_svg]:size-3 [overflow-wrap:anywhere] active:cursor-grabbing");
32
- @source inline("appearance appearance-none bg-[var(--cb-frozen-bg)] bg-card/95 bg-gradient-to-t bg-muted/30");
33
- @source inline("bg-muted/40 bg-muted/50 border-accent-solid border-accent/60 border-field-error border-l");
34
- @source inline("border-r col collapse cursor-grab data-[ending-style]:opacity-0 data-[ending-style]:scale-95");
35
- @source inline("data-[highlighted]:bg-accent/10 data-[highlighted]:text-accent-strong");
36
- @source inline("data-[starting-style]:opacity-0 data-[starting-style]:scale-95");
37
- @source inline("data-[state=closed]:slide-out-to-bottom data-[state=closed]:slide-out-to-left");
38
- @source inline("data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-bottom");
39
- @source inline("data-[state=open]:slide-in-from-left data-[state=open]:slide-in-from-right disabled:opacity-40");
40
- @source inline("duration-100 fade-in focus-visible:ring-field-ring/40 focus-within:border-field-ring");
41
- @source inline("focus-within:ring-2 focus-within:ring-field-ring/30 from-card h-105");
42
- @source inline("has-[[aria-invalid=true]]:border-field-error hover:bg-background hover:bg-border");
43
- @source inline("hover:bg-muted/40 hover:border-field-ring hover:text-warning italic justify-self-end");
44
- @source inline("last:border-0 left-0 left-11 left-2.5 list-none max-h-64 max-h-88 max-h-[var(--sheet-h-lg)]");
45
- @source inline("max-h-[var(--sheet-h-md)] max-h-[var(--sheet-h-sm)] max-w-32 max-w-36 max-w-45 max-w-48 max-w-60");
46
- @source inline("max-w-sm min-h-20 min-h-dvh min-w-0! min-w-20 min-w-24 min-w-52 min-w-[var(--anchor-width)]");
47
- @source inline("min-w-max ml-1.5 mr-2 mt-1.5 mt-3 mt-4 not-italic ordinal p-0! pb-1.5 pl-1.5 pl-2 pr-0.5 pr-1");
48
- @source inline("pr-10 pr-16 pr-3 pr-9 pt-1 resize-none resize-y right-3 ring-field-error/30 rounded-l-xl");
49
- @source inline("rounded-r-xl rounded-t-xl self self-center self-stretch shadow-[1px_0_0_0_var(--color-border)]");
50
- @source inline("shrink-[2] size-2 size-7 slide-in-from-top-1 table-fixed text-accent-solid-foreground");
51
- @source inline("text-field-placeholder text-muted-foreground/60 text-success text-xl to-transparent top-3");
52
- @source inline("transition-[transform,opacity] w-0! w-11 w-20 w-32 w-36 w-40 w-88");
32
+ @source inline("[&>*]:hidden [&>*]:max-w-none [&>svg]:size-3.5 [&_svg]:size-3 [overflow-wrap:anywhere]");
33
+ @source inline("active:cursor-grabbing appearance appearance-none bg-[var(--cb-frozen-bg)] bg-card/95");
34
+ @source inline("bg-gradient-to-t bg-muted/30 bg-muted/40 bg-muted/50 border-accent-solid border-accent/60");
35
+ @source inline("border-field-error border-l border-r col collapse cursor-grab data-[ending-style]:opacity-0");
36
+ @source inline("data-[ending-style]:scale-95 data-[highlighted]:bg-accent/10");
37
+ @source inline("data-[highlighted]:text-accent-strong data-[starting-style]:opacity-0");
38
+ @source inline("data-[starting-style]:scale-95 data-[state=closed]:slide-out-to-bottom");
39
+ @source inline("data-[state=closed]:slide-out-to-left data-[state=closed]:slide-out-to-right");
40
+ @source inline("data-[state=open]:slide-in-from-bottom data-[state=open]:slide-in-from-left");
41
+ @source inline("data-[state=open]:slide-in-from-right disabled:opacity-40 duration-100 fade-in");
42
+ @source inline("focus-visible:ring-field-ring/40 focus-within:border-field-ring focus-within:ring-2");
43
+ @source inline("focus-within:ring-field-ring/30 from-card h-105 has-[[aria-invalid=true]]:border-field-error");
44
+ @source inline("hover:bg-background hover:bg-border hover:bg-muted/40 hover:border-field-ring hover:text-warning");
45
+ @source inline("italic justify-self-end last:border-0 left-0 left-11 left-2.5 list-none max-h-64 max-h-88");
46
+ @source inline("max-h-[var(--sheet-h-lg)] max-h-[var(--sheet-h-md)] max-h-[var(--sheet-h-sm)] max-w-32 max-w-36");
47
+ @source inline("max-w-45 max-w-48 max-w-60 max-w-sm min-h-20 min-h-dvh min-w-0! min-w-20 min-w-24 min-w-52");
48
+ @source inline("min-w-[var(--anchor-width)] min-w-max ml-1.5 mr-2 mt-1.5 mt-3 mt-4 not-italic ordinal p-0!");
49
+ @source inline("pb-1.5 pl-1.5 pl-2 pr-0.5 pr-1 pr-10 pr-16 pr-3 pr-9 pt-1 resize-none resize-y right-3");
50
+ @source inline("ring-field-error/30 rounded-l-xl rounded-r-xl rounded-t-xl self self-center self-stretch");
51
+ @source inline("shadow-[1px_0_0_0_var(--color-border)] shrink-[2] size-2 size-7 slide-in-from-top-1 table-fixed");
52
+ @source inline("text-accent-solid-foreground text-field-placeholder text-muted-foreground/60 text-success");
53
+ @source inline("text-xl to-transparent top-3 transition-[transform,opacity] w-0! w-11 w-20 w-32 w-36 w-40 w-88");
@@ -18,7 +18,7 @@
18
18
  * after an app's responsive variant (task 2124). An app that imports every area gets exactly
19
19
  * what `cursedbelt/styles-utilities.css` emits — proved rule by rule in
20
20
  * src/stylesAreas.spec.ts, which is also what fails when this file is stale.
21
- * 79 candidate(s).
21
+ * 78 candidate(s).
22
22
  *
23
23
  * Written by scripts/generateAreaStyles.ts (`bun run styles:areas`), which
24
24
  * `bun run build` runs. The map of what is in which area, and why the areas are
@@ -32,9 +32,9 @@
32
32
  @source inline("dark:bg-sky-900/40 dark:bg-slate-800 dark:bg-teal-900/40 dark:bg-violet-900/40");
33
33
  @source inline("dark:text-amber-200 dark:text-blue-200 dark:text-emerald-200 dark:text-green-200");
34
34
  @source inline("dark:text-purple-200 dark:text-rose-200 dark:text-sky-200 dark:text-slate-200 dark:text-teal-200");
35
- @source inline("dark:text-violet-200 focus:opacity-100 g grid-cols-[repeat(7,minmax(0,1fr))]");
35
+ @source inline("dark:text-violet-200 focus:opacity-100 grid-cols-[repeat(7,minmax(0,1fr))]");
36
36
  @source inline("group-focus-within:opacity-100 h-3.5 hover:bg-muted/30 hover:bg-muted/40 hover:border-border/80");
37
37
  @source inline("last:border-r-0 line-through min-h-28 min-h-40 outline-1 outline-dashed outline-tone-amber-ink");
38
- @source inline("p-1.5 sm:flex-row sm:gap-1 sm:items-center sm:min-h-0 sm:min-h-24 sm:px-1.5 sm:text-xs");
38
+ @source inline("p-1.5 sm:inline sm:min-h-0 sm:min-h-24 sm:px-1.5 sm:text-xs text-accent-solid-foreground");
39
39
  @source inline("text-amber-900 text-blue-800 text-emerald-800 text-green-800 text-purple-800 text-rose-800");
40
- @source inline("text-sky-800 text-slate-800 text-teal-800 text-tone-amber-ink text-violet-800 w-3.5");
40
+ @source inline("text-sky-800 text-slate-800 text-teal-800 text-tone-amber-ink text-violet-800 w-20 w-3.5");
@@ -18,35 +18,36 @@
18
18
  * after an app's responsive variant (task 2124). An app that imports every area gets exactly
19
19
  * what `cursedbelt/styles-utilities.css` emits — proved rule by rule in
20
20
  * src/stylesAreas.spec.ts, which is also what fails when this file is stale.
21
- * 144 candidate(s).
21
+ * 150 candidate(s).
22
22
  *
23
23
  * Written by scripts/generateAreaStyles.ts (`bun run styles:areas`), which
24
24
  * `bun run build` runs. The map of what is in which area, and why the areas are
25
25
  * import closures rather than a hand-written list, is scripts/styleAreas.ts.
26
26
  */
27
- @source inline("-m-1 -mt-2 -mt-6 -mx-1 -my-2.5 -my-3 @3xl:block @3xl:hidden @max-2xl:[&>*]:hidden");
28
- @source inline("@max-2xl:border-0 @max-2xl:min-w-0! @max-2xl:p-0! @max-2xl:w-0! @max-4xl:[&>*]:hidden");
27
+ @source inline("-m-1 -mt-2 -mt-6 -mx-1 -my-2.5 -my-3 @3xl:block @3xl:hidden @max-2xl @max-2xl:[&>*]:hidden");
28
+ @source inline("@max-2xl:border-0 @max-2xl:inline-flex @max-2xl:justify-center @max-2xl:min-w-0!");
29
+ @source inline("@max-2xl:min-w-11 @max-2xl:p-0! @max-2xl:sr-only @max-2xl:w-0! @max-4xl:[&>*]:hidden");
29
30
  @source inline("@max-4xl:border-0 @max-4xl:min-w-0! @max-4xl:p-0! @max-4xl:w-0! @max-6xl:[&>*]:hidden");
30
31
  @source inline("@max-6xl:border-0 @max-6xl:min-w-0! @max-6xl:p-0! @max-6xl:w-0! @xl:block @xl:hidden");
31
- @source inline("[&>*]:hidden [&>*]:max-w-none [&_svg]:size-3 [overflow-wrap:anywhere] active:cursor-grabbing");
32
- @source inline("appearance appearance-none bg-[var(--cb-frozen-bg)] bg-card/95 bg-gradient-to-t bg-muted/30");
33
- @source inline("bg-muted/50 border-accent-solid border-accent/60 border-field-error border-l border-r col");
34
- @source inline("collapse cursor-grab data-[ending-style]:opacity-0 data-[ending-style]:scale-95");
35
- @source inline("data-[highlighted]:bg-accent/10 data-[highlighted]:text-accent-strong");
36
- @source inline("data-[starting-style]:opacity-0 data-[starting-style]:scale-95");
37
- @source inline("data-[state=closed]:slide-out-to-bottom data-[state=closed]:slide-out-to-left");
38
- @source inline("data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-bottom");
39
- @source inline("data-[state=open]:slide-in-from-left data-[state=open]:slide-in-from-right disabled:opacity-40");
40
- @source inline("duration-100 fade-in focus-visible:ring-field-ring/40 focus-within:border-field-ring");
41
- @source inline("focus-within:ring-2 focus-within:ring-field-ring/30 from-card h-105");
42
- @source inline("has-[[aria-invalid=true]]:border-field-error hover:bg-background hover:bg-border");
43
- @source inline("hover:bg-muted/40 hover:border-field-ring hover:text-warning justify-self-end last:border-0");
44
- @source inline("left-0 left-11 left-2.5 list-none max-h-64 max-h-88 max-h-[var(--sheet-h-lg)]");
45
- @source inline("max-h-[var(--sheet-h-md)] max-h-[var(--sheet-h-sm)] max-w-32 max-w-36 max-w-45 max-w-48 max-w-60");
46
- @source inline("max-w-sm min-h-20 min-h-dvh min-w-0! min-w-24 min-w-52 min-w-[var(--anchor-width)] min-w-max");
47
- @source inline("ml-1.5 mr-2 mt-1.5 mt-3 mt-4 not-italic ordinal p-0! pb-1.5 pl-1.5 pl-2 pr-0.5 pr-1 pr-10 pr-16");
48
- @source inline("pr-3 pr-9 pt-1 resize-none resize-y right-3 ring-field-error/30 rounded-l-xl rounded-r-xl");
49
- @source inline("rounded-t-xl self self-center self-stretch shadow-[1px_0_0_0_var(--color-border)] size-2 size-7");
50
- @source inline("slide-in-from-top-1 table-fixed text-accent-solid-foreground text-field-placeholder");
51
- @source inline("text-muted-foreground/60 text-success text-xl to-transparent top-3");
52
- @source inline("transition-[transform,opacity] w-0! w-11 w-20 w-32 w-36 w-40 w-88");
32
+ @source inline("[&>*]:hidden [&>*]:max-w-none [&>svg]:size-3.5 [&_svg]:size-3 [overflow-wrap:anywhere]");
33
+ @source inline("active:cursor-grabbing appearance appearance-none bg-[var(--cb-frozen-bg)] bg-card/95");
34
+ @source inline("bg-gradient-to-t bg-muted/30 bg-muted/50 border-accent-solid border-accent/60 border-field-error");
35
+ @source inline("border-l border-r col collapse cursor-grab data-[ending-style]:opacity-0");
36
+ @source inline("data-[ending-style]:scale-95 data-[highlighted]:bg-accent/10");
37
+ @source inline("data-[highlighted]:text-accent-strong data-[starting-style]:opacity-0");
38
+ @source inline("data-[starting-style]:scale-95 data-[state=closed]:slide-out-to-bottom");
39
+ @source inline("data-[state=closed]:slide-out-to-left data-[state=closed]:slide-out-to-right");
40
+ @source inline("data-[state=open]:slide-in-from-bottom data-[state=open]:slide-in-from-left");
41
+ @source inline("data-[state=open]:slide-in-from-right disabled:opacity-40 duration-100 fade-in");
42
+ @source inline("focus-visible:ring-field-ring/40 focus-within:border-field-ring focus-within:ring-2");
43
+ @source inline("focus-within:ring-field-ring/30 from-card h-105 has-[[aria-invalid=true]]:border-field-error");
44
+ @source inline("hover:bg-background hover:bg-border hover:bg-muted/40 hover:border-field-ring hover:text-warning");
45
+ @source inline("justify-self-end last:border-0 left-0 left-11 left-2.5 list-none max-h-64 max-h-88");
46
+ @source inline("max-h-[var(--sheet-h-lg)] max-h-[var(--sheet-h-md)] max-h-[var(--sheet-h-sm)] max-w-32 max-w-36");
47
+ @source inline("max-w-45 max-w-48 max-w-60 max-w-sm min-h-20 min-h-dvh min-w-0! min-w-24 min-w-52");
48
+ @source inline("min-w-[var(--anchor-width)] min-w-max ml-1.5 mr-2 mt-1.5 mt-3 mt-4 not-italic ordinal p-0!");
49
+ @source inline("pb-1.5 pl-1.5 pl-2 pr-0.5 pr-1 pr-10 pr-16 pr-3 pr-9 pt-1 resize-none resize-y right-3");
50
+ @source inline("ring-field-error/30 rounded-l-xl rounded-r-xl rounded-t-xl self self-center self-stretch");
51
+ @source inline("shadow-[1px_0_0_0_var(--color-border)] size-2 size-7 slide-in-from-top-1 table-fixed");
52
+ @source inline("text-accent-solid-foreground text-field-placeholder text-muted-foreground/60 text-success");
53
+ @source inline("text-xl to-transparent top-3 transition-[transform,opacity] w-0! w-11 w-20 w-32 w-36 w-40 w-88");
@@ -4337,18 +4337,9 @@
4337
4337
  .sm\:grid-cols-\[minmax\(7rem\,34rem\)_minmax\(4rem\,1fr\)_auto\] {
4338
4338
  grid-template-columns: minmax(7rem,34rem) minmax(4rem,1fr) auto;
4339
4339
  }
4340
- .sm\:flex-row {
4341
- flex-direction: row;
4342
- }
4343
4340
  .sm\:flex-nowrap {
4344
4341
  flex-wrap: nowrap;
4345
4342
  }
4346
- .sm\:items-center {
4347
- align-items: center;
4348
- }
4349
- .sm\:gap-1 {
4350
- gap: var(--spacing);
4351
- }
4352
4343
  .sm\:gap-3 {
4353
4344
  gap: calc(var(--spacing) * 3);
4354
4345
  }
@@ -4504,12 +4495,32 @@
4504
4495
  }
4505
4496
  }
4506
4497
  @container (width < 42rem) {
4498
+ .\@max-2xl\:sr-only {
4499
+ position: absolute;
4500
+ width: 1px;
4501
+ height: 1px;
4502
+ padding: 0;
4503
+ margin: -1px;
4504
+ overflow: hidden;
4505
+ clip-path: inset(50%);
4506
+ white-space: nowrap;
4507
+ border-width: 0;
4508
+ }
4509
+ .\@max-2xl\:inline-flex {
4510
+ display: inline-flex;
4511
+ }
4507
4512
  .\@max-2xl\:w-0\! {
4508
4513
  width: 0px !important;
4509
4514
  }
4510
4515
  .\@max-2xl\:min-w-0\! {
4511
4516
  min-width: 0px !important;
4512
4517
  }
4518
+ .\@max-2xl\:min-w-11 {
4519
+ min-width: calc(var(--spacing) * 11);
4520
+ }
4521
+ .\@max-2xl\:justify-center {
4522
+ justify-content: center;
4523
+ }
4513
4524
  .\@max-2xl\:border-0 {
4514
4525
  border-style: var(--tw-border-style);
4515
4526
  border-width: 0px;
@@ -4957,6 +4968,10 @@
4957
4968
  .\[\&\>span\>button\]\:rounded-none > span > button {
4958
4969
  border-radius: 0;
4959
4970
  }
4971
+ .\[\&\>svg\]\:size-3\.5 > svg {
4972
+ width: calc(var(--spacing) * 3.5);
4973
+ height: calc(var(--spacing) * 3.5);
4974
+ }
4960
4975
  }
4961
4976
  @property --tw-translate-x {
4962
4977
  syntax: "*";