cursedbelt 5.113.1 → 5.115.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 (199) hide show
  1. package/dist/react/components/BannerLane.d.ts +9 -2
  2. package/dist/react/components/BannerLane.d.ts.map +1 -1
  3. package/dist/react/components/BannerLane.js +1 -1
  4. package/dist/react/components/BannerLane.js.map +1 -1
  5. package/dist/react/components/InlineEditableText.d.ts +11 -1
  6. package/dist/react/components/InlineEditableText.d.ts.map +1 -1
  7. package/dist/react/components/InlineEditableText.js +5 -4
  8. package/dist/react/components/InlineEditableText.js.map +1 -1
  9. package/dist/react/components/Modal.d.ts +11 -3
  10. package/dist/react/components/Modal.d.ts.map +1 -1
  11. package/dist/react/components/Modal.js +20 -6
  12. package/dist/react/components/Modal.js.map +1 -1
  13. package/dist/react/media/CutTimeline.d.ts +11 -2
  14. package/dist/react/media/CutTimeline.d.ts.map +1 -1
  15. package/dist/react/media/CutTimeline.js +105 -34
  16. package/dist/react/media/CutTimeline.js.map +1 -1
  17. package/dist/react/media/FrameGrabber.d.ts +7 -1
  18. package/dist/react/media/FrameGrabber.d.ts.map +1 -1
  19. package/dist/react/media/FrameGrabber.js +6 -17
  20. package/dist/react/media/FrameGrabber.js.map +1 -1
  21. package/dist/react/media/HoverScrubVideoThumb.d.ts.map +1 -1
  22. package/dist/react/media/HoverScrubVideoThumb.js +2 -0
  23. package/dist/react/media/HoverScrubVideoThumb.js.map +1 -1
  24. package/dist/react/media/PictureEditor.d.ts +7 -1
  25. package/dist/react/media/PictureEditor.d.ts.map +1 -1
  26. package/dist/react/media/PictureEditor.js +2 -2
  27. package/dist/react/media/PictureEditor.js.map +1 -1
  28. package/dist/react/media/ScrubControls.d.ts +117 -0
  29. package/dist/react/media/ScrubControls.d.ts.map +1 -0
  30. package/dist/react/media/ScrubControls.js +233 -0
  31. package/dist/react/media/ScrubControls.js.map +1 -0
  32. package/dist/react/media/TrimRangeSlider.d.ts +8 -1
  33. package/dist/react/media/TrimRangeSlider.d.ts.map +1 -1
  34. package/dist/react/media/TrimRangeSlider.js +65 -29
  35. package/dist/react/media/TrimRangeSlider.js.map +1 -1
  36. package/dist/react/media/VideoChapterEditor.d.ts +7 -1
  37. package/dist/react/media/VideoChapterEditor.d.ts.map +1 -1
  38. package/dist/react/media/VideoChapterEditor.js +2 -2
  39. package/dist/react/media/VideoChapterEditor.js.map +1 -1
  40. package/dist/react/media/VideoCutEditor.d.ts +8 -3
  41. package/dist/react/media/VideoCutEditor.d.ts.map +1 -1
  42. package/dist/react/media/VideoCutEditor.js +38 -20
  43. package/dist/react/media/VideoCutEditor.js.map +1 -1
  44. package/dist/react/media/VideoPlayer.d.ts +2 -2
  45. package/dist/react/media/VideoPlayer.d.ts.map +1 -1
  46. package/dist/react/media/VideoPlayer.js +41 -18
  47. package/dist/react/media/VideoPlayer.js.map +1 -1
  48. package/dist/react/media/VideoTurner.d.ts +7 -1
  49. package/dist/react/media/VideoTurner.d.ts.map +1 -1
  50. package/dist/react/media/VideoTurner.js +2 -2
  51. package/dist/react/media/VideoTurner.js.map +1 -1
  52. package/dist/react/media/cutTimelineMath.d.ts +8 -0
  53. package/dist/react/media/cutTimelineMath.d.ts.map +1 -1
  54. package/dist/react/media/cutTimelineMath.js +11 -4
  55. package/dist/react/media/cutTimelineMath.js.map +1 -1
  56. package/dist/react/media/denseGallery.css +8 -0
  57. package/dist/react/media/index.d.ts +2 -0
  58. package/dist/react/media/index.d.ts.map +1 -1
  59. package/dist/react/media/index.js +6 -0
  60. package/dist/react/media/index.js.map +1 -1
  61. package/dist/react/media/pictureEditor.css +7 -0
  62. package/dist/react/media/playbackWatch.d.ts +7 -1
  63. package/dist/react/media/playbackWatch.d.ts.map +1 -1
  64. package/dist/react/media/playbackWatch.js +14 -3
  65. package/dist/react/media/playbackWatch.js.map +1 -1
  66. package/dist/react/media/playerFineScrub.d.ts +7 -0
  67. package/dist/react/media/playerFineScrub.d.ts.map +1 -0
  68. package/dist/react/media/playerFineScrub.js +138 -0
  69. package/dist/react/media/playerFineScrub.js.map +1 -0
  70. package/dist/react/media/previewPlay.d.ts +10 -0
  71. package/dist/react/media/previewPlay.d.ts.map +1 -0
  72. package/dist/react/media/previewPlay.js +124 -0
  73. package/dist/react/media/previewPlay.js.map +1 -0
  74. package/dist/react/media/scrub.d.ts +132 -0
  75. package/dist/react/media/scrub.d.ts.map +1 -0
  76. package/dist/react/media/scrub.js +179 -0
  77. package/dist/react/media/scrub.js.map +1 -0
  78. package/dist/react/media/scrubControls.css +147 -0
  79. package/dist/react/media/sourceFault.d.ts +85 -2
  80. package/dist/react/media/sourceFault.d.ts.map +1 -1
  81. package/dist/react/media/sourceFault.js +289 -17
  82. package/dist/react/media/sourceFault.js.map +1 -1
  83. package/dist/react/media/usePlayerHealth.d.ts +47 -0
  84. package/dist/react/media/usePlayerHealth.d.ts.map +1 -0
  85. package/dist/react/media/usePlayerHealth.js +270 -0
  86. package/dist/react/media/usePlayerHealth.js.map +1 -0
  87. package/dist/react/media/videoCutEditor.css +157 -3
  88. package/dist/react/media/videoPlayer.css +40 -0
  89. package/dist/react/media-gallery/GalleryTable.d.ts.map +1 -1
  90. package/dist/react/media-gallery/GalleryTable.js +2 -0
  91. package/dist/react/media-gallery/GalleryTable.js.map +1 -1
  92. package/dist/react/media-gallery/MediaGallery.d.ts +18 -1
  93. package/dist/react/media-gallery/MediaGallery.d.ts.map +1 -1
  94. package/dist/react/media-gallery/MediaGallery.js +98 -28
  95. package/dist/react/media-gallery/MediaGallery.js.map +1 -1
  96. package/dist/react/media-gallery/MediaMetaEditor.d.ts +7 -1
  97. package/dist/react/media-gallery/MediaMetaEditor.d.ts.map +1 -1
  98. package/dist/react/media-gallery/MediaMetaEditor.js +2 -2
  99. package/dist/react/media-gallery/MediaMetaEditor.js.map +1 -1
  100. package/dist/react/media-gallery/galleryDeepLink.d.ts +41 -0
  101. package/dist/react/media-gallery/galleryDeepLink.d.ts.map +1 -1
  102. package/dist/react/media-gallery/galleryDeepLink.js +89 -17
  103. package/dist/react/media-gallery/galleryDeepLink.js.map +1 -1
  104. package/dist/react/media-gallery/index.d.ts +1 -1
  105. package/dist/react/media-gallery/index.d.ts.map +1 -1
  106. package/dist/react/media-gallery/index.js +1 -1
  107. package/dist/react/media-gallery/index.js.map +1 -1
  108. package/dist/react/media-gallery/mediaGallery.css +25 -0
  109. package/dist/react/notifications/IssueViews.d.ts +31 -0
  110. package/dist/react/notifications/IssueViews.d.ts.map +1 -0
  111. package/dist/react/notifications/IssueViews.js +48 -0
  112. package/dist/react/notifications/IssueViews.js.map +1 -0
  113. package/dist/react/notifications/IssuesPage.d.ts +14 -0
  114. package/dist/react/notifications/IssuesPage.d.ts.map +1 -0
  115. package/dist/react/notifications/IssuesPage.js +74 -0
  116. package/dist/react/notifications/IssuesPage.js.map +1 -0
  117. package/dist/react/notifications/NotificationBell.d.ts +5 -0
  118. package/dist/react/notifications/NotificationBell.d.ts.map +1 -1
  119. package/dist/react/notifications/NotificationBell.js +4 -3
  120. package/dist/react/notifications/NotificationBell.js.map +1 -1
  121. package/dist/react/notifications/index.d.ts +4 -0
  122. package/dist/react/notifications/index.d.ts.map +1 -1
  123. package/dist/react/notifications/index.js +6 -0
  124. package/dist/react/notifications/index.js.map +1 -1
  125. package/dist/react/notifications/issueCapture.d.ts +16 -0
  126. package/dist/react/notifications/issueCapture.d.ts.map +1 -0
  127. package/dist/react/notifications/issueCapture.js +263 -0
  128. package/dist/react/notifications/issueCapture.js.map +1 -0
  129. package/dist/react/notifications/issues.d.ts +105 -0
  130. package/dist/react/notifications/issues.d.ts.map +1 -0
  131. package/dist/react/notifications/issues.js +250 -0
  132. package/dist/react/notifications/issues.js.map +1 -0
  133. package/dist/styles-areas/core.css +35 -34
  134. package/dist/styles-areas/media.css +8 -7
  135. package/dist/styles-areas/nav.css +2 -2
  136. package/dist/styles-areas/notifications.css +10 -7
  137. package/dist/styles-utilities.css +23 -2
  138. package/dist/theme.css +4 -0
  139. package/package.json +2 -2
  140. package/scripts/guardrails-baseline.json +2 -2
  141. package/src/declaredImports.spec.ts +9 -1
  142. package/src/react/components/BannerLane.tsx +19 -4
  143. package/src/react/components/InlineEditableText.spec.tsx +25 -0
  144. package/src/react/components/InlineEditableText.tsx +16 -4
  145. package/src/react/components/Modal.spec.tsx +34 -0
  146. package/src/react/components/Modal.tsx +61 -23
  147. package/src/react/media/CutTimeline.tsx +109 -20
  148. package/src/react/media/FrameGrabber.spec.tsx +4 -3
  149. package/src/react/media/FrameGrabber.tsx +21 -22
  150. package/src/react/media/HoverScrubVideoThumb.tsx +2 -0
  151. package/src/react/media/PictureEditor.tsx +8 -1
  152. package/src/react/media/ScrubControls.tsx +380 -0
  153. package/src/react/media/TrimRangeSlider.spec.tsx +41 -0
  154. package/src/react/media/TrimRangeSlider.tsx +105 -51
  155. package/src/react/media/VideoChapterEditor.tsx +8 -1
  156. package/src/react/media/VideoCutEditor.tsx +77 -27
  157. package/src/react/media/VideoPlayer.tsx +50 -24
  158. package/src/react/media/VideoTurner.tsx +8 -1
  159. package/src/react/media/cutTimelineMath.ts +12 -4
  160. package/src/react/media/denseGallery.css +8 -0
  161. package/src/react/media/index.ts +18 -0
  162. package/src/react/media/pictureEditor.css +7 -0
  163. package/src/react/media/playbackWatch.spec.ts +25 -0
  164. package/src/react/media/playbackWatch.ts +18 -4
  165. package/src/react/media/playerFineScrub.tsx +139 -0
  166. package/src/react/media/previewPlay.spec.tsx +74 -0
  167. package/src/react/media/previewPlay.ts +123 -0
  168. package/src/react/media/scrub.spec.ts +130 -0
  169. package/src/react/media/scrub.ts +245 -0
  170. package/src/react/media/scrubControls.css +147 -0
  171. package/src/react/media/sourceFault.spec.tsx +164 -6
  172. package/src/react/media/sourceFault.tsx +349 -21
  173. package/src/react/media/usePlayerHealth.spec.ts +156 -0
  174. package/src/react/media/usePlayerHealth.ts +331 -0
  175. package/src/react/media/videoCutEditor.css +157 -3
  176. package/src/react/media/videoPlayer.css +40 -0
  177. package/src/react/media-gallery/GalleryTable.tsx +2 -0
  178. package/src/react/media-gallery/MediaGallery.spec.tsx +114 -0
  179. package/src/react/media-gallery/MediaGallery.tsx +144 -17
  180. package/src/react/media-gallery/MediaMetaEditor.tsx +8 -1
  181. package/src/react/media-gallery/galleryDeepLink.spec.tsx +97 -1
  182. package/src/react/media-gallery/galleryDeepLink.ts +108 -16
  183. package/src/react/media-gallery/index.ts +3 -0
  184. package/src/react/media-gallery/mediaGallery.css +25 -0
  185. package/src/react/notifications/IssueViews.spec.tsx +98 -0
  186. package/src/react/notifications/IssueViews.tsx +93 -0
  187. package/src/react/notifications/IssuesPage.tsx +214 -0
  188. package/src/react/notifications/NotificationBell.tsx +6 -3
  189. package/src/react/notifications/index.ts +19 -0
  190. package/src/react/notifications/issueCapture.spec.ts +158 -0
  191. package/src/react/notifications/issueCapture.ts +263 -0
  192. package/src/react/notifications/issues.spec.ts +144 -0
  193. package/src/react/notifications/issues.ts +328 -0
  194. package/src/styles-areas/core.css +35 -34
  195. package/src/styles-areas/media.css +8 -7
  196. package/src/styles-areas/nav.css +2 -2
  197. package/src/styles-areas/notifications.css +10 -7
  198. package/src/styles-utilities.css +23 -2
  199. package/src/theme.css +4 -0
