@fgv/ts-app-shell 5.1.0-4 → 5.1.0-6

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.
@@ -27,31 +27,123 @@
27
27
  *
28
28
  * @packageDocumentation
29
29
  */
30
- import React, { useCallback, useState } from 'react';
30
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
31
+ import { StarIcon as StarIconOutline } from '@heroicons/react/24/outline';
32
+ import { StarIcon as StarIconSolid, ExclamationTriangleIcon, BuildingLibraryIcon, ShieldCheckIcon, ShieldExclamationIcon, ArrowDownTrayIcon, PencilSquareIcon, ArrowsPointingInIcon, TrashIcon, FolderPlusIcon, ArchiveBoxArrowDownIcon, FolderOpenIcon, ArrowUpTrayIcon, PlusIcon, ChevronRightIcon } from '@heroicons/react/20/solid';
33
+ // ============================================================================
34
+ // Context Menu (internal)
35
+ // ============================================================================
36
+ const LONG_PRESS_MS = 500;
37
+ function CollectionContextMenu(props) {
38
+ const { menu, isHidden, onHide, onShow, onClose } = props;
39
+ const ref = useRef(null);
40
+ useEffect(() => {
41
+ function handleClickOutside(e) {
42
+ if (ref.current && !ref.current.contains(e.target)) {
43
+ onClose();
44
+ }
45
+ }
46
+ document.addEventListener('mousedown', handleClickOutside);
47
+ return () => document.removeEventListener('mousedown', handleClickOutside);
48
+ }, [onClose]);
49
+ const action = isHidden ? onShow : onHide;
50
+ if (!action) {
51
+ return React.createElement(React.Fragment, null);
52
+ }
53
+ return (React.createElement("div", { ref: ref, className: "fixed z-50 bg-surface-raised border border-border rounded shadow-lg py-1 min-w-[140px]", style: { left: menu.x, top: menu.y } },
54
+ React.createElement("button", { className: "w-full text-left px-3 py-1.5 text-sm text-secondary hover:bg-hover transition-colors", onClick: () => {
55
+ action(menu.collectionId);
56
+ onClose();
57
+ } }, isHidden ? 'Show collection' : 'Hide collection')));
58
+ }
59
+ // ============================================================================
60
+ // Long-press hook (internal)
61
+ // ============================================================================
62
+ function useLongPress(onLongPress) {
63
+ const timerRef = useRef(undefined);
64
+ const firedRef = useRef(false);
65
+ const onTouchStart = useCallback((e) => {
66
+ firedRef.current = false;
67
+ timerRef.current = setTimeout(() => {
68
+ firedRef.current = true;
69
+ onLongPress(e);
70
+ }, LONG_PRESS_MS);
71
+ }, [onLongPress]);
72
+ const cancel = useCallback(() => {
73
+ if (timerRef.current !== undefined) {
74
+ clearTimeout(timerRef.current);
75
+ timerRef.current = undefined;
76
+ }
77
+ }, []);
78
+ return { onTouchStart, onTouchEnd: cancel, onTouchMove: cancel };
79
+ }
31
80
  // ============================================================================
32
81
  // CollectionRow (internal)
33
82
  // ============================================================================
