@fgv/ts-app-shell 5.1.0-2 → 5.1.0-20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/dist/index.browser.js.map +1 -0
  2. package/dist/index.js.map +1 -0
  3. package/dist/packlets/ai-assist/index.js.map +1 -0
  4. package/dist/packlets/ai-assist/useAiAssist.js +192 -2
  5. package/dist/packlets/ai-assist/useAiAssist.js.map +1 -0
  6. package/dist/packlets/badge/index.js +23 -0
  7. package/dist/packlets/badge/index.js.map +1 -0
  8. package/dist/packlets/badge/renderStatusBadge.js +44 -0
  9. package/dist/packlets/badge/renderStatusBadge.js.map +1 -0
  10. package/dist/packlets/cascade/CascadeContainer.js.map +1 -0
  11. package/dist/packlets/cascade/ComparisonView.js.map +1 -0
  12. package/dist/packlets/cascade/EntityTabLayout.js.map +1 -0
  13. package/dist/packlets/cascade/MobileCascadeStack.js.map +1 -0
  14. package/dist/packlets/cascade/index.js.map +1 -0
  15. package/dist/packlets/cascade/model.js.map +1 -0
  16. package/dist/packlets/cascade/useCascadeOps.js.map +1 -0
  17. package/dist/packlets/cascade/useCascadeTransitions.js.map +1 -0
  18. package/dist/packlets/detail/DetailHelpers.js.map +1 -0
  19. package/dist/packlets/detail/index.js.map +1 -0
  20. package/dist/packlets/drop-zone/JsonDropZone.js.map +1 -0
  21. package/dist/packlets/drop-zone/index.js.map +1 -0
  22. package/dist/packlets/editing/EditFieldHelpers.js +4 -4
  23. package/dist/packlets/editing/EditFieldHelpers.js.map +1 -0
  24. package/dist/packlets/editing/MultiActionButton.js.map +1 -0
  25. package/dist/packlets/editing/NumericInput.js.map +1 -0
  26. package/dist/packlets/editing/TypeaheadInput.js.map +1 -0
  27. package/dist/packlets/editing/index.js.map +1 -0
  28. package/dist/packlets/editing/useTypeaheadMatch.js.map +1 -0
  29. package/dist/packlets/keyboard/index.js.map +1 -0
  30. package/dist/packlets/keyboard/registry.js.map +1 -0
  31. package/dist/packlets/keyboard/useKeyboardShortcuts.js.map +1 -0
  32. package/dist/packlets/messages/MessagesContext.js.map +1 -0
  33. package/dist/packlets/messages/MessagesLogger.js.map +1 -0
  34. package/dist/packlets/messages/StatusBar.js.map +1 -0
  35. package/dist/packlets/messages/Toast.js.map +1 -0
  36. package/dist/packlets/messages/index.js.map +1 -0
  37. package/dist/packlets/messages/model.js.map +1 -0
  38. package/dist/packlets/messages/useLogReporter.js.map +1 -0
  39. package/dist/packlets/modal/ConfirmDialog.js.map +1 -0
  40. package/dist/packlets/modal/Modal.js.map +1 -0
  41. package/dist/packlets/modal/index.js.map +1 -0
  42. package/dist/packlets/print/PrintEnclosure.js.map +1 -0
  43. package/dist/packlets/print/index.js.map +1 -0
  44. package/dist/packlets/print/openPrintWindow.js.map +1 -0
  45. package/dist/packlets/responsive/ResponsiveProvider.js.map +1 -0
  46. package/dist/packlets/responsive/index.js.map +1 -0
  47. package/dist/packlets/responsive/useResponsiveLayout.js.map +1 -0
  48. package/dist/packlets/selectors/EntityRow.js.map +1 -0
  49. package/dist/packlets/selectors/PreferredSelector.js.map +1 -0
  50. package/dist/packlets/selectors/index.js.map +1 -0
  51. package/dist/packlets/sidebar/CollectionSection.js +158 -43
  52. package/dist/packlets/sidebar/CollectionSection.js.map +1 -0
  53. package/dist/packlets/sidebar/EntityList.js.map +1 -0
  54. package/dist/packlets/sidebar/FilterBar.js.map +1 -0
  55. package/dist/packlets/sidebar/FilterRow.js.map +1 -0
  56. package/dist/packlets/sidebar/GroupedEntityList.js.map +1 -0
  57. package/dist/packlets/sidebar/SearchBar.js.map +1 -0
  58. package/dist/packlets/sidebar/SidebarLayout.js.map +1 -0
  59. package/dist/packlets/sidebar/index.js.map +1 -0
  60. package/dist/packlets/theme/ThemeProvider.js.map +1 -0
  61. package/dist/packlets/theme/index.js.map +1 -0
  62. package/dist/packlets/top-bar/ModeSelector.js.map +1 -0
  63. package/dist/packlets/top-bar/TabBar.js +4 -1
  64. package/dist/packlets/top-bar/TabBar.js.map +1 -0
  65. package/dist/packlets/top-bar/index.js.map +1 -0
  66. package/dist/packlets/url-sync/index.js.map +1 -0
  67. package/dist/packlets/url-sync/useUrlSync.js.map +1 -0
  68. package/lib/index.browser.d.ts.map +1 -0
  69. package/lib/index.browser.js.map +1 -0
  70. package/lib/index.d.ts.map +1 -0
  71. package/lib/index.js.map +1 -0
  72. package/lib/packlets/ai-assist/index.d.ts.map +1 -0
  73. package/lib/packlets/ai-assist/index.js.map +1 -0
  74. package/lib/packlets/ai-assist/useAiAssist.d.ts +26 -0
  75. package/lib/packlets/ai-assist/useAiAssist.d.ts.map +1 -0
  76. package/lib/packlets/ai-assist/useAiAssist.js +192 -2
  77. package/lib/packlets/ai-assist/useAiAssist.js.map +1 -0
  78. package/lib/packlets/badge/index.d.ts +2 -0
  79. package/lib/packlets/badge/index.d.ts.map +1 -0
  80. package/lib/packlets/badge/index.js +27 -0
  81. package/lib/packlets/badge/index.js.map +1 -0
  82. package/lib/packlets/badge/renderStatusBadge.d.ts +11 -0
  83. package/lib/packlets/badge/renderStatusBadge.d.ts.map +1 -0
  84. package/lib/packlets/badge/renderStatusBadge.js +50 -0
  85. package/lib/packlets/badge/renderStatusBadge.js.map +1 -0
  86. package/lib/packlets/cascade/CascadeContainer.d.ts.map +1 -0
  87. package/lib/packlets/cascade/CascadeContainer.js.map +1 -0
  88. package/lib/packlets/cascade/ComparisonView.d.ts.map +1 -0
  89. package/lib/packlets/cascade/ComparisonView.js.map +1 -0
  90. package/lib/packlets/cascade/EntityTabLayout.d.ts.map +1 -0
  91. package/lib/packlets/cascade/EntityTabLayout.js.map +1 -0
  92. package/lib/packlets/cascade/MobileCascadeStack.d.ts.map +1 -0
  93. package/lib/packlets/cascade/MobileCascadeStack.js.map +1 -0
  94. package/lib/packlets/cascade/index.d.ts.map +1 -0
  95. package/lib/packlets/cascade/index.js.map +1 -0
  96. package/lib/packlets/cascade/model.d.ts.map +1 -0
  97. package/lib/packlets/cascade/model.js.map +1 -0
  98. package/lib/packlets/cascade/useCascadeOps.d.ts.map +1 -0
  99. package/lib/packlets/cascade/useCascadeOps.js.map +1 -0
  100. package/lib/packlets/cascade/useCascadeTransitions.d.ts.map +1 -0
  101. package/lib/packlets/cascade/useCascadeTransitions.js.map +1 -0
  102. package/lib/packlets/detail/DetailHelpers.d.ts.map +1 -0
  103. package/lib/packlets/detail/DetailHelpers.js.map +1 -0
  104. package/lib/packlets/detail/index.d.ts.map +1 -0
  105. package/lib/packlets/detail/index.js.map +1 -0
  106. package/lib/packlets/drop-zone/JsonDropZone.d.ts.map +1 -0
  107. package/lib/packlets/drop-zone/JsonDropZone.js.map +1 -0
  108. package/lib/packlets/drop-zone/index.d.ts.map +1 -0
  109. package/lib/packlets/drop-zone/index.js.map +1 -0
  110. package/lib/packlets/editing/EditFieldHelpers.d.ts +6 -2
  111. package/lib/packlets/editing/EditFieldHelpers.d.ts.map +1 -0
  112. package/lib/packlets/editing/EditFieldHelpers.js +4 -4
  113. package/lib/packlets/editing/EditFieldHelpers.js.map +1 -0
  114. package/lib/packlets/editing/MultiActionButton.d.ts.map +1 -0
  115. package/lib/packlets/editing/MultiActionButton.js.map +1 -0
  116. package/lib/packlets/editing/NumericInput.d.ts.map +1 -0
  117. package/lib/packlets/editing/NumericInput.js.map +1 -0
  118. package/lib/packlets/editing/TypeaheadInput.d.ts.map +1 -0
  119. package/lib/packlets/editing/TypeaheadInput.js.map +1 -0
  120. package/lib/packlets/editing/index.d.ts.map +1 -0
  121. package/lib/packlets/editing/index.js.map +1 -0
  122. package/lib/packlets/editing/useTypeaheadMatch.d.ts.map +1 -0
  123. package/lib/packlets/editing/useTypeaheadMatch.js.map +1 -0
  124. package/lib/packlets/keyboard/index.d.ts.map +1 -0
  125. package/lib/packlets/keyboard/index.js.map +1 -0
  126. package/lib/packlets/keyboard/registry.d.ts.map +1 -0
  127. package/lib/packlets/keyboard/registry.js.map +1 -0
  128. package/lib/packlets/keyboard/useKeyboardShortcuts.d.ts.map +1 -0
  129. package/lib/packlets/keyboard/useKeyboardShortcuts.js.map +1 -0
  130. package/lib/packlets/messages/MessagesContext.d.ts.map +1 -0
  131. package/lib/packlets/messages/MessagesContext.js.map +1 -0
  132. package/lib/packlets/messages/MessagesLogger.d.ts.map +1 -0
  133. package/lib/packlets/messages/MessagesLogger.js.map +1 -0
  134. package/lib/packlets/messages/StatusBar.d.ts.map +1 -0
  135. package/lib/packlets/messages/StatusBar.js.map +1 -0
  136. package/lib/packlets/messages/Toast.d.ts.map +1 -0
  137. package/lib/packlets/messages/Toast.js.map +1 -0
  138. package/lib/packlets/messages/index.d.ts.map +1 -0
  139. package/lib/packlets/messages/index.js.map +1 -0
  140. package/lib/packlets/messages/model.d.ts.map +1 -0
  141. package/lib/packlets/messages/model.js.map +1 -0
  142. package/lib/packlets/messages/useLogReporter.d.ts.map +1 -0
  143. package/lib/packlets/messages/useLogReporter.js.map +1 -0
  144. package/lib/packlets/modal/ConfirmDialog.d.ts.map +1 -0
  145. package/lib/packlets/modal/ConfirmDialog.js.map +1 -0
  146. package/lib/packlets/modal/Modal.d.ts.map +1 -0
  147. package/lib/packlets/modal/Modal.js.map +1 -0
  148. package/lib/packlets/modal/index.d.ts.map +1 -0
  149. package/lib/packlets/modal/index.js.map +1 -0
  150. package/lib/packlets/print/PrintEnclosure.d.ts.map +1 -0
  151. package/lib/packlets/print/PrintEnclosure.js.map +1 -0
  152. package/lib/packlets/print/index.d.ts.map +1 -0
  153. package/lib/packlets/print/index.js.map +1 -0
  154. package/lib/packlets/print/openPrintWindow.d.ts.map +1 -0
  155. package/lib/packlets/print/openPrintWindow.js.map +1 -0
  156. package/lib/packlets/responsive/ResponsiveProvider.d.ts.map +1 -0
  157. package/lib/packlets/responsive/ResponsiveProvider.js.map +1 -0
  158. package/lib/packlets/responsive/index.d.ts.map +1 -0
  159. package/lib/packlets/responsive/index.js.map +1 -0
  160. package/lib/packlets/responsive/useResponsiveLayout.d.ts.map +1 -0
  161. package/lib/packlets/responsive/useResponsiveLayout.js.map +1 -0
  162. package/lib/packlets/selectors/EntityRow.d.ts.map +1 -0
  163. package/lib/packlets/selectors/EntityRow.js.map +1 -0
  164. package/lib/packlets/selectors/PreferredSelector.d.ts.map +1 -0
  165. package/lib/packlets/selectors/PreferredSelector.js.map +1 -0
  166. package/lib/packlets/selectors/index.d.ts.map +1 -0
  167. package/lib/packlets/selectors/index.js.map +1 -0
  168. package/lib/packlets/sidebar/CollectionSection.d.ts +40 -2
  169. package/lib/packlets/sidebar/CollectionSection.d.ts.map +1 -0
  170. package/lib/packlets/sidebar/CollectionSection.js +157 -42
  171. package/lib/packlets/sidebar/CollectionSection.js.map +1 -0
  172. package/lib/packlets/sidebar/EntityList.d.ts.map +1 -0
  173. package/lib/packlets/sidebar/EntityList.js.map +1 -0
  174. package/lib/packlets/sidebar/FilterBar.d.ts.map +1 -0
  175. package/lib/packlets/sidebar/FilterBar.js.map +1 -0
  176. package/lib/packlets/sidebar/FilterRow.d.ts.map +1 -0
  177. package/lib/packlets/sidebar/FilterRow.js.map +1 -0
  178. package/lib/packlets/sidebar/GroupedEntityList.d.ts.map +1 -0
  179. package/lib/packlets/sidebar/GroupedEntityList.js.map +1 -0
  180. package/lib/packlets/sidebar/SearchBar.d.ts.map +1 -0
  181. package/lib/packlets/sidebar/SearchBar.js.map +1 -0
  182. package/lib/packlets/sidebar/SidebarLayout.d.ts.map +1 -0
  183. package/lib/packlets/sidebar/SidebarLayout.js.map +1 -0
  184. package/lib/packlets/sidebar/index.d.ts +1 -1
  185. package/lib/packlets/sidebar/index.d.ts.map +1 -0
  186. package/lib/packlets/sidebar/index.js.map +1 -0
  187. package/lib/packlets/theme/ThemeProvider.d.ts.map +1 -0
  188. package/lib/packlets/theme/ThemeProvider.js.map +1 -0
  189. package/lib/packlets/theme/index.d.ts.map +1 -0
  190. package/lib/packlets/theme/index.js.map +1 -0
  191. package/lib/packlets/top-bar/ModeSelector.d.ts.map +1 -0
  192. package/lib/packlets/top-bar/ModeSelector.js.map +1 -0
  193. package/lib/packlets/top-bar/TabBar.d.ts +12 -0
  194. package/lib/packlets/top-bar/TabBar.d.ts.map +1 -0
  195. package/lib/packlets/top-bar/TabBar.js +4 -1
  196. package/lib/packlets/top-bar/TabBar.js.map +1 -0
  197. package/lib/packlets/top-bar/index.d.ts +1 -1
  198. package/lib/packlets/top-bar/index.d.ts.map +1 -0
  199. package/lib/packlets/top-bar/index.js.map +1 -0
  200. package/lib/packlets/url-sync/index.d.ts.map +1 -0
  201. package/lib/packlets/url-sync/index.js.map +1 -0
  202. package/lib/packlets/url-sync/useUrlSync.d.ts.map +1 -0
  203. package/lib/packlets/url-sync/useUrlSync.js.map +1 -0
  204. package/package.json +16 -16
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EntityRow.js","sourceRoot":"","sources":["../../../src/packlets/selectors/EntityRow.tsx"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * EntityRow — clickable text row with optional discrete swap-icon for alternates.\n *\n * Renders a uniform row regardless of whether alternates exist:\n * - Left slot: swap icon (when alternates exist) or empty spacer\n * - Name text: clickable for drill-down\n * - Optional right-side content (amount, sublabel, etc.)\n * - Drill-down chevron ›\n *\n * @packageDocumentation\n */\n\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport ReactDOM from 'react-dom';\n\nimport type { ISelectableItem } from './PreferredSelector';\n\n// ============================================================================\n// EntityRow Types\n// ============================================================================\n\n/**\n * Props for the EntityRow component.\n * @public\n */\nexport interface IEntityRowProps<TId extends string = string> {\n /** All options (preferred + alternates). If length === 1, no swap icon is shown. */\n readonly items: ReadonlyArray<ISelectableItem<TId>>;\n /** ID of the preferred/default item (shows ★ marker) */\n readonly preferredId?: TId;\n /** Controlled selected ID (overrides internal state). Use with onSelect. */\n readonly selectedId?: TId;\n /** Callback when the displayed item changes via the swap picker */\n readonly onSelect?: (id: TId) => void;\n /** Callback when the row is clicked (drill-down navigation) */\n readonly onClick?: (id: TId) => void;\n /** Callback to compare items. When provided, popover shows compare buttons. */\n readonly onCompare?: (ids: ReadonlyArray<TId>) => void;\n /** Optional right-side content rendered before the › chevron */\n readonly rightContent?: React.ReactNode;\n /** Optional section label displayed above the row */\n readonly label?: string;\n}\n\n// ============================================================================\n// EntityRow Component\n// ============================================================================\n\n/**\n * A clickable text row with an optional discrete swap-icon popover for\n * switching between alternates. Clicking the row itself always triggers\n * drill-down navigation. The swap icon opens a small picker to change\n * which alternate is displayed.\n *\n * @public\n */\nexport function EntityRow<TId extends string = string>(props: IEntityRowProps<TId>): React.ReactElement {\n const { items, preferredId, onClick, onSelect, onCompare, rightContent, label } = props;\n\n const hasAlternates = items.length > 1;\n const [internalId, setInternalId] = useState<TId>(preferredId ?? items[0]?.id ?? ('' as TId));\n const displayedId = props.selectedId ?? internalId;\n const [pickerOpen, setPickerOpen] = useState(false);\n const [compareMode, setCompareMode] = useState(false);\n const [checkedIds, setCheckedIds] = useState<ReadonlySet<TId>>(new Set());\n const [focusIndex, setFocusIndex] = useState(-1);\n const pickerRef = useRef<HTMLDivElement>(null);\n const pickerBtnRef = useRef<HTMLButtonElement>(null);\n const itemRefs = useRef<Array<HTMLButtonElement | null>>([]);\n const [pickerPos, setPickerPos] = useState<{ top: number; left: number }>({ top: 0, left: 0 });\n\n // Reset displayed item when items or preferred change (e.g. variation switch)\n useEffect(() => {\n setInternalId(preferredId ?? items[0]?.id ?? ('' as TId));\n }, [preferredId, items]);\n\n // Reset compare mode when picker closes; set initial focus when it opens\n useEffect(() => {\n if (!pickerOpen) {\n setCompareMode(false);\n setCheckedIds(new Set());\n setFocusIndex(-1);\n } else {\n const idx = items.findIndex((i) => i.id === displayedId);\n setFocusIndex(idx >= 0 ? idx : 0);\n }\n }, [pickerOpen, items, displayedId]);\n\n const displayedItem = useMemo(\n () => items.find((i) => i.id === displayedId) ?? items[0],\n [items, displayedId]\n );\n const isPreferred = displayedId === preferredId;\n\n // Position the portal popover relative to the swap button\n useEffect(() => {\n if (pickerOpen && pickerBtnRef.current) {\n const rect = pickerBtnRef.current.getBoundingClientRect();\n setPickerPos({ top: rect.bottom + 2, left: rect.left });\n }\n }, [pickerOpen]);\n\n // Close picker on outside click or scroll\n useEffect(() => {\n if (!pickerOpen) {\n return;\n }\n const handleClickOutside = (e: MouseEvent): void => {\n if (\n pickerRef.current &&\n !pickerRef.current.contains(e.target as Node) &&\n pickerBtnRef.current &&\n !pickerBtnRef.current.contains(e.target as Node)\n ) {\n setPickerOpen(false);\n }\n };\n const handleScroll = (): void => {\n setPickerOpen(false);\n };\n document.addEventListener('mousedown', handleClickOutside);\n // Close on any scroll so the popover doesn't float away from the button\n document.addEventListener('scroll', handleScroll, true);\n return (): void => {\n document.removeEventListener('mousedown', handleClickOutside);\n document.removeEventListener('scroll', handleScroll, true);\n };\n }, [pickerOpen]);\n\n const handleRowClick = useCallback((): void => {\n if (onClick) {\n onClick(displayedId);\n }\n }, [onClick, displayedId]);\n\n const handleRowKeyDown = useCallback(\n (e: React.KeyboardEvent): void => {\n if (onClick && (e.key === 'Enter' || e.key === ' ')) {\n e.preventDefault();\n onClick(displayedId);\n }\n },\n [onClick, displayedId]\n );\n\n const handlePickerToggle = useCallback((e: React.MouseEvent): void => {\n e.stopPropagation();\n setPickerOpen((prev) => !prev);\n }, []);\n\n const handlePickItem = useCallback(\n (id: TId): void => {\n setInternalId(id);\n if (onSelect) {\n onSelect(id);\n }\n setPickerOpen(false);\n },\n [onSelect]\n );\n\n const handleCompareAll = useCallback((): void => {\n if (onCompare) {\n onCompare(items.map((i) => i.id));\n }\n setPickerOpen(false);\n }, [onCompare, items]);\n\n const toggleChecked = useCallback((id: TId): void => {\n setCheckedIds((prev) => {\n const next = new Set(prev);\n if (next.has(id)) {\n next.delete(id);\n } else if (next.size < 4) {\n next.add(id);\n }\n return next;\n });\n }, []);\n\n const handleCompareSelected = useCallback((): void => {\n if (onCompare && checkedIds.size >= 2) {\n onCompare(Array.from(checkedIds));\n setPickerOpen(false);\n }\n }, [onCompare, checkedIds]);\n\n const handlePickerKeyDown = useCallback(\n (e: React.KeyboardEvent): void => {\n switch (e.key) {\n case 'Escape': {\n e.preventDefault();\n e.stopPropagation();\n if (compareMode) {\n setCompareMode(false);\n setCheckedIds(new Set());\n } else {\n setPickerOpen(false);\n }\n break;\n }\n case 'ArrowDown': {\n e.preventDefault();\n setFocusIndex((prev) => (prev < items.length - 1 ? prev + 1 : 0));\n break;\n }\n case 'ArrowUp': {\n e.preventDefault();\n setFocusIndex((prev) => (prev > 0 ? prev - 1 : items.length - 1));\n break;\n }\n case 'Enter': {\n e.preventDefault();\n if (focusIndex >= 0 && focusIndex < items.length) {\n if (compareMode) {\n toggleChecked(items[focusIndex].id);\n } else {\n handlePickItem(items[focusIndex].id);\n }\n }\n break;\n }\n case ' ': {\n if (compareMode && focusIndex >= 0 && focusIndex < items.length) {\n e.preventDefault();\n toggleChecked(items[focusIndex].id);\n }\n break;\n }\n default:\n break;\n }\n },\n [items, focusIndex, compareMode, toggleChecked, handlePickItem]\n );\n\n // Render the picker popover via portal so it escapes ancestor overflow clipping\n const pickerPopover = pickerOpen\n ? ReactDOM.createPortal(\n <div\n ref={pickerRef}\n style={{ position: 'fixed', top: pickerPos.top, left: pickerPos.left }}\n className=\"z-50 min-w-[180px] max-h-[200px] bg-surface border border-border rounded-lg shadow-lg flex flex-col overflow-hidden\"\n onKeyDown={handlePickerKeyDown}\n >\n <div className=\"flex items-center justify-between px-2.5 py-1 border-b border-border-subtle shrink-0\">\n {onCompare && items.length >= 2 ? (\n compareMode ? (\n <>\n <div className=\"flex items-center gap-1.5\">\n {checkedIds.size >= 2 && (\n <button\n onClick={(e): void => {\n e.stopPropagation();\n handleCompareSelected();\n }}\n className=\"text-[10px] text-white bg-brand-primary rounded px-1.5 py-0.5 hover:bg-brand-primary/90 transition-colors font-medium\"\n >\n Compare {checkedIds.size}\n </button>\n )}\n <button\n onClick={(e): void => {\n e.stopPropagation();\n handleCompareAll();\n }}\n className=\"text-[10px] text-brand-accent hover:text-brand-primary transition-colors font-medium\"\n >\n All\n </button>\n </div>\n <button\n onClick={(e): void => {\n e.stopPropagation();\n setCompareMode(false);\n setCheckedIds(new Set());\n }}\n className=\"text-muted hover:text-secondary transition-colors\"\n aria-label=\"Exit compare\"\n >\n <svg\n className=\"w-3.5 h-3.5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={2}\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </>\n ) : (\n <>\n <span className=\"text-[10px] font-medium text-muted uppercase tracking-wider\">Options</span>\n <button\n onClick={(e): void => {\n e.stopPropagation();\n setCompareMode(true);\n }}\n className=\"text-[10px] text-brand-accent hover:text-brand-primary transition-colors font-medium\"\n >\n Compare…\n </button>\n </>\n )\n ) : (\n <span className=\"text-[10px] font-medium text-muted uppercase tracking-wider\">Alternates</span>\n )}\n </div>\n <div className=\"overflow-y-auto flex-1\">\n {items.map((item, index) => {\n const isCurrent = item.id === displayedId;\n const isPref = item.id === preferredId;\n const isFocused = index === focusIndex;\n const isChecked = compareMode && checkedIds.has(item.id);\n return (\n <button\n key={item.id}\n ref={(el): void => {\n itemRefs.current[index] = el;\n }}\n onClick={(e): void => {\n e.stopPropagation();\n if (compareMode) {\n toggleChecked(item.id);\n } else {\n handlePickItem(item.id);\n }\n }}\n className={`flex items-center gap-2 w-full px-2.5 py-1.5 text-left text-sm transition-colors ${\n isChecked\n ? 'bg-selected text-brand-primary'\n : isCurrent && !compareMode\n ? 'bg-selected text-brand-primary font-medium'\n : isFocused\n ? 'bg-surface-alt text-primary'\n : 'text-secondary hover:bg-hover'\n }`}\n >\n {compareMode ? (\n <span\n className={`flex items-center justify-center w-3.5 h-3.5 rounded border shrink-0 transition-colors ${\n isChecked\n ? 'bg-brand-accent border-brand-accent text-white'\n : 'border-border bg-surface'\n }`}\n >\n {isChecked && (\n <svg\n className=\"w-2.5 h-2.5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={3}\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n )}\n </span>\n ) : (\n <span className=\"w-3 shrink-0 text-center text-xs\">\n {isPref ? <span className=\"text-star\">★</span> : ''}\n </span>\n )}\n <span className=\"flex-1 min-w-0 truncate\">\n {item.label}\n {item.sublabel && <span className=\"ml-1.5 text-xs text-muted\">{item.sublabel}</span>}\n </span>\n {!compareMode && isCurrent && (\n <svg\n className=\"w-3.5 h-3.5 text-brand-accent shrink-0\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={3}\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n )}\n </button>\n );\n })}\n </div>\n </div>,\n document.body\n )\n : null;\n\n return (\n <div>\n {label && <div className=\"text-xs font-semibold text-muted uppercase tracking-wider mb-1\">{label}</div>}\n <div\n className={`flex items-center gap-1.5 py-1.5 pl-0 pr-2 rounded-md ${\n onClick ? 'cursor-pointer hover:bg-hover transition-colors' : ''\n }`}\n onClick={handleRowClick}\n role={onClick ? 'button' : undefined}\n tabIndex={onClick ? 0 : undefined}\n onKeyDown={handleRowKeyDown}\n >\n {/* Fixed-width left slot for swap icon — keeps names aligned */}\n <span className=\"w-4 shrink-0 flex items-center justify-center\">\n {hasAlternates ? (\n <button\n ref={pickerBtnRef}\n onClick={handlePickerToggle}\n className=\"text-muted hover:text-brand-accent p-0 transition-colors\"\n aria-label=\"Switch alternate\"\n tabIndex={-1}\n >\n <svg\n className=\"w-3.5 h-3.5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={2}\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n d=\"M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4\"\n />\n </svg>\n </button>\n ) : null}\n </span>\n <span className=\"text-sm text-primary flex-1 truncate\">\n {displayedItem?.label ?? ''}\n {hasAlternates && isPreferred && <span className=\"ml-1 text-xs text-star\">★</span>}\n {displayedItem?.sublabel && (\n <span className=\"ml-1.5 text-xs text-muted\">{displayedItem.sublabel}</span>\n )}\n </span>\n {rightContent}\n {onClick && <span className=\"text-faint text-xs shrink-0\">›</span>}\n </div>\n {pickerPopover}\n </div>\n );\n}\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PreferredSelector.js","sourceRoot":"","sources":["../../../src/packlets/selectors/PreferredSelector.tsx"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * PreferredSelector — compact popover for choosing one item from a set of options.\n * @packageDocumentation\n */\n\nimport React, { useCallback, useEffect, useRef, useState } from 'react';\n\n// ============================================================================\n// PreferredSelector Types\n// ============================================================================\n\n/**\n * A single selectable item in the PreferredSelector.\n * @public\n */\nexport interface ISelectableItem<TId extends string = string> {\n /** Unique identifier */\n readonly id: TId;\n /** Primary display label */\n readonly label: string;\n /** Optional secondary label */\n readonly sublabel?: string;\n}\n\n/**\n * Props for the PreferredSelector component.\n * @public\n */\nexport interface IPreferredSelectorProps<TId extends string = string> {\n /** Available items to choose from */\n readonly items: ReadonlyArray<ISelectableItem<TId>>;\n /** Currently selected item ID */\n readonly selectedId: TId;\n /** ID of the preferred/default item (shows ★ marker) */\n readonly preferredId?: TId;\n /** Callback when an item is selected */\n readonly onSelect: (id: TId) => void;\n /** Callback to compare selected items side-by-side. Receives the IDs to compare. */\n readonly onCompare?: (ids: ReadonlyArray<TId>) => void;\n /** Callback for drill-down navigation on an item (shows › chevron per row) */\n readonly onDrillDown?: (id: TId) => void;\n /** Section title displayed above the trigger */\n readonly label?: string;\n}\n\n// ============================================================================\n// PreferredSelector Component\n// ============================================================================\n\n/**\n * Compact inline selector with popover for choosing one item from a set.\n *\n * When there is only one item, renders as a static label.\n * When there are multiple items, renders a clickable trigger that opens\n * a popover with all options. If `onCompare` is provided, the popover\n * includes a compare toggle that enables checkboxes for multi-select,\n * with \"Compare All\" and \"Compare Selected\" actions.\n *\n * @public\n */\nexport function PreferredSelector<TId extends string = string>(\n props: IPreferredSelectorProps<TId>\n): React.ReactElement {\n const { items, selectedId, preferredId, onSelect, onCompare, onDrillDown, label } = props;\n const [open, setOpen] = useState(false);\n const [focusIndex, setFocusIndex] = useState(-1);\n const [compareMode, setCompareMode] = useState(false);\n const [checkedIds, setCheckedIds] = useState<ReadonlySet<TId>>(new Set());\n const triggerRef = useRef<HTMLButtonElement>(null);\n const popoverRef = useRef<HTMLDivElement>(null);\n const itemRefs = useRef<Array<HTMLButtonElement | null>>([]);\n\n const selectedItem = items.find((item) => item.id === selectedId);\n const hasMultiple = items.length > 1;\n const canCompare = onCompare !== undefined && items.length >= 2;\n\n // Reset compare mode when popover closes\n useEffect(() => {\n if (!open) {\n setCompareMode(false);\n setCheckedIds(new Set());\n }\n }, [open]);\n\n // Close popover when clicking outside\n useEffect(() => {\n if (!open) {\n return;\n }\n const handleClickOutside = (e: MouseEvent): void => {\n if (\n popoverRef.current &&\n !popoverRef.current.contains(e.target as Node) &&\n triggerRef.current &&\n !triggerRef.current.contains(e.target as Node)\n ) {\n setOpen(false);\n }\n };\n document.addEventListener('mousedown', handleClickOutside);\n return (): void => {\n document.removeEventListener('mousedown', handleClickOutside);\n };\n }, [open]);\n\n // Focus first item when popover opens\n useEffect(() => {\n if (open) {\n const selectedIdx = items.findIndex((item) => item.id === selectedId);\n setFocusIndex(selectedIdx >= 0 ? selectedIdx : 0);\n }\n }, [open, items, selectedId]);\n\n // Scroll focused item into view\n useEffect(() => {\n if (open && focusIndex >= 0 && itemRefs.current[focusIndex]) {\n itemRefs.current[focusIndex]?.scrollIntoView({ block: 'nearest' });\n }\n }, [open, focusIndex]);\n\n const handleToggle = useCallback((): void => {\n if (hasMultiple) {\n setOpen((prev) => !prev);\n }\n }, [hasMultiple]);\n\n const handleSelect = useCallback(\n (id: TId): void => {\n onSelect(id);\n setOpen(false);\n },\n [onSelect]\n );\n\n const handleDrillDown = useCallback(\n (id: TId, e: React.MouseEvent): void => {\n e.stopPropagation();\n onDrillDown?.(id);\n setOpen(false);\n },\n [onDrillDown]\n );\n\n const toggleChecked = useCallback((id: TId): void => {\n setCheckedIds((prev) => {\n const next = new Set(prev);\n if (next.has(id)) {\n next.delete(id);\n } else if (next.size < 4) {\n next.add(id);\n }\n return next;\n });\n }, []);\n\n const handleCompareAll = useCallback((): void => {\n if (onCompare) {\n onCompare(items.map((item) => item.id));\n setOpen(false);\n }\n }, [onCompare, items]);\n\n const handleCompareSelected = useCallback((): void => {\n if (onCompare && checkedIds.size >= 2) {\n onCompare(Array.from(checkedIds));\n setOpen(false);\n }\n }, [onCompare, checkedIds]);\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent): void => {\n if (!open) {\n if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') {\n e.preventDefault();\n setOpen(true);\n }\n return;\n }\n\n switch (e.key) {\n case 'Escape': {\n e.preventDefault();\n e.stopPropagation();\n if (compareMode) {\n setCompareMode(false);\n setCheckedIds(new Set());\n } else {\n setOpen(false);\n triggerRef.current?.focus();\n }\n break;\n }\n case 'ArrowDown': {\n e.preventDefault();\n setFocusIndex((prev) => (prev < items.length - 1 ? prev + 1 : 0));\n break;\n }\n case 'ArrowUp': {\n e.preventDefault();\n setFocusIndex((prev) => (prev > 0 ? prev - 1 : items.length - 1));\n break;\n }\n case 'Enter': {\n e.preventDefault();\n if (focusIndex >= 0 && focusIndex < items.length) {\n if (compareMode) {\n toggleChecked(items[focusIndex].id);\n } else {\n handleSelect(items[focusIndex].id);\n }\n }\n break;\n }\n case ' ': {\n if (compareMode && focusIndex >= 0 && focusIndex < items.length) {\n e.preventDefault();\n toggleChecked(items[focusIndex].id);\n }\n break;\n }\n default:\n break;\n }\n },\n [open, items, focusIndex, handleSelect, compareMode, toggleChecked]\n );\n\n return (\n <div className=\"relative\">\n {/* Section label */}\n {label && <div className=\"text-xs font-medium text-muted mb-1\">{label}</div>}\n\n {/* Trigger */}\n <button\n ref={triggerRef}\n onClick={handleToggle}\n onKeyDown={handleKeyDown}\n className={`flex items-center gap-1.5 px-2 py-1 text-sm rounded-md border transition-colors w-full text-left ${\n hasMultiple\n ? 'border-border hover:border-brand-primary cursor-pointer bg-surface'\n : 'border-transparent cursor-default bg-transparent'\n } ${open ? 'border-brand-primary ring-1 ring-focus-ring/20' : ''}`}\n aria-expanded={open}\n aria-haspopup=\"listbox\"\n >\n <span className=\"flex-1 min-w-0 truncate text-primary\">\n {selectedItem?.label ?? selectedId}\n {preferredId !== undefined && selectedId === preferredId && (\n <span className=\"ml-1 text-xs text-star\">★</span>\n )}\n </span>\n {hasMultiple && (\n <>\n <span className=\"text-[10px] text-muted shrink-0 tabular-nums\">{items.length}</span>\n <svg\n className={`w-3 h-3 text-muted shrink-0 transition-transform ${open ? 'rotate-180' : ''}`}\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={2}\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M19 9l-7 7-7-7\" />\n </svg>\n </>\n )}\n </button>\n\n {/* Popover */}\n {open && (\n <div\n ref={popoverRef}\n className=\"absolute z-50 mt-1 w-full min-w-[200px] max-h-[280px] bg-surface border border-border rounded-lg shadow-lg flex flex-col overflow-hidden\"\n role=\"listbox\"\n onKeyDown={handleKeyDown}\n >\n {/* Popover header */}\n {canCompare && (\n <div className=\"flex items-center justify-between px-2.5 py-1.5 border-b border-border-subtle shrink-0\">\n {compareMode ? (\n <div className=\"flex items-center gap-1.5\">\n {checkedIds.size >= 2 && (\n <button\n onClick={handleCompareSelected}\n className=\"text-xs text-white bg-brand-primary rounded px-1.5 py-0.5 hover:bg-brand-primary/90 transition-colors font-medium\"\n >\n Compare {checkedIds.size}\n </button>\n )}\n <button\n onClick={handleCompareAll}\n className=\"text-xs text-brand-accent hover:text-brand-primary transition-colors font-medium\"\n >\n All\n </button>\n </div>\n ) : (\n <button\n onClick={(): void => setCompareMode(true)}\n className=\"text-xs text-brand-accent hover:text-brand-primary transition-colors font-medium\"\n >\n Compare…\n </button>\n )}\n <button\n onClick={(): void => {\n if (compareMode) {\n setCompareMode(false);\n setCheckedIds(new Set());\n } else {\n setOpen(false);\n }\n }}\n className=\"text-muted hover:text-secondary transition-colors\"\n aria-label={compareMode ? 'Exit compare' : 'Close'}\n >\n <svg\n className=\"w-3.5 h-3.5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={2}\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n )}\n\n {/* Items list */}\n <div className=\"overflow-y-auto flex-1\">\n {items.map((item, index) => {\n const isSelected = item.id === selectedId;\n const isPreferred = preferredId !== undefined && item.id === preferredId;\n const isFocused = index === focusIndex;\n const isChecked = compareMode && checkedIds.has(item.id);\n\n return (\n <button\n key={item.id}\n ref={(el): void => {\n itemRefs.current[index] = el;\n }}\n role=\"option\"\n aria-selected={isSelected}\n onClick={(): void => {\n if (compareMode) {\n toggleChecked(item.id);\n } else {\n handleSelect(item.id);\n }\n }}\n className={`flex items-center gap-2 w-full px-2.5 py-1.5 text-left text-sm transition-colors ${\n isChecked\n ? 'bg-selected text-brand-primary'\n : isSelected && !compareMode\n ? 'bg-selected text-brand-primary font-medium'\n : isFocused\n ? 'bg-surface-alt text-primary'\n : 'text-secondary hover:bg-hover'\n }`}\n >\n {/* Checkbox (compare mode) or preferred star */}\n {compareMode ? (\n <span\n className={`flex items-center justify-center w-3.5 h-3.5 rounded border shrink-0 transition-colors ${\n isChecked\n ? 'bg-brand-accent border-brand-accent text-white'\n : 'border-border bg-surface'\n }`}\n >\n {isChecked && (\n <svg\n className=\"w-2.5 h-2.5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={3}\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n )}\n </span>\n ) : (\n <span className=\"w-3 shrink-0 text-center text-xs\">\n {isPreferred ? <span className=\"text-star\">★</span> : ''}\n </span>\n )}\n\n {/* Label + sublabel */}\n <span className=\"flex-1 min-w-0\">\n <span className=\"truncate block\">{item.label}</span>\n {item.sublabel && (\n <span className=\"text-xs text-muted truncate block\">{item.sublabel}</span>\n )}\n </span>\n\n {/* Selection check (normal mode only) */}\n {!compareMode && isSelected && (\n <svg\n className=\"w-3.5 h-3.5 text-brand-accent shrink-0\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={3}\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n )}\n\n {/* Drill-down chevron (normal mode only) */}\n {!compareMode && onDrillDown && (\n <button\n onClick={(e): void => handleDrillDown(item.id, e)}\n className=\"text-faint hover:text-brand-accent shrink-0 p-0.5 -mr-1\"\n aria-label={`Open ${item.label}`}\n >\n <svg\n className=\"w-3 h-3\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={2}\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M9 5l7 7-7 7\" />\n </svg>\n </button>\n )}\n </button>\n );\n })}\n </div>\n </div>\n )}\n </div>\n );\n}\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/selectors/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nexport { EntityRow } from './EntityRow';\nexport type { IEntityRowProps } from './EntityRow';\nexport { PreferredSelector } from './PreferredSelector';\nexport type { IPreferredSelectorProps, ISelectableItem } from './PreferredSelector';\n"]}
@@ -27,31 +27,124 @@
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, BuildingStorefrontIcon, ShieldCheckIcon, ShieldExclamationIcon, ArrowDownTrayIcon, PencilSquareIcon, ArrowsPointingInIcon, TrashIcon, FolderPlusIcon, ArchiveBoxArrowDownIcon, FolderOpenIcon, ArrowUpTrayIcon, PlusIcon, ChevronRightIcon } from '@heroicons/react/20/solid';
33
+ import { renderStatusBadge } from '../badge';
34
+ // ============================================================================
35
+ // Context Menu (internal)
36
+ // ============================================================================
37
+ const LONG_PRESS_MS = 500;
38
+ function CollectionContextMenu(props) {
39
+ const { menu, isHidden, onHide, onShow, onClose } = props;
40
+ const ref = useRef(null);
41
+ useEffect(() => {
42
+ function handleClickOutside(e) {
43
+ if (ref.current && !ref.current.contains(e.target)) {
44
+ onClose();
45
+ }
46
+ }
47
+ document.addEventListener('mousedown', handleClickOutside);
48
+ return () => document.removeEventListener('mousedown', handleClickOutside);
49
+ }, [onClose]);
50
+ const action = isHidden ? onShow : onHide;
51
+ if (!action) {
52
+ return React.createElement(React.Fragment, null);
53
+ }
54
+ 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 } },
55
+ React.createElement("button", { className: "w-full text-left px-3 py-1.5 text-sm text-secondary hover:bg-hover transition-colors", onClick: () => {
56
+ action(menu.collectionId);
57
+ onClose();
58
+ } }, isHidden ? 'Show collection' : 'Hide collection')));
59
+ }
60
+ // ============================================================================
61
+ // Long-press hook (internal)
62
+ // ============================================================================
63
+ function useLongPress(onLongPress) {
64
+ const timerRef = useRef(undefined);
65
+ const firedRef = useRef(false);
66
+ const onTouchStart = useCallback((e) => {
67
+ firedRef.current = false;
68
+ timerRef.current = setTimeout(() => {
69
+ firedRef.current = true;
70
+ onLongPress(e);
71
+ }, LONG_PRESS_MS);
72
+ }, [onLongPress]);
73
+ const cancel = useCallback(() => {
74
+ if (timerRef.current !== undefined) {
75
+ clearTimeout(timerRef.current);
76
+ timerRef.current = undefined;
77
+ }
78
+ }, []);
79
+ return { onTouchStart, onTouchEnd: cancel, onTouchMove: cancel };
80
+ }
31
81
  // ============================================================================
