@fgv/ts-app-shell 5.1.0-1
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 +26 -0
- package/dist/index.browser.js +3 -0
- package/dist/index.js +43 -0
- package/dist/packlets/ai-assist/index.js +6 -0
- package/dist/packlets/ai-assist/useAiAssist.js +219 -0
- package/dist/packlets/cascade/CascadeContainer.js +83 -0
- package/dist/packlets/cascade/ComparisonView.js +48 -0
- package/dist/packlets/cascade/EntityTabLayout.js +104 -0
- package/dist/packlets/cascade/MobileCascadeStack.js +63 -0
- package/dist/packlets/cascade/index.js +37 -0
- package/dist/packlets/cascade/model.js +30 -0
- package/dist/packlets/cascade/useCascadeOps.js +206 -0
- package/dist/packlets/cascade/useCascadeTransitions.js +58 -0
- package/dist/packlets/detail/DetailHelpers.js +103 -0
- package/dist/packlets/detail/index.js +6 -0
- package/dist/packlets/drop-zone/JsonDropZone.js +112 -0
- package/dist/packlets/drop-zone/index.js +6 -0
- package/dist/packlets/editing/EditFieldHelpers.js +130 -0
- package/dist/packlets/editing/MultiActionButton.js +73 -0
- package/dist/packlets/editing/NumericInput.js +119 -0
- package/dist/packlets/editing/TypeaheadInput.js +207 -0
- package/dist/packlets/editing/index.js +10 -0
- package/dist/packlets/editing/useTypeaheadMatch.js +102 -0
- package/dist/packlets/keyboard/index.js +7 -0
- package/dist/packlets/keyboard/registry.js +133 -0
- package/dist/packlets/keyboard/useKeyboardShortcuts.js +117 -0
- package/dist/packlets/messages/MessagesContext.js +76 -0
- package/dist/packlets/messages/MessagesLogger.js +103 -0
- package/dist/packlets/messages/StatusBar.js +154 -0
- package/dist/packlets/messages/Toast.js +68 -0
- package/dist/packlets/messages/index.js +11 -0
- package/dist/packlets/messages/model.js +56 -0
- package/dist/packlets/messages/useLogReporter.js +66 -0
- package/dist/packlets/modal/ConfirmDialog.js +78 -0
- package/dist/packlets/modal/Modal.js +55 -0
- package/dist/packlets/modal/index.js +7 -0
- package/dist/packlets/print/PrintEnclosure.js +60 -0
- package/dist/packlets/print/index.js +7 -0
- package/dist/packlets/print/openPrintWindow.js +112 -0
- package/dist/packlets/responsive/ResponsiveProvider.js +56 -0
- package/dist/packlets/responsive/index.js +7 -0
- package/dist/packlets/responsive/useResponsiveLayout.js +118 -0
- package/dist/packlets/selectors/EntityRow.js +276 -0
- package/dist/packlets/selectors/PreferredSelector.js +251 -0
- package/dist/packlets/selectors/index.js +24 -0
- package/dist/packlets/sidebar/CollectionSection.js +107 -0
- package/dist/packlets/sidebar/EntityList.js +164 -0
- package/dist/packlets/sidebar/FilterBar.js +42 -0
- package/dist/packlets/sidebar/FilterRow.js +182 -0
- package/dist/packlets/sidebar/GroupedEntityList.js +183 -0
- package/dist/packlets/sidebar/SearchBar.js +34 -0
- package/dist/packlets/sidebar/SidebarLayout.js +62 -0
- package/dist/packlets/sidebar/index.js +12 -0
- package/dist/packlets/theme/ThemeProvider.js +141 -0
- package/dist/packlets/theme/index.js +6 -0
- package/dist/packlets/top-bar/ModeSelector.js +46 -0
- package/dist/packlets/top-bar/TabBar.js +37 -0
- package/dist/packlets/top-bar/index.js +7 -0
- package/dist/packlets/url-sync/index.js +6 -0
- package/dist/packlets/url-sync/useUrlSync.js +157 -0
- package/eslint.config.js +22 -0
- package/lib/index.browser.d.ts +2 -0
- package/lib/index.browser.js +19 -0
- package/lib/index.d.ts +28 -0
- package/lib/index.js +59 -0
- package/lib/packlets/ai-assist/index.d.ts +6 -0
- package/lib/packlets/ai-assist/index.js +11 -0
- package/lib/packlets/ai-assist/useAiAssist.d.ts +77 -0
- package/lib/packlets/ai-assist/useAiAssist.js +223 -0
- package/lib/packlets/cascade/CascadeContainer.d.ts +44 -0
- package/lib/packlets/cascade/CascadeContainer.js +119 -0
- package/lib/packlets/cascade/ComparisonView.d.ts +35 -0
- package/lib/packlets/cascade/ComparisonView.js +54 -0
- package/lib/packlets/cascade/EntityTabLayout.d.ts +47 -0
- package/lib/packlets/cascade/EntityTabLayout.js +110 -0
- package/lib/packlets/cascade/MobileCascadeStack.d.ts +20 -0
- package/lib/packlets/cascade/MobileCascadeStack.js +99 -0
- package/lib/packlets/cascade/index.d.ts +12 -0
- package/lib/packlets/cascade/index.js +48 -0
- package/lib/packlets/cascade/model.d.ts +57 -0
- package/lib/packlets/cascade/model.js +33 -0
- package/lib/packlets/cascade/useCascadeOps.d.ts +111 -0
- package/lib/packlets/cascade/useCascadeOps.js +209 -0
- package/lib/packlets/cascade/useCascadeTransitions.d.ts +19 -0
- package/lib/packlets/cascade/useCascadeTransitions.js +62 -0
- package/lib/packlets/detail/DetailHelpers.d.ts +83 -0
- package/lib/packlets/detail/DetailHelpers.js +113 -0
- package/lib/packlets/detail/index.d.ts +6 -0
- package/lib/packlets/detail/index.js +14 -0
- package/lib/packlets/drop-zone/JsonDropZone.d.ts +40 -0
- package/lib/packlets/drop-zone/JsonDropZone.js +149 -0
- package/lib/packlets/drop-zone/index.d.ts +6 -0
- package/lib/packlets/drop-zone/index.js +10 -0
- package/lib/packlets/editing/EditFieldHelpers.d.ts +171 -0
- package/lib/packlets/editing/EditFieldHelpers.js +144 -0
- package/lib/packlets/editing/MultiActionButton.d.ts +45 -0
- package/lib/packlets/editing/MultiActionButton.js +109 -0
- package/lib/packlets/editing/NumericInput.d.ts +47 -0
- package/lib/packlets/editing/NumericInput.js +155 -0
- package/lib/packlets/editing/TypeaheadInput.d.ts +46 -0
- package/lib/packlets/editing/TypeaheadInput.js +243 -0
- package/lib/packlets/editing/index.d.ts +10 -0
- package/lib/packlets/editing/index.js +26 -0
- package/lib/packlets/editing/useTypeaheadMatch.d.ts +42 -0
- package/lib/packlets/editing/useTypeaheadMatch.js +105 -0
- package/lib/packlets/keyboard/index.d.ts +7 -0
- package/lib/packlets/keyboard/index.js +15 -0
- package/lib/packlets/keyboard/registry.d.ts +92 -0
- package/lib/packlets/keyboard/registry.js +138 -0
- package/lib/packlets/keyboard/useKeyboardShortcuts.d.ts +50 -0
- package/lib/packlets/keyboard/useKeyboardShortcuts.js +155 -0
- package/lib/packlets/messages/MessagesContext.d.ts +40 -0
- package/lib/packlets/messages/MessagesContext.js +113 -0
- package/lib/packlets/messages/MessagesLogger.d.ts +50 -0
- package/lib/packlets/messages/MessagesLogger.js +107 -0
- package/lib/packlets/messages/StatusBar.d.ts +22 -0
- package/lib/packlets/messages/StatusBar.js +190 -0
- package/lib/packlets/messages/Toast.d.ts +31 -0
- package/lib/packlets/messages/Toast.js +105 -0
- package/lib/packlets/messages/index.d.ts +11 -0
- package/lib/packlets/messages/index.js +24 -0
- package/lib/packlets/messages/model.d.ts +59 -0
- package/lib/packlets/messages/model.js +61 -0
- package/lib/packlets/messages/useLogReporter.d.ts +22 -0
- package/lib/packlets/messages/useLogReporter.js +69 -0
- package/lib/packlets/modal/ConfirmDialog.d.ts +39 -0
- package/lib/packlets/modal/ConfirmDialog.js +114 -0
- package/lib/packlets/modal/Modal.d.ts +22 -0
- package/lib/packlets/modal/Modal.js +91 -0
- package/lib/packlets/modal/index.d.ts +7 -0
- package/lib/packlets/modal/index.js +12 -0
- package/lib/packlets/print/PrintEnclosure.d.ts +33 -0
- package/lib/packlets/print/PrintEnclosure.js +96 -0
- package/lib/packlets/print/index.d.ts +7 -0
- package/lib/packlets/print/index.js +12 -0
- package/lib/packlets/print/openPrintWindow.d.ts +35 -0
- package/lib/packlets/print/openPrintWindow.js +118 -0
- package/lib/packlets/responsive/ResponsiveProvider.d.ts +35 -0
- package/lib/packlets/responsive/ResponsiveProvider.js +93 -0
- package/lib/packlets/responsive/index.d.ts +7 -0
- package/lib/packlets/responsive/index.js +13 -0
- package/lib/packlets/responsive/useResponsiveLayout.d.ts +48 -0
- package/lib/packlets/responsive/useResponsiveLayout.js +121 -0
- package/lib/packlets/selectors/EntityRow.d.ts +45 -0
- package/lib/packlets/selectors/EntityRow.js +315 -0
- package/lib/packlets/selectors/PreferredSelector.d.ts +50 -0
- package/lib/packlets/selectors/PreferredSelector.js +287 -0
- package/lib/packlets/selectors/index.d.ts +5 -0
- package/lib/packlets/selectors/index.js +29 -0
- package/lib/packlets/sidebar/CollectionSection.d.ts +82 -0
- package/lib/packlets/sidebar/CollectionSection.js +143 -0
- package/lib/packlets/sidebar/EntityList.d.ts +105 -0
- package/lib/packlets/sidebar/EntityList.js +200 -0
- package/lib/packlets/sidebar/FilterBar.d.ts +26 -0
- package/lib/packlets/sidebar/FilterBar.js +48 -0
- package/lib/packlets/sidebar/FilterRow.d.ts +42 -0
- package/lib/packlets/sidebar/FilterRow.js +218 -0
- package/lib/packlets/sidebar/GroupedEntityList.d.ts +59 -0
- package/lib/packlets/sidebar/GroupedEntityList.js +219 -0
- package/lib/packlets/sidebar/SearchBar.d.ts +19 -0
- package/lib/packlets/sidebar/SearchBar.js +40 -0
- package/lib/packlets/sidebar/SidebarLayout.d.ts +28 -0
- package/lib/packlets/sidebar/SidebarLayout.js +98 -0
- package/lib/packlets/sidebar/index.d.ts +12 -0
- package/lib/packlets/sidebar/index.js +22 -0
- package/lib/packlets/theme/ThemeProvider.d.ts +68 -0
- package/lib/packlets/theme/ThemeProvider.js +178 -0
- package/lib/packlets/theme/index.d.ts +6 -0
- package/lib/packlets/theme/index.js +11 -0
- package/lib/packlets/top-bar/ModeSelector.d.ts +38 -0
- package/lib/packlets/top-bar/ModeSelector.js +52 -0
- package/lib/packlets/top-bar/TabBar.d.ts +31 -0
- package/lib/packlets/top-bar/TabBar.js +43 -0
- package/lib/packlets/top-bar/index.d.ts +7 -0
- package/lib/packlets/top-bar/index.js +12 -0
- package/lib/packlets/url-sync/index.d.ts +6 -0
- package/lib/packlets/url-sync/index.js +12 -0
- package/lib/packlets/url-sync/useUrlSync.d.ts +75 -0
- package/lib/packlets/url-sync/useUrlSync.js +162 -0
- package/package.json +82 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2026 Erik Fortune
|
|
4
|
+
*
|
|
5
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
* in the Software without restriction, including without limitation the rights
|
|
8
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
* furnished to do so, subject to the following conditions:
|
|
11
|
+
*
|
|
12
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
* copies or substantial portions of the Software.
|
|
14
|
+
*
|
|
15
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
* SOFTWARE.
|
|
22
|
+
*/
|
|
23
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
24
|
+
if (k2 === undefined) k2 = k;
|
|
25
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
26
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
27
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
28
|
+
}
|
|
29
|
+
Object.defineProperty(o, k2, desc);
|
|
30
|
+
}) : (function(o, m, k, k2) {
|
|
31
|
+
if (k2 === undefined) k2 = k;
|
|
32
|
+
o[k2] = m[k];
|
|
33
|
+
}));
|
|
34
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
35
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
36
|
+
}) : function(o, v) {
|
|
37
|
+
o["default"] = v;
|
|
38
|
+
});
|
|
39
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
40
|
+
var ownKeys = function(o) {
|
|
41
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
42
|
+
var ar = [];
|
|
43
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
44
|
+
return ar;
|
|
45
|
+
};
|
|
46
|
+
return ownKeys(o);
|
|
47
|
+
};
|
|
48
|
+
return function (mod) {
|
|
49
|
+
if (mod && mod.__esModule) return mod;
|
|
50
|
+
var result = {};
|
|
51
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
52
|
+
__setModuleDefault(result, mod);
|
|
53
|
+
return result;
|
|
54
|
+
};
|
|
55
|
+
})();
|
|
56
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
57
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
58
|
+
};
|
|
59
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
60
|
+
exports.EntityRow = EntityRow;
|
|
61
|
+
/**
|
|
62
|
+
* EntityRow — clickable text row with optional discrete swap-icon for alternates.
|
|
63
|
+
*
|
|
64
|
+
* Renders a uniform row regardless of whether alternates exist:
|
|
65
|
+
* - Left slot: swap icon (when alternates exist) or empty spacer
|
|
66
|
+
* - Name text: clickable for drill-down
|
|
67
|
+
* - Optional right-side content (amount, sublabel, etc.)
|
|
68
|
+
* - Drill-down chevron ›
|
|
69
|
+
*
|
|
70
|
+
* @packageDocumentation
|
|
71
|
+
*/
|
|
72
|
+
const react_1 = __importStar(require("react"));
|
|
73
|
+
const react_dom_1 = __importDefault(require("react-dom"));
|
|
74
|
+
// ============================================================================
|
|
75
|
+
// EntityRow Component
|
|
76
|
+
// ============================================================================
|
|
77
|
+
/**
|
|
78
|
+
* A clickable text row with an optional discrete swap-icon popover for
|
|
79
|
+
* switching between alternates. Clicking the row itself always triggers
|
|
80
|
+
* drill-down navigation. The swap icon opens a small picker to change
|
|
81
|
+
* which alternate is displayed.
|
|
82
|
+
*
|
|
83
|
+
* @public
|
|
84
|
+
*/
|
|
85
|
+
function EntityRow(props) {
|
|
86
|
+
var _a, _b, _c, _d;
|
|
87
|
+
const { items, preferredId, onClick, onSelect, onCompare, rightContent, label } = props;
|
|
88
|
+
const hasAlternates = items.length > 1;
|
|
89
|
+
const [internalId, setInternalId] = (0, react_1.useState)((_b = preferredId !== null && preferredId !== void 0 ? preferredId : (_a = items[0]) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : '');
|
|
90
|
+
const displayedId = (_c = props.selectedId) !== null && _c !== void 0 ? _c : internalId;
|
|
91
|
+
const [pickerOpen, setPickerOpen] = (0, react_1.useState)(false);
|
|
92
|
+
const [compareMode, setCompareMode] = (0, react_1.useState)(false);
|
|
93
|
+
const [checkedIds, setCheckedIds] = (0, react_1.useState)(new Set());
|
|
94
|
+
const [focusIndex, setFocusIndex] = (0, react_1.useState)(-1);
|
|
95
|
+
const pickerRef = (0, react_1.useRef)(null);
|
|
96
|
+
const pickerBtnRef = (0, react_1.useRef)(null);
|
|
97
|
+
const itemRefs = (0, react_1.useRef)([]);
|
|
98
|
+
const [pickerPos, setPickerPos] = (0, react_1.useState)({ top: 0, left: 0 });
|
|
99
|
+
// Reset displayed item when items or preferred change (e.g. variation switch)
|
|
100
|
+
(0, react_1.useEffect)(() => {
|
|
101
|
+
var _a, _b;
|
|
102
|
+
setInternalId((_b = preferredId !== null && preferredId !== void 0 ? preferredId : (_a = items[0]) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : '');
|
|
103
|
+
}, [preferredId, items]);
|
|
104
|
+
// Reset compare mode when picker closes; set initial focus when it opens
|
|
105
|
+
(0, react_1.useEffect)(() => {
|
|
106
|
+
if (!pickerOpen) {
|
|
107
|
+
setCompareMode(false);
|
|
108
|
+
setCheckedIds(new Set());
|
|
109
|
+
setFocusIndex(-1);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
const idx = items.findIndex((i) => i.id === displayedId);
|
|
113
|
+
setFocusIndex(idx >= 0 ? idx : 0);
|
|
114
|
+
}
|
|
115
|
+
}, [pickerOpen, items, displayedId]);
|
|
116
|
+
const displayedItem = (0, react_1.useMemo)(() => { var _a; return (_a = items.find((i) => i.id === displayedId)) !== null && _a !== void 0 ? _a : items[0]; }, [items, displayedId]);
|
|
117
|
+
const isPreferred = displayedId === preferredId;
|
|
118
|
+
// Position the portal popover relative to the swap button
|
|
119
|
+
(0, react_1.useEffect)(() => {
|
|
120
|
+
if (pickerOpen && pickerBtnRef.current) {
|
|
121
|
+
const rect = pickerBtnRef.current.getBoundingClientRect();
|
|
122
|
+
setPickerPos({ top: rect.bottom + 2, left: rect.left });
|
|
123
|
+
}
|
|
124
|
+
}, [pickerOpen]);
|
|
125
|
+
// Close picker on outside click or scroll
|
|
126
|
+
(0, react_1.useEffect)(() => {
|
|
127
|
+
if (!pickerOpen) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const handleClickOutside = (e) => {
|
|
131
|
+
if (pickerRef.current &&
|
|
132
|
+
!pickerRef.current.contains(e.target) &&
|
|
133
|
+
pickerBtnRef.current &&
|
|
134
|
+
!pickerBtnRef.current.contains(e.target)) {
|
|
135
|
+
setPickerOpen(false);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
const handleScroll = () => {
|
|
139
|
+
setPickerOpen(false);
|
|
140
|
+
};
|
|
141
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
142
|
+
// Close on any scroll so the popover doesn't float away from the button
|
|
143
|
+
document.addEventListener('scroll', handleScroll, true);
|
|
144
|
+
return () => {
|
|
145
|
+
document.removeEventListener('mousedown', handleClickOutside);
|
|
146
|
+
document.removeEventListener('scroll', handleScroll, true);
|
|
147
|
+
};
|
|
148
|
+
}, [pickerOpen]);
|
|
149
|
+
const handleRowClick = (0, react_1.useCallback)(() => {
|
|
150
|
+
if (onClick) {
|
|
151
|
+
onClick(displayedId);
|
|
152
|
+
}
|
|
153
|
+
}, [onClick, displayedId]);
|
|
154
|
+
const handleRowKeyDown = (0, react_1.useCallback)((e) => {
|
|
155
|
+
if (onClick && (e.key === 'Enter' || e.key === ' ')) {
|
|
156
|
+
e.preventDefault();
|
|
157
|
+
onClick(displayedId);
|
|
158
|
+
}
|
|
159
|
+
}, [onClick, displayedId]);
|
|
160
|
+
const handlePickerToggle = (0, react_1.useCallback)((e) => {
|
|
161
|
+
e.stopPropagation();
|
|
162
|
+
setPickerOpen((prev) => !prev);
|
|
163
|
+
}, []);
|
|
164
|
+
const handlePickItem = (0, react_1.useCallback)((id) => {
|
|
165
|
+
setInternalId(id);
|
|
166
|
+
if (onSelect) {
|
|
167
|
+
onSelect(id);
|
|
168
|
+
}
|
|
169
|
+
setPickerOpen(false);
|
|
170
|
+
}, [onSelect]);
|
|
171
|
+
const handleCompareAll = (0, react_1.useCallback)(() => {
|
|
172
|
+
if (onCompare) {
|
|
173
|
+
onCompare(items.map((i) => i.id));
|
|
174
|
+
}
|
|
175
|
+
setPickerOpen(false);
|
|
176
|
+
}, [onCompare, items]);
|
|
177
|
+
const toggleChecked = (0, react_1.useCallback)((id) => {
|
|
178
|
+
setCheckedIds((prev) => {
|
|
179
|
+
const next = new Set(prev);
|
|
180
|
+
if (next.has(id)) {
|
|
181
|
+
next.delete(id);
|
|
182
|
+
}
|
|
183
|
+
else if (next.size < 4) {
|
|
184
|
+
next.add(id);
|
|
185
|
+
}
|
|
186
|
+
return next;
|
|
187
|
+
});
|
|
188
|
+
}, []);
|
|
189
|
+
const handleCompareSelected = (0, react_1.useCallback)(() => {
|
|
190
|
+
if (onCompare && checkedIds.size >= 2) {
|
|
191
|
+
onCompare(Array.from(checkedIds));
|
|
192
|
+
setPickerOpen(false);
|
|
193
|
+
}
|
|
194
|
+
}, [onCompare, checkedIds]);
|
|
195
|
+
const handlePickerKeyDown = (0, react_1.useCallback)((e) => {
|
|
196
|
+
switch (e.key) {
|
|
197
|
+
case 'Escape': {
|
|
198
|
+
e.preventDefault();
|
|
199
|
+
e.stopPropagation();
|
|
200
|
+
if (compareMode) {
|
|
201
|
+
setCompareMode(false);
|
|
202
|
+
setCheckedIds(new Set());
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
setPickerOpen(false);
|
|
206
|
+
}
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
case 'ArrowDown': {
|
|
210
|
+
e.preventDefault();
|
|
211
|
+
setFocusIndex((prev) => (prev < items.length - 1 ? prev + 1 : 0));
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
case 'ArrowUp': {
|
|
215
|
+
e.preventDefault();
|
|
216
|
+
setFocusIndex((prev) => (prev > 0 ? prev - 1 : items.length - 1));
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
case 'Enter': {
|
|
220
|
+
e.preventDefault();
|
|
221
|
+
if (focusIndex >= 0 && focusIndex < items.length) {
|
|
222
|
+
if (compareMode) {
|
|
223
|
+
toggleChecked(items[focusIndex].id);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
handlePickItem(items[focusIndex].id);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
case ' ': {
|
|
232
|
+
if (compareMode && focusIndex >= 0 && focusIndex < items.length) {
|
|
233
|
+
e.preventDefault();
|
|
234
|
+
toggleChecked(items[focusIndex].id);
|
|
235
|
+
}
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
default:
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
}, [items, focusIndex, compareMode, toggleChecked, handlePickItem]);
|
|
242
|
+
// Render the picker popover via portal so it escapes ancestor overflow clipping
|
|
243
|
+
const pickerPopover = pickerOpen
|
|
244
|
+
? react_dom_1.default.createPortal(react_1.default.createElement("div", { ref: pickerRef, style: { position: 'fixed', top: pickerPos.top, left: pickerPos.left }, className: "z-50 min-w-[180px] max-h-[200px] bg-surface border border-border rounded-lg shadow-lg flex flex-col overflow-hidden", onKeyDown: handlePickerKeyDown },
|
|
245
|
+
react_1.default.createElement("div", { className: "flex items-center justify-between px-2.5 py-1 border-b border-border-subtle shrink-0" }, onCompare && items.length >= 2 ? (compareMode ? (react_1.default.createElement(react_1.default.Fragment, null,
|
|
246
|
+
react_1.default.createElement("div", { className: "flex items-center gap-1.5" },
|
|
247
|
+
checkedIds.size >= 2 && (react_1.default.createElement("button", { onClick: (e) => {
|
|
248
|
+
e.stopPropagation();
|
|
249
|
+
handleCompareSelected();
|
|
250
|
+
}, className: "text-[10px] text-white bg-brand-primary rounded px-1.5 py-0.5 hover:bg-brand-primary/90 transition-colors font-medium" },
|
|
251
|
+
"Compare ",
|
|
252
|
+
checkedIds.size)),
|
|
253
|
+
react_1.default.createElement("button", { onClick: (e) => {
|
|
254
|
+
e.stopPropagation();
|
|
255
|
+
handleCompareAll();
|
|
256
|
+
}, className: "text-[10px] text-brand-accent hover:text-brand-primary transition-colors font-medium" }, "All")),
|
|
257
|
+
react_1.default.createElement("button", { onClick: (e) => {
|
|
258
|
+
e.stopPropagation();
|
|
259
|
+
setCompareMode(false);
|
|
260
|
+
setCheckedIds(new Set());
|
|
261
|
+
}, className: "text-muted hover:text-secondary transition-colors", "aria-label": "Exit compare" },
|
|
262
|
+
react_1.default.createElement("svg", { className: "w-3.5 h-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2 },
|
|
263
|
+
react_1.default.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" }))))) : (react_1.default.createElement(react_1.default.Fragment, null,
|
|
264
|
+
react_1.default.createElement("span", { className: "text-[10px] font-medium text-muted uppercase tracking-wider" }, "Options"),
|
|
265
|
+
react_1.default.createElement("button", { onClick: (e) => {
|
|
266
|
+
e.stopPropagation();
|
|
267
|
+
setCompareMode(true);
|
|
268
|
+
}, className: "text-[10px] text-brand-accent hover:text-brand-primary transition-colors font-medium" }, "Compare\u2026")))) : (react_1.default.createElement("span", { className: "text-[10px] font-medium text-muted uppercase tracking-wider" }, "Alternates"))),
|
|
269
|
+
react_1.default.createElement("div", { className: "overflow-y-auto flex-1" }, items.map((item, index) => {
|
|
270
|
+
const isCurrent = item.id === displayedId;
|
|
271
|
+
const isPref = item.id === preferredId;
|
|
272
|
+
const isFocused = index === focusIndex;
|
|
273
|
+
const isChecked = compareMode && checkedIds.has(item.id);
|
|
274
|
+
return (react_1.default.createElement("button", { key: item.id, ref: (el) => {
|
|
275
|
+
itemRefs.current[index] = el;
|
|
276
|
+
}, onClick: (e) => {
|
|
277
|
+
e.stopPropagation();
|
|
278
|
+
if (compareMode) {
|
|
279
|
+
toggleChecked(item.id);
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
handlePickItem(item.id);
|
|
283
|
+
}
|
|
284
|
+
}, className: `flex items-center gap-2 w-full px-2.5 py-1.5 text-left text-sm transition-colors ${isChecked
|
|
285
|
+
? 'bg-selected text-brand-primary'
|
|
286
|
+
: isCurrent && !compareMode
|
|
287
|
+
? 'bg-selected text-brand-primary font-medium'
|
|
288
|
+
: isFocused
|
|
289
|
+
? 'bg-surface-alt text-primary'
|
|
290
|
+
: 'text-secondary hover:bg-hover'}` },
|
|
291
|
+
compareMode ? (react_1.default.createElement("span", { className: `flex items-center justify-center w-3.5 h-3.5 rounded border shrink-0 transition-colors ${isChecked
|
|
292
|
+
? 'bg-brand-accent border-brand-accent text-white'
|
|
293
|
+
: 'border-border bg-surface'}` }, isChecked && (react_1.default.createElement("svg", { className: "w-2.5 h-2.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 3 },
|
|
294
|
+
react_1.default.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }))))) : (react_1.default.createElement("span", { className: "w-3 shrink-0 text-center text-xs" }, isPref ? react_1.default.createElement("span", { className: "text-star" }, "\u2605") : '')),
|
|
295
|
+
react_1.default.createElement("span", { className: "flex-1 min-w-0 truncate" },
|
|
296
|
+
item.label,
|
|
297
|
+
item.sublabel && react_1.default.createElement("span", { className: "ml-1.5 text-xs text-muted" }, item.sublabel)),
|
|
298
|
+
!compareMode && isCurrent && (react_1.default.createElement("svg", { className: "w-3.5 h-3.5 text-brand-accent shrink-0", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 3 },
|
|
299
|
+
react_1.default.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" })))));
|
|
300
|
+
}))), document.body)
|
|
301
|
+
: null;
|
|
302
|
+
return (react_1.default.createElement("div", null,
|
|
303
|
+
label && react_1.default.createElement("div", { className: "text-xs font-semibold text-muted uppercase tracking-wider mb-1" }, label),
|
|
304
|
+
react_1.default.createElement("div", { className: `flex items-center gap-1.5 py-1.5 pl-0 pr-2 rounded-md ${onClick ? 'cursor-pointer hover:bg-hover transition-colors' : ''}`, onClick: handleRowClick, role: onClick ? 'button' : undefined, tabIndex: onClick ? 0 : undefined, onKeyDown: handleRowKeyDown },
|
|
305
|
+
react_1.default.createElement("span", { className: "w-4 shrink-0 flex items-center justify-center" }, hasAlternates ? (react_1.default.createElement("button", { ref: pickerBtnRef, onClick: handlePickerToggle, className: "text-muted hover:text-brand-accent p-0 transition-colors", "aria-label": "Switch alternate", tabIndex: -1 },
|
|
306
|
+
react_1.default.createElement("svg", { className: "w-3.5 h-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2 },
|
|
307
|
+
react_1.default.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4" })))) : null),
|
|
308
|
+
react_1.default.createElement("span", { className: "text-sm text-primary flex-1 truncate" }, (_d = displayedItem === null || displayedItem === void 0 ? void 0 : displayedItem.label) !== null && _d !== void 0 ? _d : '',
|
|
309
|
+
hasAlternates && isPreferred && react_1.default.createElement("span", { className: "ml-1 text-xs text-star" }, "\u2605"),
|
|
310
|
+
(displayedItem === null || displayedItem === void 0 ? void 0 : displayedItem.sublabel) && (react_1.default.createElement("span", { className: "ml-1.5 text-xs text-muted" }, displayedItem.sublabel))),
|
|
311
|
+
rightContent,
|
|
312
|
+
onClick && react_1.default.createElement("span", { className: "text-faint text-xs shrink-0" }, "\u203A")),
|
|
313
|
+
pickerPopover));
|
|
314
|
+
}
|
|
315
|
+
//# sourceMappingURL=EntityRow.js.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PreferredSelector — compact popover for choosing one item from a set of options.
|
|
3
|
+
* @packageDocumentation
|
|
4
|
+
*/
|
|
5
|
+
import React from 'react';
|
|
6
|
+
/**
|
|
7
|
+
* A single selectable item in the PreferredSelector.
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
export interface ISelectableItem<TId extends string = string> {
|
|
11
|
+
/** Unique identifier */
|
|
12
|
+
readonly id: TId;
|
|
13
|
+
/** Primary display label */
|
|
14
|
+
readonly label: string;
|
|
15
|
+
/** Optional secondary label */
|
|
16
|
+
readonly sublabel?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Props for the PreferredSelector component.
|
|
20
|
+
* @public
|
|
21
|
+
*/
|
|
22
|
+
export interface IPreferredSelectorProps<TId extends string = string> {
|
|
23
|
+
/** Available items to choose from */
|
|
24
|
+
readonly items: ReadonlyArray<ISelectableItem<TId>>;
|
|
25
|
+
/** Currently selected item ID */
|
|
26
|
+
readonly selectedId: TId;
|
|
27
|
+
/** ID of the preferred/default item (shows ★ marker) */
|
|
28
|
+
readonly preferredId?: TId;
|
|
29
|
+
/** Callback when an item is selected */
|
|
30
|
+
readonly onSelect: (id: TId) => void;
|
|
31
|
+
/** Callback to compare selected items side-by-side. Receives the IDs to compare. */
|
|
32
|
+
readonly onCompare?: (ids: ReadonlyArray<TId>) => void;
|
|
33
|
+
/** Callback for drill-down navigation on an item (shows › chevron per row) */
|
|
34
|
+
readonly onDrillDown?: (id: TId) => void;
|
|
35
|
+
/** Section title displayed above the trigger */
|
|
36
|
+
readonly label?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Compact inline selector with popover for choosing one item from a set.
|
|
40
|
+
*
|
|
41
|
+
* When there is only one item, renders as a static label.
|
|
42
|
+
* When there are multiple items, renders a clickable trigger that opens
|
|
43
|
+
* a popover with all options. If `onCompare` is provided, the popover
|
|
44
|
+
* includes a compare toggle that enables checkboxes for multi-select,
|
|
45
|
+
* with "Compare All" and "Compare Selected" actions.
|
|
46
|
+
*
|
|
47
|
+
* @public
|
|
48
|
+
*/
|
|
49
|
+
export declare function PreferredSelector<TId extends string = string>(props: IPreferredSelectorProps<TId>): React.ReactElement;
|
|
50
|
+
//# sourceMappingURL=PreferredSelector.d.ts.map
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2026 Erik Fortune
|
|
4
|
+
*
|
|
5
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
* in the Software without restriction, including without limitation the rights
|
|
8
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
* furnished to do so, subject to the following conditions:
|
|
11
|
+
*
|
|
12
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
* copies or substantial portions of the Software.
|
|
14
|
+
*
|
|
15
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
* SOFTWARE.
|
|
22
|
+
*/
|
|
23
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
24
|
+
if (k2 === undefined) k2 = k;
|
|
25
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
26
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
27
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
28
|
+
}
|
|
29
|
+
Object.defineProperty(o, k2, desc);
|
|
30
|
+
}) : (function(o, m, k, k2) {
|
|
31
|
+
if (k2 === undefined) k2 = k;
|
|
32
|
+
o[k2] = m[k];
|
|
33
|
+
}));
|
|
34
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
35
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
36
|
+
}) : function(o, v) {
|
|
37
|
+
o["default"] = v;
|
|
38
|
+
});
|
|
39
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
40
|
+
var ownKeys = function(o) {
|
|
41
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
42
|
+
var ar = [];
|
|
43
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
44
|
+
return ar;
|
|
45
|
+
};
|
|
46
|
+
return ownKeys(o);
|
|
47
|
+
};
|
|
48
|
+
return function (mod) {
|
|
49
|
+
if (mod && mod.__esModule) return mod;
|
|
50
|
+
var result = {};
|
|
51
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
52
|
+
__setModuleDefault(result, mod);
|
|
53
|
+
return result;
|
|
54
|
+
};
|
|
55
|
+
})();
|
|
56
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
57
|
+
exports.PreferredSelector = PreferredSelector;
|
|
58
|
+
/**
|
|
59
|
+
* PreferredSelector — compact popover for choosing one item from a set of options.
|
|
60
|
+
* @packageDocumentation
|
|
61
|
+
*/
|
|
62
|
+
const react_1 = __importStar(require("react"));
|
|
63
|
+
// ============================================================================
|
|
64
|
+
// PreferredSelector Component
|
|
65
|
+
// ============================================================================
|
|
66
|
+
/**
|
|
67
|
+
* Compact inline selector with popover for choosing one item from a set.
|
|
68
|
+
*
|
|
69
|
+
* When there is only one item, renders as a static label.
|
|
70
|
+
* When there are multiple items, renders a clickable trigger that opens
|
|
71
|
+
* a popover with all options. If `onCompare` is provided, the popover
|
|
72
|
+
* includes a compare toggle that enables checkboxes for multi-select,
|
|
73
|
+
* with "Compare All" and "Compare Selected" actions.
|
|
74
|
+
*
|
|
75
|
+
* @public
|
|
76
|
+
*/
|
|
77
|
+
function PreferredSelector(props) {
|
|
78
|
+
var _a;
|
|
79
|
+
const { items, selectedId, preferredId, onSelect, onCompare, onDrillDown, label } = props;
|
|
80
|
+
const [open, setOpen] = (0, react_1.useState)(false);
|
|
81
|
+
const [focusIndex, setFocusIndex] = (0, react_1.useState)(-1);
|
|
82
|
+
const [compareMode, setCompareMode] = (0, react_1.useState)(false);
|
|
83
|
+
const [checkedIds, setCheckedIds] = (0, react_1.useState)(new Set());
|
|
84
|
+
const triggerRef = (0, react_1.useRef)(null);
|
|
85
|
+
const popoverRef = (0, react_1.useRef)(null);
|
|
86
|
+
const itemRefs = (0, react_1.useRef)([]);
|
|
87
|
+
const selectedItem = items.find((item) => item.id === selectedId);
|
|
88
|
+
const hasMultiple = items.length > 1;
|
|
89
|
+
const canCompare = onCompare !== undefined && items.length >= 2;
|
|
90
|
+
// Reset compare mode when popover closes
|
|
91
|
+
(0, react_1.useEffect)(() => {
|
|
92
|
+
if (!open) {
|
|
93
|
+
setCompareMode(false);
|
|
94
|
+
setCheckedIds(new Set());
|
|
95
|
+
}
|
|
96
|
+
}, [open]);
|
|
97
|
+
// Close popover when clicking outside
|
|
98
|
+
(0, react_1.useEffect)(() => {
|
|
99
|
+
if (!open) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const handleClickOutside = (e) => {
|
|
103
|
+
if (popoverRef.current &&
|
|
104
|
+
!popoverRef.current.contains(e.target) &&
|
|
105
|
+
triggerRef.current &&
|
|
106
|
+
!triggerRef.current.contains(e.target)) {
|
|
107
|
+
setOpen(false);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
111
|
+
return () => {
|
|
112
|
+
document.removeEventListener('mousedown', handleClickOutside);
|
|
113
|
+
};
|
|
114
|
+
}, [open]);
|
|
115
|
+
// Focus first item when popover opens
|
|
116
|
+
(0, react_1.useEffect)(() => {
|
|
117
|
+
if (open) {
|
|
118
|
+
const selectedIdx = items.findIndex((item) => item.id === selectedId);
|
|
119
|
+
setFocusIndex(selectedIdx >= 0 ? selectedIdx : 0);
|
|
120
|
+
}
|
|
121
|
+
}, [open, items, selectedId]);
|
|
122
|
+
// Scroll focused item into view
|
|
123
|
+
(0, react_1.useEffect)(() => {
|
|
124
|
+
var _a;
|
|
125
|
+
if (open && focusIndex >= 0 && itemRefs.current[focusIndex]) {
|
|
126
|
+
(_a = itemRefs.current[focusIndex]) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ block: 'nearest' });
|
|
127
|
+
}
|
|
128
|
+
}, [open, focusIndex]);
|
|
129
|
+
const handleToggle = (0, react_1.useCallback)(() => {
|
|
130
|
+
if (hasMultiple) {
|
|
131
|
+
setOpen((prev) => !prev);
|
|
132
|
+
}
|
|
133
|
+
}, [hasMultiple]);
|
|
134
|
+
const handleSelect = (0, react_1.useCallback)((id) => {
|
|
135
|
+
onSelect(id);
|
|
136
|
+
setOpen(false);
|
|
137
|
+
}, [onSelect]);
|
|
138
|
+
const handleDrillDown = (0, react_1.useCallback)((id, e) => {
|
|
139
|
+
e.stopPropagation();
|
|
140
|
+
onDrillDown === null || onDrillDown === void 0 ? void 0 : onDrillDown(id);
|
|
141
|
+
setOpen(false);
|
|
142
|
+
}, [onDrillDown]);
|
|
143
|
+
const toggleChecked = (0, react_1.useCallback)((id) => {
|
|
144
|
+
setCheckedIds((prev) => {
|
|
145
|
+
const next = new Set(prev);
|
|
146
|
+
if (next.has(id)) {
|
|
147
|
+
next.delete(id);
|
|
148
|
+
}
|
|
149
|
+
else if (next.size < 4) {
|
|
150
|
+
next.add(id);
|
|
151
|
+
}
|
|
152
|
+
return next;
|
|
153
|
+
});
|
|
154
|
+
}, []);
|
|
155
|
+
const handleCompareAll = (0, react_1.useCallback)(() => {
|
|
156
|
+
if (onCompare) {
|
|
157
|
+
onCompare(items.map((item) => item.id));
|
|
158
|
+
setOpen(false);
|
|
159
|
+
}
|
|
160
|
+
}, [onCompare, items]);
|
|
161
|
+
const handleCompareSelected = (0, react_1.useCallback)(() => {
|
|
162
|
+
if (onCompare && checkedIds.size >= 2) {
|
|
163
|
+
onCompare(Array.from(checkedIds));
|
|
164
|
+
setOpen(false);
|
|
165
|
+
}
|
|
166
|
+
}, [onCompare, checkedIds]);
|
|
167
|
+
const handleKeyDown = (0, react_1.useCallback)((e) => {
|
|
168
|
+
var _a;
|
|
169
|
+
if (!open) {
|
|
170
|
+
if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') {
|
|
171
|
+
e.preventDefault();
|
|
172
|
+
setOpen(true);
|
|
173
|
+
}
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
switch (e.key) {
|
|
177
|
+
case 'Escape': {
|
|
178
|
+
e.preventDefault();
|
|
179
|
+
e.stopPropagation();
|
|
180
|
+
if (compareMode) {
|
|
181
|
+
setCompareMode(false);
|
|
182
|
+
setCheckedIds(new Set());
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
setOpen(false);
|
|
186
|
+
(_a = triggerRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
187
|
+
}
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
case 'ArrowDown': {
|
|
191
|
+
e.preventDefault();
|
|
192
|
+
setFocusIndex((prev) => (prev < items.length - 1 ? prev + 1 : 0));
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
case 'ArrowUp': {
|
|
196
|
+
e.preventDefault();
|
|
197
|
+
setFocusIndex((prev) => (prev > 0 ? prev - 1 : items.length - 1));
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
case 'Enter': {
|
|
201
|
+
e.preventDefault();
|
|
202
|
+
if (focusIndex >= 0 && focusIndex < items.length) {
|
|
203
|
+
if (compareMode) {
|
|
204
|
+
toggleChecked(items[focusIndex].id);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
handleSelect(items[focusIndex].id);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
case ' ': {
|
|
213
|
+
if (compareMode && focusIndex >= 0 && focusIndex < items.length) {
|
|
214
|
+
e.preventDefault();
|
|
215
|
+
toggleChecked(items[focusIndex].id);
|
|
216
|
+
}
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
default:
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
}, [open, items, focusIndex, handleSelect, compareMode, toggleChecked]);
|
|
223
|
+
return (react_1.default.createElement("div", { className: "relative" },
|
|
224
|
+
label && react_1.default.createElement("div", { className: "text-xs font-medium text-muted mb-1" }, label),
|
|
225
|
+
react_1.default.createElement("button", { ref: triggerRef, onClick: handleToggle, onKeyDown: handleKeyDown, className: `flex items-center gap-1.5 px-2 py-1 text-sm rounded-md border transition-colors w-full text-left ${hasMultiple
|
|
226
|
+
? 'border-border hover:border-brand-primary cursor-pointer bg-surface'
|
|
227
|
+
: 'border-transparent cursor-default bg-transparent'} ${open ? 'border-brand-primary ring-1 ring-focus-ring/20' : ''}`, "aria-expanded": open, "aria-haspopup": "listbox" },
|
|
228
|
+
react_1.default.createElement("span", { className: "flex-1 min-w-0 truncate text-primary" }, (_a = selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.label) !== null && _a !== void 0 ? _a : selectedId,
|
|
229
|
+
preferredId !== undefined && selectedId === preferredId && (react_1.default.createElement("span", { className: "ml-1 text-xs text-star" }, "\u2605"))),
|
|
230
|
+
hasMultiple && (react_1.default.createElement(react_1.default.Fragment, null,
|
|
231
|
+
react_1.default.createElement("span", { className: "text-[10px] text-muted shrink-0 tabular-nums" }, items.length),
|
|
232
|
+
react_1.default.createElement("svg", { className: `w-3 h-3 text-muted shrink-0 transition-transform ${open ? 'rotate-180' : ''}`, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2 },
|
|
233
|
+
react_1.default.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 9l-7 7-7-7" }))))),
|
|
234
|
+
open && (react_1.default.createElement("div", { ref: popoverRef, 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", role: "listbox", onKeyDown: handleKeyDown },
|
|
235
|
+
canCompare && (react_1.default.createElement("div", { className: "flex items-center justify-between px-2.5 py-1.5 border-b border-border-subtle shrink-0" },
|
|
236
|
+
compareMode ? (react_1.default.createElement("div", { className: "flex items-center gap-1.5" },
|
|
237
|
+
checkedIds.size >= 2 && (react_1.default.createElement("button", { onClick: handleCompareSelected, className: "text-xs text-white bg-brand-primary rounded px-1.5 py-0.5 hover:bg-brand-primary/90 transition-colors font-medium" },
|
|
238
|
+
"Compare ",
|
|
239
|
+
checkedIds.size)),
|
|
240
|
+
react_1.default.createElement("button", { onClick: handleCompareAll, className: "text-xs text-brand-accent hover:text-brand-primary transition-colors font-medium" }, "All"))) : (react_1.default.createElement("button", { onClick: () => setCompareMode(true), className: "text-xs text-brand-accent hover:text-brand-primary transition-colors font-medium" }, "Compare\u2026")),
|
|
241
|
+
react_1.default.createElement("button", { onClick: () => {
|
|
242
|
+
if (compareMode) {
|
|
243
|
+
setCompareMode(false);
|
|
244
|
+
setCheckedIds(new Set());
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
setOpen(false);
|
|
248
|
+
}
|
|
249
|
+
}, className: "text-muted hover:text-secondary transition-colors", "aria-label": compareMode ? 'Exit compare' : 'Close' },
|
|
250
|
+
react_1.default.createElement("svg", { className: "w-3.5 h-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2 },
|
|
251
|
+
react_1.default.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" }))))),
|
|
252
|
+
react_1.default.createElement("div", { className: "overflow-y-auto flex-1" }, items.map((item, index) => {
|
|
253
|
+
const isSelected = item.id === selectedId;
|
|
254
|
+
const isPreferred = preferredId !== undefined && item.id === preferredId;
|
|
255
|
+
const isFocused = index === focusIndex;
|
|
256
|
+
const isChecked = compareMode && checkedIds.has(item.id);
|
|
257
|
+
return (react_1.default.createElement("button", { key: item.id, ref: (el) => {
|
|
258
|
+
itemRefs.current[index] = el;
|
|
259
|
+
}, role: "option", "aria-selected": isSelected, onClick: () => {
|
|
260
|
+
if (compareMode) {
|
|
261
|
+
toggleChecked(item.id);
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
handleSelect(item.id);
|
|
265
|
+
}
|
|
266
|
+
}, className: `flex items-center gap-2 w-full px-2.5 py-1.5 text-left text-sm transition-colors ${isChecked
|
|
267
|
+
? 'bg-selected text-brand-primary'
|
|
268
|
+
: isSelected && !compareMode
|
|
269
|
+
? 'bg-selected text-brand-primary font-medium'
|
|
270
|
+
: isFocused
|
|
271
|
+
? 'bg-surface-alt text-primary'
|
|
272
|
+
: 'text-secondary hover:bg-hover'}` },
|
|
273
|
+
compareMode ? (react_1.default.createElement("span", { className: `flex items-center justify-center w-3.5 h-3.5 rounded border shrink-0 transition-colors ${isChecked
|
|
274
|
+
? 'bg-brand-accent border-brand-accent text-white'
|
|
275
|
+
: 'border-border bg-surface'}` }, isChecked && (react_1.default.createElement("svg", { className: "w-2.5 h-2.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 3 },
|
|
276
|
+
react_1.default.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }))))) : (react_1.default.createElement("span", { className: "w-3 shrink-0 text-center text-xs" }, isPreferred ? react_1.default.createElement("span", { className: "text-star" }, "\u2605") : '')),
|
|
277
|
+
react_1.default.createElement("span", { className: "flex-1 min-w-0" },
|
|
278
|
+
react_1.default.createElement("span", { className: "truncate block" }, item.label),
|
|
279
|
+
item.sublabel && (react_1.default.createElement("span", { className: "text-xs text-muted truncate block" }, item.sublabel))),
|
|
280
|
+
!compareMode && isSelected && (react_1.default.createElement("svg", { className: "w-3.5 h-3.5 text-brand-accent shrink-0", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 3 },
|
|
281
|
+
react_1.default.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }))),
|
|
282
|
+
!compareMode && onDrillDown && (react_1.default.createElement("button", { onClick: (e) => handleDrillDown(item.id, e), className: "text-faint hover:text-brand-accent shrink-0 p-0.5 -mr-1", "aria-label": `Open ${item.label}` },
|
|
283
|
+
react_1.default.createElement("svg", { className: "w-3 h-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2 },
|
|
284
|
+
react_1.default.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9 5l7 7-7 7" }))))));
|
|
285
|
+
}))))));
|
|
286
|
+
}
|
|
287
|
+
//# sourceMappingURL=PreferredSelector.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { EntityRow } from './EntityRow';
|
|
2
|
+
export type { IEntityRowProps } from './EntityRow';
|
|
3
|
+
export { PreferredSelector } from './PreferredSelector';
|
|
4
|
+
export type { IPreferredSelectorProps, ISelectableItem } from './PreferredSelector';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|