34
83
  function CollectionRow(props) {
35
- var _a, _b, _c, _d;
36
- const { collection, onToggleVisibility, onSetDefault, onDelete, onExport, onUnlock, onRename, onMerge } = props;
84
+ var _a;
85
+ const { collection, onToggleVisibility, onSetDefault, onDelete, onExport, onUnlock, onRename, onMerge, borderColorClass, onContextMenu, isHiddenRow } = props;
37
86
  const displayName = (_a = collection.name) !== null && _a !== void 0 ? _a : collection.id;
38
- return (React.createElement("div", { className: `flex items-center gap-1.5 px-3 py-1.5 text-sm transition-colors hover:bg-hover ${collection.isVisible ? 'text-secondary' : 'text-muted'}` },
39
- React.createElement("button", { onClick: () => onToggleVisibility(collection.id), className: "shrink-0 w-5 h-5 flex items-center justify-center text-xs hover:text-brand-accent transition-colors", title: collection.isVisible ? 'Hide collection' : 'Show collection', "aria-label": `${collection.isVisible ? 'Hide' : 'Show'} ${displayName}` }, collection.isVisible ? '\u{1F441}' : '\u{1F441}\u{FE0F}\u{200D}\u{1F5E8}\u{FE0F}'),
40
- onSetDefault && collection.isMutable && (React.createElement("button", { onClick: () => onSetDefault(collection.id), className: `shrink-0 w-5 h-5 flex items-center justify-center transition-colors ${collection.isDefault ? 'text-star hover:text-star' : 'text-faint hover:text-star'}`, title: collection.isDefault
41
- ? 'Default collection for new items'
42
- : 'Set as default collection for new items', "aria-label": collection.isDefault
43
- ? `${(_b = collection.name) !== null && _b !== void 0 ? _b : collection.id} is default`
44
- : `Set ${(_c = collection.name) !== null && _c !== void 0 ? _c : collection.id} as default`, "aria-pressed": collection.isDefault }, collection.isDefault ? '★' : '☆')),
45
- collection.hasConflict && (React.createElement("span", { className: "shrink-0 text-xs text-status-warning-strong cursor-default", title: "An encrypted copy of this collection from another storage root has the same ID. Go to Settings \u2192 Storage to resolve the conflict.", "aria-label": `Conflict: encrypted shadow for ${displayName}` }, '⚠')),
46
- !collection.isMutable && (React.createElement("span", { className: "shrink-0 text-xs text-muted", title: "Built-in collection (read-only)" }, '\uD83D\uDD12')),
47
- collection.isProtected &&
48
- (collection.isUnlocked || !onUnlock ? (React.createElement("span", { className: `shrink-0 text-xs ${collection.isUnlocked ? 'text-status-success-icon' : 'text-muted'}`, title: collection.isUnlocked ? 'Protected (unlocked)' : 'Protected (locked)' }, '\uD83D\uDEE1')) : (React.createElement("button", { onClick: () => onUnlock(collection.id), className: "shrink-0 text-xs text-muted hover:text-star transition-colors", title: "Click to unlock", "aria-label": `Unlock ${(_d = collection.name) !== null && _d !== void 0 ? _d : collection.id}` }, '\uD83D\uDEE1'))),
49
- React.createElement("span", { className: "flex-1 truncate", title: displayName }, displayName),
50
- React.createElement("span", { className: "shrink-0 text-xs text-muted" }, collection.itemCount),
51
- collection.isMutable && onExport && (React.createElement("button", { onClick: () => onExport(collection.id), className: "shrink-0 w-5 h-5 flex items-center justify-center text-xs text-faint hover:text-brand-accent transition-colors", title: `Export ${displayName}`, "aria-label": `Export ${displayName}` }, "\u2193")),
52
- collection.isMutable && onRename && (React.createElement("button", { onClick: () => onRename(collection.id), className: "shrink-0 w-5 h-5 flex items-center justify-center text-xs text-faint hover:text-brand-accent transition-colors", title: `Rename ${displayName}`, "aria-label": `Rename ${displayName}` }, '✎')),
53
- collection.isMutable && onMerge && (React.createElement("button", { onClick: () => onMerge(collection.id), className: "shrink-0 w-5 h-5 flex items-center justify-center text-xs text-faint hover:text-brand-accent transition-colors", title: `Merge ${displayName} into another collection`, "aria-label": `Merge ${displayName}` }, '⤵')),
54
- collection.isMutable && onDelete && (React.createElement("button", { onClick: () => onDelete(collection.id), className: "shrink-0 w-5 h-5 flex items-center justify-center text-xs text-faint hover:text-status-error-icon transition-colors", title: `Remove ${displayName}`, "aria-label": `Remove ${displayName}` }, '×'))));
87
+ const handleContextMenu = useCallback((e) => {
88
+ if (onContextMenu) {
89
+ e.preventDefault();
90
+ onContextMenu(collection.id, e.clientX, e.clientY);
91
+ }
92
+ }, [onContextMenu, collection.id]);
93
+ const handleLongPress = useCallback((e) => {
94
+ if (onContextMenu && e.touches.length > 0) {
95
+ e.preventDefault();
96
+ const touch = e.touches[0];
97
+ onContextMenu(collection.id, touch.clientX, touch.clientY);
98
+ }
99
+ }, [onContextMenu, collection.id]);
100
+ const longPress = useLongPress(handleLongPress);
101
+ return (React.createElement("div", Object.assign({ onClick: () => onToggleVisibility(collection.id), onContextMenu: handleContextMenu }, longPress, { className: `flex flex-col px-3 py-2.5 text-sm transition-colors hover:bg-hover cursor-pointer ${isHiddenRow
102
+ ? 'text-muted opacity-30 line-through'
103
+ : collection.isVisible
104
+ ? 'text-secondary'
105
+ : 'text-muted opacity-50'} ${borderColorClass ? `border-l-4 ${borderColorClass}` : ''}`, role: "button", "aria-pressed": collection.isVisible, title: collection.sourceName ? `Source: ${collection.sourceName}` : displayName }),
106
+ React.createElement("div", { className: "flex items-center gap-1.5" },
107
+ onSetDefault && collection.isMutable && (React.createElement("button", { onClick: (e) => {
108
+ e.stopPropagation();
109
+ onSetDefault(collection.id);
110
+ }, className: `shrink-0 w-5 h-5 flex items-center justify-center transition-colors ${collection.isDefault ? 'text-star hover:text-star' : 'text-faint hover:text-star'}`, title: collection.isDefault
111
+ ? 'Default collection for new items'
112
+ : 'Set as default collection for new items', "aria-label": collection.isDefault ? `${displayName} is default` : `Set ${displayName} as default`, "aria-pressed": collection.isDefault }, collection.isDefault ? (React.createElement(StarIconSolid, { className: "w-4 h-4" })) : (React.createElement(StarIconOutline, { className: "w-4 h-4" })))),
113
+ collection.hasConflict && (React.createElement("span", { className: "shrink-0 text-status-warning-strong cursor-default", title: "An encrypted copy of this collection from another storage root has the same ID. Go to Settings \u2192 Storage to resolve the conflict.", "aria-label": `Conflict: encrypted shadow for ${displayName}` },
114
+ React.createElement(ExclamationTriangleIcon, { className: "w-4 h-4" }))),
115
+ !collection.isMutable && (React.createElement("span", { className: "shrink-0 text-muted", title: "Built-in collection (read-only)" },
116
+ React.createElement(BuildingLibraryIcon, { className: "w-4 h-4" }))),
117
+ collection.isProtected &&
118
+ (collection.isUnlocked || !onUnlock ? (React.createElement("span", { className: `shrink-0 ${collection.isUnlocked ? 'text-status-success-icon' : 'text-muted'}`, title: collection.isUnlocked ? 'Protected (unlocked)' : 'Protected (locked)' }, collection.isUnlocked ? (React.createElement(ShieldCheckIcon, { className: "w-4 h-4" })) : (React.createElement(ShieldExclamationIcon, { className: "w-4 h-4" })))) : (React.createElement("button", { onClick: (e) => {
119
+ e.stopPropagation();
120
+ onUnlock(collection.id);
121
+ }, className: "shrink-0 text-muted hover:text-star transition-colors", title: "Click to unlock", "aria-label": `Unlock ${displayName}` },
122
+ React.createElement(ShieldExclamationIcon, { className: "w-4 h-4" })))),
123
+ React.createElement("span", { className: "flex-1 truncate", title: displayName }, displayName),
124
+ React.createElement("span", { className: "shrink-0 text-xs text-muted" }, collection.itemCount)),
125
+ React.createElement("div", { className: "flex items-center gap-1 mt-1" },
126
+ collection.isMutable && onExport && (React.createElement("button", { onClick: (e) => {
127
+ e.stopPropagation();
128
+ onExport(collection.id);
129
+ }, className: "shrink-0 w-5 h-5 flex items-center justify-center text-faint hover:text-brand-accent transition-colors", title: `Export ${displayName}`, "aria-label": `Export ${displayName}` },
130
+ React.createElement(ArrowDownTrayIcon, { className: "w-4 h-4" }))),
131
+ collection.isMutable && onRename && (React.createElement("button", { onClick: (e) => {
132
+ e.stopPropagation();
133
+ onRename(collection.id);
134
+ }, className: "shrink-0 w-5 h-5 flex items-center justify-center text-faint hover:text-brand-accent transition-colors", title: `Rename ${displayName}`, "aria-label": `Rename ${displayName}` },
135
+ React.createElement(PencilSquareIcon, { className: "w-4 h-4" }))),
136
+ collection.isMutable && onMerge && (React.createElement("button", { onClick: (e) => {
137
+ e.stopPropagation();
138
+ onMerge(collection.id);
139
+ }, className: "shrink-0 w-5 h-5 flex items-center justify-center text-faint hover:text-brand-accent transition-colors", title: `Merge ${displayName} into another collection`, "aria-label": `Merge ${displayName}` },
140
+ React.createElement(ArrowsPointingInIcon, { className: "w-4 h-4" }))),
141
+ collection.isMutable && onDelete && (React.createElement("button", { onClick: (e) => {
142
+ e.stopPropagation();
143
+ onDelete(collection.id);
144
+ }, className: "shrink-0 w-5 h-5 flex items-center justify-center text-faint hover:text-status-error-icon transition-colors", title: `Remove ${displayName}`, "aria-label": `Remove ${displayName}` },
145
+ React.createElement(TrashIcon, { className: "w-4 h-4" }))),
146
+ !collection.isMutable && React.createElement("span", { className: "text-xs text-muted" }, "(built-in)"))));
55
147
  }
