cursedbelt 3.0.2 → 4.1.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 (252) hide show
  1. package/dist/react/components/ListToolbar.d.ts +94 -0
  2. package/dist/react/components/ListToolbar.d.ts.map +1 -0
  3. package/dist/react/components/ListToolbar.js +66 -0
  4. package/dist/react/components/ListToolbar.js.map +1 -0
  5. package/dist/react/file-tree/FileTree.d.ts +282 -3
  6. package/dist/react/file-tree/FileTree.d.ts.map +1 -1
  7. package/dist/react/file-tree/FileTree.js +799 -131
  8. package/dist/react/file-tree/FileTree.js.map +1 -1
  9. package/dist/react/file-tree/fileTree.css +151 -0
  10. package/dist/react/file-tree/fileTreeSelection.d.ts +187 -0
  11. package/dist/react/file-tree/fileTreeSelection.d.ts.map +1 -0
  12. package/dist/react/file-tree/fileTreeSelection.js +264 -0
  13. package/dist/react/file-tree/fileTreeSelection.js.map +1 -0
  14. package/dist/react/file-tree/index.d.ts +1 -0
  15. package/dist/react/file-tree/index.d.ts.map +1 -1
  16. package/dist/react/file-tree/index.js +8 -0
  17. package/dist/react/file-tree/index.js.map +1 -1
  18. package/dist/react/index.d.ts +1 -0
  19. package/dist/react/index.d.ts.map +1 -1
  20. package/dist/react/index.js +1 -0
  21. package/dist/react/index.js.map +1 -1
  22. package/dist/react/lib/form.d.ts +3 -0
  23. package/dist/react/lib/form.d.ts.map +1 -1
  24. package/dist/react/lib/form.js +43 -5
  25. package/dist/react/lib/form.js.map +1 -1
  26. package/dist/react/media/DenseGalleryShell.d.ts +19 -3
  27. package/dist/react/media/DenseGalleryShell.d.ts.map +1 -1
  28. package/dist/react/media/DenseGalleryShell.js +55 -15
  29. package/dist/react/media/DenseGalleryShell.js.map +1 -1
  30. package/dist/react/media/FocusViewer.d.ts.map +1 -1
  31. package/dist/react/media/FocusViewer.js +14 -14
  32. package/dist/react/media/FocusViewer.js.map +1 -1
  33. package/dist/react/media/FrameGrabber.d.ts +23 -0
  34. package/dist/react/media/FrameGrabber.d.ts.map +1 -1
  35. package/dist/react/media/FrameGrabber.js +67 -1
  36. package/dist/react/media/FrameGrabber.js.map +1 -1
  37. package/dist/react/media/MediaResize.d.ts +51 -19
  38. package/dist/react/media/MediaResize.d.ts.map +1 -1
  39. package/dist/react/media/MediaResize.js +20 -13
  40. package/dist/react/media/MediaResize.js.map +1 -1
  41. package/dist/react/media/VideoChapterEditor.d.ts +67 -0
  42. package/dist/react/media/VideoChapterEditor.d.ts.map +1 -0
  43. package/dist/react/media/VideoChapterEditor.js +148 -0
  44. package/dist/react/media/VideoChapterEditor.js.map +1 -0
  45. package/dist/react/media/VideoPlayer.d.ts +46 -4
  46. package/dist/react/media/VideoPlayer.d.ts.map +1 -1
  47. package/dist/react/media/VideoPlayer.js +124 -11
  48. package/dist/react/media/VideoPlayer.js.map +1 -1
  49. package/dist/react/media/VideoTurner.d.ts +15 -2
  50. package/dist/react/media/VideoTurner.d.ts.map +1 -1
  51. package/dist/react/media/VideoTurner.js +29 -2
  52. package/dist/react/media/VideoTurner.js.map +1 -1
  53. package/dist/react/media/denseGallery.css +104 -46
  54. package/dist/react/media/hlsSource.d.ts +57 -0
  55. package/dist/react/media/hlsSource.d.ts.map +1 -1
  56. package/dist/react/media/hlsSource.js +177 -1
  57. package/dist/react/media/hlsSource.js.map +1 -1
  58. package/dist/react/media/index.d.ts +2 -0
  59. package/dist/react/media/index.d.ts.map +1 -1
  60. package/dist/react/media/index.js +6 -0
  61. package/dist/react/media/index.js.map +1 -1
  62. package/dist/react/media/mediaResize.css +127 -54
  63. package/dist/react/media/pictureEditor.css +52 -6
  64. package/dist/react/media/videoChapterEditor.css +203 -0
  65. package/dist/react/media/videoChapters.d.ts +117 -0
  66. package/dist/react/media/videoChapters.d.ts.map +1 -0
  67. package/dist/react/media/videoChapters.js +95 -0
  68. package/dist/react/media/videoChapters.js.map +1 -0
  69. package/dist/react/media-gallery/GalleryTable.d.ts +52 -1
  70. package/dist/react/media-gallery/GalleryTable.d.ts.map +1 -1
  71. package/dist/react/media-gallery/GalleryTable.js +40 -9
  72. package/dist/react/media-gallery/GalleryTable.js.map +1 -1
  73. package/dist/react/media-gallery/MediaGallery.d.ts +107 -4
  74. package/dist/react/media-gallery/MediaGallery.d.ts.map +1 -1
  75. package/dist/react/media-gallery/MediaGallery.js +484 -104
  76. package/dist/react/media-gallery/MediaGallery.js.map +1 -1
  77. package/dist/react/media-gallery/MediaMetaEditor.d.ts +48 -0
  78. package/dist/react/media-gallery/MediaMetaEditor.d.ts.map +1 -0
  79. package/dist/react/media-gallery/MediaMetaEditor.js +66 -0
  80. package/dist/react/media-gallery/MediaMetaEditor.js.map +1 -0
  81. package/dist/react/media-gallery/index.d.ts +2 -0
  82. package/dist/react/media-gallery/index.d.ts.map +1 -1
  83. package/dist/react/media-gallery/index.js +5 -0
  84. package/dist/react/media-gallery/index.js.map +1 -1
  85. package/dist/react/media-gallery/mediaGallery.css +180 -4
  86. package/dist/react/media-gallery/mediaMeta.d.ts +62 -0
  87. package/dist/react/media-gallery/mediaMeta.d.ts.map +1 -0
  88. package/dist/react/media-gallery/mediaMeta.js +56 -0
  89. package/dist/react/media-gallery/mediaMeta.js.map +1 -0
  90. package/dist/react/media-gallery/playbackPreferences.d.ts +77 -0
  91. package/dist/react/media-gallery/playbackPreferences.d.ts.map +1 -1
  92. package/dist/react/media-gallery/playbackPreferences.js +42 -0
  93. package/dist/react/media-gallery/playbackPreferences.js.map +1 -1
  94. package/dist/react/media-gallery/types.d.ts +34 -0
  95. package/dist/react/media-gallery/types.d.ts.map +1 -1
  96. package/dist/scripts/guardrailsEnforce.d.ts +29 -0
  97. package/dist/scripts/guardrailsEnforce.d.ts.map +1 -0
  98. package/dist/styles-areas/activity-bar.css +30 -0
  99. package/dist/styles-areas/analytics.css +672 -0
  100. package/dist/styles-areas/auth.css +154 -0
  101. package/dist/styles-areas/calendar.css +416 -0
  102. package/dist/styles-areas/chart-table.css +41 -0
  103. package/dist/styles-areas/charts.css +89 -0
  104. package/dist/styles-areas/chat.css +364 -0
  105. package/dist/styles-areas/clipboard.css +26 -0
  106. package/dist/styles-areas/code-editor.css +130 -0
  107. package/dist/styles-areas/companion-link.css +70 -0
  108. package/dist/styles-areas/core.css +2503 -0
  109. package/dist/styles-areas/dashboard-grid.css +84 -0
  110. package/dist/styles-areas/data-table.css +657 -0
  111. package/dist/styles-areas/deep-link.css +26 -0
  112. package/dist/styles-areas/diff-viewer.css +30 -0
  113. package/dist/styles-areas/disk-usage.css +262 -0
  114. package/dist/styles-areas/emoji.css +154 -0
  115. package/dist/styles-areas/fields.css +582 -0
  116. package/dist/styles-areas/file-tree.css +33 -0
  117. package/dist/styles-areas/filter-rail.css +235 -0
  118. package/dist/styles-areas/folder-tree.css +219 -0
  119. package/dist/styles-areas/keep-awake.css +26 -0
  120. package/dist/styles-areas/layout-engine.css +239 -0
  121. package/dist/styles-areas/markdown.css +87 -0
  122. package/dist/styles-areas/master-detail.css +239 -0
  123. package/dist/styles-areas/master-lock.css +26 -0
  124. package/dist/styles-areas/media-gallery.css +351 -0
  125. package/dist/styles-areas/media.css +658 -0
  126. package/dist/styles-areas/nav.css +488 -0
  127. package/dist/styles-areas/notifications.css +245 -0
  128. package/dist/styles-areas/overlays.css +133 -0
  129. package/dist/styles-areas/palette.css +140 -0
  130. package/dist/styles-areas/pdf-viewer.css +47 -0
  131. package/dist/styles-areas/problem-detail.css +97 -0
  132. package/dist/styles-areas/rich-text.css +167 -0
  133. package/dist/styles-areas/sharing.css +153 -0
  134. package/dist/styles-areas/spreadsheet.css +37 -0
  135. package/dist/styles-areas/stats.css +281 -0
  136. package/dist/styles-areas/test-report.css +133 -0
  137. package/dist/styles-areas/virtual.css +30 -0
  138. package/dist/styles-areas/wizard.css +381 -0
  139. package/dist/styles-areas/workbench.css +543 -0
  140. package/dist/styles-areas/workbook-viewer.css +44 -0
  141. package/dist/styles-static.css +34 -23
  142. package/dist/styles.css +34 -23
  143. package/package.json +130 -32
  144. package/scripts/checkDistExports.ts +67 -0
  145. package/scripts/cssRules.ts +125 -0
  146. package/scripts/fixtureAppCss.ts +344 -0
  147. package/scripts/generateAreaStyles.ts +253 -0
  148. package/scripts/guardrailsEnforce.spec.ts +62 -0
  149. package/scripts/guardrailsEnforce.ts +67 -3
  150. package/scripts/paths.ts +106 -0
  151. package/scripts/styleAreas.ts +585 -0
  152. package/scripts/verify.ts +5 -0
  153. package/src/barrelsReachNoOptionalPeer.spec.ts +117 -9
  154. package/src/docsMatchTheSplit.spec.ts +98 -0
  155. package/src/fixtureAppCss.spec.ts +177 -0
  156. package/src/namedSubpathsResolve.spec.ts +35 -0
  157. package/src/publishShape.spec.ts +67 -0
  158. package/src/react/components/ListToolbar.spec.tsx +172 -0
  159. package/src/react/components/ListToolbar.tsx +151 -0
  160. package/src/react/file-tree/FileTree.spec.tsx +1009 -0
  161. package/src/react/file-tree/FileTree.tsx +1459 -309
  162. package/src/react/file-tree/fileTree.css +151 -0
  163. package/src/react/file-tree/fileTreeSelection.spec.ts +327 -0
  164. package/src/react/file-tree/fileTreeSelection.ts +321 -0
  165. package/src/react/file-tree/index.ts +21 -0
  166. package/src/react/index.ts +1 -0
  167. package/src/react/lib/form.ts +51 -8
  168. package/src/react/markdown/MarkdownDoc.spec.tsx +1 -1
  169. package/src/react/media/DenseGalleryShell.tsx +108 -36
  170. package/src/react/media/FocusViewer.tsx +15 -8
  171. package/src/react/media/FrameGrabber.spec.tsx +175 -0
  172. package/src/react/media/FrameGrabber.tsx +67 -0
  173. package/src/react/media/MediaResize.spec.tsx +84 -14
  174. package/src/react/media/MediaResize.tsx +51 -19
  175. package/src/react/media/VideoChapterEditor.tsx +428 -0
  176. package/src/react/media/VideoPlayer.spec.tsx +111 -1
  177. package/src/react/media/VideoPlayer.tsx +158 -11
  178. package/src/react/media/VideoTurner.spec.tsx +67 -0
  179. package/src/react/media/VideoTurner.tsx +70 -15
  180. package/src/react/media/denseGallery.css +104 -46
  181. package/src/react/media/hlsSource.recovery.spec.ts +213 -0
  182. package/src/react/media/hlsSource.spec.ts +8 -0
  183. package/src/react/media/hlsSource.ts +189 -1
  184. package/src/react/media/index.ts +14 -0
  185. package/src/react/media/mediaResize.css +127 -54
  186. package/src/react/media/pictureEditor.css +52 -6
  187. package/src/react/media/videoChapterEditor.css +203 -0
  188. package/src/react/media/videoChapters.spec.ts +176 -0
  189. package/src/react/media/videoChapters.ts +181 -0
  190. package/src/react/media-gallery/GalleryTable.tsx +93 -3
  191. package/src/react/media-gallery/MediaGallery.spec.tsx +1156 -10
  192. package/src/react/media-gallery/MediaGallery.tsx +813 -95
  193. package/src/react/media-gallery/MediaMetaEditor.tsx +237 -0
  194. package/src/react/media-gallery/galleryTable.spec.ts +41 -1
  195. package/src/react/media-gallery/index.ts +7 -0
  196. package/src/react/media-gallery/mediaGallery.css +180 -4
  197. package/src/react/media-gallery/mediaMeta.spec.ts +95 -0
  198. package/src/react/media-gallery/mediaMeta.ts +101 -0
  199. package/src/react/media-gallery/playbackPreferences.spec.ts +172 -0
  200. package/src/react/media-gallery/playbackPreferences.ts +100 -0
  201. package/src/react/media-gallery/types.ts +34 -0
  202. package/src/shippedFilesAreTracked.spec.ts +69 -0
  203. package/src/styles-areas/activity-bar.css +30 -0
  204. package/src/styles-areas/analytics.css +672 -0
  205. package/src/styles-areas/auth.css +154 -0
  206. package/src/styles-areas/calendar.css +416 -0
  207. package/src/styles-areas/chart-table.css +41 -0
  208. package/src/styles-areas/charts.css +89 -0
  209. package/src/styles-areas/chat.css +364 -0
  210. package/src/styles-areas/clipboard.css +26 -0
  211. package/src/styles-areas/code-editor.css +130 -0
  212. package/src/styles-areas/companion-link.css +70 -0
  213. package/src/styles-areas/core.css +2503 -0
  214. package/src/styles-areas/dashboard-grid.css +84 -0
  215. package/src/styles-areas/data-table.css +657 -0
  216. package/src/styles-areas/deep-link.css +26 -0
  217. package/src/styles-areas/diff-viewer.css +30 -0
  218. package/src/styles-areas/disk-usage.css +262 -0
  219. package/src/styles-areas/emoji.css +154 -0
  220. package/src/styles-areas/fields.css +582 -0
  221. package/src/styles-areas/file-tree.css +33 -0
  222. package/src/styles-areas/filter-rail.css +235 -0
  223. package/src/styles-areas/folder-tree.css +219 -0
  224. package/src/styles-areas/keep-awake.css +26 -0
  225. package/src/styles-areas/layout-engine.css +239 -0
  226. package/src/styles-areas/markdown.css +87 -0
  227. package/src/styles-areas/master-detail.css +239 -0
  228. package/src/styles-areas/master-lock.css +26 -0
  229. package/src/styles-areas/media-gallery.css +351 -0
  230. package/src/styles-areas/media.css +658 -0
  231. package/src/styles-areas/nav.css +488 -0
  232. package/src/styles-areas/notifications.css +245 -0
  233. package/src/styles-areas/overlays.css +133 -0
  234. package/src/styles-areas/palette.css +140 -0
  235. package/src/styles-areas/pdf-viewer.css +47 -0
  236. package/src/styles-areas/problem-detail.css +97 -0
  237. package/src/styles-areas/rich-text.css +167 -0
  238. package/src/styles-areas/sharing.css +153 -0
  239. package/src/styles-areas/spreadsheet.css +37 -0
  240. package/src/styles-areas/stats.css +281 -0
  241. package/src/styles-areas/test-report.css +133 -0
  242. package/src/styles-areas/virtual.css +30 -0
  243. package/src/styles-areas/wizard.css +381 -0
  244. package/src/styles-areas/workbench.css +543 -0
  245. package/src/styles-areas/workbook-viewer.css +44 -0
  246. package/src/styles-static.css +34 -23
  247. package/src/styles.css +34 -23
  248. package/src/stylesAreas.spec.ts +247 -0
  249. package/src/stylesUtilitiesMatches.spec.ts +6 -77
  250. package/src/testFilesRunInParallel.spec.ts +79 -0
  251. package/src/typecheckCachesAreSeparate.spec.ts +128 -0
  252. package/src/verifyGraph.spec.ts +6 -0