@@ -1,9 +1,15 @@
1
- import { type ReactNode } from 'react';
1
+ import { type HTMLAttributes, type ReactNode } from 'react';
2
2
  export interface ShellBanner {
3
3
  /** Stable key. */
4
4
  id: string;
5
5
  /** ONE sentence — what is wrong, in the owner's words. Never a paragraph (CPY-1). */
6
6
  message: ReactNode;
7
+ /**
8
+ * What to expect, after the sentence and quieter — "it retries by itself", "needs a code fix".
9
+ * The owner asked for it on the line itself (forge task 4262), so the lane may wrap to TWO
10
+ * lines on a phone when a banner carries one; never more.
11
+ */
12
+ hint?: ReactNode;
7
13
  /** `warning` (default) for a degraded app, `error` for a broken one, `info` for a plain fact. */
8
14
  tone?: 'info' | 'warning' | 'error';
9
15
  /** Leading icon. Omit for the tone's own. */
@@ -15,7 +21,8 @@ export interface ShellBanner {
15
21
  href?: string;
16
22
  };
17
23
  }
18
- export interface BannerLaneProps {
24
+ /** Any other attribute (`data-testid`, …) reaches the lane's `<section>`. */
25
+ export interface BannerLaneProps extends Omit<HTMLAttributes<HTMLElement>, 'children'> {
19
26
  banners: readonly ShellBanner[];
20
27
  className?: string;
21
28
  }
@@ -1 +1 @@
1
- {"version":3,"file":"BannerLane.d.ts","sourceRoot":"","sources":["../../../src/react/components/BannerLane.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,SAAS,EAAmB,MAAM,OAAO,CAAC;AAKxD,MAAM,WAAW,WAAW;IAC1B,kBAAkB;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,qFAAqF;IACrF,OAAO,EAAE,SAAS,CAAC;IACnB,iGAAiG;IACjG,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;IACpC,6CAA6C;IAC7C,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,kEAAkE;IAClE,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACjE;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,SAAS,WAAW,EAAE,CAAC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAkBD;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,eAAe,sCAkD1E"}
1
+ {"version":3,"file":"BannerLane.d.ts","sourceRoot":"","sources":["../../../src/react/components/BannerLane.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,SAAS,EAAmB,MAAM,OAAO,CAAC;AAK7E,MAAM,WAAW,WAAW;IAC1B,kBAAkB;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,qFAAqF;IACrF,OAAO,EAAE,SAAS,CAAC;IACnB;;;;OAIG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,iGAAiG;IACjG,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;IACpC,6CAA6C;IAC7C,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,kEAAkE;IAClE,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACjE;AAED,6EAA6E;AAC7E,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,cAAc,CAAC,WAAW,CAAC,EAAE,UAAU,CAAC;IACpF,OAAO,EAAE,SAAS,WAAW,EAAE,CAAC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAkBD;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,eAAe,sCAkD1E"}
@@ -36,6 +36,6 @@ export function BannerLane({ banners, className, ...rest }) {
36
36
  function BannerLine({ banner }) {
37
37
  const tone = banner.tone ?? 'warning';
38
38
  const { action } = banner;
39
- return (_jsxs("div", { "data-cb-banner": '', role: tone === 'error' ? 'alert' : 'status', className: cn('flex min-h-10 items-center gap-3 px-3 py-1.5 text-sm md:px-6', TONE[tone]), children: [_jsx("span", { "data-cb-banner-icon": '', "aria-hidden": 'true', className: 'grid size-5 shrink-0 place-items-center [&_svg]:size-4', children: banner.icon ?? ICON[tone] }), _jsx("span", { className: 'min-w-0 flex-1 truncate text-foreground', children: banner.message }), action ? (action.href ? (_jsxs(ButtonLink, { to: action.href, variant: 'ghost', size: 'sm', onClick: action.onClick, tooltip: action.label, children: [action.label, " \u2192"] })) : (_jsxs(Button, { variant: 'ghost', size: 'sm', onClick: action.onClick, tooltip: action.label, tooltipMobile: 'off', children: [action.label, " \u2192"] }))) : null] }));
39
+ return (_jsxs("div", { "data-cb-banner": '', role: tone === 'error' ? 'alert' : 'status', className: cn('flex min-h-10 items-center gap-3 px-3 py-1.5 text-sm md:px-6', TONE[tone]), children: [_jsx("span", { "data-cb-banner-icon": '', "aria-hidden": 'true', className: 'grid size-5 shrink-0 place-items-center [&_svg]:size-4', children: banner.icon ?? ICON[tone] }), banner.hint ? (_jsxs("span", { className: 'min-w-0 flex-1 line-clamp-2 text-foreground', children: [banner.message, _jsxs("span", { className: 'text-muted-foreground', children: [" \u00B7 ", banner.hint] })] })) : (_jsx("span", { className: 'min-w-0 flex-1 truncate text-foreground', children: banner.message })), action ? (action.href ? (_jsxs(ButtonLink, { to: action.href, variant: 'ghost', size: 'sm', onClick: action.onClick, tooltip: action.label, children: [action.label, " \u2192"] })) : (_jsxs(Button, { variant: 'ghost', size: 'sm', onClick: action.onClick, tooltip: action.label, tooltipMobile: 'off', children: [action.label, " \u2192"] }))) : null] }));
40
40
  }
41
41
  //# sourceMappingURL=BannerLane.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"BannerLane.js","sourceRoot":"","sources":["../../../src/react/components/BannerLane.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"BannerLane.js","sourceRoot":"","sources":["../../../src/react/components/BannerLane.tsx"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { type ReactNode } from 'react';
1
2
  export interface InlineEditableTextProps {
2
3
  value: string;
3
4
  /** Called with the trimmed next value, only when it actually changed. */
@@ -35,6 +36,15 @@ export interface InlineEditableTextProps {
35
36
  * would otherwise replace everything they wrote.
36
37
  */
37
38
  caret?: 'select-all' | 'end';
39
+ /**
40
+ * How the value reads when it is NOT being edited — the click-to-edit display and the
41
+ * read-only text alike; the editor still edits the raw string. For text with structure
42
+ * the string only encodes (a read table's tab-separated rows drawn as a grid). The
43
+ * editable display is a `<button>`, so this returns phrasing content: spans, styled
44
+ * into whatever shape the text needs. Never called for an empty value — the
45
+ * placeholder shows then.
46
+ */
47
+ display?: (value: string) => ReactNode;
38
48
  /** The editor's character ceiling (default 10,000) — the server's limit, so the editor stops where a save would be refused. */
39
49
  maxLength?: number;
40
50
  ariaLabel?: string;
@@ -47,5 +57,5 @@ export interface InlineEditableTextProps {
47
57
  * selects its text on mount so typing replaces (and the cursor is never stranded
48
58
  * at position 0) — or, with `caret='end'`, puts the caret after the text.
49
59
  */
50
- export declare function InlineEditableText({ value, onChange, canEdit, multiline, placeholder, className, inputClassName, trigger, wrap, caret, maxLength, ariaLabel, ...rest }: InlineEditableTextProps): import("react").JSX.Element | null;
60
+ export declare function InlineEditableText({ value, onChange, canEdit, multiline, placeholder, className, inputClassName, trigger, wrap, caret, maxLength, ariaLabel, display, ...rest }: InlineEditableTextProps): import("react").JSX.Element | null;
51
61
  //# sourceMappingURL=InlineEditableText.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"InlineEditableText.d.ts","sourceRoot":"","sources":["../../../src/react/components/InlineEditableText.tsx"],"names":[],"mappings":"AAUA,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,uEAAuE;IACvE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;gEAC4D;IAC5D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;gEAC4D;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wDAAwD;IACxD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;sDAGkD;IAClD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,YAAY,GAAG,KAAK,CAAC;IAC7B,+HAA+H;IAC/H,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,KAAK,EACL,QAAQ,EACR,OAAc,EACd,SAAiB,EACjB,WAAyB,EACzB,SAAS,EACT,cAAc,EACd,OAAgB,EAChB,IAAW,EACX,KAAoB,EACpB,SAAkB,EAClB,SAAS,EACT,GAAG,IAAI,EACR,EAAE,uBAAuB,sCAgJzB"}
1
+ {"version":3,"file":"InlineEditableText.d.ts","sourceRoot":"","sources":["../../../src/react/components/InlineEditableText.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,SAAS,EAAyB,MAAM,OAAO,CAAC;AAS9D,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,uEAAuE;IACvE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;gEAC4D;IAC5D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;gEAC4D;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wDAAwD;IACxD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;sDAGkD;IAClD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,YAAY,GAAG,KAAK,CAAC;IAC7B;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,SAAS,CAAC;IACvC,+HAA+H;IAC/H,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,KAAK,EACL,QAAQ,EACR,OAAc,EACd,SAAiB,EACjB,WAAyB,EACzB,SAAS,EACT,cAAc,EACd,OAAgB,EAChB,IAAW,EACX,KAAoB,EACpB,SAAkB,EAClB,SAAS,EACT,OAAO,EACP,GAAG,IAAI,EACR,EAAE,uBAAuB,sCAkJzB"}
@@ -16,7 +16,7 @@ import { RAW_BUTTON } from '../lib/cn.js';
16
16
  * selects its text on mount so typing replaces (and the cursor is never stranded
17
17
  * at position 0) — or, with `caret='end'`, puts the caret after the text.
18
18
  */
19
- export function InlineEditableText({ value, onChange, canEdit = true, multiline = false, placeholder = 'Add text…', className, inputClassName, trigger = 'text', wrap = true, caret = 'select-all', maxLength = 10_000, ariaLabel, ...rest }) {
19
+ export function InlineEditableText({ value, onChange, canEdit = true, multiline = false, placeholder = 'Add text…', className, inputClassName, trigger = 'text', wrap = true, caret = 'select-all', maxLength = 10_000, ariaLabel, display, ...rest }) {
20
20
  const [editing, setEditing] = useState(false);
21
21
  const [draft, setDraft] = useState(value);
22
22
  const begin = () => {
@@ -74,13 +74,14 @@ export function InlineEditableText({ value, onChange, canEdit = true, multiline
74
74
  // Read-only with no value → render nothing.
75
75
  if (!canEdit && !value)
76
76
  return null;
77
+ const shown = value && display ? display(value) : value || placeholder;
77
78
  // Read-only: plain text.
78
79
  if (!canEdit)
79
- return _jsx("span", { ...rest, className: className, children: value });
80
+ return _jsx("span", { ...rest, className: className, children: shown });
80
81
  // Icon trigger: text is inert (rows where clicking the text means open/select);
81
82
  // the pencil is the explicit edit affordance.
82
83
  if (trigger === 'icon') {
83
- return (_jsxs("span", { ...rest, className: 'inline-flex min-w-0 items-center gap-1', children: [_jsx("span", { className: cn('truncate', !value && 'italic text-muted-foreground', className), children: value || placeholder }), _jsx(IconButton, { size: 'sm', label: ariaLabel ? `Edit ${ariaLabel}` : 'Edit', onClick: begin, children: _jsx(Pencil, {}) })] }));
84
+ return (_jsxs("span", { ...rest, className: 'inline-flex min-w-0 items-center gap-1', children: [_jsx("span", { className: cn('truncate', !value && 'italic text-muted-foreground', className), children: shown }), _jsx(IconButton, { size: 'sm', label: ariaLabel ? `Edit ${ariaLabel}` : 'Edit', onClick: begin, children: _jsx(Pencil, {}) })] }));
84
85
  }
85
86
  // Editable display: a real <button> so Enter/Space activation + focus come free,
86
87
  // styled to read as inline text.
@@ -103,6 +104,6 @@ export function InlineEditableText({ value, onChange, canEdit = true, multiline
103
104
  // measuring gate (and any tool that reasons about layout) still sees 24px.
104
105
  // `DESIGN.md` §5's footprint is the floor and UX_STANDARDS §0 records that it
105
106
  // wins over the band — so the box grows too, and the band stays as the extra.
106
- TAP_TARGET_HEIGHT, 'pointer-coarse:before:absolute pointer-coarse:before:top-[calc(var(--hit-slop-y)*-1)] pointer-coarse:before:bottom-[calc(var(--hit-slop-y)*-1)] pointer-coarse:before:-left-1 pointer-coarse:before:-right-1 pointer-coarse:before:content-[""]', wrap ? 'whitespace-pre-wrap break-words' : 'block w-full truncate', !value && 'italic text-muted-foreground', className), children: value || placeholder }) }));
107
+ TAP_TARGET_HEIGHT, 'pointer-coarse:before:absolute pointer-coarse:before:top-[calc(var(--hit-slop-y)*-1)] pointer-coarse:before:bottom-[calc(var(--hit-slop-y)*-1)] pointer-coarse:before:-left-1 pointer-coarse:before:-right-1 pointer-coarse:before:content-[""]', wrap ? 'whitespace-pre-wrap break-words' : 'block w-full truncate', !value && 'italic text-muted-foreground', className), children: shown }) }));
107
108
  }
108
109
  //# sourceMappingURL=InlineEditableText.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"InlineEditableText.js","sourceRoot":"","sources":["../../../src/react/components/InlineEditableText.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"InlineEditableText.js","sourceRoot":"","sources":["../../../src/react/components/InlineEditableText.tsx"],"names":[],"mappings":"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"}
@@ -1,10 +1,18 @@
1
1
  import type { ReactNode } from 'react';
2
2
  export { useDialogOpen } from '../lib/openDialogs.js';
3
3
  /**
4
- * OVL-1's three widths plus `full`. `xl` is the pre-2465 name for a wide modal, kept so the
5
- * apps that pass it still compile; it renders `lg` (the old `xl` was 896px, `lg` is 900px).
4
+ * OVL-1's three widths plus `full`, and `page`. `xl` is the pre-2465 name for a wide modal, kept
5
+ * so the apps that pass it still compile; it renders `lg` (the old `xl` was 896px, `lg` is 900px).
6
+ *
7
+ * `page` is not a modal at all to the reader: it is a whole PAGE (OVL-4: anything that needs its
8
+ * own URL belongs on a page) — the viewport covered, no scrim, no card, no sheet, and a ← Back
9
+ * at the start of its bar where a modal has its ✕. Pass it only when the caller has given the
10
+ * page an address, so Back in the browser shuts it too (`MediaGallery`'s `tool`, forge task 4262:
11
+ * *"It is simpler for everything to get its own page and that helps with deep links going to
12
+ * pages without open modal like components popping up."*). Underneath it is still this dialog —
13
+ * the focus trap, Escape, the discard guard and the gallery's keys standing down all hold.
6
14
  */
7
- export type ModalSize = 'sm' | 'md' | 'lg' | 'full' | /** @deprecated use `lg` */ 'xl';
15
+ export type ModalSize = 'sm' | 'md' | 'lg' | 'full' | 'page' | /** @deprecated use `lg` */ 'xl';
8
16
  export interface ModalProps {
9
17
  open: boolean;
10
18
  onClose: () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"Modal.d.ts","sourceRoot":"","sources":["../../../src/react/components/Modal.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAYvC,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtD;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,GAAG,2BAA2B,CAAC,IAAI,CAAC;AA6CvF,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,QAAQ,EAAE,SAAS,CAAC;IACpB;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,mFAAmF;IACnF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,6FAA6F;IAC7F,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;;;;;;;;;;OAeG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,gFAAgF;IAChF,UAAU,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,KAAK,CAAC,EACpB,IAAI,EACJ,OAAO,EACP,IAAW,EACX,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,MAAM,EACN,OAAO,EACP,OAAO,EACP,MAAM,EACN,UAAiB,EACjB,aAAoB,EACpB,KAAK,EACL,UAAiB,EACjB,SAAS,EACT,GAAG,IAAI,EACR,EAAE,UAAU,+BAgJZ"}
1
+ {"version":3,"file":"Modal.d.ts","sourceRoot":"","sources":["../../../src/react/components/Modal.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAYvC,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtD;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,2BAA2B,CAAC,IAAI,CAAC;AAsDhG,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,QAAQ,EAAE,SAAS,CAAC;IACpB;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,mFAAmF;IACnF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,6FAA6F;IAC7F,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;;;;;;;;;;OAeG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,gFAAgF;IAChF,UAAU,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,KAAK,CAAC,EACpB,IAAI,EACJ,OAAO,EACP,IAAW,EACX,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,MAAM,EACN,OAAO,EACP,OAAO,EACP,MAAM,EACN,UAAiB,EACjB,aAAoB,EACpB,KAAK,EACL,UAAiB,EACjB,SAAS,EACT,GAAG,IAAI,EACR,EAAE,UAAU,+BAqKZ"}
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { X } from 'lucide-react';
2
+ import { ArrowLeft, X } from 'lucide-react';
3
3
  import { useDiscardGuard } from '../hooks/useDiscardGuard.js';
4
4
  import { useMediaQuery } from '../hooks/useMediaQuery.js';
5
5
  import { useSwipeToDismiss } from '../hooks/useSwipeToDismiss.js';
@@ -44,6 +44,14 @@ const PHONE_QUERY = '(max-width: 639px)';
44
44
  * every native sheet does.
45
45
  */
46
46
  const SHEET = 'max-sm:inset-x-0 max-sm:bottom-0 max-sm:w-full max-sm:max-w-none max-sm:max-h-[var(--sheet-h-phone)] max-sm:rounded-t-2xl max-sm:rounded-b-none max-sm:border-x-0 max-sm:border-b-0 max-sm:data-[state=open]:slide-in-from-bottom max-sm:data-[state=closed]:slide-out-to-bottom';
47
+ /**
48
+ * `size='page'`: the whole viewport, on the page's own background — nothing floats, so there is
49
+ * no border, no radius, no shadow and no rise. The body column is capped at the dashboard width
50
+ * (LAY-1, 1152px) and centered, so a form meant for a 900px modal does not sprawl across a wide
51
+ * screen; the bar and the footer run edge to edge like any page's.
52
+ */
53
+ const PAGE = 'inset-0 h-dvh w-full max-w-none rounded-none border-0 bg-background text-foreground';
54
+ const PAGE_COLUMN = 'mx-auto w-full max-w-6xl';
47
55
  /** From `sm` up: the centered card, capped to the viewport. It rises with every dialog
48
56
  * (`DIALOG_MOTION`: 8px and 0.985); the phone's sheet above slides its whole height instead. */
49
57
  const CARD = 'sm:top-1/2 sm:left-1/2 sm:w-[calc(100vw-2rem)] sm:-translate-x-1/2 sm:-translate-y-1/2 sm:rounded-xl sm:max-h-[min(90dvh,calc(100dvh-2rem))]';
@@ -67,20 +75,26 @@ export function Modal({ open, onClose, size = 'md', title, subtitle, children, f
67
75
  onSave,
68
76
  });
69
77
  const isPhone = useMediaQuery(PHONE_QUERY);
78
+ const page = size === 'page';
70
79
  // Swiping the sheet down is a SOFT dismiss, exactly like a backdrop tap — so a dialog that
71
80
  // refuses the backdrop refuses the swipe too, and a dirty form still gets its discard prompt.
72
- const swipeRef = useSwipeToDismiss('down', guardedClose, open && isPhone && backdropClose && !isConfirming);
81
+ // A page is not a sheet: nothing comes down, so nothing swipes.
82
+ const swipeRef = useSwipeToDismiss('down', guardedClose, open && isPhone && backdropClose && !isConfirming && !page);
73
83
  return (_jsx(Dialog, { ...rest, open: open, onOpenChange: (v) => {
74
84
  if (!v)
75
85
  guardedClose();
76
- }, children: _jsxs(DialogContent, { "data-cb-modal": '', className: cn(
86
+ }, children: _jsxs(DialogContent, { "data-cb-modal": '', "data-cb-modal-page": page ? '' : undefined,
87
+ // Under a page there is nothing to dim: the page covers it whole.
88
+ overlayClassName: page ? 'bg-transparent backdrop-blur-none' : undefined, className: cn(
77
89
  // A bottom sheet on a phone, a centered card from `sm` up (see SHEET / CARD). The
78
90
  // card keeps a 1rem margin on each side at every size, then caps at the size's
79
- // max-width.
91
+ // max-width. A page covers the viewport instead (see PAGE).
80
92
  'fixed flex flex-col gap-0 overflow-hidden',
81
93
  // The body scrolls with `py-4`: a form kit preview pinned inside it sits flush with
82
94
  // the body's top edge rather than a padding's height below it (formKit.css).
83
- '[--cbfk-sticky-top:-1rem]', 'border border-border bg-popover text-popover-foreground shadow-overlay', DIALOG_MOTION, SHEET, CARD, sizeClass(size), className),
95
+ '[--cbfk-sticky-top:-1rem]', page
96
+ ? PAGE
97
+ : cn('border border-border bg-popover text-popover-foreground shadow-overlay', DIALOG_MOTION, SHEET, CARD, sizeClass(size)), className),
84
98
  // Intercept Radix's default close so a dirty form gets the discard prompt; while
85
99
  // the prompt is up, Escape cancels it (keep editing) instead of re-prompting.
86
100
  // backdropClose={false} turns the backdrop into a no-op — critical dialogs that
@@ -106,6 +120,6 @@ export function Modal({ open, onClose, size = 'md', title, subtitle, children, f
106
120
  cancelDiscard();
107
121
  else
108
122
  guardedClose();
109
- }, children: [_jsxs("div", { ref: swipeRef, "data-cb-modal-handle": '', className: 'relative flex flex-none items-center justify-between gap-3 border-b border-border px-5 py-4 max-sm:touch-none max-sm:pt-6', children: [_jsx("span", { "aria-hidden": true, className: 'pointer-events-none absolute top-2 left-1/2 h-1 w-9 -translate-x-1/2 rounded-full bg-border sm:hidden' }), title ? (_jsxs("div", { className: 'min-w-0 flex-1', children: [_jsx(DialogTitle, { className: 'wrap-break-word text-base font-semibold', children: title }), subtitle ? (_jsx("p", { "data-cb-modal-subtitle": '', className: 'mt-1 text-caption leading-relaxed text-muted-foreground', children: subtitle })) : null] })) : (_jsx(DialogTitle, { className: 'sr-only', children: "Dialog" })), _jsx(IconButton, { label: 'Close', onClick: guardedClose, children: _jsx(X, {}) })] }), aside ? (_jsxs("div", { "data-cb-modal-split": '', className: 'flex min-h-0 flex-1 flex-col md:flex-row md:divide-x md:divide-border', children: [_jsx("div", { "data-cb-modal-aside": '', className: cn('flex min-h-0 flex-col md:flex-none', asideWidth === 'md' ? 'md:w-2/5' : 'md:w-1/3'), children: _jsx(ScrollArea, { className: 'px-5 py-4', children: aside }) }), _jsx("div", { className: 'flex min-h-0 flex-1 flex-col', children: scrollBody ? (_jsx(ScrollArea, { className: 'px-5 py-4', children: children })) : (_jsx("div", { className: 'flex min-h-0 flex-1 flex-col', children: children })) })] })) : scrollBody ? (_jsx(ScrollArea, { className: 'px-5 py-4', children: children })) : (_jsx("div", { className: 'flex min-h-0 flex-1 flex-col', children: children })), problem ? (_jsx("div", { className: 'flex-none px-5 pt-3 empty:hidden', children: problem })) : null, _jsx(FooterSlot, { footer: footer }), discardOverlay] }) }));
123
+ }, children: [_jsxs("div", { ref: swipeRef, "data-cb-modal-handle": '', className: cn('relative flex flex-none items-center justify-between gap-3 border-b border-border', page ? 'px-4 py-2' : 'px-5 py-4 max-sm:touch-none max-sm:pt-6'), children: [page ? (_jsx(IconButton, { label: 'Back', tooltip: 'Back to the file (Esc)', onClick: guardedClose, children: _jsx(ArrowLeft, {}) })) : (_jsx("span", { "aria-hidden": true, className: 'pointer-events-none absolute top-2 left-1/2 h-1 w-9 -translate-x-1/2 rounded-full bg-border sm:hidden' })), title ? (_jsxs("div", { className: 'min-w-0 flex-1', children: [_jsx(DialogTitle, { className: 'wrap-break-word text-base font-semibold', children: title }), subtitle ? (_jsx("p", { "data-cb-modal-subtitle": '', className: 'mt-1 text-caption leading-relaxed text-muted-foreground', children: subtitle })) : null] })) : (_jsx(DialogTitle, { className: 'sr-only', children: "Dialog" })), page ? null : (_jsx(IconButton, { label: 'Close', onClick: guardedClose, children: _jsx(X, {}) }))] }), aside ? (_jsxs("div", { "data-cb-modal-split": '', className: 'flex min-h-0 flex-1 flex-col md:flex-row md:divide-x md:divide-border', children: [_jsx("div", { "data-cb-modal-aside": '', className: cn('flex min-h-0 flex-col md:flex-none', asideWidth === 'md' ? 'md:w-2/5' : 'md:w-1/3'), children: _jsx(ScrollArea, { className: 'px-5 py-4', children: aside }) }), _jsx("div", { className: 'flex min-h-0 flex-1 flex-col', children: scrollBody ? (_jsx(ScrollArea, { className: 'px-5 py-4', children: children })) : (_jsx("div", { className: 'flex min-h-0 flex-1 flex-col', children: children })) })] })) : scrollBody ? (_jsx(ScrollArea, { className: 'px-5 py-4', children: page ? _jsx("div", { className: PAGE_COLUMN, children: children }) : children })) : (_jsx("div", { className: 'flex min-h-0 flex-1 flex-col', children: children })), problem ? (_jsx("div", { className: 'flex-none px-5 pt-3 empty:hidden', children: problem })) : null, _jsx(FooterSlot, { footer: footer }), discardOverlay] }) }));
110
124
  }