56
148
  // ============================================================================
57
149
  // CollectionSection
@@ -65,43 +157,65 @@ function CollectionRow(props) {
65
157
  * @public
66
158
  */
67
159
  export function CollectionSection(props) {
68
- const { collections, onToggleVisibility, onToggleAllVisibility, onAddDirectory, onCreateCollection, onDeleteCollection, onSetDefaultCollection, onExportCollection, onExportAllAsZip, onImportCollection, onOpenCollectionFromFile, onUnlockCollection, onRenameCollection, onMergeCollection, defaultCollapsed = false } = props;
160
+ var _a;
161
+ const { collections, onToggleVisibility, onAddDirectory, onCreateCollection, onDeleteCollection, onSetDefaultCollection, onExportCollection, onExportAllAsZip, onImportCollection, onOpenCollectionFromFile, onUnlockCollection, onRenameCollection, onMergeCollection, onHideCollection, onShowCollection, defaultCollapsed = false, sourceColorMap, sourceColorFallback } = props;
69
162
  const [collapsed, setCollapsed] = useState(defaultCollapsed);
70
- const allVisible = collections.length > 0 && collections.every((c) => c.isVisible);
71
- const handleToggleAllVisibility = useCallback(() => {
72
- if (onToggleAllVisibility) {
73
- onToggleAllVisibility(!allVisible);
74
- }
75
- else {
76
- const ids = allVisible
77
- ? collections.map((c) => c.id)
78
- : collections.filter((c) => !c.isVisible).map((c) => c.id);
79
- ids.forEach((id) => onToggleVisibility(id));
80
- }
81
- }, [onToggleAllVisibility, onToggleVisibility, allVisible, collections]);
163
+ const [hiddenExpanded, setHiddenExpanded] = useState(false);
164
+ const [contextMenu, setContextMenu] = useState(undefined);
165
+ const visibleCollections = collections.filter((c) => !c.isHidden);
166
+ const hiddenCollections = collections.filter((c) => c.isHidden);
82
167
  const handleToggleCollapse = useCallback(() => {
83
168
  setCollapsed((prev) => !prev);
84
169
  }, []);
170
+ const handleToggleHiddenExpanded = useCallback(() => {
171
+ setHiddenExpanded((prev) => !prev);
172
+ }, []);
173
+ const handleOpenContextMenu = useCallback((collectionId, x, y) => {
174
+ setContextMenu({ collectionId, x, y });
175
+ }, []);
176
+ const handleCloseContextMenu = useCallback(() => {
177
+ setContextMenu(undefined);
178
+ }, []);
179
+ const getBorderColor = useCallback((sourceName) => {
180
+ if (!sourceColorMap)
181
+ return undefined;
182
+ if (sourceName && sourceName in sourceColorMap) {
183
+ return sourceColorMap[sourceName];
184
+ }
185
+ return sourceColorFallback;
186
+ }, [sourceColorMap, sourceColorFallback]);
187
+ const contextMenuCollection = contextMenu
188
+ ? collections.find((c) => c.id === contextMenu.collectionId)
189
+ : undefined;
85
190
  return (React.createElement("div", { className: "flex flex-col border-t border-border mt-1" },
86
191
  React.createElement("div", { className: "flex items-center justify-between px-3 py-1.5" },
87
192
  React.createElement("button", { onClick: handleToggleCollapse, className: "flex items-center gap-1 text-xs font-medium text-muted uppercase tracking-wider hover:text-secondary transition-colors" },
88
- React.createElement("span", { className: `text-[10px] transition-transform ${collapsed ? '' : 'rotate-90'}` }, '\u203A'),
193
+ React.createElement(ChevronRightIcon, { className: `w-3 h-3 transition-transform ${collapsed ? '' : 'rotate-90'}` }),
89
194
  "Collections",
90
195
  React.createElement("span", { className: "text-muted normal-case font-normal" },
91
196
  "(",
92
197
  collections.length,
93
198
  ")")),
94
- collections.length > 1 && (React.createElement("button", { onClick: handleToggleAllVisibility, className: "text-xs text-muted hover:text-brand-accent transition-colors px-1", title: allVisible ? 'Hide all collections' : 'Show all collections', "aria-label": allVisible ? 'Hide all collections' : 'Show all collections' }, allVisible ? '\u{1F441}\u{FE0F}\u{200D}\u{1F5E8}\u{FE0F}' : '\u{1F441}')),
95
199
  React.createElement("div", { className: "flex items-center gap-1" },
96
200
  onAddDirectory && (React.createElement("button", { onClick: onAddDirectory, className: "text-xs text-muted hover:text-brand-accent transition-colors px-1", title: "Add directory", "aria-label": "Add directory" },
97
- "+",
98
- '\uD83D\uDCC1')),
201
+ React.createElement(FolderPlusIcon, { className: "w-4 h-4" }))),
99
202
  onExportAllAsZip && (React.createElement("button", { onClick: onExportAllAsZip, className: "text-xs text-muted hover:text-brand-accent transition-colors px-1", title: "Export all mutable collections as zip", "aria-label": "Export all as zip" },
100
- "\u2193",
101
- '🗂')),
102
- onOpenCollectionFromFile && (React.createElement("button", { onClick: onOpenCollectionFromFile, className: "text-xs text-muted hover:text-brand-accent transition-colors px-1", title: "Open collection file for in-place editing", "aria-label": "Open collection from file" }, '📂')),
103
- onImportCollection && (React.createElement("button", { onClick: onImportCollection, className: "text-xs text-muted hover:text-brand-accent transition-colors px-1", title: "Import collection from file (in-memory)", "aria-label": "Import collection from file" }, "\u2191")),
104
- onCreateCollection && (React.createElement("button", { onClick: onCreateCollection, "data-testid": "sidebar-new-collection-button", className: "text-xs text-muted hover:text-brand-accent transition-colors px-1", title: "New collection", "aria-label": "New collection" }, "+")))),
105
- !collapsed && (React.createElement("div", { className: "flex flex-col" }, collections.length === 0 ? (React.createElement("div", { className: "px-3 py-2 text-xs text-muted" }, "No collections")) : (collections.map((collection) => (React.createElement(CollectionRow, { key: collection.id, collection: collection, onToggleVisibility: onToggleVisibility, onSetDefault: onSetDefaultCollection, onDelete: onDeleteCollection, onExport: onExportCollection, onUnlock: onUnlockCollection, onRename: onRenameCollection, onMerge: onMergeCollection }))))))));
203
+ React.createElement(ArchiveBoxArrowDownIcon, { className: "w-4 h-4" }))),
204
+ onOpenCollectionFromFile && (React.createElement("button", { onClick: onOpenCollectionFromFile, className: "text-xs text-muted hover:text-brand-accent transition-colors px-1", title: "Open collection file for in-place editing", "aria-label": "Open collection from file" },
205
+ React.createElement(FolderOpenIcon, { className: "w-4 h-4" }))),
206
+ onImportCollection && (React.createElement("button", { onClick: onImportCollection, className: "text-xs text-muted hover:text-brand-accent transition-colors px-1", title: "Import collection from file (in-memory)", "aria-label": "Import collection from file" },
207
+ React.createElement(ArrowUpTrayIcon, { className: "w-4 h-4" }))),
208
+ onCreateCollection && (React.createElement("button", { onClick: onCreateCollection, "data-testid": "sidebar-new-collection-button", className: "text-muted hover:text-brand-accent transition-colors px-1", title: "New collection", "aria-label": "New collection" },
209
+ React.createElement(PlusIcon, { className: "w-4 h-4" }))))),
210
+ !collapsed && (React.createElement("div", { className: "flex flex-col" },
211
+ visibleCollections.length === 0 && hiddenCollections.length === 0 ? (React.createElement("div", { className: "px-3 py-2 text-xs text-muted" }, "No collections")) : (visibleCollections.map((collection) => (React.createElement(CollectionRow, { key: collection.id, collection: collection, onToggleVisibility: onToggleVisibility, onSetDefault: onSetDefaultCollection, onDelete: onDeleteCollection, onExport: onExportCollection, onUnlock: onUnlockCollection, onRename: onRenameCollection, onMerge: onMergeCollection, borderColorClass: getBorderColor(collection.sourceName), onContextMenu: onHideCollection ? handleOpenContextMenu : undefined })))),
212
+ hiddenCollections.length > 0 && (React.createElement(React.Fragment, null,
213
+ React.createElement("button", { onClick: handleToggleHiddenExpanded, className: "flex items-center gap-1 px-3 py-1 text-xs text-muted hover:text-secondary transition-colors" },
214
+ React.createElement(ChevronRightIcon, { className: `w-3 h-3 transition-transform ${hiddenExpanded ? 'rotate-90' : ''}` }),
215
+ hiddenCollections.length,
216
+ " hidden"),
217
+ hiddenExpanded &&
218
+ hiddenCollections.map((collection) => (React.createElement(CollectionRow, { key: collection.id, collection: collection, onToggleVisibility: onToggleVisibility, onSetDefault: onSetDefaultCollection, onDelete: onDeleteCollection, onExport: onExportCollection, onUnlock: onUnlockCollection, onRename: onRenameCollection, onMerge: onMergeCollection, borderColorClass: getBorderColor(collection.sourceName), onContextMenu: onShowCollection ? handleOpenContextMenu : undefined, isHiddenRow: true }))))))),
219
+ contextMenu && contextMenuCollection && (React.createElement(CollectionContextMenu, { menu: contextMenu, isHidden: (_a = contextMenuCollection.isHidden) !== null && _a !== void 0 ? _a : false, onHide: onHideCollection, onShow: onShowCollection, onClose: handleCloseContextMenu }))));
106
220
  }