@@ -0,0 +1,94 @@
1
+ import type { ReactNode } from 'react';
2
+ import { type ChipOption } from './ChipFilterStrip';
3
+ export interface ListToolbarProps {
4
+ /**
5
+ * The search text. The box renders only when BOTH this and `onSearchChange`
6
+ * are given — a search input nobody is listening to is worse than none.
7
+ */
8
+ search?: string;
9
+ onSearchChange?: (value: string) => void;
10
+ /** What the reader can type, in the reader's terms — `'file name, repo or title'`. */
11
+ searchPlaceholder?: string;
12
+ /**
13
+ * The box's accessible name. Say WHAT is being searched (*"Search the queue"*),
14
+ * because a page with two lists gives a screen reader two boxes called "Search".
15
+ * Falls back to the placeholder, then to `'Search'`.
16
+ */
17
+ searchLabel?: string;
18
+ /**
19
+ * The filter chips — one dimension of the list, as data. An EMPTY array renders
20
+ * nothing at all: {@link ChipFilterStrip} always draws its "All" pill, and a lone
21
+ * "All" chip filtering nothing is noise on the line (`ConsoleView` filters its
22
+ * bucket chips down to the buckets that have rows, and can reach zero).
23
+ */
24
+ chips?: readonly ChipOption[];
25
+ /** Selected chip(s). `null` / `[]` is the "All" state. Default `null`. */
26
+ chipValue?: string | string[] | null;
27
+ onChipChange?: (value: string | string[] | null) => void;
28
+ /** Let more than one chip be active at once. Default `false`. */
29
+ multiChips?: boolean;
30
+ /** Label for the chips' clear-all pill. Default `'All'`. */
31
+ chipsAllLabel?: string;
32
+ /** Any further FILTER controls — they sit left, after the chips. */
33
+ filters?: ReactNode;
34
+ /** The sort control (a `NativeSelect`, a `ChoiceMenu`, …). Right-hand cluster. */
35
+ sort?: ReactNode;
36
+ /** The density/view-mode control — normally a `TableViewToggle`. Right-hand cluster. */
37
+ view?: ReactNode;
38
+ /** The far right: the one thing this page DOES. */
39
+ actions?: ReactNode;
40
+ className?: string;
41
+ }
42
+ /**
43
+ * The toolbar above a list: ONE line, filters left, view controls and actions right.
44
+ *
45
+ * DESIGN.md §2, quoted because this component is nothing but that paragraph:
46
+ * *"Never scatter operational controls or filter tabs across multi-row lines. Merge
47
+ * navigation filters, search boxes, query parameters, execution buttons, and
48
+ * destructive triggers into a single cohesive layout line. Position filters to the
49
+ * left/center and main execution actions to the far right."*
50
+ *
51
+ * ── Why this is in the design system, not in the app ───────────────────────
52
+ * `apps/station` composed exactly this shape inline on BOTH of its lists — the queue
53
+ * (`ConsoleView`) and the intake (`IdeasView`) — about thirty identical lines each,
54
+ * differing only in their options. The retired `cursed-satellites` station had already
55
+ * met that fork and said so in `BoardChrome.tsx`'s header: two boards with the same
56
+ * INTERACTION built twice give you *"two toolbars that drift apart on the first change
57
+ * to either"*. Task `183` was right to forbid a station-local component and `184` is
58
+ * the consequence: the shape belongs to every app's list, so it lives here.
59
+ *
60
+ * ── Layout only. It has no memory ─────────────────────────────────────────
61
+ * 🔴 Search text, the chosen chips, the sort key and the view mode are all the
62
+ * caller's state, and one of them is already solved elsewhere: `useTableViewMode`
63
+ * persists rows ⇄ cards per account, server-side. A toolbar that grew its own
64
+ * storage would either fight that hook or quietly become a second, local, worse copy
65
+ * of it. `sort`/`view`/`actions` are SLOTS for the same reason — this one must work
66
+ * for `MasterDetail` pages and card grids, which is why it is not {@link TableToolbar}
67
+ * (that one reads everything off a TanStack `table` and cannot exist without one).
68
+ *
69
+ * ── `flex-wrap`, and no `role='toolbar'` ──────────────────────────────────
70
+ * Wrapping, not a grid and not a breakpoint swap: the console runs at 1440 on a desk
71
+ * and 390 on a phone, and wrapping is the one behaviour that keeps DESIGN.md §5's 44px
72
+ * targets at both widths without a second layout. `ml-auto` on the right-hand cluster
73
+ * keeps the controls right when everything fits on one line and lets them ride down as
74
+ * a group when it does not.
75
+ *
76
+ * No `role='toolbar'`: that role promises arrow-key roving focus between its controls,
77
+ * which would take the arrow keys away from the text box and the native `<select>`
78
+ * that live on this line. Tab order is the correct affordance here.
79
+ *
80
+ * @example
81
+ * <ListToolbar
82
+ * search={search}
83
+ * onSearchChange={setSearch}
84
+ * searchPlaceholder='file name, repo or title'
85
+ * searchLabel='Search the queue'
86
+ * chips={buckets}
87
+ * chipValue={filter}
88
+ * onChipChange={(v) => setFilter(typeof v === 'string' ? v : null)}
89
+ * sort={<NativeSelect size='sm' aria-label='Sort the queue' … />}
90
+ * view={<TableViewToggle value={view.mode} onChange={view.setMode} />}
91
+ * />
92
+ */
93
+ export declare function ListToolbar({ search, onSearchChange, searchPlaceholder, searchLabel, chips, chipValue, onChipChange, multiChips, chipsAllLabel, filters, sort, view, actions, className, }: ListToolbarProps): import("react").JSX.Element;
94
+ //# sourceMappingURL=ListToolbar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ListToolbar.d.ts","sourceRoot":"","sources":["../../../src/react/components/ListToolbar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,EAAmB,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAGrE,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,sFAAsF;IACtF,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IAC9B,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAAC;IACrC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,KAAK,IAAI,CAAC;IACzD,iEAAiE;IACjE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,4DAA4D;IAC5D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oEAAoE;IACpE,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,kFAAkF;IAClF,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,wFAAwF;IACxF,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,mDAAmD;IACnD,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,wBAAgB,WAAW,CAAC,EAC1B,MAAM,EACN,cAAc,EACd,iBAAiB,EACjB,WAAW,EACX,KAAK,EACL,SAAgB,EAChB,YAAY,EACZ,UAAkB,EAClB,aAAa,EACb,OAAO,EACP,IAAI,EACJ,IAAI,EACJ,OAAO,EACP,SAAS,GACV,EAAE,gBAAgB,+BAuClB"}
@@ -0,0 +1,66 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from '../lib/cn';
3
+ import { ChipFilterStrip } from './ChipFilterStrip';
4
+ import { Input } from './Input';
5
+ /**
6
+ * The toolbar above a list: ONE line, filters left, view controls and actions right.
7
+ *
8
+ * DESIGN.md §2, quoted because this component is nothing but that paragraph:
9
+ * *"Never scatter operational controls or filter tabs across multi-row lines. Merge
10
+ * navigation filters, search boxes, query parameters, execution buttons, and
11
+ * destructive triggers into a single cohesive layout line. Position filters to the
12
+ * left/center and main execution actions to the far right."*
13
+ *
14
+ * ── Why this is in the design system, not in the app ───────────────────────
15
+ * `apps/station` composed exactly this shape inline on BOTH of its lists — the queue
16
+ * (`ConsoleView`) and the intake (`IdeasView`) — about thirty identical lines each,
17
+ * differing only in their options. The retired `cursed-satellites` station had already
18
+ * met that fork and said so in `BoardChrome.tsx`'s header: two boards with the same
19
+ * INTERACTION built twice give you *"two toolbars that drift apart on the first change
20
+ * to either"*. Task `183` was right to forbid a station-local component and `184` is
21
+ * the consequence: the shape belongs to every app's list, so it lives here.
22
+ *
23
+ * ── Layout only. It has no memory ─────────────────────────────────────────
24
+ * 🔴 Search text, the chosen chips, the sort key and the view mode are all the
25
+ * caller's state, and one of them is already solved elsewhere: `useTableViewMode`
26
+ * persists rows ⇄ cards per account, server-side. A toolbar that grew its own
27
+ * storage would either fight that hook or quietly become a second, local, worse copy
28
+ * of it. `sort`/`view`/`actions` are SLOTS for the same reason — this one must work
29
+ * for `MasterDetail` pages and card grids, which is why it is not {@link TableToolbar}
30
+ * (that one reads everything off a TanStack `table` and cannot exist without one).
31
+ *
32
+ * ── `flex-wrap`, and no `role='toolbar'` ──────────────────────────────────
33
+ * Wrapping, not a grid and not a breakpoint swap: the console runs at 1440 on a desk
34
+ * and 390 on a phone, and wrapping is the one behaviour that keeps DESIGN.md §5's 44px
35
+ * targets at both widths without a second layout. `ml-auto` on the right-hand cluster
36
+ * keeps the controls right when everything fits on one line and lets them ride down as
37
+ * a group when it does not.
38
+ *
39
+ * No `role='toolbar'`: that role promises arrow-key roving focus between its controls,
40
+ * which would take the arrow keys away from the text box and the native `<select>`
41
+ * that live on this line. Tab order is the correct affordance here.
42
+ *
43
+ * @example
44
+ * <ListToolbar
45
+ * search={search}
46
+ * onSearchChange={setSearch}
47
+ * searchPlaceholder='file name, repo or title'
48
+ * searchLabel='Search the queue'
49
+ * chips={buckets}
50
+ * chipValue={filter}
51
+ * onChipChange={(v) => setFilter(typeof v === 'string' ? v : null)}
52
+ * sort={<NativeSelect size='sm' aria-label='Sort the queue' … />}
53
+ * view={<TableViewToggle value={view.mode} onChange={view.setMode} />}
54
+ * />
55
+ */
56
+ export function ListToolbar({ search, onSearchChange, searchPlaceholder, searchLabel, chips, chipValue = null, onChipChange, multiChips = false, chipsAllLabel, filters, sort, view, actions, className, }) {
57
+ const showSearch = search !== undefined && onSearchChange !== undefined;
58
+ const showChips = chips !== undefined && chips.length > 0 && onChipChange !== undefined;
59
+ const showRight = Boolean(sort) || Boolean(view) || Boolean(actions);
60
+ return (_jsxs("div", { className: cn('flex flex-wrap items-center gap-x-3 gap-y-2', className), children: [showSearch ? (_jsx(Input, { type: 'search', value: search, onChange: (e) => onSearchChange(e.currentTarget.value), placeholder: searchPlaceholder, "aria-label": searchLabel ?? searchPlaceholder ?? 'Search',
61
+ // Full width on a phone (the chips then wrap beneath it, still tappable);
62
+ // a fixed, modest column from `sm:` so the chips share the line rather than
63
+ // being pushed off it by a box that grows with the container.
64
+ className: 'w-full min-w-40 sm:w-56' })) : null, showChips ? (_jsx(ChipFilterStrip, { options: [...chips], value: chipValue, onChange: onChipChange, multi: multiChips, ...(chipsAllLabel === undefined ? {} : { allLabel: chipsAllLabel }) })) : null, filters, showRight ? (_jsxs("span", { className: 'ml-auto flex flex-wrap items-center gap-2', children: [sort, view, actions] })) : null] }));
65
+ }
66
+ //# sourceMappingURL=ListToolbar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ListToolbar.js","sourceRoot":"","sources":["../../../src/react/components/ListToolbar.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAmB,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AA0ChC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,MAAM,UAAU,WAAW,CAAC,EAC1B,MAAM,EACN,cAAc,EACd,iBAAiB,EACjB,WAAW,EACX,KAAK,EACL,SAAS,GAAG,IAAI,EAChB,YAAY,EACZ,UAAU,GAAG,KAAK,EAClB,aAAa,EACb,OAAO,EACP,IAAI,EACJ,IAAI,EACJ,OAAO,EACP,SAAS,GACQ;IACjB,MAAM,UAAU,GAAG,MAAM,KAAK,SAAS,IAAI,cAAc,KAAK,SAAS,CAAC;IACxE,MAAM,SAAS,GAAG,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,YAAY,KAAK,SAAS,CAAC;IACxF,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAErE,OAAO,CACL,eAAK,SAAS,EAAE,EAAE,CAAC,6CAA6C,EAAE,SAAS,CAAC,aACzE,UAAU,CAAC,CAAC,CAAC,CACZ,KAAC,KAAK,IACJ,IAAI,EAAC,QAAQ,EACb,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,EACtD,WAAW,EAAE,iBAAiB,gBAClB,WAAW,IAAI,iBAAiB,IAAI,QAAQ;gBACxD,0EAA0E;gBAC1E,4EAA4E;gBAC5E,8DAA8D;gBAC9D,SAAS,EAAC,yBAAyB,GACnC,CACH,CAAC,CAAC,CAAC,IAAI,EACP,SAAS,CAAC,CAAC,CAAC,CACX,KAAC,eAAe,IACd,OAAO,EAAE,CAAC,GAAG,KAAK,CAAC,EACnB,KAAK,EAAE,SAAS,EAChB,QAAQ,EAAE,YAAY,EACtB,KAAK,EAAE,UAAU,KACb,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC,GACpE,CACH,CAAC,CAAC,CAAC,IAAI,EACP,OAAO,EACP,SAAS,CAAC,CAAC,CAAC,CACX,gBAAM,SAAS,EAAC,2CAA2C,aACxD,IAAI,EACJ,IAAI,EACJ,OAAO,IACH,CACR,CAAC,CAAC,CAAC,IAAI,IACJ,CACP,CAAC;AACJ,CAAC"}
@@ -26,7 +26,8 @@
26
26
  * graduation. They are here now and each says so where it lives:
27
27
  *
28
28
  * 1. **Reveal** — the selected row is scrolled into view, and a shut folder holding it is
29
- * marked and scrolled to instead of being forced open (see the effect below).
29
+ * marked and scrolled to rather than forced open unless `expandOnReveal` says otherwise
30
+ * (see the effect below, and the section on it).
30
31
  * 2. **The path is marked** — `data-ancestor` on the folders between the root and the
31
32
  * selection, `data-holds-open` on the shut one holding it (`openPath`).
32
33
  * 3. **Escape cancels a drag** (`beginDrag`), which `pointercancel` never did.
@@ -45,6 +46,100 @@
45
46
  * needs and a design system should not have is an argument for a prop, not for a copy: the
46
47
  * three claimants collapse into one `selectedId`, and a kind-specific glyph is `renderIcon`.
47
48
  *
49
+ * ── 🔴 Multi-select, 2026-09-15 — and why `selectedId` did not move ─────────────────────
50
+ * Owner, `ideas/file-tree.md` item 2: shift-click a range, ⌘-click to add one, drag the whole
51
+ * selection to another folder or to the root, ⌘⌫ the lot, and ⌘X/⌘C/⌘V to move or copy them.
52
+ * Three apps were waiting on it and none of them forked the component to get it.
53
+ *
54
+ * Every one of those is a change to this SHARED contract, and the trap in it is that
55
+ * `selectedId: string | null` is load-bearing in all three: it is the row the main pane is
56
+ * showing in `apps/collections`, and the folder the library is filtered by in `apps/music`.
57
+ * So it did not move. {@link FileTreeProps.selectedIds} sits BESIDE it, the selection is
58
+ * `[selectedId]` for a consumer that passes neither, and every plural callback is its own
59
+ * optional prop — `onMoveMany`, `onDeleteMany`, `onCopy` — with one rule holding it together:
60
+ * **N > 1 goes through the plural verb, and without it the gesture acts on the one row it
61
+ * started on**. A tree that silently became multi-select would have broken "what am I looking
62
+ * at" in two shipped apps on the day it published.
63
+ *
64
+ * The arithmetic is `fileTreeSelection.ts`, DOM-free and unit-tested, for the same reason
65
+ * `core/file-tree/fileTreeModel.ts` is: happy-dom computes no layout, so the pointer half of
66
+ * a multi-drag cannot be asserted anywhere and the RULES have to be reachable without it.
67
+ *
68
+ * ── 🔴 New Folder with Selection, 2026-09-16 ────────────────────────────────────────────
69
+ * Owner, 2026-09-13: *"allow make dir from file where dir wraps file location and gets name
70
+ * of file"*. Half of it was already here — the {@link FileTreeProps.draft} row, which names a
71
+ * folder before anything is created — and the other half is `onMove`/`onMoveMany`, so this is
72
+ * one gesture out of two things that existed rather than a new mechanism: the draft grew a
73
+ * `wrap` list, and committing hands the name AND the ids back in a single call. Two calls is
74
+ * how a folder ends up made and empty when the second one fails.
75
+ *
76
+ * It is Finder's *New Folder with Selection* and VS Code's ⌃⌘N, invented no further: the
77
+ * folder is made where the row already is, named after it with the extension dropped, and the
78
+ * row moves in. A selection of twenty is the same code path with N > 1, named after the first
79
+ * in TREE order — see `planWrap`, which is where all of that is decided without a DOM.
80
+ *
81
+ * ── 🔴 Pinned rows, 2026-09-16 ──────────────────────────────────────────────────────────
82
+ * Owner, 2026-09-13: *"need ces ability to pin open files where they stay at the top of the
83
+ * file tree in a new section above the folders. Pinned will mean I want to keep them there
84
+ * until I unpin them. This makes it easier to keep track of important files and folders. They
85
+ * still will exist in the folder they were stored in and unpinning takes it back to normal.
86
+ * Pinning or unpinning one place doesn't change pinning in the other place."*
87
+ *
88
+ * Five things in that paragraph, and the last two are the ones a sort order loses:
89
+ *
90
+ * 1. **A SECTION, not an order.** A pinned row is drawn TWICE — once at the top and once
91
+ * where it lives, because *"they still will exist in the folder they were stored in"*.
92
+ * Lifting the row out would make pinning a move, and unpinning an undo of one.
93
+ * 2. **Files and folders both** — *"important files and folders"*.
94
+ * 3. **Sticky until unpinned.** It is not a recents list and it does not age out, which is
95
+ * why the list is the consumer's to persist rather than the tree's to remember.
96
+ * 4. **Unpinning leaves nothing behind**: the top copy goes and the row in the folder is
97
+ * the row it always was.
98
+ * 5. 🔴 **Per place.** *"Pinning or unpinning one place doesn't change pinning in the other
99
+ * place."* That is free here and only because the state is CONTROLLED — the consumer
100
+ * holds one list per tree, exactly as `media/stageResize.ts` holds one height per
101
+ * consumer key (*"one shared key would make sizing either of them silently resize the
102
+ * other"* — the same defect, one surface along).
103
+ *
104
+ * 🔴 **One identity, two row keys.** Every selection, drag, reveal and aria path in here is
105
+ * keyed on `node.id`, and drawing a row twice would break each of them if both copies claimed
106
+ * the same key: `openPath`/`data-holds-open` walks for the selected row and would find the
107
+ * pinned copy, marking the wrong ancestors. So the node's id stays its identity and the COPY
108
+ * is addressed separately — two ref maps (`rowRefs`, `pinnedRefs`), and `focused` says which
109
+ * section it is in. `apps/collections` makes the same split with `itemRowId`/`nodeRowId`.
110
+ *
111
+ * 🔴 **Its own `role='tree'`, outside the scroller.** `aria-setsize`/`aria-posinset` below are
112
+ * computed from the rows ON SCREEN, on the stated assumption that every sibling of a visible
113
+ * row is itself visible — folding pins into that row list would make the counts wrong for
114
+ * every row in the tree. The section is a separate widget with counts of its own, and it sits
115
+ * ABOVE `.cbft-scroll` rather than sticky inside it, so the reveal below, the autoscroll and
116
+ * the blank-space drop target all still mean exactly what they meant.
117
+ *
118
+ * The pinned copy is a SHORTCUT, not a second editing surface: it is flat (no twisty, no
119
+ * depth), a drop aimed at it lands in the real folder, and the rename field draws on the real
120
+ * row whenever the real row is on screen.
121
+ *
122
+ * ── 🔴 The reveal OPENS the folder now, if it is asked to, 2026-09-16 ───────────────────
123
+ * Owner, `ideas/collections-09-14.md`: *"clicking a file in media gallery content window
124
+ * should show that file in the file tree by like vscode does when you select a tab with a
125
+ * file in it."* Almost all of that was already here — `selectedId`, `openPath`,
126
+ * `data-ancestor`/`data-holds-open` and the scroll — and the one thing missing was the one
127
+ * thing this file had DECIDED against: limit 1 of the reveal, *a shut folder stays shut*.
128
+ *
129
+ * He named VS Code, whose `explorer.autoReveal` is `true` and expands, and he named the exact
130
+ * gesture. So the ask wins over the default, and the decision is recorded in full on the
131
+ * effect itself rather than here: {@link FileTreeProps.expandOnReveal} is OFF unless a
132
+ * consumer wires it, and what it opens goes into an overlay of this component's own
133
+ * (`revealOpened`) which is **never handed to `onCollapsedChange`**. The persisted collapse
134
+ * is read back unchanged on the next mount, which is the half of limit 1's objection that was
135
+ * never a matter of taste.
136
+ *
137
+ * 🔴 Silent is not the same as invisible, and {@link FileTreeProps.onRevealExpand} is the
138
+ * difference. A tree that FETCHES a folder's children when it opens — `apps/collections` asks
139
+ * the server per open folder — would otherwise draw the folder the reveal opened with nothing
140
+ * in it, because the consumer never learned the expansion happened. So it is told, in a
141
+ * callback that exists to feed a fetch plan and explicitly not to be stored.
142
+ *
48
143
  * ── Pointer events, not HTML5 drag-and-drop, for INTERNAL drags ─────────────────────────
49
144
  * The ghost image is a translucent screenshot nobody asked for, `dragover` fires at the
50
145
  * mercy of the browser, and touch does not have HTML5 dnd at all. The drag only *starts*
@@ -68,6 +163,29 @@ export interface FileTreeProps {
68
163
  /** The row drawn as selected — usually "what the main pane is showing". */
69
164
  selectedId: string | null;
70
165
  onSelect: (node: FileTreeNode) => void;
166
+ /**
167
+ * The rows drawn as selected when there is more than one — shift-click and ⌘-click.
168
+ *
169
+ * 🔴 BESIDE {@link selectedId}, never instead of it, and that is the whole shape of this
170
+ * feature. `selectedId` is load-bearing in every consumer this tree has: in
171
+ * `apps/collections` it is the row the main pane is showing, in `apps/music` it is the
172
+ * folder the library is filtered by. Those are singular questions with singular answers,
173
+ * and a tree that silently became multi-select would break "what am I looking at" in two
174
+ * shipped apps on the day it published. So a consumer that passes neither this nor
175
+ * {@link onSelectionChange} gets EXACTLY today's behaviour, byte for byte.
176
+ *
177
+ * Omit it and the selection is `[selectedId]`, which is what makes ⌘-click work for a
178
+ * consumer that stores only the one id and grows into the array afterwards.
179
+ */
180
+ selectedIds?: readonly string[];
181
+ /**
182
+ * The selection changed by a gesture. **Wiring it is what turns multi-select on.**
183
+ *
184
+ * A plain click reports `[id]` and still calls {@link onSelect}; shift-click and ⌘-click
185
+ * report the new set and do NOT — in every file explorer, extending a selection is not
186
+ * also navigating, and in a consumer that opens on select it would be.
187
+ */
188
+ onSelectionChange?: (ids: string[]) => void;
71
189
  /**
72
190
  * Double-click, or Space on a focused row. Falls back to `onSelect` when omitted.
73
191
  *
@@ -77,11 +195,95 @@ export interface FileTreeProps {
77
195
  /** Shut folders, as a controlled value. Omit both for the tree's own memory. */
78
196
  collapsed?: readonly string[];
79
197
  onCollapsedChange?: (next: string[]) => void;
198
+ /**
199
+ * **Open the shut folders on the way to the selection**, instead of marking the shut one
200
+ * and stopping there — VS Code's `explorer.autoReveal`, which is on by default over there.
201
+ *
202
+ * Owner, `ideas/collections-09-14.md`: *"clicking a file in media gallery content window
203
+ * should show that file in the file tree by like vscode does when you select a tab with a
204
+ * file in it."* He named the application and he named the gesture, so this is what the
205
+ * reveal does when it is asked for by name — see limit 1 on the reveal effect for the
206
+ * argument this prop is the opt-in half of, and why it stays the default.
207
+ *
208
+ * 🔴 **It never touches {@link collapsed}.** The folders it opens are held in an overlay of
209
+ * the tree's own and {@link onCollapsedChange} is not called, so the collapse the consumer
210
+ * persisted is still the collapse it persisted — on the next mount the tree reads it back
211
+ * unchanged. Shutting one of them by hand drops it from the overlay and the persisted value
212
+ * decides again; nothing about it is ever written out. That is the whole of the objection
213
+ * limit 1 records, answered without giving up the behaviour he asked for.
214
+ *
215
+ * Off unless it is wired, like every other verb here: a consumer that passes nothing gets
216
+ * byte for byte today's reveal.
217
+ */
218
+ expandOnReveal?: boolean;
219
+ /**
220
+ * The folders {@link expandOnReveal} just opened, told to the consumer once per reveal.
221
+ *
222
+ * 🔴 **Not a request to persist them** — that is the one thing this must never be read as,
223
+ * and the whole reason the overlay exists. It is for a tree whose children are FETCHED when
224
+ * a folder opens: `apps/collections` asks the server for a folder's files only for the
225
+ * folders it believes are open (`ExplorerSidebar.tsx` → `nodesToFetch`), so an expansion it
226
+ * cannot see would draw the folder open and EMPTY, with the file the reveal was asked for
227
+ * still missing. A silent overlay is the right default and an unobservable one is a trap.
228
+ *
229
+ * Wire it to whatever decides the fetch plan, never to the stored collapse.
230
+ */
231
+ onRevealExpand?: (ids: readonly string[]) => void;
232
+ /**
233
+ * The rows kept in a section of their own ABOVE the folders — *"pin open files where they
234
+ * stay at the top of the file tree in a new section above the folders"*.
235
+ *
236
+ * Files and folders both, in the order they were pinned, and a pin the tree no longer holds
237
+ * is simply not drawn rather than drawn as a ghost. Each one appears TWICE: at the top and
238
+ * in the folder it lives in, which is the requirement — *"they still will exist in the
239
+ * folder they were stored in"*.
240
+ *
241
+ * 🔴 CONTROLLED, with no fallback to a memory of the tree's own — the one place this
242
+ * differs from {@link collapsed}, and deliberately. *"Pinned will mean I want to keep them
243
+ * there until I unpin them"*: a list this component remembered would be a list that
244
+ * evaporated on the next mount, which is the opposite of what the word means. Holding it
245
+ * outside is also the whole of *"pinning or unpinning one place doesn't change pinning in
246
+ * the other place"* — one list per tree, per app, wherever the consumer persists it, the
247
+ * same reasoning `media/stageResize.ts` records for its per-consumer height key.
248
+ *
249
+ * Omit it and there is no section, no pin control and no chord: byte for byte today's tree.
250
+ */
251
+ pinnedIds?: readonly string[];
252
+ /**
253
+ * Pin or unpin a row. **Wiring it is what puts the pin control on every row and ⌃⌘T on the
254
+ * keyboard**, the same rule every other verb here follows.
255
+ *
256
+ * The next list arrives whole (a new pin is appended, so the section keeps the order it was
257
+ * built in) and the consumer stores it. Pass {@link pinnedIds} without this to draw a
258
+ * read-only section and put the unpin on your own ⋯ menu.
259
+ */
260
+ onPinnedChange?: (next: string[]) => void;
261
+ /**
262
+ * What the pinned section is called — its heading, and its `aria-label`. Default `Pinned`.
263
+ *
264
+ * An app whose vocabulary differs ("Favourites", "Starred") passes its own, for the same
265
+ * reason {@link accessCopy} exists: the mechanism is general and the words are not.
266
+ */
267
+ pinnedLabel?: string;
80
268
  /**
81
269
  * Commit a drag. Omit it and the tree cannot be rearranged at all — no press starts a
82
270
  * drag, and {@link unavailableReason} says why on hover.
83
271
  */
84
272
  onMove?: (nodeId: string, drop: FileTreeDrop) => void;
273
+ /**
274
+ * Commit a move of MORE THAN ONE row — a dragged selection, or ⌘X then ⌘V.
275
+ *
276
+ * 🔴 The single rule that keeps every plural gesture from surprising an old consumer:
277
+ * **N > 1 always goes through this prop, and without it every gesture degrades to the one
278
+ * row it started on.** A tree that fell back to calling `onMove` N times would hand a
279
+ * consumer N separate writes against a tree that is re-derived between each of them, which
280
+ * is how a batch move half-lands.
281
+ *
282
+ * The ids arrive with descendants of other selected rows already removed — see
283
+ * `topmostSelected`. Moving a folder moves what is in it, so a selected child is the same
284
+ * move named twice, and acting on both flattens the child out of its own parent.
285
+ */
286
+ onMoveMany?: (nodeIds: readonly string[], drop: FileTreeDrop) => void;
85
287
  /** See `ResolveDropOptions.allowReorder`. Off unless the consumer stores an order. */
86
288
  allowReorder?: boolean;
87
289
  /** Words for refusing a drop into a node whose `acceptsDrop` is false. */
@@ -115,18 +317,95 @@ export interface FileTreeProps {
115
317
  * consumer owns the confirmation — this only reports which row the key was pressed on.
116
318
  */
117
319
  onDelete?: (node: FileTreeNode) => void;
320
+ /**
321
+ * The same keys on a SELECTION of more than one — *"hold command and click delete to
322
+ * delete all the selected files"*.
323
+ *
324
+ * Without it, ⌫ on a multi-selection deletes the focused row alone, which is today's
325
+ * behaviour and a consumer that never asked for the plural keeps it. The nodes are handed
326
+ * over rather than the ids, because a confirmation dialog needs their names.
327
+ */
328
+ onDeleteMany?: (nodes: readonly FileTreeNode[]) => void;
329
+ /**
330
+ * ⌘C then ⌘V — *"Copying should allow copying in another folder or the same folder."*
331
+ *
332
+ * Wiring it is what puts ⌘C on the keyboard at all. `drop` is where the paste landed,
333
+ * resolved by exactly the rules a drag obeys, so a copy can never reach a destination the
334
+ * pointer refuses.
335
+ */
336
+ onCopy?: (nodeIds: readonly string[], drop: FileTreeDrop) => void;
337
+ /**
338
+ * The consumer's veto on copying a row — words to refuse with, `null` to allow. The same
339
+ * shape as {@link canDrop}, and consulted BEFORE ⌘C takes, never after ⌘V.
340
+ *
341
+ * 🔴 Copy is the one clipboard verb that is not universal. A folder in `apps/music` is
342
+ * derived from the `folder` column of the tracks in it, so copying one would mean
343
+ * duplicating audio there is exactly one copy of — the tree must not assume every consumer
344
+ * can paste. See `refuseCopy`.
345
+ */
346
+ canCopy?: (node: FileTreeNode) => string | null;
118
347
  /**
119
348
  * A row being named that does not exist yet — the "New folder" gesture.
120
349
  *
121
350
  * 🔴 Nothing is created until the name is committed. A folder made first and renamed
122
351
  * afterwards is a row that spent a moment called "untitled", and a row in the way if the
123
352
  * field is cancelled.
353
+ *
354
+ * `wrap` is the same draft asked to close AROUND rows that already exist — *"allow make
355
+ * dir from file where dir wraps file location and gets name of file"*, which is Finder's
356
+ * **New Folder with Selection** and VS Code's ⌃⌘N. It is the ids to re-file into the
357
+ * folder the moment it is made; the field opens pre-filled with the first one's name minus
358
+ * its extension, and {@link onWrapStart} is how the tree ASKS for one of these.
124
359
  */
125
360
  draft?: {
126
361
  parentId: string | null;
362
+ wrap?: readonly string[];
127
363
  } | null;
128
- onDraftCommit?: (name: string) => void;
364
+ /**
365
+ * The name was committed: make the folder.
366
+ *
367
+ * 🔴 `wrap` arrives in the SAME call, never as a second one, and that is the whole reason
368
+ * this prop grew an argument instead of a sibling callback being added beside it. "Create
369
+ * the folder, then move four things into it" is two round trips, and the failure mode of
370
+ * the second one is a folder that exists, is empty, is named after a file that is still
371
+ * sitting outside it, and that nobody asked for. One call is one transaction on the
372
+ * consumer's side, which is where the only transaction can be.
373
+ *
374
+ * It is absent — not empty — for an ordinary new folder, so a consumer written before this
375
+ * existed is called exactly as it always was. The ids are pruned of rows inside other
376
+ * wrapped rows and are in tree order; see `planWrap`.
377
+ */
378
+ onDraftCommit?: (name: string, wrap?: readonly string[]) => void;
129
379
  onDraftCancel?: () => void;
380
+ /**
381
+ * **⌃⌘N** on a focused row — *make a folder around this*. Wiring it is what puts the chord
382
+ * on the keyboard at all, and a consumer that does not is byte-for-byte unchanged.
383
+ *
384
+ * The tree does not own `draft`, so it cannot open one itself: this is the same shape
385
+ * {@link onRenameStart} has, and the answer to it is to set
386
+ * `draft = { parentId, wrap: nodes.map(n => n.id) }`. Both arguments are already worked
387
+ * out — the nodes in tree order with descendants of other chosen rows dropped, and the
388
+ * parent of the first of them, which is *where the file already is*. Wire a row menu's own
389
+ * "New folder with selection" to the SAME call so the two cannot drift.
390
+ *
391
+ * On a selection of more than one it is the whole selection (Finder's rule); on a row
392
+ * outside the selection it is that row alone, which is the rule every other plural gesture
393
+ * here already follows.
394
+ */
395
+ onWrapStart?: (nodes: readonly FileTreeNode[], parentId: string | null) => void;
396
+ /**
397
+ * The consumer's veto on wrapping a row — words to refuse with, `null` to allow. The twin
398
+ * of {@link canCopy}, consulted BEFORE the draft opens.
399
+ *
400
+ * 🔴 A tree whose folders are DERIVED has nothing to wrap with. `apps/music` builds a
401
+ * folder out of the `folder` column of the tracks inside it, so there is no folder for this
402
+ * gesture to make — and offering a name field that cannot be honoured is the "control that
403
+ * fails on commit" this component refuses everywhere else. See `refuseWrap`.
404
+ *
405
+ * A row the consumer marked `draggable: false` is refused without asking, because wrapping
406
+ * it MOVES it — the same all-or-nothing rule `beginDrag` holds.
407
+ */
408
+ canWrap?: (node: FileTreeNode) => string | null;
130
409
  /**
131
410
  * The focused row's own menu, reached by **Shift+F10** or the **Menu** key — the keyboard's
132
411
  * route to whatever {@link renderTools} draws.
@@ -179,5 +458,5 @@ export interface FileTreeProps {
179
458
  ariaLabel?: string;
180
459
  className?: string;
181
460
  }
182
- export declare function FileTree({ nodes, selectedId, onSelect, onOpen, collapsed: collapsedProp, onCollapsedChange, onMove, allowReorder, lockedReason, canDrop, unavailableReason, renamingId, onRenameStart, onRename, onRenameCancel, onDelete, draft, onDraftCommit, onDraftCancel, onRowMenu, renderTools, renderIcon, renderAccessory, onDropExternal, acceptsExternalDrag, accessCopy, filter, empty, ariaLabel, className, }: FileTreeProps): JSX.Element;
461
+ export declare function FileTree({ nodes, selectedId, onSelect, selectedIds, onSelectionChange, onOpen, collapsed: collapsedProp, onCollapsedChange, expandOnReveal, onRevealExpand, pinnedIds, onPinnedChange, pinnedLabel, onMove, onMoveMany, allowReorder, lockedReason, canDrop, unavailableReason, renamingId, onRenameStart, onRename, onRenameCancel, onDelete, onDeleteMany, onCopy, canCopy, draft, onDraftCommit, onDraftCancel, onWrapStart, canWrap, onRowMenu, renderTools, renderIcon, renderAccessory, onDropExternal, acceptsExternalDrag, accessCopy, filter, empty, ariaLabel, className, }: FileTreeProps): JSX.Element;
183
462
  //# sourceMappingURL=FileTree.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"FileTree.d.ts","sourceRoot":"","sources":["../../../src/react/file-tree/FileTree.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AACH,OAAO,KAAK,EACV,SAAS,IAAI,cAAc,EAC3B,GAAG,EAEH,SAAS,EACV,MAAM,OAAO,CAAC;AAEf,OAAO,EACL,KAAK,cAAc,EAKnB,KAAK,YAAY,EACjB,KAAK,YAAY,EAOlB,MAAM,2CAA2C,CAAC;AAGnD,OAAO,gBAAgB,CAAC;AA2CxB,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,SAAS,YAAY,EAAE,CAAC;IAC/B,2EAA2E;IAC3E,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IACvC;;;;OAIG;IACH,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IAEtC,gFAAgF;IAChF,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAE7C;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IACtD,sFAAsF;IACtF,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0FAA0F;IAC1F,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,YAAY,KAAK,MAAM,GAAG,IAAI,CAAC;IACzE,kFAAkF;IAClF,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B,2FAA2F;IAC3F,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IAC7C,sCAAsC;IACtC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACtD,6DAA6D;IAC7D,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IAExC;;;;;;OAMG;IACH,KAAK,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IAC3C,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAE3B;;;;;;;;;;;;;OAaG;IACH,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAEvE,oFAAoF;IACpF,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,SAAS,CAAC;IAChD,qDAAqD;IACrD,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,SAAS,CAAC;IAC/C,2DAA2D;IAC3D,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,SAAS,CAAC;IAEpD;;;;;OAKG;IACH,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,EAAE,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IAC5E,oFAAoF;IACpF,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,OAAO,CAAC;IAEzD;;;;;;;;;;OAUG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC;IAE5B,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2FAA2F;IAC3F,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA0ED,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,UAAU,EACV,QAAQ,EACR,MAAM,EACN,SAAS,EAAE,aAAa,EACxB,iBAAiB,EACjB,MAAM,EACN,YAAoB,EACpB,YAAY,EACZ,OAAO,EACP,iBAAiB,EACjB,UAAU,EACV,aAAa,EACb,QAAQ,EACR,cAAc,EACd,QAAQ,EACR,KAAK,EACL,aAAa,EACb,aAAa,EACb,SAAS,EACT,WAAW,EACX,UAAU,EACV,eAAe,EACf,cAAc,EACd,mBAAmB,EACnB,UAAU,EACV,MAAM,EACN,KAA2B,EAC3B,SAAmB,EACnB,SAAS,GACV,EAAE,aAAa,GAAG,GAAG,CAAC,OAAO,CA++B7B"}
1
+ {"version":3,"file":"FileTree.d.ts","sourceRoot":"","sources":["../../../src/react/file-tree/FileTree.tsx"],"names":[],"mappings":"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"}