111
125
  //# sourceMappingURL=Modal.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Modal.js","sourceRoot":"","sources":["../../../src/react/components/Modal.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,cAAc,CAAC;AAEjC,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACnF,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAQtD,wFAAwF;AACxF,mFAAmF;AACnF,2FAA2F;AAC3F,6FAA6F;AAC7F,+FAA+F;AAC/F,4FAA4F;AAC5F,+DAA+D;AAC/D,MAAM,IAAI,GAA6C;IACrD,EAAE,EAAE,8BAA8B;IAClC,EAAE,EAAE,8BAA8B;IAClC,EAAE,EAAE,8BAA8B;IAClC,IAAI,EAAE,4GAA4G;CACnH,CAAC;AAEF,kEAAkE;AAClE,SAAS,SAAS,CAAC,IAAe;IAChC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,WAAW,CAAC,eAAe,EAAE,6GAA6G,CAAC,CAAC;QAC5I,OAAO,IAAI,CAAC,EAAE,CAAC;IACjB,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC;AACpB,CAAC;AAED,wEAAwE;AACxE,MAAM,WAAW,GAAG,oBAAoB,CAAC;AAEzC;;;;;;;;GAQG;AACH,MAAM,KAAK,GACT,kRAAkR,CAAC;AAErR;iGACiG;AACjG,MAAM,IAAI,GACR,8IAA8I,CAAC;AAqFjJ;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,KAAK,CAAC,EACpB,IAAI,EACJ,OAAO,EACP,IAAI,GAAG,IAAI,EACX,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,MAAM,EACN,OAAO,EACP,OAAO,EACP,MAAM,EACN,UAAU,GAAG,IAAI,EACjB,aAAa,GAAG,IAAI,EACpB,KAAK,EACL,UAAU,GAAG,IAAI,EACjB,SAAS,EACT,GAAG,IAAI,EACI;IACX,MAAM,EAAE,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,aAAa,EAAE,GAAG,eAAe,CAAC;QACpF,IAAI;QACJ,OAAO;QACP,OAAO;QACP,MAAM;KACP,CAAC,CAAC;IACH,MAAM,OAAO,GAAG,aAAa,CAAC,WAAW,CAAC,CAAC;IAC3C,2FAA2F;IAC3F,8FAA8F;IAC9F,MAAM,QAAQ,GAAG,iBAAiB,CAChC,MAAM,EACN,YAAY,EACZ,IAAI,IAAI,OAAO,IAAI,aAAa,IAAI,CAAC,YAAY,CAClD,CAAC;IAEF,OAAO,CACL,KAAC,MAAM,OACD,IAAI,EACR,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,CAAC,CAAC,EAAE,EAAE;YAClB,IAAI,CAAC,CAAC;gBAAE,YAAY,EAAE,CAAC;QACzB,CAAC,YAED,MAAC,aAAa,qBACE,EAAE,EAChB,SAAS,EAAE,EAAE;YACX,kFAAkF;YAClF,+EAA+E;YAC/E,aAAa;YACb,2CAA2C;YAC3C,oFAAoF;YACpF,6EAA6E;YAC7E,2BAA2B,EAC3B,wEAAwE,EACxE,aAAa,EACb,KAAK,EACL,IAAI,EACJ,SAAS,CAAC,IAAI,CAAC,EACf,SAAS,CACV;YACD,iFAAiF;YACjF,8EAA8E;YAC9E,gFAAgF;YAChF,gDAAgD;YAChD,EAAE;YACF,6EAA6E;YAC7E,4EAA4E;YAC5E,8EAA8E;YAC9E,yEAAyE;YACzE,8EAA8E;YAC9E,8EAA8E;YAC9E,4EAA4E;YAC5E,6EAA6E;YAC7E,+EAA+E;YAC/E,oEAAoE;YACpE,oBAAoB,EAAE,CAAC,CAAC,EAAE,EAAE;gBAC1B,CAAC,CAAC,cAAc,EAAE,CAAC;gBACnB,IAAI,aAAa;oBAAE,YAAY,EAAE,CAAC;YACpC,CAAC,EACD,iBAAiB,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,EAAE,EAC5C,eAAe,EAAE,gBAAgB,EACjC,eAAe,EAAE,CAAC,CAAC,EAAE,EAAE;gBACrB,CAAC,CAAC,cAAc,EAAE,CAAC;gBACnB,IAAI,YAAY;oBAAE,aAAa,EAAE,CAAC;;oBAC7B,YAAY,EAAE,CAAC;YACtB,CAAC,aAED,eACE,GAAG,EAAE,QAAQ,0BACQ,EAAE,EACvB,SAAS,EAAC,2HAA2H,aAGrI,oCAEE,SAAS,EAAC,uGAAuG,GACjH,EACD,KAAK,CAAC,CAAC,CAAC,CACP,eAAK,SAAS,EAAC,gBAAgB,aAC7B,KAAC,WAAW,IAAC,SAAS,EAAC,yCAAyC,YAAE,KAAK,GAAe,EACrF,QAAQ,CAAC,CAAC,CAAC,CACV,sCAA0B,EAAE,EAAC,SAAS,EAAC,yDAAyD,YAAE,QAAQ,GAAK,CAChH,CAAC,CAAC,CAAC,IAAI,IACJ,CACP,CAAC,CAAC,CAAC,CAEF,KAAC,WAAW,IAAC,SAAS,EAAC,SAAS,uBAAqB,CACtD,EACD,KAAC,UAAU,IAAC,KAAK,EAAC,OAAO,EAAC,OAAO,EAAE,YAAY,YAC7C,KAAC,CAAC,KAAG,GACM,IACT,EAEL,KAAK,CAAC,CAAC,CAAC,CAIP,sCACsB,EAAE,EACtB,SAAS,EAAC,uEAAuE,aAEjF,qCACsB,EAAE,EACtB,SAAS,EAAE,EAAE,CACX,oCAAoC,EACpC,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAC9C,YAED,KAAC,UAAU,IAAC,SAAS,EAAC,WAAW,YAAE,KAAK,GAAc,GAClD,EACN,cAAK,SAAS,EAAC,8BAA8B,YAC1C,UAAU,CAAC,CAAC,CAAC,CACZ,KAAC,UAAU,IAAC,SAAS,EAAC,WAAW,YAAE,QAAQ,GAAc,CAC1D,CAAC,CAAC,CAAC,CACF,cAAK,SAAS,EAAC,8BAA8B,YAAE,QAAQ,GAAO,CAC/D,GACG,IACF,CACP,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAEf,KAAC,UAAU,IAAC,SAAS,EAAC,WAAW,YAAE,QAAQ,GAAc,CAC1D,CAAC,CAAC,CAAC,CAGF,cAAK,SAAS,EAAC,8BAA8B,YAAE,QAAQ,GAAO,CAC/D,EAEA,OAAO,CAAC,CAAC,CAAC,CAKT,cAAK,SAAS,EAAC,kCAAkC,YAAE,OAAO,GAAO,CAClE,CAAC,CAAC,CAAC,IAAI,EAER,KAAC,UAAU,IAAC,MAAM,EAAE,MAAM,GAAI,EAI7B,cAAc,IACD,GACT,CACV,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"Modal.js","sourceRoot":"","sources":["../../../src/react/components/Modal.tsx"],"names":[],"mappings":"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"}