107
221
  //# sourceMappingURL=CollectionSection.js.map
@@ -33,18 +33,25 @@ export interface ICollectionRowItem {
33
33
  * in another storage root. When true, a repair action should be offered.
34
34
  */
35
35
  readonly hasConflict?: boolean;
36
+ /** Whether this collection is explicitly hidden by the user */
37
+ readonly isHidden?: boolean;
38
+ /** The name of the storage source this collection was loaded from */
39
+ readonly sourceName?: string;
36
40
  }
37
41
  /**
38
42
  * Props for the CollectionSection component.
39
43
  * @public
40
44
  */
45
+ /**
46
+ * Maps source names to Tailwind border color classes for the left-border indicator.
47
+ * @public
48
+ */
49
+ export type SourceColorMap = Readonly<Record<string, string>>;
41
50
  export interface ICollectionSectionProps {
42
51
  /** Collection items to display */
43
52
  readonly collections: ReadonlyArray<ICollectionRowItem>;
44
53
  /** Callback when visibility is toggled for a collection */
45
54
  readonly onToggleVisibility: (collectionId: string) => void;
46
- /** Callback when all-visible toggle is clicked; receives true to show all, false to hide all */
47
- readonly onToggleAllVisibility?: (showAll: boolean) => void;
48
55
  /** Callback when "Add Directory" is clicked */
49
56
  readonly onAddDirectory?: () => void;
50
57
  /** Callback when "New Collection" is clicked */
@@ -67,8 +74,16 @@ export interface ICollectionSectionProps {
67
74
  readonly onRenameCollection?: (collectionId: string) => void;
68
75
  /** Callback when merge is clicked for a mutable collection */
69
76
  readonly onMergeCollection?: (collectionId: string) => void;
77
+ /** Callback when hide is selected from the context menu */
78
+ readonly onHideCollection?: (collectionId: string) => void;
79
+ /** Callback when show (unhide) is selected from the context menu */
80
+ readonly onShowCollection?: (collectionId: string) => void;
70
81
  /** Whether the section starts collapsed */
71
82
  readonly defaultCollapsed?: boolean;
83
+ /** Maps sourceName values to Tailwind border-l color classes */
84
+ readonly sourceColorMap?: SourceColorMap;
85
+ /** Fallback border-l color class when sourceName is not in the map */
86
+ readonly sourceColorFallback?: string;
72
87
  }
73
88
  /**
74
89
  * Collapsible sidebar section for managing collections.
@@ -64,30 +64,122 @@ exports.CollectionSection = CollectionSection;
64
64
  * @packageDocumentation
65
65
  */
66
66
  const react_1 = __importStar(require("react"));
67
+ const outline_1 = require("@heroicons/react/24/outline");
68
+ const solid_1 = require("@heroicons/react/20/solid");
69
+ // ============================================================================
70
+ // Context Menu (internal)
71
+ // ============================================================================
72
+ const LONG_PRESS_MS = 500;
73
+ function CollectionContextMenu(props) {
74
+ const { menu, isHidden, onHide, onShow, onClose } = props;
75
+ const ref = (0, react_1.useRef)(null);
76
+ (0, react_1.useEffect)(() => {
77
+ function handleClickOutside(e) {
78
+ if (ref.current && !ref.current.contains(e.target)) {
79
+ onClose();
80
+ }
81
+ }
82
+ document.addEventListener('mousedown', handleClickOutside);
83
+ return () => document.removeEventListener('mousedown', handleClickOutside);
84
+ }, [onClose]);
85
+ const action = isHidden ? onShow : onHide;
86
+ if (!action) {
87
+ return react_1.default.createElement(react_1.default.Fragment, null);
88
+ }
89
+ return (react_1.default.createElement("div", { ref: ref, className: "fixed z-50 bg-surface-raised border border-border rounded shadow-lg py-1 min-w-[140px]", style: { left: menu.x, top: menu.y } },
90
+ react_1.default.createElement("button", { className: "w-full text-left px-3 py-1.5 text-sm text-secondary hover:bg-hover transition-colors", onClick: () => {
91
+ action(menu.collectionId);
92
+ onClose();
93
+ } }, isHidden ? 'Show collection' : 'Hide collection')));
94
+ }
95
+ // ============================================================================
96
+ // Long-press hook (internal)
97
+ // ============================================================================
98
+ function useLongPress(onLongPress) {
99
+ const timerRef = (0, react_1.useRef)(undefined);
100
+ const firedRef = (0, react_1.useRef)(false);
101
+ const onTouchStart = (0, react_1.useCallback)((e) => {
102
+ firedRef.current = false;
103
+ timerRef.current = setTimeout(() => {
104
+ firedRef.current = true;
105
+ onLongPress(e);
106
+ }, LONG_PRESS_MS);
107
+ }, [onLongPress]);
108
+ const cancel = (0, react_1.useCallback)(() => {
109
+ if (timerRef.current !== undefined) {
110
+ clearTimeout(timerRef.current);
111
+ timerRef.current = undefined;
112
+ }
113
+ }, []);
114
+ return { onTouchStart, onTouchEnd: cancel, onTouchMove: cancel };
115
+ }
67
116
  // ============================================================================
68
117
  // CollectionRow (internal)
69
118
  // ============================================================================
70
119
  function CollectionRow(props) {
71
- var _a, _b, _c, _d;
72
- const { collection, onToggleVisibility, onSetDefault, onDelete, onExport, onUnlock, onRename, onMerge } = props;
120
+ var _a;
121
+ const { collection, onToggleVisibility, onSetDefault, onDelete, onExport, onUnlock, onRename, onMerge, borderColorClass, onContextMenu, isHiddenRow } = props;
73
122
  const displayName = (_a = collection.name) !== null && _a !== void 0 ? _a : collection.id;
74
- return (react_1.default.createElement("div", { className: `flex items-center gap-1.5 px-3 py-1.5 text-sm transition-colors hover:bg-hover ${collection.isVisible ? 'text-secondary' : 'text-muted'}` },
75
- react_1.default.createElement("button", { onClick: () => onToggleVisibility(collection.id), className: "shrink-0 w-5 h-5 flex items-center justify-center text-xs hover:text-brand-accent transition-colors", title: collection.isVisible ? 'Hide collection' : 'Show collection', "aria-label": `${collection.isVisible ? 'Hide' : 'Show'} ${displayName}` }, collection.isVisible ? '\u{1F441}' : '\u{1F441}\u{FE0F}\u{200D}\u{1F5E8}\u{FE0F}'),
76
- onSetDefault && collection.isMutable && (react_1.default.createElement("button", { onClick: () => onSetDefault(collection.id), className: `shrink-0 w-5 h-5 flex items-center justify-center transition-colors ${collection.isDefault ? 'text-star hover:text-star' : 'text-faint hover:text-star'}`, title: collection.isDefault
77
- ? 'Default collection for new items'
78
- : 'Set as default collection for new items', "aria-label": collection.isDefault
79
- ? `${(_b = collection.name) !== null && _b !== void 0 ? _b : collection.id} is default`
80
- : `Set ${(_c = collection.name) !== null && _c !== void 0 ? _c : collection.id} as default`, "aria-pressed": collection.isDefault }, collection.isDefault ? '★' : '☆')),
81
- collection.hasConflict && (react_1.default.createElement("span", { className: "shrink-0 text-xs text-status-warning-strong cursor-default", title: "An encrypted copy of this collection from another storage root has the same ID. Go to Settings \u2192 Storage to resolve the conflict.", "aria-label": `Conflict: encrypted shadow for ${displayName}` }, '⚠')),
82
- !collection.isMutable && (react_1.default.createElement("span", { className: "shrink-0 text-xs text-muted", title: "Built-in collection (read-only)" }, '\uD83D\uDD12')),
83
- collection.isProtected &&
84
- (collection.isUnlocked || !onUnlock ? (react_1.default.createElement("span", { className: `shrink-0 text-xs ${collection.isUnlocked ? 'text-status-success-icon' : 'text-muted'}`, title: collection.isUnlocked ? 'Protected (unlocked)' : 'Protected (locked)' }, '\uD83D\uDEE1')) : (react_1.default.createElement("button", { onClick: () => onUnlock(collection.id), className: "shrink-0 text-xs text-muted hover:text-star transition-colors", title: "Click to unlock", "aria-label": `Unlock ${(_d = collection.name) !== null && _d !== void 0 ? _d : collection.id}` }, '\uD83D\uDEE1'))),
85
- react_1.default.createElement("span", { className: "flex-1 truncate", title: displayName }, displayName),
86
- react_1.default.createElement("span", { className: "shrink-0 text-xs text-muted" }, collection.itemCount),
87
- collection.isMutable && onExport && (react_1.default.createElement("button", { onClick: () => onExport(collection.id), className: "shrink-0 w-5 h-5 flex items-center justify-center text-xs text-faint hover:text-brand-accent transition-colors", title: `Export ${displayName}`, "aria-label": `Export ${displayName}` }, "\u2193")),
88
- collection.isMutable && onRename && (react_1.default.createElement("button", { onClick: () => onRename(collection.id), className: "shrink-0 w-5 h-5 flex items-center justify-center text-xs text-faint hover:text-brand-accent transition-colors", title: `Rename ${displayName}`, "aria-label": `Rename ${displayName}` }, '✎')),
89
- collection.isMutable && onMerge && (react_1.default.createElement("button", { onClick: () => onMerge(collection.id), className: "shrink-0 w-5 h-5 flex items-center justify-center text-xs text-faint hover:text-brand-accent transition-colors", title: `Merge ${displayName} into another collection`, "aria-label": `Merge ${displayName}` }, '⤵')),
90
- collection.isMutable && onDelete && (react_1.default.createElement("button", { onClick: () => onDelete(collection.id), className: "shrink-0 w-5 h-5 flex items-center justify-center text-xs text-faint hover:text-status-error-icon transition-colors", title: `Remove ${displayName}`, "aria-label": `Remove ${displayName}` }, '×'))));
123
+ const handleContextMenu = (0, react_1.useCallback)((e) => {
124
+ if (onContextMenu) {
125
+ e.preventDefault();
126
+ onContextMenu(collection.id, e.clientX, e.clientY);
127
+ }
128
+ }, [onContextMenu, collection.id]);
129
+ const handleLongPress = (0, react_1.useCallback)((e) => {
130
+ if (onContextMenu && e.touches.length > 0) {
131
+ e.preventDefault();
132
+ const touch = e.touches[0];
133
+ onContextMenu(collection.id, touch.clientX, touch.clientY);
134
+ }
135
+ }, [onContextMenu, collection.id]);
136
+ const longPress = useLongPress(handleLongPress);
137
+ return (react_1.default.createElement("div", Object.assign({ onClick: () => onToggleVisibility(collection.id), onContextMenu: handleContextMenu }, longPress, { className: `flex flex-col px-3 py-2.5 text-sm transition-colors hover:bg-hover cursor-pointer ${isHiddenRow
138
+ ? 'text-muted opacity-30 line-through'
139
+ : collection.isVisible
140
+ ? 'text-secondary'
141
+ : 'text-muted opacity-50'} ${borderColorClass ? `border-l-4 ${borderColorClass}` : ''}`, role: "button", "aria-pressed": collection.isVisible, title: collection.sourceName ? `Source: ${collection.sourceName}` : displayName }),
142
+ react_1.default.createElement("div", { className: "flex items-center gap-1.5" },
143
+ onSetDefault && collection.isMutable && (react_1.default.createElement("button", { onClick: (e) => {
144
+ e.stopPropagation();
145
+ onSetDefault(collection.id);
146
+ }, className: `shrink-0 w-5 h-5 flex items-center justify-center transition-colors ${collection.isDefault ? 'text-star hover:text-star' : 'text-faint hover:text-star'}`, title: collection.isDefault
147
+ ? 'Default collection for new items'
148
+ : 'Set as default collection for new items', "aria-label": collection.isDefault ? `${displayName} is default` : `Set ${displayName} as default`, "aria-pressed": collection.isDefault }, collection.isDefault ? (react_1.default.createElement(solid_1.StarIcon, { className: "w-4 h-4" })) : (react_1.default.createElement(outline_1.StarIcon, { className: "w-4 h-4" })))),
149
+ collection.hasConflict && (react_1.default.createElement("span", { className: "shrink-0 text-status-warning-strong cursor-default", title: "An encrypted copy of this collection from another storage root has the same ID. Go to Settings \u2192 Storage to resolve the conflict.", "aria-label": `Conflict: encrypted shadow for ${displayName}` },
150
+ react_1.default.createElement(solid_1.ExclamationTriangleIcon, { className: "w-4 h-4" }))),
151
+ !collection.isMutable && (react_1.default.createElement("span", { className: "shrink-0 text-muted", title: "Built-in collection (read-only)" },
152
+ react_1.default.createElement(solid_1.BuildingLibraryIcon, { className: "w-4 h-4" }))),
153
+ collection.isProtected &&
154
+ (collection.isUnlocked || !onUnlock ? (react_1.default.createElement("span", { className: `shrink-0 ${collection.isUnlocked ? 'text-status-success-icon' : 'text-muted'}`, title: collection.isUnlocked ? 'Protected (unlocked)' : 'Protected (locked)' }, collection.isUnlocked ? (react_1.default.createElement(solid_1.ShieldCheckIcon, { className: "w-4 h-4" })) : (react_1.default.createElement(solid_1.ShieldExclamationIcon, { className: "w-4 h-4" })))) : (react_1.default.createElement("button", { onClick: (e) => {
155
+ e.stopPropagation();
156
+ onUnlock(collection.id);
157
+ }, className: "shrink-0 text-muted hover:text-star transition-colors", title: "Click to unlock", "aria-label": `Unlock ${displayName}` },
158
+ react_1.default.createElement(solid_1.ShieldExclamationIcon, { className: "w-4 h-4" })))),
159
+ react_1.default.createElement("span", { className: "flex-1 truncate", title: displayName }, displayName),
160
+ react_1.default.createElement("span", { className: "shrink-0 text-xs text-muted" }, collection.itemCount)),
161
+ react_1.default.createElement("div", { className: "flex items-center gap-1 mt-1" },
162
+ collection.isMutable && onExport && (react_1.default.createElement("button", { onClick: (e) => {
163
+ e.stopPropagation();
164
+ onExport(collection.id);
165
+ }, className: "shrink-0 w-5 h-5 flex items-center justify-center text-faint hover:text-brand-accent transition-colors", title: `Export ${displayName}`, "aria-label": `Export ${displayName}` },
166
+ react_1.default.createElement(solid_1.ArrowDownTrayIcon, { className: "w-4 h-4" }))),
167
+ collection.isMutable && onRename && (react_1.default.createElement("button", { onClick: (e) => {
168
+ e.stopPropagation();
169
+ onRename(collection.id);
170
+ }, className: "shrink-0 w-5 h-5 flex items-center justify-center text-faint hover:text-brand-accent transition-colors", title: `Rename ${displayName}`, "aria-label": `Rename ${displayName}` },
171
+ react_1.default.createElement(solid_1.PencilSquareIcon, { className: "w-4 h-4" }))),
172
+ collection.isMutable && onMerge && (react_1.default.createElement("button", { onClick: (e) => {
173
+ e.stopPropagation();
174
+ onMerge(collection.id);
175
+ }, className: "shrink-0 w-5 h-5 flex items-center justify-center text-faint hover:text-brand-accent transition-colors", title: `Merge ${displayName} into another collection`, "aria-label": `Merge ${displayName}` },
176
+ react_1.default.createElement(solid_1.ArrowsPointingInIcon, { className: "w-4 h-4" }))),
177
+ collection.isMutable && onDelete && (react_1.default.createElement("button", { onClick: (e) => {
178
+ e.stopPropagation();
179
+ onDelete(collection.id);
180
+ }, className: "shrink-0 w-5 h-5 flex items-center justify-center text-faint hover:text-status-error-icon transition-colors", title: `Remove ${displayName}`, "aria-label": `Remove ${displayName}` },
181
+ react_1.default.createElement(solid_1.TrashIcon, { className: "w-4 h-4" }))),
182
+ !collection.isMutable && react_1.default.createElement("span", { className: "text-xs text-muted" }, "(built-in)"))));
91
183
  }
92
184
  // ============================================================================
93
185
  // CollectionSection
@@ -101,43 +193,65 @@ function CollectionRow(props) {
101
193
  * @public
102
194
  */
103
195
  function CollectionSection(props) {
104
- const { collections, onToggleVisibility, onToggleAllVisibility, onAddDirectory, onCreateCollection, onDeleteCollection, onSetDefaultCollection, onExportCollection, onExportAllAsZip, onImportCollection, onOpenCollectionFromFile, onUnlockCollection, onRenameCollection, onMergeCollection, defaultCollapsed = false } = props;
196
+ var _a;
197
+ const { collections, onToggleVisibility, onAddDirectory, onCreateCollection, onDeleteCollection, onSetDefaultCollection, onExportCollection, onExportAllAsZip, onImportCollection, onOpenCollectionFromFile, onUnlockCollection, onRenameCollection, onMergeCollection, onHideCollection, onShowCollection, defaultCollapsed = false, sourceColorMap, sourceColorFallback } = props;
105
198
  const [collapsed, setCollapsed] = (0, react_1.useState)(defaultCollapsed);
106
- const allVisible = collections.length > 0 && collections.every((c) => c.isVisible);
107
- const handleToggleAllVisibility = (0, react_1.useCallback)(() => {
108
- if (onToggleAllVisibility) {
109
- onToggleAllVisibility(!allVisible);
110
- }
111
- else {
112
- const ids = allVisible
113
- ? collections.map((c) => c.id)
114
- : collections.filter((c) => !c.isVisible).map((c) => c.id);
115
- ids.forEach((id) => onToggleVisibility(id));
116
- }
117
- }, [onToggleAllVisibility, onToggleVisibility, allVisible, collections]);
199
+ const [hiddenExpanded, setHiddenExpanded] = (0, react_1.useState)(false);
200
+ const [contextMenu, setContextMenu] = (0, react_1.useState)(undefined);
201
+ const visibleCollections = collections.filter((c) => !c.isHidden);
202
+ const hiddenCollections = collections.filter((c) => c.isHidden);
118
203
  const handleToggleCollapse = (0, react_1.useCallback)(() => {
119
204
  setCollapsed((prev) => !prev);
120
205
  }, []);
206
+ const handleToggleHiddenExpanded = (0, react_1.useCallback)(() => {
207
+ setHiddenExpanded((prev) => !prev);
208
+ }, []);
209
+ const handleOpenContextMenu = (0, react_1.useCallback)((collectionId, x, y) => {
210
+ setContextMenu({ collectionId, x, y });
211
+ }, []);
212
+ const handleCloseContextMenu = (0, react_1.useCallback)(() => {
213
+ setContextMenu(undefined);
214
+ }, []);
215
+ const getBorderColor = (0, react_1.useCallback)((sourceName) => {
216
+ if (!sourceColorMap)
217
+ return undefined;
218
+ if (sourceName && sourceName in sourceColorMap) {
219
+ return sourceColorMap[sourceName];
220
+ }
221
+ return sourceColorFallback;
222
+ }, [sourceColorMap, sourceColorFallback]);
223
+ const contextMenuCollection = contextMenu
224
+ ? collections.find((c) => c.id === contextMenu.collectionId)
225
+ : undefined;
121
226
  return (react_1.default.createElement("div", { className: "flex flex-col border-t border-border mt-1" },
122
227
  react_1.default.createElement("div", { className: "flex items-center justify-between px-3 py-1.5" },
123
228
  react_1.default.createElement("button", { onClick: handleToggleCollapse, className: "flex items-center gap-1 text-xs font-medium text-muted uppercase tracking-wider hover:text-secondary transition-colors" },
124
- react_1.default.createElement("span", { className: `text-[10px] transition-transform ${collapsed ? '' : 'rotate-90'}` }, '\u203A'),
229
+ react_1.default.createElement(solid_1.ChevronRightIcon, { className: `w-3 h-3 transition-transform ${collapsed ? '' : 'rotate-90'}` }),
125
230
  "Collections",
126
231
  react_1.default.createElement("span", { className: "text-muted normal-case font-normal" },
127
232
  "(",
128
233
  collections.length,
129
234
  ")")),
130
- collections.length > 1 && (react_1.default.createElement("button", { onClick: handleToggleAllVisibility, className: "text-xs text-muted hover:text-brand-accent transition-colors px-1", title: allVisible ? 'Hide all collections' : 'Show all collections', "aria-label": allVisible ? 'Hide all collections' : 'Show all collections' }, allVisible ? '\u{1F441}\u{FE0F}\u{200D}\u{1F5E8}\u{FE0F}' : '\u{1F441}')),
131
235
  react_1.default.createElement("div", { className: "flex items-center gap-1" },
132
236
  onAddDirectory && (react_1.default.createElement("button", { onClick: onAddDirectory, className: "text-xs text-muted hover:text-brand-accent transition-colors px-1", title: "Add directory", "aria-label": "Add directory" },
133
- "+",
134
- '\uD83D\uDCC1')),
237
+ react_1.default.createElement(solid_1.FolderPlusIcon, { className: "w-4 h-4" }))),
135
238
  onExportAllAsZip && (react_1.default.createElement("button", { onClick: onExportAllAsZip, className: "text-xs text-muted hover:text-brand-accent transition-colors px-1", title: "Export all mutable collections as zip", "aria-label": "Export all as zip" },
136
- "\u2193",
137
- '🗂')),
138
- onOpenCollectionFromFile && (react_1.default.createElement("button", { onClick: onOpenCollectionFromFile, className: "text-xs text-muted hover:text-brand-accent transition-colors px-1", title: "Open collection file for in-place editing", "aria-label": "Open collection from file" }, '📂')),
139
- onImportCollection && (react_1.default.createElement("button", { onClick: onImportCollection, className: "text-xs text-muted hover:text-brand-accent transition-colors px-1", title: "Import collection from file (in-memory)", "aria-label": "Import collection from file" }, "\u2191")),
140
- onCreateCollection && (react_1.default.createElement("button", { onClick: onCreateCollection, "data-testid": "sidebar-new-collection-button", className: "text-xs text-muted hover:text-brand-accent transition-colors px-1", title: "New collection", "aria-label": "New collection" }, "+")))),
141
- !collapsed && (react_1.default.createElement("div", { className: "flex flex-col" }, collections.length === 0 ? (react_1.default.createElement("div", { className: "px-3 py-2 text-xs text-muted" }, "No collections")) : (collections.map((collection) => (react_1.default.createElement(CollectionRow, { key: collection.id, collection: collection, onToggleVisibility: onToggleVisibility, onSetDefault: onSetDefaultCollection, onDelete: onDeleteCollection, onExport: onExportCollection, onUnlock: onUnlockCollection, onRename: onRenameCollection, onMerge: onMergeCollection }))))))));
239
+ react_1.default.createElement(solid_1.ArchiveBoxArrowDownIcon, { className: "w-4 h-4" }))),
240
+ onOpenCollectionFromFile && (react_1.default.createElement("button", { onClick: onOpenCollectionFromFile, className: "text-xs text-muted hover:text-brand-accent transition-colors px-1", title: "Open collection file for in-place editing", "aria-label": "Open collection from file" },
241
+ react_1.default.createElement(solid_1.FolderOpenIcon, { className: "w-4 h-4" }))),
242
+ onImportCollection && (react_1.default.createElement("button", { onClick: onImportCollection, className: "text-xs text-muted hover:text-brand-accent transition-colors px-1", title: "Import collection from file (in-memory)", "aria-label": "Import collection from file" },
243
+ react_1.default.createElement(solid_1.ArrowUpTrayIcon, { className: "w-4 h-4" }))),
244
+ onCreateCollection && (react_1.default.createElement("button", { onClick: onCreateCollection, "data-testid": "sidebar-new-collection-button", className: "text-muted hover:text-brand-accent transition-colors px-1", title: "New collection", "aria-label": "New collection" },
245
+ react_1.default.createElement(solid_1.PlusIcon, { className: "w-4 h-4" }))))),
246
+ !collapsed && (react_1.default.createElement("div", { className: "flex flex-col" },
247
+ visibleCollections.length === 0 && hiddenCollections.length === 0 ? (react_1.default.createElement("div", { className: "px-3 py-2 text-xs text-muted" }, "No collections")) : (visibleCollections.map((collection) => (react_1.default.createElement(CollectionRow, { key: collection.id, collection: collection, onToggleVisibility: onToggleVisibility, onSetDefault: onSetDefaultCollection, onDelete: onDeleteCollection, onExport: onExportCollection, onUnlock: onUnlockCollection, onRename: onRenameCollection, onMerge: onMergeCollection, borderColorClass: getBorderColor(collection.sourceName), onContextMenu: onHideCollection ? handleOpenContextMenu : undefined })))),
248
+ hiddenCollections.length > 0 && (react_1.default.createElement(react_1.default.Fragment, null,
249
+ react_1.default.createElement("button", { onClick: handleToggleHiddenExpanded, className: "flex items-center gap-1 px-3 py-1 text-xs text-muted hover:text-secondary transition-colors" },
250
+ react_1.default.createElement(solid_1.ChevronRightIcon, { className: `w-3 h-3 transition-transform ${hiddenExpanded ? 'rotate-90' : ''}` }),
251
+ hiddenCollections.length,
252
+ " hidden"),
253
+ hiddenExpanded &&
254
+ hiddenCollections.map((collection) => (react_1.default.createElement(CollectionRow, { key: collection.id, collection: collection, onToggleVisibility: onToggleVisibility, onSetDefault: onSetDefaultCollection, onDelete: onDeleteCollection, onExport: onExportCollection, onUnlock: onUnlockCollection, onRename: onRenameCollection, onMerge: onMergeCollection, borderColorClass: getBorderColor(collection.sourceName), onContextMenu: onShowCollection ? handleOpenContextMenu : undefined, isHiddenRow: true }))))))),
255
+ contextMenu && contextMenuCollection && (react_1.default.createElement(CollectionContextMenu, { menu: contextMenu, isHidden: (_a = contextMenuCollection.isHidden) !== null && _a !== void 0 ? _a : false, onHide: onHideCollection, onShow: onShowCollection, onClose: handleCloseContextMenu }))));
142
256
  }