32
82
  // CollectionRow (internal)
33
83
  // ============================================================================
34
84
  function CollectionRow(props) {
35
- var _a, _b, _c, _d;
36
- const { collection, onToggleVisibility, onSetDefault, onDelete, onExport, onUnlock, onRename, onMerge } = props;
85
+ var _a, _b, _c;
86
+ const { collection, onToggleVisibility, onSetDefault, onDelete, onExport, onUnlock, onRename, onMerge, borderColorClass, onContextMenu, isHiddenRow } = props;
37
87
  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}` }, '×'))));
88
+ const handleContextMenu = useCallback((e) => {
89
+ if (onContextMenu) {
90
+ e.preventDefault();
91
+ onContextMenu(collection.id, e.clientX, e.clientY);
92
+ }
93
+ }, [onContextMenu, collection.id]);
94
+ const handleLongPress = useCallback((e) => {
95
+ if (onContextMenu && e.touches.length > 0) {
96
+ e.preventDefault();
97
+ const touch = e.touches[0];
98
+ onContextMenu(collection.id, touch.clientX, touch.clientY);
99
+ }
100
+ }, [onContextMenu, collection.id]);
101
+ const longPress = useLongPress(handleLongPress);
102
+ 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
103
+ ? 'text-muted opacity-30 line-through'
104
+ : collection.isVisible
105
+ ? 'text-secondary'
106
+ : 'text-muted opacity-50'} ${borderColorClass ? `border-l-4 ${borderColorClass}` : ''}`, role: "button", "aria-pressed": collection.isVisible, title: collection.sourceName ? `Source: ${collection.sourceName}` : displayName }),
107
+ React.createElement("div", { className: "flex items-center gap-1.5" },
108
+ onSetDefault && collection.isMutable && (React.createElement("button", { onClick: (e) => {
109
+ e.stopPropagation();
110
+ onSetDefault(collection.id);
111
+ }, 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
112
+ ? 'Default collection for new items'
113
+ : '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" })))),
114
+ 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}` },
115
+ React.createElement(ExclamationTriangleIcon, { className: "w-4 h-4" }))),
116
+ !collection.isMutable && (React.createElement("span", { className: "shrink-0 text-muted", title: (_b = collection.readOnlyLabel) !== null && _b !== void 0 ? _b : 'Built-in collection (read-only)' }, collection.isDeletable ? (React.createElement(BuildingStorefrontIcon, { className: "w-4 h-4" })) : (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
+ collection.badge && renderStatusBadge(collection.badge),
125
+ React.createElement("span", { className: "shrink-0 text-xs text-muted" }, collection.itemCount)),
126
+ React.createElement("div", { className: "flex items-center gap-1 mt-1 ml-[24px]" },
127
+ collection.isMutable && onExport && (React.createElement("button", { onClick: (e) => {
128
+ e.stopPropagation();
129
+ onExport(collection.id);
130
+ }, 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}` },
131
+ React.createElement(ArrowDownTrayIcon, { className: "w-4 h-4" }))),
132
+ collection.isMutable && onRename && (React.createElement("button", { onClick: (e) => {
133
+ e.stopPropagation();
134
+ onRename(collection.id);
135
+ }, 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}` },
136
+ React.createElement(PencilSquareIcon, { className: "w-4 h-4" }))),
137
+ collection.isMutable && onMerge && (React.createElement("button", { onClick: (e) => {
138
+ e.stopPropagation();
139
+ onMerge(collection.id);
140
+ }, 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}` },
141
+ React.createElement(ArrowsPointingInIcon, { className: "w-4 h-4" }))),
142
+ (collection.isMutable || collection.isDeletable) && onDelete && (React.createElement("button", { onClick: (e) => {
143
+ e.stopPropagation();
144
+ onDelete(collection.id);
145
+ }, 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}` },
146
+ React.createElement(TrashIcon, { className: "w-4 h-4" }))),
147
+ !collection.isMutable && (React.createElement("span", { className: "text-xs text-muted" }, (_c = collection.readOnlyLabel) !== null && _c !== void 0 ? _c : '(built-in)')))));
55
148
  }
56
149
  // ============================================================================
57
150
  // CollectionSection
@@ -65,43 +158,65 @@ function CollectionRow(props) {
65
158
  * @public
66
159
  */
67
160
  export function CollectionSection(props) {
68
- const { collections, onToggleVisibility, onToggleAllVisibility, onAddDirectory, onCreateCollection, onDeleteCollection, onSetDefaultCollection, onExportCollection, onExportAllAsZip, onImportCollection, onOpenCollectionFromFile, onUnlockCollection, onRenameCollection, onMergeCollection, defaultCollapsed = false } = props;
161
+ var _a;
162
+ const { collections, onToggleVisibility, onAddDirectory, onCreateCollection, onDeleteCollection, onSetDefaultCollection, onExportCollection, onExportAllAsZip, onImportCollection, onOpenCollectionFromFile, onUnlockCollection, onRenameCollection, onMergeCollection, onHideCollection, onShowCollection, defaultCollapsed = false, sourceColorMap, sourceColorFallback } = props;
69
163
  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]);
164
+ const [hiddenExpanded, setHiddenExpanded] = useState(false);
165
+ const [contextMenu, setContextMenu] = useState(undefined);
166
+ const visibleCollections = collections.filter((c) => !c.isHidden);
167
+ const hiddenCollections = collections.filter((c) => c.isHidden);
82
168
  const handleToggleCollapse = useCallback(() => {
83
169
  setCollapsed((prev) => !prev);
84
170
  }, []);
171
+ const handleToggleHiddenExpanded = useCallback(() => {
172
+ setHiddenExpanded((prev) => !prev);
173
+ }, []);
174
+ const handleOpenContextMenu = useCallback((collectionId, x, y) => {
175
+ setContextMenu({ collectionId, x, y });
176
+ }, []);
177
+ const handleCloseContextMenu = useCallback(() => {
178
+ setContextMenu(undefined);
179
+ }, []);
180
+ const getBorderColor = useCallback((sourceName) => {
181
+ if (!sourceColorMap)
182
+ return undefined;
183
+ if (sourceName && sourceName in sourceColorMap) {
184
+ return sourceColorMap[sourceName];
185
+ }
186
+ return sourceColorFallback;
187
+ }, [sourceColorMap, sourceColorFallback]);
188
+ const contextMenuCollection = contextMenu
189
+ ? collections.find((c) => c.id === contextMenu.collectionId)
190
+ : undefined;
85
191
  return (React.createElement("div", { className: "flex flex-col border-t border-border mt-1" },
86
192
  React.createElement("div", { className: "flex items-center justify-between px-3 py-1.5" },
87
193
  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'),
194
+ React.createElement(ChevronRightIcon, { className: `w-3 h-3 transition-transform ${collapsed ? '' : 'rotate-90'}` }),
89
195
  "Collections",
90
196
  React.createElement("span", { className: "text-muted normal-case font-normal" },
91
197
  "(",
92
198
  collections.length,
93
199
  ")")),
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
200
  React.createElement("div", { className: "flex items-center gap-1" },
96
201
  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')),
202
+ React.createElement(FolderPlusIcon, { className: "w-4 h-4" }))),
99
203
  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 }))))))));
204
+ React.createElement(ArchiveBoxArrowDownIcon, { className: "w-4 h-4" }))),
205
+ 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" },
206
+ React.createElement(FolderOpenIcon, { className: "w-4 h-4" }))),
207
+ 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" },
208
+ React.createElement(ArrowUpTrayIcon, { className: "w-4 h-4" }))),
209
+ 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" },
210
+ React.createElement(PlusIcon, { className: "w-4 h-4" }))))),
211
+ !collapsed && (React.createElement("div", { className: "flex flex-col" },
212
+ 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 })))),
213
+ hiddenCollections.length > 0 && (React.createElement(React.Fragment, null,
214
+ React.createElement("button", { onClick: handleToggleHiddenExpanded, className: "flex items-center gap-1 px-3 py-1 text-xs text-muted hover:text-secondary transition-colors" },
215
+ React.createElement(ChevronRightIcon, { className: `w-3 h-3 transition-transform ${hiddenExpanded ? 'rotate-90' : ''}` }),
216
+ hiddenCollections.length,
217
+ " hidden"),
218
+ hiddenExpanded &&
219
+ 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 }))))))),
220
+ contextMenu && contextMenuCollection && (React.createElement(CollectionContextMenu, { menu: contextMenu, isHidden: (_a = contextMenuCollection.isHidden) !== null && _a !== void 0 ? _a : false, onHide: onHideCollection, onShow: onShowCollection, onClose: handleCloseContextMenu }))));
106
221
  }
107
222
  //# sourceMappingURL=CollectionSection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CollectionSection.js","sourceRoot":"","sources":["../../../src/packlets/sidebar/CollectionSection.tsx"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * Generic collection management section for the sidebar.\n *\n * Renders a collapsible list of collections with visibility toggles,\n * status indicators, and action buttons.\n *\n * @packageDocumentation\n */\n\nimport React, { useCallback, useEffect, useRef, useState } from 'react';\n\nimport { StarIcon as StarIconOutline } from '@heroicons/react/24/outline';\nimport {\n StarIcon as StarIconSolid,\n ExclamationTriangleIcon,\n BuildingLibraryIcon,\n BuildingStorefrontIcon,\n ShieldCheckIcon,\n ShieldExclamationIcon,\n ArrowDownTrayIcon,\n PencilSquareIcon,\n ArrowsPointingInIcon,\n TrashIcon,\n FolderPlusIcon,\n ArchiveBoxArrowDownIcon,\n FolderOpenIcon,\n ArrowUpTrayIcon,\n PlusIcon,\n ChevronRightIcon\n} from '@heroicons/react/20/solid';\n\nimport { renderStatusBadge } from '../badge';\n\n// ============================================================================\n// Collection Row Item\n// ============================================================================\n\n/**\n * Describes a single collection for rendering in the sidebar.\n * @public\n */\nexport interface ICollectionBadge {\n readonly kind: 'dot' | 'count';\n readonly tone?: 'info' | 'warning' | 'danger';\n readonly count?: number;\n readonly ariaLabel?: string;\n}\n\n/**\n * Describes a single collection for rendering in the sidebar.\n * @public\n */\nexport interface ICollectionRowItem {\n /** Collection identifier */\n readonly id: string;\n /** Display name (falls back to id if undefined) */\n readonly name: string | undefined;\n /** Number of items in this collection */\n readonly itemCount: number;\n /** Whether this collection can be modified */\n readonly isMutable: boolean;\n /** Whether this collection is encrypted/protected */\n readonly isProtected: boolean;\n /** Whether the protected collection has been unlocked */\n readonly isUnlocked: boolean;\n /** Whether this collection is currently visible */\n readonly isVisible: boolean;\n /** Whether this collection is the default target for new entities */\n readonly isDefault: boolean;\n /**\n * Whether this loaded collection has an orphaned encrypted shadow with the same ID\n * in another storage root. When true, a repair action should be offered.\n */\n readonly hasConflict?: boolean;\n /** Whether this collection is explicitly hidden by the user */\n readonly isHidden?: boolean;\n /** The name of the storage source this collection was loaded from */\n readonly sourceName?: string;\n /** Optional badge rendered alongside the collection name */\n readonly badge?: ICollectionBadge;\n /**\n * Label shown for immutable collections. Defaults to `\"(built-in)\"` when\n * omitted. Set to e.g. `\"(trading post)\"` for imported read-only content.\n */\n readonly readOnlyLabel?: string;\n /**\n * When true, the collection can be deleted even though it is not mutable.\n * Use for user-imported read-only content (e.g., Trading Post collections)\n * that the user should be able to remove but not edit.\n */\n readonly isDeletable?: boolean;\n}\n\n// ============================================================================\n// Collection Section Props\n// ============================================================================\n\n/**\n * Props for the CollectionSection component.\n * @public\n */\n/**\n * Maps source names to Tailwind border color classes for the left-border indicator.\n * @public\n */\nexport type SourceColorMap = Readonly<Record<string, string>>;\n\nexport interface ICollectionSectionProps {\n /** Collection items to display */\n readonly collections: ReadonlyArray<ICollectionRowItem>;\n /** Callback when visibility is toggled for a collection */\n readonly onToggleVisibility: (collectionId: string) => void;\n /** Callback when \"Add Directory\" is clicked */\n readonly onAddDirectory?: () => void;\n /** Callback when \"New Collection\" is clicked */\n readonly onCreateCollection?: () => void;\n /** Callback when delete is clicked for a mutable collection */\n readonly onDeleteCollection?: (collectionId: string) => void;\n /** Callback when the star/default is clicked for a collection */\n readonly onSetDefaultCollection?: (collectionId: string) => void;\n /** Callback when export is clicked for a mutable collection */\n readonly onExportCollection?: (collectionId: string) => void;\n /** Callback when \"Export All as Zip\" is clicked (header-level) */\n readonly onExportAllAsZip?: () => void;\n /** Callback when \"Import Collection\" is clicked (header-level) */\n readonly onImportCollection?: () => void;\n /** Callback when \"Open from File\" is clicked (header-level, File System Access API) */\n readonly onOpenCollectionFromFile?: () => void;\n /** Callback when the unlock button is clicked for a locked protected collection */\n readonly onUnlockCollection?: (collectionId: string) => void;\n /** Callback when rename is clicked for a mutable collection */\n readonly onRenameCollection?: (collectionId: string) => void;\n /** Callback when merge is clicked for a mutable collection */\n readonly onMergeCollection?: (collectionId: string) => void;\n /** Callback when hide is selected from the context menu */\n readonly onHideCollection?: (collectionId: string) => void;\n /** Callback when show (unhide) is selected from the context menu */\n readonly onShowCollection?: (collectionId: string) => void;\n /** Whether the section starts collapsed */\n readonly defaultCollapsed?: boolean;\n /** Maps sourceName values to Tailwind border-l color classes */\n readonly sourceColorMap?: SourceColorMap;\n /** Fallback border-l color class when sourceName is not in the map */\n readonly sourceColorFallback?: string;\n}\n\n// ============================================================================\n// Context Menu (internal)\n// ============================================================================\n\nconst LONG_PRESS_MS: number = 500;\n\ninterface IContextMenuState {\n readonly collectionId: string;\n readonly x: number;\n readonly y: number;\n}\n\nfunction CollectionContextMenu(props: {\n readonly menu: IContextMenuState;\n readonly isHidden: boolean;\n readonly onHide?: (id: string) => void;\n readonly onShow?: (id: string) => void;\n readonly onClose: () => void;\n}): React.ReactElement {\n const { menu, isHidden, onHide, onShow, onClose } = props;\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n function handleClickOutside(e: MouseEvent): void {\n if (ref.current && !ref.current.contains(e.target as Node)) {\n onClose();\n }\n }\n document.addEventListener('mousedown', handleClickOutside);\n return (): void => document.removeEventListener('mousedown', handleClickOutside);\n }, [onClose]);\n\n const action = isHidden ? onShow : onHide;\n if (!action) {\n return <></>;\n }\n\n return (\n <div\n ref={ref}\n className=\"fixed z-50 bg-surface-raised border border-border rounded shadow-lg py-1 min-w-[140px]\"\n style={{ left: menu.x, top: menu.y }}\n >\n <button\n className=\"w-full text-left px-3 py-1.5 text-sm text-secondary hover:bg-hover transition-colors\"\n onClick={(): void => {\n action(menu.collectionId);\n onClose();\n }}\n >\n {isHidden ? 'Show collection' : 'Hide collection'}\n </button>\n </div>\n );\n}\n\n// ============================================================================\n// Long-press hook (internal)\n// ============================================================================\n\nfunction useLongPress(onLongPress: (e: React.TouchEvent) => void): {\n onTouchStart: (e: React.TouchEvent) => void;\n onTouchEnd: () => void;\n onTouchMove: () => void;\n} {\n const timerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n const firedRef = useRef(false);\n\n const onTouchStart = useCallback(\n (e: React.TouchEvent): void => {\n firedRef.current = false;\n timerRef.current = setTimeout(() => {\n firedRef.current = true;\n onLongPress(e);\n }, LONG_PRESS_MS);\n },\n [onLongPress]\n );\n\n const cancel = useCallback((): void => {\n if (timerRef.current !== undefined) {\n clearTimeout(timerRef.current);\n timerRef.current = undefined;\n }\n }, []);\n\n return { onTouchStart, onTouchEnd: cancel, onTouchMove: cancel };\n}\n\n// ============================================================================\n// CollectionRow (internal)\n// ============================================================================\n\nfunction CollectionRow(props: {\n readonly collection: ICollectionRowItem;\n readonly onToggleVisibility: (id: string) => void;\n readonly onSetDefault?: (id: string) => void;\n readonly onDelete?: (id: string) => void;\n readonly onExport?: (id: string) => void;\n readonly onUnlock?: (id: string) => void;\n readonly onRename?: (id: string) => void;\n readonly onMerge?: (id: string) => void;\n readonly borderColorClass?: string;\n readonly onContextMenu?: (collectionId: string, x: number, y: number) => void;\n readonly isHiddenRow?: boolean;\n}): React.ReactElement {\n const {\n collection,\n onToggleVisibility,\n onSetDefault,\n onDelete,\n onExport,\n onUnlock,\n onRename,\n onMerge,\n borderColorClass,\n onContextMenu,\n isHiddenRow\n } = props;\n const displayName = collection.name ?? collection.id;\n\n const handleContextMenu = useCallback(\n (e: React.MouseEvent): void => {\n if (onContextMenu) {\n e.preventDefault();\n onContextMenu(collection.id, e.clientX, e.clientY);\n }\n },\n [onContextMenu, collection.id]\n );\n\n const handleLongPress = useCallback(\n (e: React.TouchEvent): void => {\n if (onContextMenu && e.touches.length > 0) {\n e.preventDefault();\n const touch = e.touches[0];\n onContextMenu(collection.id, touch.clientX, touch.clientY);\n }\n },\n [onContextMenu, collection.id]\n );\n\n const longPress = useLongPress(handleLongPress);\n\n return (\n <div\n onClick={(): void => onToggleVisibility(collection.id)}\n onContextMenu={handleContextMenu}\n {...longPress}\n className={`flex flex-col px-3 py-2.5 text-sm transition-colors hover:bg-hover cursor-pointer ${\n isHiddenRow\n ? 'text-muted opacity-30 line-through'\n : collection.isVisible\n ? 'text-secondary'\n : 'text-muted opacity-50'\n } ${borderColorClass ? `border-l-4 ${borderColorClass}` : ''}`}\n role=\"button\"\n aria-pressed={collection.isVisible}\n title={collection.sourceName ? `Source: ${collection.sourceName}` : displayName}\n >\n {/* Top line: status icons + name */}\n <div className=\"flex items-center gap-1.5\">\n {/* Default collection star */}\n {onSetDefault && collection.isMutable && (\n <button\n onClick={(e): void => {\n e.stopPropagation();\n onSetDefault(collection.id);\n }}\n className={`shrink-0 w-5 h-5 flex items-center justify-center transition-colors ${\n collection.isDefault ? 'text-star hover:text-star' : 'text-faint hover:text-star'\n }`}\n title={\n collection.isDefault\n ? 'Default collection for new items'\n : 'Set as default collection for new items'\n }\n aria-label={collection.isDefault ? `${displayName} is default` : `Set ${displayName} as default`}\n aria-pressed={collection.isDefault}\n >\n {collection.isDefault ? (\n <StarIconSolid className=\"w-4 h-4\" />\n ) : (\n <StarIconOutline className=\"w-4 h-4\" />\n )}\n </button>\n )}\n\n {/* Conflict indicator */}\n {collection.hasConflict && (\n <span\n className=\"shrink-0 text-status-warning-strong cursor-default\"\n title=\"An encrypted copy of this collection from another storage root has the same ID. Go to Settings → Storage to resolve the conflict.\"\n aria-label={`Conflict: encrypted shadow for ${displayName}`}\n >\n <ExclamationTriangleIcon className=\"w-4 h-4\" />\n </span>\n )}\n\n {/* Read-only indicator */}\n {!collection.isMutable && (\n <span\n className=\"shrink-0 text-muted\"\n title={collection.readOnlyLabel ?? 'Built-in collection (read-only)'}\n >\n {collection.isDeletable ? (\n <BuildingStorefrontIcon className=\"w-4 h-4\" />\n ) : (\n <BuildingLibraryIcon className=\"w-4 h-4\" />\n )}\n </span>\n )}\n\n {/* Protected/shield indicator */}\n {collection.isProtected &&\n (collection.isUnlocked || !onUnlock ? (\n <span\n className={`shrink-0 ${collection.isUnlocked ? 'text-status-success-icon' : 'text-muted'}`}\n title={collection.isUnlocked ? 'Protected (unlocked)' : 'Protected (locked)'}\n >\n {collection.isUnlocked ? (\n <ShieldCheckIcon className=\"w-4 h-4\" />\n ) : (\n <ShieldExclamationIcon className=\"w-4 h-4\" />\n )}\n </span>\n ) : (\n <button\n onClick={(e): void => {\n e.stopPropagation();\n onUnlock(collection.id);\n }}\n className=\"shrink-0 text-muted hover:text-star transition-colors\"\n title=\"Click to unlock\"\n aria-label={`Unlock ${displayName}`}\n >\n <ShieldExclamationIcon className=\"w-4 h-4\" />\n </button>\n ))}\n\n {/* Name */}\n <span className=\"flex-1 truncate\" title={displayName}>\n {displayName}\n </span>\n {collection.badge && renderStatusBadge(collection.badge)}\n <span className=\"shrink-0 text-xs text-muted\">{collection.itemCount}</span>\n </div>\n\n {/* Bottom line: action buttons or built-in label, indented to align under name */}\n <div className=\"flex items-center gap-1 mt-1 ml-[24px]\">\n {/* Action buttons (mutable only) */}\n {collection.isMutable && onExport && (\n <button\n onClick={(e): void => {\n e.stopPropagation();\n onExport(collection.id);\n }}\n className=\"shrink-0 w-5 h-5 flex items-center justify-center text-faint hover:text-brand-accent transition-colors\"\n title={`Export ${displayName}`}\n aria-label={`Export ${displayName}`}\n >\n <ArrowDownTrayIcon className=\"w-4 h-4\" />\n </button>\n )}\n {collection.isMutable && onRename && (\n <button\n onClick={(e): void => {\n e.stopPropagation();\n onRename(collection.id);\n }}\n className=\"shrink-0 w-5 h-5 flex items-center justify-center text-faint hover:text-brand-accent transition-colors\"\n title={`Rename ${displayName}`}\n aria-label={`Rename ${displayName}`}\n >\n <PencilSquareIcon className=\"w-4 h-4\" />\n </button>\n )}\n {collection.isMutable && onMerge && (\n <button\n onClick={(e): void => {\n e.stopPropagation();\n onMerge(collection.id);\n }}\n className=\"shrink-0 w-5 h-5 flex items-center justify-center text-faint hover:text-brand-accent transition-colors\"\n title={`Merge ${displayName} into another collection`}\n aria-label={`Merge ${displayName}`}\n >\n <ArrowsPointingInIcon className=\"w-4 h-4\" />\n </button>\n )}\n {(collection.isMutable || collection.isDeletable) && onDelete && (\n <button\n onClick={(e): void => {\n e.stopPropagation();\n onDelete(collection.id);\n }}\n className=\"shrink-0 w-5 h-5 flex items-center justify-center text-faint hover:text-status-error-icon transition-colors\"\n title={`Remove ${displayName}`}\n aria-label={`Remove ${displayName}`}\n >\n <TrashIcon className=\"w-4 h-4\" />\n </button>\n )}\n\n {/* Read-only label for immutable collections */}\n {!collection.isMutable && (\n <span className=\"text-xs text-muted\">{collection.readOnlyLabel ?? '(built-in)'}</span>\n )}\n </div>\n </div>\n );\n}\n\n// ============================================================================\n// CollectionSection\n// ============================================================================\n\n/**\n * Collapsible sidebar section for managing collections.\n *\n * Renders a header with action buttons and a list of collection rows\n * with visibility toggles, status indicators, and delete actions.\n *\n * @public\n */\nexport function CollectionSection(props: ICollectionSectionProps): React.ReactElement {\n const {\n collections,\n onToggleVisibility,\n onAddDirectory,\n onCreateCollection,\n onDeleteCollection,\n onSetDefaultCollection,\n onExportCollection,\n onExportAllAsZip,\n onImportCollection,\n onOpenCollectionFromFile,\n onUnlockCollection,\n onRenameCollection,\n onMergeCollection,\n onHideCollection,\n onShowCollection,\n defaultCollapsed = false,\n sourceColorMap,\n sourceColorFallback\n } = props;\n\n const [collapsed, setCollapsed] = useState(defaultCollapsed);\n const [hiddenExpanded, setHiddenExpanded] = useState(false);\n const [contextMenu, setContextMenu] = useState<IContextMenuState | undefined>(undefined);\n\n const visibleCollections = collections.filter((c) => !c.isHidden);\n const hiddenCollections = collections.filter((c) => c.isHidden);\n\n const handleToggleCollapse = useCallback((): void => {\n setCollapsed((prev) => !prev);\n }, []);\n\n const handleToggleHiddenExpanded = useCallback((): void => {\n setHiddenExpanded((prev) => !prev);\n }, []);\n\n const handleOpenContextMenu = useCallback((collectionId: string, x: number, y: number): void => {\n setContextMenu({ collectionId, x, y });\n }, []);\n\n const handleCloseContextMenu = useCallback((): void => {\n setContextMenu(undefined);\n }, []);\n\n const getBorderColor = useCallback(\n (sourceName: string | undefined): string | undefined => {\n if (!sourceColorMap) return undefined;\n if (sourceName && sourceName in sourceColorMap) {\n return sourceColorMap[sourceName];\n }\n return sourceColorFallback;\n },\n [sourceColorMap, sourceColorFallback]\n );\n\n const contextMenuCollection = contextMenu\n ? collections.find((c) => c.id === contextMenu.collectionId)\n : undefined;\n\n return (\n <div className=\"flex flex-col border-t border-border mt-1\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-3 py-1.5\">\n <button\n onClick={handleToggleCollapse}\n className=\"flex items-center gap-1 text-xs font-medium text-muted uppercase tracking-wider hover:text-secondary transition-colors\"\n >\n <ChevronRightIcon className={`w-3 h-3 transition-transform ${collapsed ? '' : 'rotate-90'}`} />\n Collections\n <span className=\"text-muted normal-case font-normal\">({collections.length})</span>\n </button>\n\n {/* Action buttons */}\n <div className=\"flex items-center gap-1\">\n {onAddDirectory && (\n <button\n onClick={onAddDirectory}\n className=\"text-xs text-muted hover:text-brand-accent transition-colors px-1\"\n title=\"Add directory\"\n aria-label=\"Add directory\"\n >\n <FolderPlusIcon className=\"w-4 h-4\" />\n </button>\n )}\n {onExportAllAsZip && (\n <button\n onClick={onExportAllAsZip}\n className=\"text-xs text-muted hover:text-brand-accent transition-colors px-1\"\n title=\"Export all mutable collections as zip\"\n aria-label=\"Export all as zip\"\n >\n <ArchiveBoxArrowDownIcon className=\"w-4 h-4\" />\n </button>\n )}\n {onOpenCollectionFromFile && (\n <button\n onClick={onOpenCollectionFromFile}\n className=\"text-xs text-muted hover:text-brand-accent transition-colors px-1\"\n title=\"Open collection file for in-place editing\"\n aria-label=\"Open collection from file\"\n >\n <FolderOpenIcon className=\"w-4 h-4\" />\n </button>\n )}\n {onImportCollection && (\n <button\n onClick={onImportCollection}\n className=\"text-xs text-muted hover:text-brand-accent transition-colors px-1\"\n title=\"Import collection from file (in-memory)\"\n aria-label=\"Import collection from file\"\n >\n <ArrowUpTrayIcon className=\"w-4 h-4\" />\n </button>\n )}\n {onCreateCollection && (\n <button\n onClick={onCreateCollection}\n data-testid=\"sidebar-new-collection-button\"\n className=\"text-muted hover:text-brand-accent transition-colors px-1\"\n title=\"New collection\"\n aria-label=\"New collection\"\n >\n <PlusIcon className=\"w-4 h-4\" />\n </button>\n )}\n </div>\n </div>\n\n {/* Collection list */}\n {!collapsed && (\n <div className=\"flex flex-col\">\n {visibleCollections.length === 0 && hiddenCollections.length === 0 ? (\n <div className=\"px-3 py-2 text-xs text-muted\">No collections</div>\n ) : (\n visibleCollections.map((collection) => (\n <CollectionRow\n key={collection.id}\n collection={collection}\n onToggleVisibility={onToggleVisibility}\n onSetDefault={onSetDefaultCollection}\n onDelete={onDeleteCollection}\n onExport={onExportCollection}\n onUnlock={onUnlockCollection}\n onRename={onRenameCollection}\n onMerge={onMergeCollection}\n borderColorClass={getBorderColor(collection.sourceName)}\n onContextMenu={onHideCollection ? handleOpenContextMenu : undefined}\n />\n ))\n )}\n\n {/* Hidden collections expander */}\n {hiddenCollections.length > 0 && (\n <>\n <button\n onClick={handleToggleHiddenExpanded}\n className=\"flex items-center gap-1 px-3 py-1 text-xs text-muted hover:text-secondary transition-colors\"\n >\n <ChevronRightIcon\n className={`w-3 h-3 transition-transform ${hiddenExpanded ? 'rotate-90' : ''}`}\n />\n {hiddenCollections.length} hidden\n </button>\n {hiddenExpanded &&\n hiddenCollections.map((collection) => (\n <CollectionRow\n key={collection.id}\n collection={collection}\n onToggleVisibility={onToggleVisibility}\n onSetDefault={onSetDefaultCollection}\n onDelete={onDeleteCollection}\n onExport={onExportCollection}\n onUnlock={onUnlockCollection}\n onRename={onRenameCollection}\n onMerge={onMergeCollection}\n borderColorClass={getBorderColor(collection.sourceName)}\n onContextMenu={onShowCollection ? handleOpenContextMenu : undefined}\n isHiddenRow={true}\n />\n ))}\n </>\n )}\n </div>\n )}\n\n {/* Context menu */}\n {contextMenu && contextMenuCollection && (\n <CollectionContextMenu\n menu={contextMenu}\n isHidden={contextMenuCollection.isHidden ?? false}\n onHide={onHideCollection}\n onShow={onShowCollection}\n onClose={handleCloseContextMenu}\n />\n )}\n </div>\n );\n}\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EntityList.js","sourceRoot":"","sources":["../../../src/packlets/sidebar/EntityList.tsx"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport React, { useCallback, useEffect, useRef } from 'react';\nimport { useResponsive } from '../responsive';\n\n// ============================================================================\n// Entity Item Descriptor\n// ============================================================================\n\n/**\n * Describes how to extract display properties from an entity.\n * Consuming apps provide this to configure the list for their entity types.\n * @public\n */\nexport interface IEntityDescriptor<TEntity, TId extends string = string> {\n /** Extract the unique ID from an entity */\n readonly getId: (entity: TEntity) => TId;\n /** Extract the primary display label */\n readonly getLabel: (entity: TEntity) => string;\n /** Extract an optional secondary line (subtitle, description) */\n readonly getSublabel?: (entity: TEntity) => string | undefined;\n /** Extract an optional status badge or indicator */\n readonly getStatus?: (entity: TEntity) => IEntityStatus | undefined;\n}\n\n/**\n * Status indicator for an entity list item.\n * @public\n */\nexport interface IEntityStatus {\n /** Status label */\n readonly label: string;\n /** CSS color class for the status dot */\n readonly colorClass: string;\n}\n\n// ============================================================================\n// EntityList Props\n// ============================================================================\n\n/**\n * Props for the EntityList component.\n * @public\n */\nexport interface IEntityListProps<TEntity, TId extends string = string> {\n /** The entities to display */\n readonly entities: ReadonlyArray<TEntity>;\n /** Descriptor for extracting display properties */\n readonly descriptor: IEntityDescriptor<TEntity, TId>;\n /** Currently selected entity ID (if any) */\n readonly selectedId?: TId;\n /** Callback when an entity is selected (browse — list stays open) */\n readonly onSelect: (id: TId) => void;\n /** Callback when the user drills into the selected entity (Enter/→ — collapses list) */\n readonly onDrill?: () => void;\n /** Whether compare mode is active (shows checkboxes for multi-select) */\n readonly compareMode?: boolean;\n /** Entity IDs currently checked for comparison */\n readonly checkedIds?: ReadonlySet<string>;\n /** Callback to toggle an entity ID in/out of the compare selection */\n readonly onCheckedChange?: (id: TId) => void;\n /** Empty state configuration */\n readonly emptyState?: IEmptyStateConfig;\n /** Optional header content (e.g., result count) */\n readonly header?: React.ReactNode;\n /** Callback to toggle compare mode on/off (shows compare button in header) */\n readonly onToggleCompare?: () => void;\n /** Number of items currently selected for comparison */\n readonly compareCount?: number;\n /** Callback to start the comparison view (user clicks 'Compare Now') */\n readonly onStartComparison?: () => void;\n /**\n * Optional callback to delete an entity.\n * When provided, a delete button appears on each row (visible on hover/selection).\n * The consumer is responsible for showing a confirmation dialog before calling this.\n */\n readonly onDelete?: (id: TId) => void;\n /**\n * Optional predicate to control per-entity delete button visibility.\n * When `onDelete` is provided but `canDelete` is not, all rows show the delete button.\n * When both are provided, only rows where `canDelete(id)` returns true show the button.\n */\n readonly canDelete?: (id: TId) => boolean;\n}\n\n/**\n * Configuration for the empty state display.\n * @public\n */\nexport interface IEmptyStateConfig {\n /** Title for the empty state */\n readonly title: string;\n /** Description text */\n readonly description: string;\n /** Optional CTA button */\n readonly action?: IEmptyStateAction;\n}\n\n/**\n * Call-to-action for the empty state.\n * @public\n */\nexport interface IEmptyStateAction {\n /** Button label */\n readonly label: string;\n /** Callback when clicked */\n readonly onClick: () => void;\n}\n\n// ============================================================================\n// EntityList Component\n// ============================================================================\n\n/**\n * Generic entity list component for the sidebar.\n *\n * Renders a scrollable list of entities with:\n * - Primary label + optional sublabel\n * - Optional status indicator\n * - Selection highlighting\n * - Empty state with optional CTA\n *\n * @public\n */\nexport function EntityList<TEntity, TId extends string = string>(\n props: IEntityListProps<TEntity, TId>\n): React.ReactElement {\n const {\n entities,\n descriptor,\n selectedId,\n onSelect,\n onDrill,\n compareMode,\n checkedIds,\n onCheckedChange,\n emptyState,\n header,\n onDelete,\n canDelete\n } = props;\n const listRef = useRef<HTMLDivElement>(null);\n const { layoutMode } = useResponsive();\n const isMobile = layoutMode === 'mobile';\n\n // Scroll the selected item into view when selection changes\n useEffect(() => {\n if (selectedId && listRef.current) {\n const selectedButton = listRef.current.querySelector(`[data-entity-id=\"${selectedId}\"]`);\n if (selectedButton) {\n selectedButton.scrollIntoView({ block: 'nearest' });\n }\n }\n }, [selectedId]);\n\n // Find the index of the currently selected entity\n const selectedIndex = entities.findIndex((e) => descriptor.getId(e) === selectedId);\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent): void => {\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault();\n const nextIndex = selectedIndex < entities.length - 1 ? selectedIndex + 1 : 0;\n onSelect(descriptor.getId(entities[nextIndex]));\n break;\n }\n case 'ArrowUp': {\n e.preventDefault();\n const prevIndex = selectedIndex > 0 ? selectedIndex - 1 : entities.length - 1;\n onSelect(descriptor.getId(entities[prevIndex]));\n break;\n }\n case 'Enter':\n case 'ArrowRight': {\n if (selectedId !== undefined && onDrill) {\n e.preventDefault();\n onDrill();\n }\n break;\n }\n default:\n break;\n }\n },\n [entities, descriptor, selectedId, selectedIndex, onSelect, onDrill]\n );\n\n if (entities.length === 0 && emptyState) {\n return <EmptyState config={emptyState} />;\n }\n\n return (\n <div className=\"flex flex-col flex-1 overflow-hidden\" onKeyDown={handleKeyDown}>\n {/* Header with item count and optional compare toggle */}\n {header !== undefined ? (\n <div className=\"px-3 py-1.5 text-xs text-muted border-b border-border-subtle\">{header}</div>\n ) : entities.length > 0 ? (\n <div className=\"flex items-center justify-between px-3 py-1.5 border-b border-border-subtle\">\n <span className=\"text-xs text-muted\">\n {entities.length} item{entities.length !== 1 ? 's' : ''}\n {compareMode && props.compareCount !== undefined && props.compareCount > 0 && (\n <span className=\"ml-1.5 text-brand-accent\">· {props.compareCount} selected</span>\n )}\n </span>\n <div className=\"flex items-center gap-1\">\n {compareMode &&\n props.onStartComparison &&\n props.compareCount !== undefined &&\n props.compareCount >= 2 && (\n <button\n onClick={(e): void => {\n e.stopPropagation();\n props.onStartComparison?.();\n }}\n className=\"px-2 py-0.5 text-[11px] rounded border transition-colors bg-brand-primary text-white border-brand-primary hover:bg-brand-primary/90\"\n >\n Compare Now\n </button>\n )}\n {props.onToggleCompare && (\n <button\n onClick={(e): void => {\n e.stopPropagation();\n props.onToggleCompare?.();\n }}\n className={`px-2 py-0.5 text-[11px] rounded border transition-colors ${\n compareMode\n ? 'bg-brand-accent text-white border-brand-accent'\n : 'bg-surface text-muted border-border hover:border-brand-accent hover:text-brand-accent'\n }`}\n >\n {compareMode ? 'Cancel' : 'Compare'}\n </button>\n )}\n </div>\n </div>\n ) : null}\n\n {/* List */}\n <div ref={listRef} className=\"flex-1 overflow-y-auto\">\n {entities.map((entity) => {\n const id = descriptor.getId(entity);\n const label = descriptor.getLabel(entity);\n const sublabel = descriptor.getSublabel?.(entity);\n const status = descriptor.getStatus?.(entity);\n const isSelected = id === selectedId;\n const isChecked = compareMode === true && checkedIds !== undefined && checkedIds.has(id);\n\n return (\n <div\n key={id}\n className={`group flex items-center gap-2 w-full border-b border-border-subtle transition-colors ${\n isChecked\n ? 'bg-selected border-l-2 border-l-selected-border'\n : isSelected && !compareMode\n ? 'bg-selected border-l-2 border-l-selected-border'\n : 'hover:bg-hover border-l-2 border-l-transparent'\n }`}\n >\n <button\n data-entity-id={id}\n onClick={(): void => {\n if (compareMode && onCheckedChange) {\n onCheckedChange(id);\n } else if (isSelected && onDrill) {\n onDrill();\n } else {\n onSelect(id);\n }\n }}\n className={`flex items-center gap-2 flex-1 min-w-0 px-3 text-left ${\n isMobile ? 'py-3' : 'py-2'\n }`}\n >\n {compareMode && (\n <span\n className={`flex items-center justify-center w-4 h-4 rounded border shrink-0 transition-colors ${\n isChecked\n ? 'bg-brand-accent border-brand-accent text-white'\n : 'border-border bg-surface'\n }`}\n >\n {isChecked && (\n <svg\n className=\"w-3 h-3\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={3}\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n )}\n </span>\n )}\n <div className=\"flex-1 min-w-0\">\n <div\n className={`text-sm truncate ${\n (isSelected && !compareMode) || isChecked\n ? 'font-medium text-brand-primary'\n : 'text-primary'\n }`}\n >\n {label}\n </div>\n {sublabel && <div className=\"text-xs text-muted truncate mt-0.5\">{sublabel}</div>}\n </div>\n {status && (\n <span className=\"flex items-center gap-1 shrink-0 mt-0.5\">\n <span className={`w-2 h-2 rounded-full ${status.colorClass}`} />\n <span className=\"text-xs text-muted\">{status.label}</span>\n </span>\n )}\n </button>\n\n {onDelete && !compareMode && (!canDelete || canDelete(id)) && (\n <button\n onClick={(e): void => {\n e.stopPropagation();\n onDelete(id);\n }}\n className={`shrink-0 mr-1 flex items-center justify-center text-muted hover:text-status-error-text transition-opacity rounded ${\n isMobile\n ? 'w-11 h-11 opacity-100'\n : 'w-6 h-6 opacity-0 group-hover:opacity-100 focus:opacity-100'\n }`}\n title={`Delete ${label}`}\n aria-label={`Delete ${label}`}\n >\n <svg\n className=\"w-3.5 h-3.5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={2}\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\"\n />\n </svg>\n </button>\n )}\n </div>\n );\n })}\n </div>\n </div>\n );\n}\n\n// ============================================================================\n// Empty State\n// ============================================================================\n\nfunction EmptyState({ config }: { readonly config: IEmptyStateConfig }): React.ReactElement {\n return (\n <div className=\"flex flex-col items-center justify-center flex-1 p-6 text-center\">\n <h3 className=\"text-sm font-medium text-secondary mb-1\">{config.title}</h3>\n <p className=\"text-xs text-muted mb-4\">{config.description}</p>\n {config.action && (\n <button\n onClick={config.action.onClick}\n className=\"px-3 py-1.5 text-xs font-medium text-white bg-brand-accent rounded-md hover:bg-brand-primary transition-colors\"\n >\n {config.action.label}\n </button>\n )}\n </div>\n );\n}\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilterBar.js","sourceRoot":"","sources":["../../../src/packlets/sidebar/FilterBar.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,SAAS,EAAwB,MAAM,aAAa,CAAC;AAiB9D;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CAAC,KAAsB;IAC9C,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IAElE,MAAM,gBAAgB,GAAG,iBAAiB,GAAG,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IAE1E,OAAO,CACL,6BAAK,SAAS,EAAC,eAAe;QAE5B,oBAAC,SAAS,oBAAK,MAAM,EAAI;QAGzB,6BAAK,SAAS,EAAC,6CAA6C;YAC1D,8BAAM,SAAS,EAAC,yDAAyD,cAAe;YACvF,gBAAgB,IAAI,CACnB,gCAAQ,OAAO,EAAE,UAAU,EAAE,SAAS,EAAC,oDAAoD,gBAElF,CACV,CACG;QAGN,6BAAK,SAAS,EAAC,eAAe,IAAE,QAAQ,CAAO,CAC3C,CACP,CAAC;AACJ,CAAC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport React from 'react';\n\nimport { SearchBar, type ISearchBarProps } from './SearchBar';\n\n/**\n * Props for the FilterBar component.\n * @public\n */\nexport interface IFilterBarProps {\n /** Search bar props */\n readonly search: ISearchBarProps;\n /** Total number of active filters across all filter rows */\n readonly activeFilterCount: number;\n /** Callback to clear all filters (search + all filter rows) */\n readonly onClearAll: () => void;\n /** Filter row children (FilterRow components) */\n readonly children: React.ReactNode;\n}\n\n/**\n * Composite sidebar filter bar: search input + filter rows + clear-all.\n *\n * Renders the search bar at the top, filter rows in the middle,\n * and a \"Clear all\" button when any filters are active.\n *\n * @public\n */\nexport function FilterBar(props: IFilterBarProps): React.ReactElement {\n const { search, activeFilterCount, onClearAll, children } = props;\n\n const hasActiveFilters = activeFilterCount > 0 || search.value.length > 0;\n\n return (\n <div className=\"flex flex-col\">\n {/* Search */}\n <SearchBar {...search} />\n\n {/* Filter header with clear-all */}\n <div className=\"flex items-center justify-between px-3 py-1\">\n <span className=\"text-xs font-medium text-muted uppercase tracking-wider\">Filters</span>\n {hasActiveFilters && (\n <button onClick={onClearAll} className=\"text-xs text-brand-accent hover:text-brand-primary\">\n Clear all\n </button>\n )}\n </div>\n\n {/* Filter rows */}\n <div className=\"flex flex-col\">{children}</div>\n </div>\n );\n}\n"]}