@@ -7,9 +7,18 @@ import { type PlanSegment, type VideoCut } from 'cursedbelt-core/media/video-cut
7
7
  * The gestures are a map's and an NLE's, so nothing here has to be learned:
8
8
  * · ⌘/Ctrl + wheel, or a trackpad pinch (which Chromium and Safari deliver as a ctrl-wheel),
9
9
  * zooms around the POINTER; two fingers on a touch screen pinch the same way.
10
- * · a plain wheel, or a drag anywhere on the track, pans.
11
- * · a click on the ruler moves the playhead there, onto the frame under the pointer.
10
+ * · a plain wheel, or a drag on the pieces, pans; ⌥ + wheel steps one frame per notch.
11
+ * · a press on the ruler moves the playhead there, onto the frame under the pointer, and
12
+ * dragging on SCRUBS it (forge task 4262): at the ruler's own speed, slower and slower as the
13
+ * pointer slides away from the track, or with ⇧ (quarter) or ⌥ (frame by frame) held — the
14
+ * bands of `scrub.ts`, named in a badge while the drag lasts.
12
15
  * · a click on a piece is the ✗ it always was — a drag that started on one never toggles it.
16
+ *
17
+ * 🔴 On a touch screen (`pointer: coarse`) the playhead is FIXED in the middle and the film
18
+ * slides under it — iMovie's and Photos' timeline on an iPhone, where a finger hides whatever it
19
+ * is on, so the frame you are choosing must never be under it. Any drag on the track scrubs (the
20
+ * film moves the way the finger does), sliding the finger up off the track slows it down to a
21
+ * frame at a time, a tap on the ruler jumps there, and two fingers pinch the zoom.
13
22
  * The slider, ± and Fit do the same with no gesture at all (CNV-1: the wheel alone never zooms;
14
23
  * CNV-3: a fit control on every canvas).