143
257
  //# sourceMappingURL=CollectionSection.js.map
@@ -8,5 +8,5 @@ export { FilterRow, type IFilterRowProps, type IFilterOption } from './FilterRow
8
8
  export { FilterBar, type IFilterBarProps } from './FilterBar';
9
9
  export { EntityList, type IEntityListProps, type IEntityDescriptor, type IEntityStatus, type IEmptyStateConfig, type IEmptyStateAction } from './EntityList';
10
10
  export { GroupedEntityList, type IGroupedEntityListProps, type IEntityGroupDescriptor } from './GroupedEntityList';
11
- export { CollectionSection, type ICollectionSectionProps, type ICollectionRowItem } from './CollectionSection';
11
+ export { CollectionSection, type ICollectionSectionProps, type ICollectionRowItem, type SourceColorMap } from './CollectionSection';
12
12
  //# sourceMappingURL=index.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fgv/ts-app-shell",
3
- "version": "5.1.0-4",
3
+ "version": "5.1.0-6",
4
4
  "description": "Shared React UI primitives for application shells: column cascade, sidebar, toast/log messages, command palette, keybinding registry",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -17,8 +17,8 @@
17
17
  "dependencies": {
18
18
  "@heroicons/react": "~2.2.0",
19
19
  "tslib": "^2.8.1",
20
- "@fgv/ts-utils": "5.1.0-4",
21
- "@fgv/ts-extras": "5.1.0-4"
20
+ "@fgv/ts-utils": "5.1.0-6",
21
+ "@fgv/ts-extras": "5.1.0-6"
22
22
  },
23
23
  "peerDependencies": {
24
24
  "react": ">=18.0.0",
@@ -51,8 +51,8 @@
51
51
  "@rushstack/heft-jest-plugin": "1.2.6",
52
52
  "@testing-library/dom": "^10.4.0",
53
53
  "@rushstack/heft-node-rig": "2.11.27",
54
- "@fgv/heft-dual-rig": "5.1.0-4",
55
- "@fgv/ts-utils-jest": "5.1.0-4"
54
+ "@fgv/heft-dual-rig": "5.1.0-6",
55
+ "@fgv/ts-utils-jest": "5.1.0-6"
56
56
  },
57
57
  "exports": {
58
58
  ".": {