@fgv/ts-app-shell 5.1.0-4 → 5.1.0-40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +182 -0
- package/dist/index.browser.js.map +1 -0
- package/dist/index.js.map +1 -0
- package/dist/packlets/ai-assist/index.js.map +1 -0
- package/dist/packlets/ai-assist/useAiAssist.js +225 -18
- package/dist/packlets/ai-assist/useAiAssist.js.map +1 -0
- package/dist/packlets/badge/index.js +23 -0
- package/dist/packlets/badge/index.js.map +1 -0
- package/dist/packlets/badge/renderStatusBadge.js +44 -0
- package/dist/packlets/badge/renderStatusBadge.js.map +1 -0
- package/dist/packlets/cascade/CascadeContainer.js.map +1 -0
- package/dist/packlets/cascade/ComparisonView.js.map +1 -0
- package/dist/packlets/cascade/EntityTabLayout.js.map +1 -0
- package/dist/packlets/cascade/MobileCascadeStack.js.map +1 -0
- package/dist/packlets/cascade/index.js.map +1 -0
- package/dist/packlets/cascade/model.js.map +1 -0
- package/dist/packlets/cascade/useCascadeOps.js.map +1 -0
- package/dist/packlets/cascade/useCascadeTransitions.js.map +1 -0
- package/dist/packlets/detail/DetailHelpers.js.map +1 -0
- package/dist/packlets/detail/index.js.map +1 -0
- package/dist/packlets/drop-zone/JsonDropZone.js.map +1 -0
- package/dist/packlets/drop-zone/index.js.map +1 -0
- package/dist/packlets/editing/EditFieldHelpers.js +4 -4
- package/dist/packlets/editing/EditFieldHelpers.js.map +1 -0
- package/dist/packlets/editing/MultiActionButton.js.map +1 -0
- package/dist/packlets/editing/NumericInput.js.map +1 -0
- package/dist/packlets/editing/TypeaheadInput.js.map +1 -0
- package/dist/packlets/editing/index.js.map +1 -0
- package/dist/packlets/editing/useTypeaheadMatch.js.map +1 -0
- package/dist/packlets/keyboard/index.js.map +1 -0
- package/dist/packlets/keyboard/registry.js.map +1 -0
- package/dist/packlets/keyboard/useKeyboardShortcuts.js.map +1 -0
- package/dist/packlets/messages/MessagesContext.js +79 -3
- package/dist/packlets/messages/MessagesContext.js.map +1 -0
- package/dist/packlets/messages/MessagesLogger.js +10 -31
- package/dist/packlets/messages/MessagesLogger.js.map +1 -0
- package/dist/packlets/messages/StatusBar.js +53 -28
- package/dist/packlets/messages/StatusBar.js.map +1 -0
- package/dist/packlets/messages/Toast.js +7 -4
- package/dist/packlets/messages/Toast.js.map +1 -0
- package/dist/packlets/messages/index.js +2 -2
- package/dist/packlets/messages/index.js.map +1 -0
- package/dist/packlets/messages/model.js +31 -4
- package/dist/packlets/messages/model.js.map +1 -0
- package/dist/packlets/messages/useLogReporter.js.map +1 -0
- package/dist/packlets/modal/ConfirmDialog.js.map +1 -0
- package/dist/packlets/modal/Modal.js.map +1 -0
- package/dist/packlets/modal/index.js.map +1 -0
- package/dist/packlets/print/PrintEnclosure.js.map +1 -0
- package/dist/packlets/print/index.js.map +1 -0
- package/dist/packlets/print/openPrintWindow.js.map +1 -0
- package/dist/packlets/responsive/ResponsiveProvider.js.map +1 -0
- package/dist/packlets/responsive/index.js.map +1 -0
- package/dist/packlets/responsive/useResponsiveLayout.js.map +1 -0
- package/dist/packlets/selectors/EntityRow.js.map +1 -0
- package/dist/packlets/selectors/PreferredSelector.js.map +1 -0
- package/dist/packlets/selectors/index.js.map +1 -0
- package/dist/packlets/sidebar/CollectionSection.js +158 -43
- package/dist/packlets/sidebar/CollectionSection.js.map +1 -0
- package/dist/packlets/sidebar/EntityList.js.map +1 -0
- package/dist/packlets/sidebar/FilterBar.js.map +1 -0
- package/dist/packlets/sidebar/FilterRow.js.map +1 -0
- package/dist/packlets/sidebar/GroupedEntityList.js.map +1 -0
- package/dist/packlets/sidebar/SearchBar.js.map +1 -0
- package/dist/packlets/sidebar/SidebarLayout.js.map +1 -0
- package/dist/packlets/sidebar/index.js.map +1 -0
- package/dist/packlets/theme/ThemeProvider.js.map +1 -0
- package/dist/packlets/theme/index.js.map +1 -0
- package/dist/packlets/top-bar/ModeSelector.js.map +1 -0
- package/dist/packlets/top-bar/TabBar.js +4 -1
- package/dist/packlets/top-bar/TabBar.js.map +1 -0
- package/dist/packlets/top-bar/index.js.map +1 -0
- package/dist/packlets/url-sync/index.js.map +1 -0
- package/dist/packlets/url-sync/useUrlSync.js.map +1 -0
- package/dist/styles/theme.css +194 -0
- package/lib/index.browser.d.ts.map +1 -0
- package/lib/index.browser.js.map +1 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js.map +1 -0
- package/lib/packlets/ai-assist/index.d.ts.map +1 -0
- package/lib/packlets/ai-assist/index.js.map +1 -0
- package/lib/packlets/ai-assist/useAiAssist.d.ts +26 -0
- package/lib/packlets/ai-assist/useAiAssist.d.ts.map +1 -0
- package/lib/packlets/ai-assist/useAiAssist.js +225 -18
- package/lib/packlets/ai-assist/useAiAssist.js.map +1 -0
- package/lib/packlets/badge/index.d.ts +2 -0
- package/lib/packlets/badge/index.d.ts.map +1 -0
- package/lib/packlets/badge/index.js +27 -0
- package/lib/packlets/badge/index.js.map +1 -0
- package/lib/packlets/badge/renderStatusBadge.d.ts +11 -0
- package/lib/packlets/badge/renderStatusBadge.d.ts.map +1 -0
- package/lib/packlets/badge/renderStatusBadge.js +50 -0
- package/lib/packlets/badge/renderStatusBadge.js.map +1 -0
- package/lib/packlets/cascade/CascadeContainer.d.ts.map +1 -0
- package/lib/packlets/cascade/CascadeContainer.js.map +1 -0
- package/lib/packlets/cascade/ComparisonView.d.ts.map +1 -0
- package/lib/packlets/cascade/ComparisonView.js.map +1 -0
- package/lib/packlets/cascade/EntityTabLayout.d.ts.map +1 -0
- package/lib/packlets/cascade/EntityTabLayout.js.map +1 -0
- package/lib/packlets/cascade/MobileCascadeStack.d.ts.map +1 -0
- package/lib/packlets/cascade/MobileCascadeStack.js.map +1 -0
- package/lib/packlets/cascade/index.d.ts.map +1 -0
- package/lib/packlets/cascade/index.js.map +1 -0
- package/lib/packlets/cascade/model.d.ts.map +1 -0
- package/lib/packlets/cascade/model.js.map +1 -0
- package/lib/packlets/cascade/useCascadeOps.d.ts.map +1 -0
- package/lib/packlets/cascade/useCascadeOps.js.map +1 -0
- package/lib/packlets/cascade/useCascadeTransitions.d.ts.map +1 -0
- package/lib/packlets/cascade/useCascadeTransitions.js.map +1 -0
- package/lib/packlets/detail/DetailHelpers.d.ts.map +1 -0
- package/lib/packlets/detail/DetailHelpers.js.map +1 -0
- package/lib/packlets/detail/index.d.ts.map +1 -0
- package/lib/packlets/detail/index.js.map +1 -0
- package/lib/packlets/drop-zone/JsonDropZone.d.ts.map +1 -0
- package/lib/packlets/drop-zone/JsonDropZone.js.map +1 -0
- package/lib/packlets/drop-zone/index.d.ts.map +1 -0
- package/lib/packlets/drop-zone/index.js.map +1 -0
- package/lib/packlets/editing/EditFieldHelpers.d.ts +6 -2
- package/lib/packlets/editing/EditFieldHelpers.d.ts.map +1 -0
- package/lib/packlets/editing/EditFieldHelpers.js +4 -4
- package/lib/packlets/editing/EditFieldHelpers.js.map +1 -0
- package/lib/packlets/editing/MultiActionButton.d.ts.map +1 -0
- package/lib/packlets/editing/MultiActionButton.js.map +1 -0
- package/lib/packlets/editing/NumericInput.d.ts.map +1 -0
- package/lib/packlets/editing/NumericInput.js.map +1 -0
- package/lib/packlets/editing/TypeaheadInput.d.ts.map +1 -0
- package/lib/packlets/editing/TypeaheadInput.js.map +1 -0
- package/lib/packlets/editing/index.d.ts.map +1 -0
- package/lib/packlets/editing/index.js.map +1 -0
- package/lib/packlets/editing/useTypeaheadMatch.d.ts.map +1 -0
- package/lib/packlets/editing/useTypeaheadMatch.js.map +1 -0
- package/lib/packlets/keyboard/index.d.ts.map +1 -0
- package/lib/packlets/keyboard/index.js.map +1 -0
- package/lib/packlets/keyboard/registry.d.ts.map +1 -0
- package/lib/packlets/keyboard/registry.js.map +1 -0
- package/lib/packlets/keyboard/useKeyboardShortcuts.d.ts.map +1 -0
- package/lib/packlets/keyboard/useKeyboardShortcuts.js.map +1 -0
- package/lib/packlets/messages/MessagesContext.d.ts +47 -3
- package/lib/packlets/messages/MessagesContext.d.ts.map +1 -0
- package/lib/packlets/messages/MessagesContext.js +79 -2
- package/lib/packlets/messages/MessagesContext.js.map +1 -0
- package/lib/packlets/messages/MessagesLogger.d.ts +8 -3
- package/lib/packlets/messages/MessagesLogger.d.ts.map +1 -0
- package/lib/packlets/messages/MessagesLogger.js +10 -31
- package/lib/packlets/messages/MessagesLogger.js.map +1 -0
- package/lib/packlets/messages/StatusBar.d.ts +10 -1
- package/lib/packlets/messages/StatusBar.d.ts.map +1 -0
- package/lib/packlets/messages/StatusBar.js +52 -27
- package/lib/packlets/messages/StatusBar.js.map +1 -0
- package/lib/packlets/messages/Toast.d.ts.map +1 -0
- package/lib/packlets/messages/Toast.js +6 -3
- package/lib/packlets/messages/Toast.js.map +1 -0
- package/lib/packlets/messages/index.d.ts +2 -2
- package/lib/packlets/messages/index.d.ts.map +1 -0
- package/lib/packlets/messages/index.js +6 -1
- package/lib/packlets/messages/index.js.map +1 -0
- package/lib/packlets/messages/model.d.ts +74 -8
- package/lib/packlets/messages/model.d.ts.map +1 -0
- package/lib/packlets/messages/model.js +32 -4
- package/lib/packlets/messages/model.js.map +1 -0
- package/lib/packlets/messages/useLogReporter.d.ts.map +1 -0
- package/lib/packlets/messages/useLogReporter.js.map +1 -0
- package/lib/packlets/modal/ConfirmDialog.d.ts.map +1 -0
- package/lib/packlets/modal/ConfirmDialog.js.map +1 -0
- package/lib/packlets/modal/Modal.d.ts.map +1 -0
- package/lib/packlets/modal/Modal.js.map +1 -0
- package/lib/packlets/modal/index.d.ts.map +1 -0
- package/lib/packlets/modal/index.js.map +1 -0
- package/lib/packlets/print/PrintEnclosure.d.ts.map +1 -0
- package/lib/packlets/print/PrintEnclosure.js.map +1 -0
- package/lib/packlets/print/index.d.ts.map +1 -0
- package/lib/packlets/print/index.js.map +1 -0
- package/lib/packlets/print/openPrintWindow.d.ts.map +1 -0
- package/lib/packlets/print/openPrintWindow.js.map +1 -0
- package/lib/packlets/responsive/ResponsiveProvider.d.ts.map +1 -0
- package/lib/packlets/responsive/ResponsiveProvider.js.map +1 -0
- package/lib/packlets/responsive/index.d.ts.map +1 -0
- package/lib/packlets/responsive/index.js.map +1 -0
- package/lib/packlets/responsive/useResponsiveLayout.d.ts.map +1 -0
- package/lib/packlets/responsive/useResponsiveLayout.js.map +1 -0
- package/lib/packlets/selectors/EntityRow.d.ts.map +1 -0
- package/lib/packlets/selectors/EntityRow.js.map +1 -0
- package/lib/packlets/selectors/PreferredSelector.d.ts.map +1 -0
- package/lib/packlets/selectors/PreferredSelector.js.map +1 -0
- package/lib/packlets/selectors/index.d.ts.map +1 -0
- package/lib/packlets/selectors/index.js.map +1 -0
- package/lib/packlets/sidebar/CollectionSection.d.ts +40 -2
- package/lib/packlets/sidebar/CollectionSection.d.ts.map +1 -0
- package/lib/packlets/sidebar/CollectionSection.js +157 -42
- package/lib/packlets/sidebar/CollectionSection.js.map +1 -0
- package/lib/packlets/sidebar/EntityList.d.ts.map +1 -0
- package/lib/packlets/sidebar/EntityList.js.map +1 -0
- package/lib/packlets/sidebar/FilterBar.d.ts.map +1 -0
- package/lib/packlets/sidebar/FilterBar.js.map +1 -0
- package/lib/packlets/sidebar/FilterRow.d.ts.map +1 -0
- package/lib/packlets/sidebar/FilterRow.js.map +1 -0
- package/lib/packlets/sidebar/GroupedEntityList.d.ts.map +1 -0
- package/lib/packlets/sidebar/GroupedEntityList.js.map +1 -0
- package/lib/packlets/sidebar/SearchBar.d.ts.map +1 -0
- package/lib/packlets/sidebar/SearchBar.js.map +1 -0
- package/lib/packlets/sidebar/SidebarLayout.d.ts.map +1 -0
- package/lib/packlets/sidebar/SidebarLayout.js.map +1 -0
- package/lib/packlets/sidebar/index.d.ts +1 -1
- package/lib/packlets/sidebar/index.d.ts.map +1 -0
- package/lib/packlets/sidebar/index.js.map +1 -0
- package/lib/packlets/theme/ThemeProvider.d.ts.map +1 -0
- package/lib/packlets/theme/ThemeProvider.js.map +1 -0
- package/lib/packlets/theme/index.d.ts.map +1 -0
- package/lib/packlets/theme/index.js.map +1 -0
- package/lib/packlets/top-bar/ModeSelector.d.ts.map +1 -0
- package/lib/packlets/top-bar/ModeSelector.js.map +1 -0
- package/lib/packlets/top-bar/TabBar.d.ts +12 -0
- package/lib/packlets/top-bar/TabBar.d.ts.map +1 -0
- package/lib/packlets/top-bar/TabBar.js +4 -1
- package/lib/packlets/top-bar/TabBar.js.map +1 -0
- package/lib/packlets/top-bar/index.d.ts +1 -1
- package/lib/packlets/top-bar/index.d.ts.map +1 -0
- package/lib/packlets/top-bar/index.js.map +1 -0
- package/lib/packlets/url-sync/index.d.ts.map +1 -0
- package/lib/packlets/url-sync/index.js.map +1 -0
- package/lib/packlets/url-sync/useUrlSync.d.ts.map +1 -0
- package/lib/packlets/url-sync/useUrlSync.js.map +1 -0
- package/lib/styles/theme.css +194 -0
- package/package.json +11 -7
- package/tailwind-preset.cjs +102 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConfirmDialog.js","sourceRoot":"","sources":["../../../src/packlets/modal/ConfirmDialog.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 } from 'react';\nimport { useResponsive } from '../responsive';\n\n/**\n * Severity level for the confirm dialog.\n * Controls the color of the confirm button.\n * @public\n */\nexport type ConfirmDialogSeverity = 'danger' | 'warning' | 'info';\n\n/**\n * Props for the ConfirmDialog component.\n * @public\n */\nexport interface IConfirmDialogProps {\n /** Whether the dialog is open */\n readonly isOpen: boolean;\n /** Dialog title */\n readonly title: string;\n /** Dialog message body */\n readonly message: React.ReactNode;\n /** Label for the confirm button (defaults to 'Confirm') */\n readonly confirmLabel?: string;\n /** Label for the cancel button (defaults to 'Cancel') */\n readonly cancelLabel?: string;\n /** Severity level controlling confirm button color (defaults to 'danger') */\n readonly severity?: ConfirmDialogSeverity;\n /** Called when the user confirms */\n readonly onConfirm: () => void;\n /** Called when the user cancels or closes */\n readonly onCancel: () => void;\n}\n\nconst severityClasses: Record<ConfirmDialogSeverity, string> = {\n danger: 'bg-status-error-btn hover:bg-status-error-btn-hover focus:ring-status-error-accent',\n warning: 'bg-status-warning-btn hover:bg-status-warning-btn-hover focus:ring-status-warning-strong',\n info: 'bg-status-info-btn hover:bg-status-info-btn-hover focus:ring-status-info-icon'\n};\n\nconst severityIconClasses: Record<ConfirmDialogSeverity, string> = {\n danger: 'text-status-error-icon',\n warning: 'text-status-warning-icon',\n info: 'text-status-info-icon'\n};\n\n/**\n * Generic confirmation dialog.\n *\n * Renders a modal overlay with a title, message, and confirm/cancel buttons.\n * Closes on Escape key or backdrop click (treated as cancel).\n *\n * @public\n */\nexport function ConfirmDialog(props: IConfirmDialogProps): React.ReactElement | null {\n const {\n isOpen,\n title,\n message,\n confirmLabel = 'Confirm',\n cancelLabel = 'Cancel',\n severity = 'danger',\n onConfirm,\n onCancel\n } = props;\n const { layoutMode } = useResponsive();\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent): void => {\n if (e.key === 'Escape') {\n onCancel();\n }\n },\n [onCancel]\n );\n\n useEffect(() => {\n if (isOpen) {\n document.addEventListener('keydown', handleKeyDown);\n return (): void => document.removeEventListener('keydown', handleKeyDown);\n }\n return undefined;\n }, [isOpen, handleKeyDown]);\n\n if (!isOpen) {\n return null;\n }\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n {/* Backdrop */}\n <div className=\"absolute inset-0 bg-backdrop\" onClick={onCancel} role=\"presentation\" />\n\n {/* Dialog */}\n <div\n className={`relative bg-surface rounded-lg shadow-xl ${\n layoutMode === 'mobile' ? 'w-[95vw]' : 'max-w-md w-full mx-4'\n }`}\n role=\"alertdialog\"\n aria-modal=\"true\"\n aria-labelledby=\"confirm-dialog-title\"\n aria-describedby=\"confirm-dialog-message\"\n >\n <div className=\"p-6\">\n <div className=\"flex items-start gap-4\">\n {/* Icon */}\n <div\n className={`flex-shrink-0 w-10 h-10 flex items-center justify-center rounded-full ${\n severity === 'danger'\n ? 'bg-status-error-bg'\n : severity === 'warning'\n ? 'bg-status-warning-bg'\n : 'bg-status-info-bg'\n }`}\n >\n <svg\n className={`w-6 h-6 ${severityIconClasses[severity]}`}\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={2}\n >\n {severity === 'info' ? (\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n d=\"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n ) : (\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n )}\n </svg>\n </div>\n\n {/* Content */}\n <div className=\"flex-1 min-w-0\">\n <h3 id=\"confirm-dialog-title\" className=\"text-base font-semibold text-primary leading-6\">\n {title}\n </h3>\n <div id=\"confirm-dialog-message\" className=\"mt-2 text-sm text-secondary\">\n {typeof message === 'string' ? <p>{message}</p> : message}\n </div>\n </div>\n </div>\n\n {/* Buttons */}\n <div className=\"mt-6 flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"px-4 py-2 text-sm font-medium text-secondary bg-surface border border-border rounded-md hover:bg-hover focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-focus-ring transition-colors\"\n >\n {cancelLabel}\n </button>\n <button\n type=\"button\"\n onClick={onConfirm}\n className={`px-4 py-2 text-sm font-medium text-white rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 transition-colors ${severityClasses[severity]}`}\n >\n {confirmLabel}\n </button>\n </div>\n </div>\n </div>\n </div>\n );\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Modal.js","sourceRoot":"","sources":["../../../src/packlets/modal/Modal.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAiB9C;;;;GAIG;AACH,MAAM,UAAU,KAAK,CAAC,KAAkB;IACtC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IACnD,MAAM,EAAE,UAAU,EAAE,GAAG,aAAa,EAAE,CAAC;IAEvC,MAAM,aAAa,GAAG,WAAW,CAC/B,CAAC,CAAgB,EAAQ,EAAE;QACzB,IAAI,CAAC,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;YACvB,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC,EACD,CAAC,OAAO,CAAC,CACV,CAAC;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,MAAM,EAAE,CAAC;YACX,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;YACpD,OAAO,GAAS,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;QAC5E,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC;IAE5B,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,CACL,6BAAK,SAAS,EAAC,qDAAqD;QAElE,6BAAK,SAAS,EAAC,8BAA8B,EAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAC,cAAc,GAAG;QAGtF,6BACE,SAAS,EAAE,0DACT,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,mCACtD,EAAE,EACF,IAAI,EAAC,QAAQ,gBACF,MAAM,gBACL,KAAK;YAGjB,6BAAK,SAAS,EAAC,oEAAoE;gBACjF,4BAAI,SAAS,EAAC,oCAAoC,IAAE,KAAK,CAAM;gBAC/D,gCACE,OAAO,EAAE,OAAO,EAChB,SAAS,EAAC,mDAAmD,gBAClD,OAAO,aAGX,CACL;YAGN,6BAAK,SAAS,EAAC,kCAAkC,IAAE,QAAQ,CAAO,CAC9D,CACF,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, { useCallback, useEffect } from 'react';\nimport { useResponsive } from '../responsive';\n\n/**\n * Props for the Modal component.\n * @public\n */\nexport interface IModalProps {\n /** Whether the modal is open */\n readonly isOpen: boolean;\n /** Callback to close the modal */\n readonly onClose: () => void;\n /** Modal title */\n readonly title: string;\n /** Modal content */\n readonly children: React.ReactNode;\n}\n\n/**\n * Generic modal overlay component.\n * Renders a centered dialog with a backdrop that closes on click or Escape.\n * @public\n */\nexport function Modal(props: IModalProps): React.ReactElement | null {\n const { isOpen, onClose, title, children } = props;\n const { layoutMode } = useResponsive();\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent): void => {\n if (e.key === 'Escape') {\n onClose();\n }\n },\n [onClose]\n );\n\n useEffect(() => {\n if (isOpen) {\n document.addEventListener('keydown', handleKeyDown);\n return (): void => document.removeEventListener('keydown', handleKeyDown);\n }\n return undefined;\n }, [isOpen, handleKeyDown]);\n\n if (!isOpen) {\n return null;\n }\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n {/* Backdrop */}\n <div className=\"absolute inset-0 bg-backdrop\" onClick={onClose} role=\"presentation\" />\n\n {/* Dialog */}\n <div\n className={`relative bg-surface rounded-lg shadow-xl flex flex-col ${\n layoutMode === 'mobile' ? 'w-[95vw] max-h-[95vh]' : 'max-w-lg w-full mx-4 max-h-[80vh]'\n }`}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={title}\n >\n {/* Header */}\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-border\">\n <h2 className=\"text-lg font-semibold text-primary\">{title}</h2>\n <button\n onClick={onClose}\n className=\"text-muted hover:text-secondary transition-colors\"\n aria-label=\"Close\"\n >\n ×\n </button>\n </div>\n\n {/* Content */}\n <div className=\"px-6 py-4 overflow-y-auto flex-1\">{children}</div>\n </div>\n </div>\n );\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/modal/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,KAAK,EAAoB,MAAM,SAAS,CAAC;AAElD,OAAO,EAAE,aAAa,EAAwD,MAAM,iBAAiB,CAAC","sourcesContent":["/**\n * Modal packlet - generic overlay dialog component.\n * @packageDocumentation\n */\n\nexport { Modal, type IModalProps } from './Modal';\n\nexport { ConfirmDialog, type IConfirmDialogProps, type ConfirmDialogSeverity } from './ConfirmDialog';\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PrintEnclosure.js","sourceRoot":"","sources":["../../../src/packlets/print/PrintEnclosure.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH;;;GAGG;AAEH,OAAO,KAAK,EAAE,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAiBrE;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,cAAc,CAAC,EAC7B,KAAK,EACL,aAAa,EACb,WAAW,EACX,QAAQ,EACa;IACrB,MAAM,WAAW,GAAG,WAAW,CAAC,GAAS,EAAE;QACzC,WAAW,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;IAElB,MAAM,WAAW,GAAG,WAAW,CAAC,GAAS,EAAE;QACzC,WAAW,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;IAElB,OAAO,CACL;QAEE,6BAAK,SAAS,EAAC,oIAAoI;YACjJ,4BAAI,SAAS,EAAC,6CAA6C,IAAE,KAAK,CAAM;YACxE,6BAAK,SAAS,EAAC,kCAAkC;gBAC9C,aAAa;gBACd,gCACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,WAAW,EACpB,SAAS,EAAC,qJAAqJ;oBAE/J,oBAAC,WAAW,IAAC,SAAS,EAAC,SAAS,GAAG;4BAE5B;gBACT,gCACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,WAAW,EACpB,SAAS,EAAC,wJAAwJ;oBAElK,oBAAC,SAAS,IAAC,SAAS,EAAC,SAAS,GAAG;4BAE1B,CACL,CACF;QAGN,6BAAK,SAAS,EAAC,wBAAwB,IAAE,QAAQ,CAAO,CACpD,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\n/**\n * Print enclosure component rendered inside the popup window.\n * @packageDocumentation\n */\n\nimport React, { useCallback } from 'react';\nimport { PrinterIcon, XMarkIcon } from '@heroicons/react/24/outline';\n\n/**\n * Props for {@link PrintEnclosure}.\n * @public\n */\nexport interface IPrintEnclosureProps {\n /** Title displayed in the toolbar. */\n readonly title: string;\n /** Optional extra controls rendered in the toolbar (e.g. toggles). */\n readonly toolbarExtras?: React.ReactNode;\n /** Reference to the popup window (used for print and close actions). */\n readonly popupWindow: Window;\n /** Content to display and print. */\n readonly children: React.ReactNode;\n}\n\n/**\n * A container component rendered inside a popup window that provides a\n * toolbar with Print and Close buttons and renders children as the\n * printable content.\n *\n * @remarks\n * The toolbar is hidden when printing via CSS (`print-toolbar` class).\n * Children are rendered at full width with no scroll constraints so\n * multi-page content flows naturally to the printer.\n *\n * @public\n */\nexport function PrintEnclosure({\n title,\n toolbarExtras,\n popupWindow,\n children\n}: IPrintEnclosureProps): React.JSX.Element {\n const handlePrint = useCallback((): void => {\n popupWindow.print();\n }, [popupWindow]);\n\n const handleClose = useCallback((): void => {\n popupWindow.close();\n }, [popupWindow]);\n\n return (\n <div>\n {/* Toolbar - hidden when printing */}\n <div className=\"print-toolbar sticky top-0 z-10 flex items-center justify-between gap-4 bg-surface border-b border-border px-6 py-3 mb-6 shadow-sm\">\n <h1 className=\"text-lg font-semibold text-primary truncate\">{title}</h1>\n <div className=\"flex items-center gap-3 shrink-0\">\n {toolbarExtras}\n <button\n type=\"button\"\n onClick={handlePrint}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-white bg-brand-primary hover:bg-brand-primary/90 rounded-md transition-colors\"\n >\n <PrinterIcon className=\"w-4 h-4\" />\n Print\n </button>\n <button\n type=\"button\"\n onClick={handleClose}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-secondary bg-surface-raised hover:bg-surface-raised rounded-md transition-colors\"\n >\n <XMarkIcon className=\"w-4 h-4\" />\n Close\n </button>\n </div>\n </div>\n\n {/* Printable content */}\n <div className=\"max-w-4xl mx-auto px-6\">{children}</div>\n </div>\n );\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/print/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,eAAe,EAA4B,MAAM,mBAAmB,CAAC;AAC9E,OAAO,EAAE,cAAc,EAA6B,MAAM,kBAAkB,CAAC","sourcesContent":["/**\n * Print packlet - popup window for print-optimized content rendering.\n * @packageDocumentation\n */\n\nexport { openPrintWindow, type IPrintWindowOptions } from './openPrintWindow';\nexport { PrintEnclosure, type IPrintEnclosureProps } from './PrintEnclosure';\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openPrintWindow.js","sourceRoot":"","sources":["../../../src/packlets/print/openPrintWindow.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH;;;GAGG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAiBlD;;;GAGG;AACH,SAAS,WAAW,CAAC,MAAgB,EAAE,MAAgB;IACrD,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC;IAC5D,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IACjD,CAAC;IACD,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,wBAAwB,CAAC,CAAC,CAAC;IAC5E,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IAChD,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,eAAe,CAAC,OAA2B,EAAE,OAA4B;IACvF,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,GAAG,EAAE,MAAM,GAAG,GAAG,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC;IAEpE,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,SAAS,KAAK,WAAW,MAAM,EAAE,CAAC,CAAC;IACrE,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC;;;;WAIZ,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA8B9B,CAAC,CAAC;IACR,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;IAEvB,WAAW,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IAEtC,MAAM,SAAS,GAAG,KAAK,CAAC,QAAQ,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC;IAC9D,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,KAAK,CAAC,KAAK,EAAE,CAAC;QACd,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,IAAI,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;IACnC,IAAI,CAAC,MAAM,CACT,oBAAC,cAAc,IAAC,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,KAAK,IAC3E,OAAO,CACO,CAClB,CAAC;IAEF,KAAK,CAAC,gBAAgB,CAAC,cAAc,EAAE,GAAG,EAAE;QAC1C,IAAI,CAAC,OAAO,EAAE,CAAC;IACjB,CAAC,CAAC,CAAC;IAEH,OAAO,KAAK,CAAC;AACf,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\n/**\n * Opens a popup window for print-optimized content rendering.\n * @packageDocumentation\n */\n\nimport React from 'react';\nimport { createRoot } from 'react-dom/client';\n\nimport { PrintEnclosure } from './PrintEnclosure';\n\n/**\n * Options for {@link openPrintWindow}.\n * @public\n */\nexport interface IPrintWindowOptions {\n /** Title for the popup window. */\n readonly title: string;\n /** Width of the popup window in pixels (default 900). */\n readonly width?: number;\n /** Height of the popup window in pixels (default 700). */\n readonly height?: number;\n /** Extra toolbar controls to render in the print enclosure header. */\n readonly toolbarExtras?: React.ReactNode;\n}\n\n/**\n * Clones all stylesheet-related elements from the source document head\n * into the target document head so that styles are available in the popup.\n */\nfunction cloneStyles(source: Document, target: Document): void {\n const styles = Array.from(source.querySelectorAll('style'));\n for (const style of styles) {\n target.head.appendChild(style.cloneNode(true));\n }\n const links = Array.from(source.querySelectorAll('link[rel=\"stylesheet\"]'));\n for (const link of links) {\n target.head.appendChild(link.cloneNode(true));\n }\n}\n\n/**\n * Opens a popup window and renders the given React content inside a\n * {@link PrintEnclosure} with Print and Close toolbar buttons.\n *\n * @remarks\n * Must be called synchronously from a user click handler to avoid\n * popup blockers. If the popup is blocked, returns `null`.\n *\n * Styles are cloned from the parent document so that Tailwind and other\n * CSS frameworks work in the popup. An independent React root is created\n * in the popup (not a portal) so the popup is fully self-contained.\n *\n * @public\n */\nexport function openPrintWindow(content: React.ReactElement, options: IPrintWindowOptions): Window | null {\n const { title, width = 900, height = 700, toolbarExtras } = options;\n\n const popup = window.open('', '', `width=${width},height=${height}`);\n if (!popup) {\n return null;\n }\n\n popup.document.write(`<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\" />\n <title>${title.replace(/</g, '<')}</title>\n <style>\n @media screen {\n body {\n background: #f9fafb;\n padding: 2rem;\n margin: 0;\n }\n }\n @media print {\n body {\n margin: 0;\n padding: 0;\n background: white;\n }\n .print-toolbar {\n display: none !important;\n }\n .break-before-page {\n break-before: page;\n }\n .break-inside-avoid {\n break-inside: avoid;\n }\n }\n </style>\n</head>\n<body>\n <div id=\"print-root\"></div>\n</body>\n</html>`);\n popup.document.close();\n\n cloneStyles(document, popup.document);\n\n const container = popup.document.getElementById('print-root');\n if (!container) {\n popup.close();\n return null;\n }\n\n const root = createRoot(container);\n root.render(\n <PrintEnclosure title={title} toolbarExtras={toolbarExtras} popupWindow={popup}>\n {content}\n </PrintEnclosure>\n );\n\n popup.addEventListener('beforeunload', () => {\n root.unmount();\n });\n\n return popup;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ResponsiveProvider.js","sourceRoot":"","sources":["../../../src/packlets/responsive/ResponsiveProvider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,EAA2C,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAErG,MAAM,iBAAiB,GAAiD,aAAa,CAEnF,SAAS,CAAC,CAAC;AAab;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,kBAAkB,CAAC,EACjC,eAAe,EACf,QAAQ,EACiB;IACzB,MAAM,MAAM,GAAG,mBAAmB,CAAC,eAAe,CAAC,CAAC;IACpD,OAAO,oBAAC,iBAAiB,CAAC,QAAQ,IAAC,KAAK,EAAE,MAAM,IAAG,QAAQ,CAA8B,CAAC;AAC5F,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa;IAC3B,MAAM,OAAO,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC;IAC9C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;IAC5E,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,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, { createContext, useContext } from 'react';\nimport { type IResponsiveLayout, type LayoutMode, useResponsiveLayout } from './useResponsiveLayout';\n\nconst ResponsiveContext: React.Context<IResponsiveLayout | undefined> = createContext<\n IResponsiveLayout | undefined\n>(undefined);\n\n/**\n * Props for {@link ResponsiveProvider}.\n * @public\n */\nexport interface IResponsiveProviderProps {\n /** Optional layout mode override (for testing or storybook). */\n readonly forceLayoutMode?: LayoutMode;\n /** Children */\n readonly children: React.ReactNode;\n}\n\n/**\n * Provides responsive layout information to the component tree via context.\n *\n * Wrap your app (or a subtree) with this provider. Descendants use\n * {@link useResponsive} to read the current layout mode, device type, etc.\n *\n * @example\n * ```tsx\n * <ResponsiveProvider>\n * <App />\n * </ResponsiveProvider>\n * ```\n * @public\n */\nexport function ResponsiveProvider({\n forceLayoutMode,\n children\n}: IResponsiveProviderProps): React.JSX.Element {\n const layout = useResponsiveLayout(forceLayoutMode);\n return <ResponsiveContext.Provider value={layout}>{children}</ResponsiveContext.Provider>;\n}\n\n/**\n * Access the current responsive layout information.\n *\n * Must be called within a {@link ResponsiveProvider}.\n * @public\n */\nexport function useResponsive(): IResponsiveLayout {\n const context = useContext(ResponsiveContext);\n if (!context) {\n throw new Error('useResponsive must be used within a ResponsiveProvider');\n }\n return context;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/responsive/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,mBAAmB,EAKpB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAiC,MAAM,sBAAsB,CAAC","sourcesContent":["/**\n * Responsive layout detection and context provider.\n * @packageDocumentation\n */\n\nexport {\n useResponsiveLayout,\n type DeviceType,\n type ScreenOrientation,\n type LayoutMode,\n type IResponsiveLayout\n} from './useResponsiveLayout';\n\nexport { ResponsiveProvider, useResponsive, type IResponsiveProviderProps } from './ResponsiveProvider';\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useResponsiveLayout.js","sourceRoot":"","sources":["../../../src/packlets/responsive/useResponsiveLayout.ts"],"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 { useState, useEffect } from 'react';\n\n/**\n * Device type detection result.\n * @public\n */\nexport type DeviceType = 'mobile' | 'tablet' | 'desktop';\n\n/**\n * Screen orientation.\n * @public\n */\nexport type ScreenOrientation = 'portrait' | 'landscape';\n\n/**\n * Layout mode that drives structural rendering decisions.\n * - `full`: Desktop — sidebar visible, cascade columns side-by-side.\n * - `compact`: Tablet — sidebar as drawer, cascade still horizontal.\n * - `mobile`: Phone — sidebar as drawer, cascade becomes a view stack.\n * @public\n */\nexport type LayoutMode = 'full' | 'compact' | 'mobile';\n\n/**\n * Responsive layout information exposed by the hook and context.\n * @public\n */\nexport interface IResponsiveLayout {\n /** Device type based on screen size and touch capability */\n readonly deviceType: DeviceType;\n\n /** Current screen orientation */\n readonly orientation: ScreenOrientation;\n\n /** Structural layout mode driving component tree decisions */\n readonly layoutMode: LayoutMode;\n\n /** Viewport width in pixels */\n readonly screenWidth: number;\n\n /** Viewport height in pixels */\n readonly screenHeight: number;\n\n /** Whether the device supports touch input */\n readonly isTouchDevice: boolean;\n}\n\n/**\n * Breakpoints for responsive design (in CSS pixels).\n */\nconst BREAKPOINTS = {\n /** At or below this width, use mobile layout */\n mobile: 640,\n /** At or below this width, use compact layout */\n compact: 1024\n} as const;\n\n/**\n * Detect if the device supports touch input.\n */\nfunction detectTouchSupport(): boolean {\n return (\n 'ontouchstart' in window ||\n navigator.maxTouchPoints > 0 ||\n ((navigator as unknown as { msMaxTouchPoints?: number }).msMaxTouchPoints ?? 0) > 0\n );\n}\n\n/**\n * Determine device type from viewport dimensions and touch capability.\n */\nfunction determineDeviceType(width: number, isTouch: boolean): DeviceType {\n if (width <= BREAKPOINTS.mobile) {\n return 'mobile';\n }\n if (width <= BREAKPOINTS.compact && isTouch) {\n return 'tablet';\n }\n return 'desktop';\n}\n\n/**\n * Determine the structural layout mode from viewport width.\n */\nfunction determineLayoutMode(width: number): LayoutMode {\n if (width <= BREAKPOINTS.mobile) {\n return 'mobile';\n }\n if (width <= BREAKPOINTS.compact) {\n return 'compact';\n }\n return 'full';\n}\n\n/**\n * Compute the full layout info from current viewport state.\n */\nfunction computeLayout(forceLayoutMode?: LayoutMode): IResponsiveLayout {\n const width = window.innerWidth;\n const height = window.innerHeight;\n const isTouchDevice = detectTouchSupport();\n const orientation: ScreenOrientation = width > height ? 'landscape' : 'portrait';\n const deviceType = determineDeviceType(width, isTouchDevice);\n const layoutMode = forceLayoutMode ?? determineLayoutMode(width);\n\n return {\n deviceType,\n orientation,\n layoutMode,\n screenWidth: width,\n screenHeight: height,\n isTouchDevice\n };\n}\n\n/**\n * Hook that tracks responsive layout information for the current viewport.\n *\n * Listens to `resize` and `orientationchange` events and recomputes layout\n * on every change. Use the `layoutMode` field to drive structural rendering\n * decisions (e.g., sidebar as drawer vs. fixed panel).\n *\n * @param forceLayoutMode - Optional override for testing or storybook use.\n * @public\n */\nexport function useResponsiveLayout(forceLayoutMode?: LayoutMode): IResponsiveLayout {\n const [layout, setLayout] = useState<IResponsiveLayout>(() => computeLayout(forceLayoutMode));\n\n useEffect(() => {\n if (forceLayoutMode) {\n return;\n }\n\n function updateLayout(): void {\n setLayout(computeLayout());\n }\n\n window.addEventListener('resize', updateLayout);\n\n const onOrientationChange = (): void => {\n // Small delay to let the browser update viewport dimensions\n setTimeout(updateLayout, 100);\n };\n window.addEventListener('orientationchange', onOrientationChange);\n\n // Sync on mount in case viewport changed between SSR and hydration\n updateLayout();\n\n return () => {\n window.removeEventListener('resize', updateLayout);\n window.removeEventListener('orientationchange', onOrientationChange);\n };\n }, [forceLayoutMode]);\n\n return layout;\n}\n"]}
|
|
@@ -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":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH;;;GAGG;AAEH,OAAO,KAAK,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAwCxE,+EAA+E;AAC/E,8BAA8B;AAC9B,+EAA+E;AAE/E;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAAmC;;IAEnC,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;IAC1F,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IACjD,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACtD,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAmB,IAAI,GAAG,EAAE,CAAC,CAAC;IAC1E,MAAM,UAAU,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAC;IACnD,MAAM,UAAU,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAC;IAChD,MAAM,QAAQ,GAAG,MAAM,CAAkC,EAAE,CAAC,CAAC;IAE7D,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,UAAU,CAAC,CAAC;IAClE,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IACrC,MAAM,UAAU,GAAG,SAAS,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;IAEhE,yCAAyC;IACzC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,cAAc,CAAC,KAAK,CAAC,CAAC;YACtB,aAAa,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAEX,sCAAsC;IACtC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO;QACT,CAAC;QACD,MAAM,kBAAkB,GAAG,CAAC,CAAa,EAAQ,EAAE;YACjD,IACE,UAAU,CAAC,OAAO;gBAClB,CAAC,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAc,CAAC;gBAC9C,UAAU,CAAC,OAAO;gBAClB,CAAC,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAc,CAAC,EAC9C,CAAC;gBACD,OAAO,CAAC,KAAK,CAAC,CAAC;YACjB,CAAC;QACH,CAAC,CAAC;QACF,QAAQ,CAAC,gBAAgB,CAAC,WAAW,EAAE,kBAAkB,CAAC,CAAC;QAC3D,OAAO,GAAS,EAAE;YAChB,QAAQ,CAAC,mBAAmB,CAAC,WAAW,EAAE,kBAAkB,CAAC,CAAC;QAChE,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAEX,sCAAsC;IACtC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,IAAI,EAAE,CAAC;YACT,MAAM,WAAW,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,UAAU,CAAC,CAAC;YACtE,aAAa,CAAC,WAAW,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACpD,CAAC;IACH,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC;IAE9B,gCAAgC;IAChC,SAAS,CAAC,GAAG,EAAE;;QACb,IAAI,IAAI,IAAI,UAAU,IAAI,CAAC,IAAI,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;YAC5D,MAAA,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC,0CAAE,cAAc,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;QACrE,CAAC;IACH,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC;IAEvB,MAAM,YAAY,GAAG,WAAW,CAAC,GAAS,EAAE;QAC1C,IAAI,WAAW,EAAE,CAAC;YAChB,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;IAElB,MAAM,YAAY,GAAG,WAAW,CAC9B,CAAC,EAAO,EAAQ,EAAE;QAChB,QAAQ,CAAC,EAAE,CAAC,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,CAAC;IACjB,CAAC,EACD,CAAC,QAAQ,CAAC,CACX,CAAC;IAEF,MAAM,eAAe,GAAG,WAAW,CACjC,CAAC,EAAO,EAAE,CAAmB,EAAQ,EAAE;QACrC,CAAC,CAAC,eAAe,EAAE,CAAC;QACpB,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAG,EAAE,CAAC,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,CAAC;IACjB,CAAC,EACD,CAAC,WAAW,CAAC,CACd,CAAC;IAEF,MAAM,aAAa,GAAG,WAAW,CAAC,CAAC,EAAO,EAAQ,EAAE;QAClD,aAAa,CAAC,CAAC,IAAI,EAAE,EAAE;YACrB,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC;YAC3B,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;gBACjB,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAClB,CAAC;iBAAM,IAAI,IAAI,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;gBACzB,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACf,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,gBAAgB,GAAG,WAAW,CAAC,GAAS,EAAE;QAC9C,IAAI,SAAS,EAAE,CAAC;YACd,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;YACxC,OAAO,CAAC,KAAK,CAAC,CAAC;QACjB,CAAC;IACH,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC;IAEvB,MAAM,qBAAqB,GAAG,WAAW,CAAC,GAAS,EAAE;QACnD,IAAI,SAAS,IAAI,UAAU,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC;YACtC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;YAClC,OAAO,CAAC,KAAK,CAAC,CAAC;QACjB,CAAC;IACH,CAAC,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC;IAE5B,MAAM,aAAa,GAAG,WAAW,CAC/B,CAAC,CAAsB,EAAQ,EAAE;;QAC/B,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,IAAI,CAAC,CAAC,GAAG,KAAK,OAAO,IAAI,CAAC,CAAC,GAAG,KAAK,GAAG,IAAI,CAAC,CAAC,GAAG,KAAK,WAAW,EAAE,CAAC;gBAChE,CAAC,CAAC,cAAc,EAAE,CAAC;gBACnB,OAAO,CAAC,IAAI,CAAC,CAAC;YAChB,CAAC;YACD,OAAO;QACT,CAAC;QAED,QAAQ,CAAC,CAAC,GAAG,EAAE,CAAC;YACd,KAAK,QAAQ,CAAC,CAAC,CAAC;gBACd,CAAC,CAAC,cAAc,EAAE,CAAC;gBACnB,CAAC,CAAC,eAAe,EAAE,CAAC;gBACpB,IAAI,WAAW,EAAE,CAAC;oBAChB,cAAc,CAAC,KAAK,CAAC,CAAC;oBACtB,aAAa,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;gBAC3B,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,KAAK,CAAC,CAAC;oBACf,MAAA,UAAU,CAAC,OAAO,0CAAE,KAAK,EAAE,CAAC;gBAC9B,CAAC;gBACD,MAAM;YACR,CAAC;YACD,KAAK,WAAW,CAAC,CAAC,CAAC;gBACjB,CAAC,CAAC,cAAc,EAAE,CAAC;gBACnB,aAAa,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBAClE,MAAM;YACR,CAAC;YACD,KAAK,SAAS,CAAC,CAAC,CAAC;gBACf,CAAC,CAAC,cAAc,EAAE,CAAC;gBACnB,aAAa,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;gBAClE,MAAM;YACR,CAAC;YACD,KAAK,OAAO,CAAC,CAAC,CAAC;gBACb,CAAC,CAAC,cAAc,EAAE,CAAC;gBACnB,IAAI,UAAU,IAAI,CAAC,IAAI,UAAU,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;oBACjD,IAAI,WAAW,EAAE,CAAC;wBAChB,aAAa,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC;oBACtC,CAAC;yBAAM,CAAC;wBACN,YAAY,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC;oBACrC,CAAC;gBACH,CAAC;gBACD,MAAM;YACR,CAAC;YACD,KAAK,GAAG,CAAC,CAAC,CAAC;gBACT,IAAI,WAAW,IAAI,UAAU,IAAI,CAAC,IAAI,UAAU,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;oBAChE,CAAC,CAAC,cAAc,EAAE,CAAC;oBACnB,aAAa,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC;gBACtC,CAAC;gBACD,MAAM;YACR,CAAC;YACD;gBACE,MAAM;QACV,CAAC;IACH,CAAC,EACD,CAAC,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,CAAC,CACpE,CAAC;IAEF,OAAO,CACL,6BAAK,SAAS,EAAC,UAAU;QAEtB,KAAK,IAAI,6BAAK,SAAS,EAAC,qCAAqC,IAAE,KAAK,CAAO;QAG5E,gCACE,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,YAAY,EACrB,SAAS,EAAE,aAAa,EACxB,SAAS,EAAE,oGACT,WAAW;gBACT,CAAC,CAAC,oEAAoE;gBACtE,CAAC,CAAC,kDACN,IAAI,IAAI,CAAC,CAAC,CAAC,gDAAgD,CAAC,CAAC,CAAC,EAAE,EAAE,mBACnD,IAAI,mBACL,SAAS;YAEvB,8BAAM,SAAS,EAAC,sCAAsC,IACnD,MAAA,YAAY,aAAZ,YAAY,uBAAZ,YAAY,CAAE,KAAK,mCAAI,UAAU;gBACjC,WAAW,KAAK,SAAS,IAAI,UAAU,KAAK,WAAW,IAAI,CAC1D,8BAAM,SAAS,EAAC,wBAAwB,aAAS,CAClD,CACI;YACN,WAAW,IAAI,CACd;gBACE,8BAAM,SAAS,EAAC,8CAA8C,IAAE,KAAK,CAAC,MAAM,CAAQ;gBACpF,6BACE,SAAS,EAAE,oDAAoD,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE,EACzF,IAAI,EAAC,MAAM,EACX,OAAO,EAAC,WAAW,EACnB,MAAM,EAAC,cAAc,EACrB,WAAW,EAAE,CAAC;oBAEd,8BAAM,aAAa,EAAC,OAAO,EAAC,cAAc,EAAC,OAAO,EAAC,CAAC,EAAC,gBAAgB,GAAG,CACpE,CACL,CACJ,CACM;QAGR,IAAI,IAAI,CACP,6BACE,GAAG,EAAE,UAAU,EACf,SAAS,EAAC,0IAA0I,EACpJ,IAAI,EAAC,SAAS,EACd,SAAS,EAAE,aAAa;YAGvB,UAAU,IAAI,CACb,6BAAK,SAAS,EAAC,wFAAwF;gBACpG,WAAW,CAAC,CAAC,CAAC,CACb,6BAAK,SAAS,EAAC,2BAA2B;oBACvC,UAAU,CAAC,IAAI,IAAI,CAAC,IAAI,CACvB,gCACE,OAAO,EAAE,qBAAqB,EAC9B,SAAS,EAAC,mHAAmH;;wBAEpH,UAAU,CAAC,IAAI,CACjB,CACV;oBACD,gCACE,OAAO,EAAE,gBAAgB,EACzB,SAAS,EAAC,kFAAkF,UAGrF,CACL,CACP,CAAC,CAAC,CAAC,CACF,gCACE,OAAO,EAAE,GAAS,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,EACzC,SAAS,EAAC,kFAAkF,oBAGrF,CACV;gBACD,gCACE,OAAO,EAAE,GAAS,EAAE;wBAClB,IAAI,WAAW,EAAE,CAAC;4BAChB,cAAc,CAAC,KAAK,CAAC,CAAC;4BACtB,aAAa,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;wBAC3B,CAAC;6BAAM,CAAC;4BACN,OAAO,CAAC,KAAK,CAAC,CAAC;wBACjB,CAAC;oBACH,CAAC,EACD,SAAS,EAAC,mDAAmD,gBACjD,WAAW,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,OAAO;oBAElD,6BACE,SAAS,EAAC,aAAa,EACvB,IAAI,EAAC,MAAM,EACX,OAAO,EAAC,WAAW,EACnB,MAAM,EAAC,cAAc,EACrB,WAAW,EAAE,CAAC;wBAEd,8BAAM,aAAa,EAAC,OAAO,EAAC,cAAc,EAAC,OAAO,EAAC,CAAC,EAAC,sBAAsB,GAAG,CAC1E,CACC,CACL,CACP;YAGD,6BAAK,SAAS,EAAC,wBAAwB,IACpC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;gBACzB,MAAM,UAAU,GAAG,IAAI,CAAC,EAAE,KAAK,UAAU,CAAC;gBAC1C,MAAM,WAAW,GAAG,WAAW,KAAK,SAAS,IAAI,IAAI,CAAC,EAAE,KAAK,WAAW,CAAC;gBACzE,MAAM,SAAS,GAAG,KAAK,KAAK,UAAU,CAAC;gBACvC,MAAM,SAAS,GAAG,WAAW,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBAEzD,OAAO,CACL,gCACE,GAAG,EAAE,IAAI,CAAC,EAAE,EACZ,GAAG,EAAE,CAAC,EAAE,EAAQ,EAAE;wBAChB,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;oBAC/B,CAAC,EACD,IAAI,EAAC,QAAQ,mBACE,UAAU,EACzB,OAAO,EAAE,GAAS,EAAE;wBAClB,IAAI,WAAW,EAAE,CAAC;4BAChB,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;wBACzB,CAAC;6BAAM,CAAC;4BACN,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;wBACxB,CAAC;oBACH,CAAC,EACD,SAAS,EAAE,oFACT,SAAS;wBACP,CAAC,CAAC,gCAAgC;wBAClC,CAAC,CAAC,UAAU,IAAI,CAAC,WAAW;4BAC5B,CAAC,CAAC,4CAA4C;4BAC9C,CAAC,CAAC,SAAS;gCACX,CAAC,CAAC,6BAA6B;gCAC/B,CAAC,CAAC,+BACN,EAAE;oBAGD,WAAW,CAAC,CAAC,CAAC,CACb,8BACE,SAAS,EAAE,0FACT,SAAS;4BACP,CAAC,CAAC,gDAAgD;4BAClD,CAAC,CAAC,0BACN,EAAE,IAED,SAAS,IAAI,CACZ,6BACE,SAAS,EAAC,aAAa,EACvB,IAAI,EAAC,MAAM,EACX,OAAO,EAAC,WAAW,EACnB,MAAM,EAAC,cAAc,EACrB,WAAW,EAAE,CAAC;wBAEd,8BAAM,aAAa,EAAC,OAAO,EAAC,cAAc,EAAC,OAAO,EAAC,CAAC,EAAC,gBAAgB,GAAG,CACpE,CACP,CACI,CACR,CAAC,CAAC,CAAC,CACF,8BAAM,SAAS,EAAC,kCAAkC,IAC/C,WAAW,CAAC,CAAC,CAAC,8BAAM,SAAS,EAAC,WAAW,aAAS,CAAC,CAAC,CAAC,EAAE,CACnD,CACR;oBAGD,8BAAM,SAAS,EAAC,gBAAgB;wBAC9B,8BAAM,SAAS,EAAC,gBAAgB,IAAE,IAAI,CAAC,KAAK,CAAQ;wBACnD,IAAI,CAAC,QAAQ,IAAI,CAChB,8BAAM,SAAS,EAAC,mCAAmC,IAAE,IAAI,CAAC,QAAQ,CAAQ,CAC3E,CACI;oBAGN,CAAC,WAAW,IAAI,UAAU,IAAI,CAC7B,6BACE,SAAS,EAAC,wCAAwC,EAClD,IAAI,EAAC,MAAM,EACX,OAAO,EAAC,WAAW,EACnB,MAAM,EAAC,cAAc,EACrB,WAAW,EAAE,CAAC;wBAEd,8BAAM,aAAa,EAAC,OAAO,EAAC,cAAc,EAAC,OAAO,EAAC,CAAC,EAAC,gBAAgB,GAAG,CACpE,CACP;oBAGA,CAAC,WAAW,IAAI,WAAW,IAAI,CAC9B,gCACE,OAAO,EAAE,CAAC,CAAC,EAAQ,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,EACjD,SAAS,EAAC,yDAAyD,gBACvD,QAAQ,IAAI,CAAC,KAAK,EAAE;wBAEhC,6BACE,SAAS,EAAC,SAAS,EACnB,IAAI,EAAC,MAAM,EACX,OAAO,EAAC,WAAW,EACnB,MAAM,EAAC,cAAc,EACrB,WAAW,EAAE,CAAC;4BAEd,8BAAM,aAAa,EAAC,OAAO,EAAC,cAAc,EAAC,OAAO,EAAC,CAAC,EAAC,cAAc,GAAG,CAClE,CACC,CACV,CACM,CACV,CAAC;YACJ,CAAC,CAAC,CACE,CACF,CACP,CACG,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\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
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
(collection.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
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
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
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(
|
|
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
|
-
"
|
|
101
|
-
|
|
102
|
-
|
|
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" },
|
|
104
|
-
|
|
105
|
-
|
|
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
|