cursedbelt 3.0.1 → 4.0.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.
- package/dist/react/components/ListToolbar.d.ts +94 -0
- package/dist/react/components/ListToolbar.d.ts.map +1 -0
- package/dist/react/components/ListToolbar.js +66 -0
- package/dist/react/components/ListToolbar.js.map +1 -0
- package/dist/react/file-tree/FileTree.d.ts +282 -3
- package/dist/react/file-tree/FileTree.d.ts.map +1 -1
- package/dist/react/file-tree/FileTree.js +799 -131
- package/dist/react/file-tree/FileTree.js.map +1 -1
- package/dist/react/file-tree/fileTree.css +151 -0
- package/dist/react/file-tree/fileTreeSelection.d.ts +187 -0
- package/dist/react/file-tree/fileTreeSelection.d.ts.map +1 -0
- package/dist/react/file-tree/fileTreeSelection.js +264 -0
- package/dist/react/file-tree/fileTreeSelection.js.map +1 -0
- package/dist/react/file-tree/index.d.ts +1 -0
- package/dist/react/file-tree/index.d.ts.map +1 -1
- package/dist/react/file-tree/index.js +8 -0
- package/dist/react/file-tree/index.js.map +1 -1
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +1 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/lib/form.d.ts +3 -0
- package/dist/react/lib/form.d.ts.map +1 -1
- package/dist/react/lib/form.js +43 -5
- package/dist/react/lib/form.js.map +1 -1
- package/dist/react/media/DenseGalleryShell.d.ts +19 -3
- package/dist/react/media/DenseGalleryShell.d.ts.map +1 -1
- package/dist/react/media/DenseGalleryShell.js +55 -15
- package/dist/react/media/DenseGalleryShell.js.map +1 -1
- package/dist/react/media/FocusViewer.d.ts.map +1 -1
- package/dist/react/media/FocusViewer.js +14 -14
- package/dist/react/media/FocusViewer.js.map +1 -1
- package/dist/react/media/FrameGrabber.d.ts +23 -0
- package/dist/react/media/FrameGrabber.d.ts.map +1 -1
- package/dist/react/media/FrameGrabber.js +67 -1
- package/dist/react/media/FrameGrabber.js.map +1 -1
- package/dist/react/media/MediaResize.d.ts +51 -19
- package/dist/react/media/MediaResize.d.ts.map +1 -1
- package/dist/react/media/MediaResize.js +20 -13
- package/dist/react/media/MediaResize.js.map +1 -1
- package/dist/react/media/VideoChapterEditor.d.ts +67 -0
- package/dist/react/media/VideoChapterEditor.d.ts.map +1 -0
- package/dist/react/media/VideoChapterEditor.js +148 -0
- package/dist/react/media/VideoChapterEditor.js.map +1 -0
- package/dist/react/media/VideoPlayer.d.ts +46 -4
- package/dist/react/media/VideoPlayer.d.ts.map +1 -1
- package/dist/react/media/VideoPlayer.js +124 -11
- package/dist/react/media/VideoPlayer.js.map +1 -1
- package/dist/react/media/VideoTurner.d.ts +15 -2
- package/dist/react/media/VideoTurner.d.ts.map +1 -1
- package/dist/react/media/VideoTurner.js +29 -2
- package/dist/react/media/VideoTurner.js.map +1 -1
- package/dist/react/media/denseGallery.css +104 -46
- package/dist/react/media/hlsSource.d.ts +57 -0
- package/dist/react/media/hlsSource.d.ts.map +1 -1
- package/dist/react/media/hlsSource.js +177 -1
- package/dist/react/media/hlsSource.js.map +1 -1
- package/dist/react/media/index.d.ts +2 -0
- package/dist/react/media/index.d.ts.map +1 -1
- package/dist/react/media/index.js +6 -0
- package/dist/react/media/index.js.map +1 -1
- package/dist/react/media/mediaResize.css +127 -54
- package/dist/react/media/pictureEditor.css +52 -6
- package/dist/react/media/videoChapterEditor.css +203 -0
- package/dist/react/media/videoChapters.d.ts +117 -0
- package/dist/react/media/videoChapters.d.ts.map +1 -0
- package/dist/react/media/videoChapters.js +95 -0
- package/dist/react/media/videoChapters.js.map +1 -0
- package/dist/react/media-gallery/GalleryTable.d.ts +52 -1
- package/dist/react/media-gallery/GalleryTable.d.ts.map +1 -1
- package/dist/react/media-gallery/GalleryTable.js +40 -9
- package/dist/react/media-gallery/GalleryTable.js.map +1 -1
- package/dist/react/media-gallery/MediaGallery.d.ts +107 -4
- package/dist/react/media-gallery/MediaGallery.d.ts.map +1 -1
- package/dist/react/media-gallery/MediaGallery.js +484 -104
- package/dist/react/media-gallery/MediaGallery.js.map +1 -1
- package/dist/react/media-gallery/MediaMetaEditor.d.ts +48 -0
- package/dist/react/media-gallery/MediaMetaEditor.d.ts.map +1 -0
- package/dist/react/media-gallery/MediaMetaEditor.js +66 -0
- package/dist/react/media-gallery/MediaMetaEditor.js.map +1 -0
- package/dist/react/media-gallery/index.d.ts +2 -0
- package/dist/react/media-gallery/index.d.ts.map +1 -1
- package/dist/react/media-gallery/index.js +5 -0
- package/dist/react/media-gallery/index.js.map +1 -1
- package/dist/react/media-gallery/mediaGallery.css +180 -4
- package/dist/react/media-gallery/mediaMeta.d.ts +62 -0
- package/dist/react/media-gallery/mediaMeta.d.ts.map +1 -0
- package/dist/react/media-gallery/mediaMeta.js +56 -0
- package/dist/react/media-gallery/mediaMeta.js.map +1 -0
- package/dist/react/media-gallery/playbackPreferences.d.ts +77 -0
- package/dist/react/media-gallery/playbackPreferences.d.ts.map +1 -1
- package/dist/react/media-gallery/playbackPreferences.js +42 -0
- package/dist/react/media-gallery/playbackPreferences.js.map +1 -1
- package/dist/react/media-gallery/types.d.ts +34 -0
- package/dist/react/media-gallery/types.d.ts.map +1 -1
- package/dist/scripts/guardrailsEnforce.d.ts +29 -0
- package/dist/scripts/guardrailsEnforce.d.ts.map +1 -0
- package/dist/styles-areas/activity-bar.css +30 -0
- package/dist/styles-areas/analytics.css +672 -0
- package/dist/styles-areas/auth.css +154 -0
- package/dist/styles-areas/calendar.css +416 -0
- package/dist/styles-areas/chart-table.css +41 -0
- package/dist/styles-areas/charts.css +89 -0
- package/dist/styles-areas/chat.css +364 -0
- package/dist/styles-areas/clipboard.css +26 -0
- package/dist/styles-areas/code-editor.css +130 -0
- package/dist/styles-areas/companion-link.css +70 -0
- package/dist/styles-areas/core.css +2503 -0
- package/dist/styles-areas/dashboard-grid.css +84 -0
- package/dist/styles-areas/data-table.css +657 -0
- package/dist/styles-areas/deep-link.css +26 -0
- package/dist/styles-areas/diff-viewer.css +30 -0
- package/dist/styles-areas/disk-usage.css +262 -0
- package/dist/styles-areas/emoji.css +154 -0
- package/dist/styles-areas/fields.css +582 -0
- package/dist/styles-areas/file-tree.css +33 -0
- package/dist/styles-areas/filter-rail.css +235 -0
- package/dist/styles-areas/folder-tree.css +219 -0
- package/dist/styles-areas/keep-awake.css +26 -0
- package/dist/styles-areas/layout-engine.css +239 -0
- package/dist/styles-areas/markdown.css +87 -0
- package/dist/styles-areas/master-detail.css +239 -0
- package/dist/styles-areas/master-lock.css +26 -0
- package/dist/styles-areas/media-gallery.css +351 -0
- package/dist/styles-areas/media.css +658 -0
- package/dist/styles-areas/nav.css +488 -0
- package/dist/styles-areas/notifications.css +245 -0
- package/dist/styles-areas/overlays.css +133 -0
- package/dist/styles-areas/palette.css +140 -0
- package/dist/styles-areas/pdf-viewer.css +47 -0
- package/dist/styles-areas/problem-detail.css +97 -0
- package/dist/styles-areas/rich-text.css +167 -0
- package/dist/styles-areas/sharing.css +153 -0
- package/dist/styles-areas/spreadsheet.css +37 -0
- package/dist/styles-areas/stats.css +281 -0
- package/dist/styles-areas/test-report.css +133 -0
- package/dist/styles-areas/virtual.css +30 -0
- package/dist/styles-areas/wizard.css +381 -0
- package/dist/styles-areas/workbench.css +543 -0
- package/dist/styles-areas/workbook-viewer.css +44 -0
- package/dist/styles-static.css +34 -23
- package/dist/styles.css +34 -23
- package/package.json +288 -202
- package/scripts/checkDistExports.ts +67 -0
- package/scripts/cssRules.ts +125 -0
- package/scripts/fixtureAppCss.ts +344 -0
- package/scripts/generateAreaStyles.ts +253 -0
- package/scripts/guardrailsEnforce.spec.ts +62 -0
- package/scripts/guardrailsEnforce.ts +67 -3
- package/scripts/styleAreas.ts +585 -0
- package/scripts/verify.ts +5 -0
- package/src/barrelsReachNoOptionalPeer.spec.ts +117 -9
- package/src/docsMatchTheSplit.spec.ts +98 -0
- package/src/fixtureAppCss.spec.ts +177 -0
- package/src/namedSubpathsResolve.spec.ts +35 -0
- package/src/publishShape.spec.ts +67 -0
- package/src/react/components/ListToolbar.spec.tsx +172 -0
- package/src/react/components/ListToolbar.tsx +151 -0
- package/src/react/file-tree/FileTree.spec.tsx +1009 -0
- package/src/react/file-tree/FileTree.tsx +1459 -309
- package/src/react/file-tree/fileTree.css +151 -0
- package/src/react/file-tree/fileTreeSelection.spec.ts +327 -0
- package/src/react/file-tree/fileTreeSelection.ts +321 -0
- package/src/react/file-tree/index.ts +21 -0
- package/src/react/index.ts +1 -0
- package/src/react/lib/form.ts +51 -8
- package/src/react/media/DenseGalleryShell.tsx +108 -36
- package/src/react/media/FocusViewer.tsx +15 -8
- package/src/react/media/FrameGrabber.spec.tsx +175 -0
- package/src/react/media/FrameGrabber.tsx +67 -0
- package/src/react/media/MediaResize.spec.tsx +84 -14
- package/src/react/media/MediaResize.tsx +51 -19
- package/src/react/media/VideoChapterEditor.tsx +428 -0
- package/src/react/media/VideoPlayer.spec.tsx +111 -1
- package/src/react/media/VideoPlayer.tsx +158 -11
- package/src/react/media/VideoTurner.spec.tsx +67 -0
- package/src/react/media/VideoTurner.tsx +70 -15
- package/src/react/media/denseGallery.css +104 -46
- package/src/react/media/hlsSource.recovery.spec.ts +213 -0
- package/src/react/media/hlsSource.spec.ts +8 -0
- package/src/react/media/hlsSource.ts +189 -1
- package/src/react/media/index.ts +14 -0
- package/src/react/media/mediaResize.css +127 -54
- package/src/react/media/pictureEditor.css +52 -6
- package/src/react/media/videoChapterEditor.css +203 -0
- package/src/react/media/videoChapters.spec.ts +176 -0
- package/src/react/media/videoChapters.ts +181 -0
- package/src/react/media-gallery/GalleryTable.tsx +93 -3
- package/src/react/media-gallery/MediaGallery.spec.tsx +1156 -10
- package/src/react/media-gallery/MediaGallery.tsx +813 -95
- package/src/react/media-gallery/MediaMetaEditor.tsx +237 -0
- package/src/react/media-gallery/galleryTable.spec.ts +41 -1
- package/src/react/media-gallery/index.ts +7 -0
- package/src/react/media-gallery/mediaGallery.css +180 -4
- package/src/react/media-gallery/mediaMeta.spec.ts +95 -0
- package/src/react/media-gallery/mediaMeta.ts +101 -0
- package/src/react/media-gallery/playbackPreferences.spec.ts +172 -0
- package/src/react/media-gallery/playbackPreferences.ts +100 -0
- package/src/react/media-gallery/types.ts +34 -0
- package/src/shippedFilesAreTracked.spec.ts +69 -0
- package/src/styles-areas/activity-bar.css +30 -0
- package/src/styles-areas/analytics.css +672 -0
- package/src/styles-areas/auth.css +154 -0
- package/src/styles-areas/calendar.css +416 -0
- package/src/styles-areas/chart-table.css +41 -0
- package/src/styles-areas/charts.css +89 -0
- package/src/styles-areas/chat.css +364 -0
- package/src/styles-areas/clipboard.css +26 -0
- package/src/styles-areas/code-editor.css +130 -0
- package/src/styles-areas/companion-link.css +70 -0
- package/src/styles-areas/core.css +2503 -0
- package/src/styles-areas/dashboard-grid.css +84 -0
- package/src/styles-areas/data-table.css +657 -0
- package/src/styles-areas/deep-link.css +26 -0
- package/src/styles-areas/diff-viewer.css +30 -0
- package/src/styles-areas/disk-usage.css +262 -0
- package/src/styles-areas/emoji.css +154 -0
- package/src/styles-areas/fields.css +582 -0
- package/src/styles-areas/file-tree.css +33 -0
- package/src/styles-areas/filter-rail.css +235 -0
- package/src/styles-areas/folder-tree.css +219 -0
- package/src/styles-areas/keep-awake.css +26 -0
- package/src/styles-areas/layout-engine.css +239 -0
- package/src/styles-areas/markdown.css +87 -0
- package/src/styles-areas/master-detail.css +239 -0
- package/src/styles-areas/master-lock.css +26 -0
- package/src/styles-areas/media-gallery.css +351 -0
- package/src/styles-areas/media.css +658 -0
- package/src/styles-areas/nav.css +488 -0
- package/src/styles-areas/notifications.css +245 -0
- package/src/styles-areas/overlays.css +133 -0
- package/src/styles-areas/palette.css +140 -0
- package/src/styles-areas/pdf-viewer.css +47 -0
- package/src/styles-areas/problem-detail.css +97 -0
- package/src/styles-areas/rich-text.css +167 -0
- package/src/styles-areas/sharing.css +153 -0
- package/src/styles-areas/spreadsheet.css +37 -0
- package/src/styles-areas/stats.css +281 -0
- package/src/styles-areas/test-report.css +133 -0
- package/src/styles-areas/virtual.css +30 -0
- package/src/styles-areas/wizard.css +381 -0
- package/src/styles-areas/workbench.css +543 -0
- package/src/styles-areas/workbook-viewer.css +44 -0
- package/src/styles-static.css +34 -23
- package/src/styles.css +34 -23
- package/src/stylesAreas.spec.ts +247 -0
- package/src/stylesUtilitiesMatches.spec.ts +6 -77
- package/src/testFilesRunInParallel.spec.ts +79 -0
- package/src/typecheckCachesAreSeparate.spec.ts +128 -0
- 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
|
|
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
|
-
|
|
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
|
|
1
|
+
{"version":3,"file":"FileTree.d.ts","sourceRoot":"","sources":["../../../src/react/file-tree/FileTree.tsx"],"names":[],"mappings":"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"}
|