15
24
  *
@@ -1 +1 @@
1
- {"version":3,"file":"CutTimeline.d.ts","sourceRoot":"","sources":["../../../src/react/media/CutTimeline.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAe,KAAK,WAAW,EAAE,KAAK,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAmBhG;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAC;IACjC,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,QAAQ,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,IAAI,CAAC;IACtC,qFAAqF;IACrF,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC7B;AAeD,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,WAAW,EAAE,EAAE,gBAAgB,qBA4T/H"}
1
+ {"version":3,"file":"CutTimeline.d.ts","sourceRoot":"","sources":["../../../src/react/media/CutTimeline.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAe,KAAK,WAAW,EAAE,KAAK,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAsBhG;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAC;IACjC,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,QAAQ,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,IAAI,CAAC;IACtC,qFAAqF;IACrF,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC7B;AAiBD,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,WAAW,EAAE,EAAE,gBAAgB,qBAuY/H"}
@@ -6,14 +6,19 @@ import { IconButton } from '../components/IconButton.js';
6
6
  import { Button } from '../components/Button.js';
7
7
  import { Slider } from '../components/Slider.js';
8
8
  import { Tooltip } from '../components/Tooltip.js';
9
+ import { useMediaQuery } from '../hooks/useMediaQuery.js';
9
10
  import { cn, RAW_BUTTON } from '../lib/cn.js';
10
11
  import { clampViewStart, frameAt, frameStart, rulerLabel, rulerStep, sliderToZoom, snapToFrame, zoomAround, zoomBounds, zoomToSlider, } from './cutTimelineMath.js';
12
+ import { stepFrames } from './scrub.js';
13
+ import { ScrubSpeedBadge, useScrubGesture } from './ScrubControls.js';
11
14
  /** A drag shorter than this is a click — the slop every pointer library uses. */
12
15
  const DRAG_SLOP_PX = 4;
13
16
  /** The width drawn at before the track is measured. */
14
17
  const NOMINAL_WIDTH_PX = 1000;
15
18
  /** Frame ticks are drawn once frames are at least this far apart. */
16
19
  const FRAME_TICK_MIN_PX = 6;
20
+ /** A ruler label this close to the right edge is drawn to the left of its tick. */
21
+ const LABEL_ROOM_PX = 80;
17
22
  export function CutTimeline({ duration, fps, playhead, segments, onSeek, onToggle, transport, ...passThrough }) {
18
23
  const trackRef = React.useRef(null);
19
24
  const [measured, setWidth] = React.useState(0);
@@ -22,6 +27,7 @@ export function CutTimeline({ duration, fps, playhead, segments, onSeek, onToggl
22
27
  // measurement lands in a layout effect, before anything paints.
23
28
  const width = measured > 0 ? measured : NOMINAL_WIDTH_PX;
24
29
  const [view, setView] = React.useState({ start: 0, pps: null });
30
+ const centered = useMediaQuery('(pointer: coarse)');
25
31
  React.useLayoutEffect(() => {
26
32
  const track = trackRef.current;
27
33
  if (!track)
@@ -34,20 +40,30 @@ export function CutTimeline({ duration, fps, playhead, segments, onSeek, onToggl
34
40
  }, []);
35
41
  const bounds = zoomBounds(width, duration, fps);
36
42
  const pps = Math.min(bounds.max, Math.max(bounds.fit, view.pps ?? bounds.fit));
37
- const start = clampViewStart(view.start, pps, width, duration);
38
43
  const span = pps > 0 ? width / pps : 0;
44
+ // Centered, the view is wherever the playhead is — before 0 and past the end included, which
45
+ // is drawn as empty track rather than clamped, so the playhead can reach both ends.
46
+ const start = centered ? playhead - span / 2 : clampViewStart(view.start, pps, width, duration);
39
47
  const canZoom = bounds.max > bounds.fit && bounds.fit > 0;
40
48
  // The handlers below are bound once (the wheel has to be non-passive, so it is a native
41
49
  // listener) and read the current numbers from here.
42
- const now = React.useRef({ pps, start, width, duration, bounds });
43
- now.current = { pps, start, width, duration, bounds };
50
+ const now = React.useRef({ pps, start, width, duration, bounds, centered, playhead, fps, onSeek });
51
+ now.current = { pps, start, width, duration, bounds, centered, playhead, fps, onSeek };
52
+ const scrubDirection = React.useRef(1);
53
+ const scrub = useScrubGesture({
54
+ track: () => {
55
+ const n = now.current;
56
+ return { secondsPerPx: n.pps > 0 ? 1 / n.pps : 0, fps: n.fps, min: 0, max: n.duration, direction: scrubDirection.current };
57
+ },
58
+ onScrub: (seconds) => now.current.onSeek(seconds),
59
+ });
44
60
  const zoomTo = React.useCallback((next, anchorX) => {
45
61
  const n = now.current;
46
62
  setView(zoomAround({ start: n.start, pps: n.pps }, next, anchorX, n.width, n.duration, n.bounds));
47
63
  }, []);
48
64
  /** Zoom by a factor around the playhead when it is on screen, else around the middle. */
49
65
  const zoomBy = (factor) => {
50
- const x = playhead >= start && playhead <= start + span ? (playhead - start) * pps : width / 2;
66
+ const x = !centered && playhead >= start && playhead <= start + span ? (playhead - start) * pps : width / 2;
51
67
  zoomTo(pps * factor, x);
52
68
  };
53
69
  React.useEffect(() => {
@@ -58,13 +74,25 @@ export function CutTimeline({ duration, fps, playhead, segments, onSeek, onToggl
58
74
  event.preventDefault();
59
75
  const n = now.current;
60
76
  const lines = event.deltaMode === 1 ? 16 : event.deltaMode === 2 ? n.width : 1;
77
+ const along = Math.abs(event.deltaX) > Math.abs(event.deltaY) ? event.deltaX : event.deltaY;
78
+ if (event.altKey) {
79
+ // ⌥ + wheel: a frame per notch, the slowest way along there is (forge task 4262).
80
+ if (along !== 0)
81
+ n.onSeek(stepFrames(n.playhead, Math.sign(along), n.fps, n.duration));
82
+ return;
83
+ }
61
84
  if (event.ctrlKey || event.metaKey) {
62
85
  const x = event.clientX - track.getBoundingClientRect().left;
63
86
  zoomTo(n.pps * Math.exp(-event.deltaY * lines * 0.01), x);
64
87
  return;
65
88
  }
66
89
  // A vertical wheel pans too: most mice have no other, and the page has nothing to scroll here.
67
- const dx = Math.abs(event.deltaX) > Math.abs(event.deltaY) ? event.deltaX : event.deltaY;
90
+ const dx = along;
91
+ if (n.centered) {
92
+ // The view IS the playhead's, so moving the view moves the playhead — by the zoom's scale.
93
+ n.onSeek(stepFrames(Math.min(n.duration, Math.max(0, n.playhead + (dx * lines) / n.pps)), 0, n.fps, n.duration));
94
+ return;
95
+ }
68
96
  setView({ pps: n.pps, start: clampViewStart(n.start + (dx * lines) / n.pps, n.pps, n.width, n.duration) });
69
97
  };
70
98
  track.addEventListener('wheel', onWheel, { passive: false });
@@ -74,7 +102,7 @@ export function CutTimeline({ duration, fps, playhead, segments, onSeek, onToggl
74
102
  // a page-turn, as Resolve does, rather than a scroll that never lets the eye settle.
75
103
  React.useEffect(() => {
76
104
  const n = now.current;
77
- if (n.width <= 0 || n.pps <= n.bounds.fit + 1e-9)
105
+ if (n.centered || n.width <= 0 || n.pps <= n.bounds.fit + 1e-9)
78
106
  return;
79
107
  const viewSpan = n.width / n.pps;
80
108
  if (playhead < n.start || playhead > n.start + viewSpan) {
@@ -85,7 +113,7 @@ export function CutTimeline({ duration, fps, playhead, segments, onSeek, onToggl
85
113
  * Pointers. One pointer that moves past the slop pans; two pinch. One that does not move is a
86
114
  * click: on the ruler it seeks, on a piece the piece's own button does its job.
87
115
  */
88
- const gesture = React.useRef({ pointers: new Map(), fromX: 0, fromStart: 0, dragged: false, pinch: null });
116
+ const gesture = React.useRef({ pointers: new Map(), fromX: 0, fromStart: 0, dragged: false, scrubbing: false, pinch: null });
89
117
  const onPointerDown = (event) => {
90
118
  if (event.pointerType === 'mouse' && event.button !== 0)
91
119
  return;
@@ -95,9 +123,26 @@ export function CutTimeline({ duration, fps, playhead, segments, onSeek, onToggl
95
123
  g.fromX = event.clientX;
96
124
  g.fromStart = start;
97
125
  g.dragged = false;
126
+ g.scrubbing = false;
98
127
  g.pinch = null;
128
+ // A mouse or pen on the ruler: the playhead goes there now, and a drag scrubs it from there.
129
+ // Holding ⇧ or ⌥ as you press means "fine-tune from where it is", so it does not jump.
130
+ if (!centered && event.target.closest('.cbvc-ruler')) {
131
+ const n = now.current;
132
+ let from = playhead;
133
+ if (!event.shiftKey && !event.altKey) {
134
+ from = Math.min(n.duration, snapToFrame(n.start + (event.clientX - event.currentTarget.getBoundingClientRect().left) / n.pps, fps));
135
+ onSeek(from);
136
+ }
137
+ g.scrubbing = true;
138
+ scrubDirection.current = 1;
139
+ scrub.begin(event, from);
140
+ }
99
141
  }
100
142
  else if (g.pointers.size === 2) {
143
+ if (g.scrubbing)
144
+ scrub.cancel();
145
+ g.scrubbing = false;
101
146
  const [a, b] = [...g.pointers.values()];
102
147
  g.pinch = { distance: Math.max(1, Math.abs(a - b)), pps };
103
148
  g.dragged = true;
@@ -114,11 +159,20 @@ export function CutTimeline({ duration, fps, playhead, segments, onSeek, onToggl
114
159
  zoomTo(g.pinch.pps * (Math.max(1, Math.abs(a - b)) / g.pinch.distance), (a + b) / 2 - left);
115
160
  return;
116
161
  }
162
+ if (g.scrubbing)
163
+ return;
117
164
  const dx = event.clientX - g.fromX;
118
165
  if (!g.dragged && Math.abs(dx) < DRAG_SLOP_PX)
119
166
  return;
120
167
  if (!g.dragged) {
121
168
  g.dragged = true;
169
+ if (centered) {
170
+ // The film slides under the fixed playhead: drag it left and the film moves forward.
171
+ g.scrubbing = true;
172
+ scrubDirection.current = -1;
173
+ scrub.begin(event, playhead);
174
+ return;
175
+ }
122
176
  // Captured only once it is a drag: capturing on press would retarget the click of a
123
177
  // piece's button to the track, and the ✗ would stop working.
124
178
  event.currentTarget.setPointerCapture(event.pointerId);
@@ -133,6 +187,10 @@ export function CutTimeline({ duration, fps, playhead, segments, onSeek, onToggl
133
187
  if (g.pointers.size > 0)
134
188
  return;
135
189
  g.pinch = null;
190
+ if (g.scrubbing) {
191
+ g.scrubbing = false;
192
+ return;
193
+ }
136
194
  if (g.dragged || event.target.closest('.cbvc-seg'))
137
195
  return;
138
196
  const n = now.current;
@@ -149,49 +207,62 @@ export function CutTimeline({ duration, fps, playhead, segments, onSeek, onToggl
149
207
  };
150
208
  const x = (seconds) => (seconds - start) * pps;
151
209
  const end = start + span;
210
+ // Nothing is drawn past the track's right edge — not a tick, not a label, not a pixel of a
211
+ // piece — so the track never holds more than it shows (LAY-10's every-child check, 4262).
212
+ const inside = (px) => px >= 0 && px <= width - 1;
152
213
  // The ruler: a labeled tick per step, and a tick per FRAME once frames are far enough apart.
153
214
  const step = rulerStep(pps, fps);
154
215
  const labels = [];
155
216
  if (pps > 0 && duration > 0) {
156
217
  // Counted in frames when the step is, so 0.1 s × 3 lands on frame 3 and not on 0.30000000000000004.
157
218
  const stepFrames = Math.round(step.label * fps);
158
- for (let k = Math.floor(start / step.label); k * step.label <= end + 1e-9; k += 1) {
219
+ for (let k = Math.floor(Math.max(0, start) / step.label); k * step.label <= end + 1e-9; k += 1) {
159
220
  const t = step.frames ? frameStart(k * stepFrames, fps) : k * step.label;
160
- if (t >= start - 1e-9 && t <= duration + 1e-9)
221
+ if (t >= start - 1e-9 && t >= 0 && t <= duration + 1e-9 && inside(x(t)))
161
222
  labels.push(t);
162
223
  }
163
224
  }
164
225
  const frameTicks = [];
165
226
  if (pps / fps >= FRAME_TICK_MIN_PX) {
166
- for (let k = frameAt(start, fps); frameStart(k, fps) <= Math.min(end, duration) + 1e-9; k += 1)
167
- frameTicks.push(k);
227
+ for (let k = frameAt(Math.max(0, start), fps); frameStart(k, fps) <= Math.min(end, duration) + 1e-9; k += 1) {
228
+ if (inside(x(frameStart(k, fps))))
229
+ frameTicks.push(k);
230
+ }
168
231
  }
169
232
  return (_jsxs("div", { ...passThrough, className: 'cbvc-timeline', children: [_jsxs("div", { className: 'cbvc-tools', children: [transport, _jsxs("div", { className: 'cbvc-zoom', children: [_jsx(IconButton, { size: 'sm', label: 'Zoom out', tooltip: 'Zoom out (\u2318 or Ctrl + wheel, or pinch)', disabled: !canZoom || pps <= bounds.fit + 1e-9, onClick: () => zoomBy(0.5), children: _jsx(ZoomOut, { "aria-hidden": 'true' }) }), _jsx(Slider, { className: 'cbvc-zoom-slider', "aria-label": 'Zoom', "aria-valuetext": `${(pps / fps).toFixed(pps / fps < 1 ? 2 : 1)} px per frame`, min: 0, max: 1000, step: 1, disabled: !canZoom, value: Math.round(zoomToSlider(pps, bounds) * 1000), onChange: (value) => {
170
233
  const next = sliderToZoom(value / 1000, bounds);
171
234
  const x0 = playhead >= start && playhead <= end ? (playhead - start) * pps : width / 2;
172
235
  zoomTo(next, x0);
173
- } }), _jsx(IconButton, { size: 'sm', label: 'Zoom in', tooltip: 'Zoom in \u2014 down to single frames', disabled: !canZoom || pps >= bounds.max - 1e-9, onClick: () => zoomBy(2), children: _jsx(ZoomIn, { "aria-hidden": 'true' }) }), _jsxs(Button, { size: 'sm', variant: 'ghost', tooltip: 'Fit the whole clip in the timeline', disabled: view.pps === null || pps <= bounds.fit + 1e-9, onClick: () => setView({ start: 0, pps: null }), children: [_jsx(Maximize2, { "aria-hidden": 'true', size: 14 }), " Fit"] })] })] }), _jsxs("div", { ref: trackRef, className: 'cbvc-track', "data-px-per-second": pps, "data-view-start": start, onPointerDown: onPointerDown, onPointerMove: onPointerMove, onPointerUp: onPointerUp, onPointerCancel: onPointerUp, onClickCapture: onClickCapture, children: [_jsxs("div", { className: 'cbvc-ruler', "aria-hidden": 'true', children: [frameTicks.map((k) => (_jsx("span", { className: 'cbvc-frame-tick', "data-frame": k, style: { left: `${x(frameStart(k, fps))}px` } }, `f${k}`))), labels.map((t) => (_jsx("span", { className: 'cbvc-tick', style: { left: `${x(t)}px` }, children: _jsx("span", { className: 'cbvc-tick-label', children: rulerLabel(t, fps, step.frames, duration) }) }, `l${t}`)))] }), _jsx("ul", { className: 'cbvc-strip', "aria-label": segments.length <= 1
174
- ? 'The whole clip, uncut'
175
- : `${segments.length} sections; ${segments.filter((segment) => segment.removed).length} marked for removal`, children: segments
176
- .filter((segment) => segment.end > start && segment.start < end)
177
- .map((segment) => {
178
- // Clipped to the view (a pixel past each edge, so no border shows at the cut-off).
179
- const left = Math.max(-1, x(segment.start));
180
- const right = Math.min(width + 1, x(segment.end));
181
- const words = segment.removed
182
- ? `Keep ${cutTimecode(segment.start)} to ${cutTimecode(segment.end)} after all`
183
- : `Remove ${cutTimecode(segment.start)} to ${cutTimecode(segment.end)}`;
184
- return (_jsx("li", { className: 'cbvc-seg-item', style: { left: `${left}px`, width: `${Math.max(0, right - left)}px` }, children: _jsx(Tooltip, { content: words, children: _jsxs("button", {
185
- // guardrails-ignore no-raw-action-button: a segment of the timeline, sized to
186
- // its own duration — a `Button` carries padding and a min-width, which is
187
- // exactly wrong for a control whose WIDTH is the information.
188
- type: 'button', className: cn(RAW_BUTTON, `cbvc-seg${segment.removed ? ' is-removed' : ''}`), "aria-pressed": segment.removed, "aria-label": words, "data-start": segment.start, "data-end": segment.end, onClick: () => onToggle(segment), children: [_jsx(X, { className: 'cbvc-seg-x', "aria-hidden": 'true', size: 12 }), _jsx("span", { className: 'cbvc-seg-label', children: segment.removed ? 'Removed' : cutTimecode(segment.end - segment.start) })] }) }) }, segment.key));
189
- }) }), playhead >= start - 1e-9 && playhead <= end + 1e-9 ? (_jsx("span", { className: 'cbvc-strip-head', style: { left: `${x(playhead)}px` } })) : null] }), pps > bounds.fit + 1e-9 ? (_jsxs("div", { className: 'cbvc-overview', "aria-hidden": 'true', onClick: (event) => {
190
- const rect = event.currentTarget.getBoundingClientRect();
191
- const t = ((event.clientX - rect.left) / rect.width) * duration;
192
- setView({ pps, start: clampViewStart(t - span / 2, pps, width, duration) });
193
- }, children: [segments
194
- .filter((segment) => segment.removed)
195
- .map((segment) => (_jsx("span", { className: 'cbvc-overview-cut', style: { left: `${(segment.start / duration) * 100}%`, width: `${((segment.end - segment.start) / duration) * 100}%` } }, segment.key))), _jsx("span", { className: 'cbvc-overview-window', style: { left: `${(start / duration) * 100}%`, width: `${Math.max(0.5, (span / duration) * 100)}%` } }), _jsx("span", { className: 'cbvc-overview-head', style: { left: `${(playhead / duration) * 100}%` } })] })) : null] }));
236
+ } }), _jsx(IconButton, { size: 'sm', label: 'Zoom in', tooltip: 'Zoom in \u2014 down to single frames', disabled: !canZoom || pps >= bounds.max - 1e-9, onClick: () => zoomBy(2), children: _jsx(ZoomIn, { "aria-hidden": 'true' }) }), _jsxs(Button, { size: 'sm', variant: 'ghost', tooltip: 'Fit the whole clip in the timeline', disabled: view.pps === null || pps <= bounds.fit + 1e-9, onClick: () => setView({ start: 0, pps: null }), children: [_jsx(Maximize2, { "aria-hidden": 'true', size: 14 }), " Fit"] })] })] }), _jsxs("div", { className: 'cbvc-lane', children: [_jsxs("div", { ref: trackRef, className: 'cbvc-track', "data-centered": centered || undefined, "data-px-per-second": pps, "data-view-start": start, onPointerDown: onPointerDown, onPointerMove: onPointerMove, onPointerUp: onPointerUp, onPointerCancel: onPointerUp, onClickCapture: onClickCapture, children: [_jsxs("div", { className: 'cbvc-ruler', "aria-hidden": 'true', children: [frameTicks.map((k) => (_jsx("span", { className: 'cbvc-frame-tick', "data-frame": k, style: { left: `${x(frameStart(k, fps))}px` } }, `f${k}`))), labels.map((t) => (_jsxs(React.Fragment, { children: [_jsx("span", { className: 'cbvc-tick', style: { left: `${x(t)}px` } }), _jsx("span", { className: cn('cbvc-tick-label', x(t) > width - LABEL_ROOM_PX && 'is-end'), style: { left: `${x(t)}px` }, children: rulerLabel(t, fps, step.frames, duration) })] }, `l${t}`)))] }), _jsx("ul", { className: 'cbvc-strip', "aria-label": segments.length <= 1
237
+ ? 'The whole clip, uncut'
238
+ : `${segments.length} sections; ${segments.filter((segment) => segment.removed).length} marked for removal`, children: segments
239
+ .filter((segment) => segment.end > start && segment.start < end)
240
+ .map((segment) => {
241
+ // Clipped to the view (a pixel past each edge, so no border shows at the cut-off).
242
+ const left = Math.max(-1, x(segment.start));
243
+ const right = Math.min(width, x(segment.end));
244
+ const clippedEnd = x(segment.end) > width;
245
+ const words = segment.removed
246
+ ? `Keep ${cutTimecode(segment.start)} to ${cutTimecode(segment.end)} after all`
247
+ : `Remove ${cutTimecode(segment.start)} to ${cutTimecode(segment.end)}`;
248
+ return (_jsx("li", { className: cn('cbvc-seg-item', clippedEnd && 'is-clipped-end'), style: { left: `${left}px`, width: `${Math.max(0, right - left)}px` }, children: _jsx(Tooltip, { content: words, children: _jsxs("button", {
249
+ // guardrails-ignore no-raw-action-button: a segment of the timeline, sized to
250
+ // its own duration — a `Button` carries padding and a min-width, which is
251
+ // exactly wrong for a control whose WIDTH is the information.
252
+ type: 'button', className: cn(RAW_BUTTON, `cbvc-seg${segment.removed ? ' is-removed' : ''}`), "aria-pressed": segment.removed, "aria-label": words, "data-start": segment.start, "data-end": segment.end, onClick: () => onToggle(segment), children: [_jsx(X, { className: 'cbvc-seg-x', "aria-hidden": 'true', size: 12 }), _jsx("span", { className: 'cbvc-seg-label', children: segment.removed ? 'Removed' : cutTimecode(segment.end - segment.start) })] }) }) }, segment.key));
253
+ }) }), playhead >= start - 1e-9 && playhead <= end + 1e-9 ? (_jsx("span", { className: 'cbvc-strip-head', style: { left: `${Math.min(width - 1, Math.max(1, x(playhead)))}px` } })) : null] }), _jsx(ScrubSpeedBadge, { active: scrub.active, fps: fps }), pps > bounds.fit + 1e-9 ? (_jsxs("div", { className: 'cbvc-overview', "aria-hidden": 'true', onClick: (event) => {
254
+ const rect = event.currentTarget.getBoundingClientRect();
255
+ const t = ((event.clientX - rect.left) / rect.width) * duration;
256
+ if (centered) {
257
+ onSeek(snapToFrame(Math.min(duration, Math.max(0, t)), fps));
258
+ return;
259
+ }
260
+ setView({ pps, start: clampViewStart(t - span / 2, pps, width, duration) });
261
+ }, children: [segments
262
+ .filter((segment) => segment.removed)
263
+ .map((segment) => (_jsx("span", { className: 'cbvc-overview-cut', style: { left: `${(segment.start / duration) * 100}%`, width: `${((segment.end - segment.start) / duration) * 100}%` } }, segment.key))), _jsx("span", { className: 'cbvc-overview-window', style: {
264
+ left: `${(Math.max(0, start) / duration) * 100}%`,
265
+ width: `${Math.max(0.5, ((Math.min(duration, end) - Math.max(0, start)) / duration) * 100)}%`,
266
+ } }), _jsx("span", { className: 'cbvc-overview-head', style: { left: `min(calc(100% - 2px), ${(playhead / duration) * 100}%)` } })] })) : null] })] }));
196
267
  }
197
268
  //# sourceMappingURL=CutTimeline.js.map