@cratis/components 4.26.4 → 4.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/compat-manifest.json +11 -11
  2. package/dist/esm/Common/CratisComponentsProvider.d.ts +4 -0
  3. package/dist/esm/Common/CratisComponentsProvider.d.ts.map +1 -1
  4. package/dist/esm/Common/CratisComponentsProvider.js +1 -0
  5. package/dist/esm/Common/CratisComponentsProvider.js.map +1 -1
  6. package/dist/esm/DataPage/DataPage.d.ts +2 -0
  7. package/dist/esm/DataPage/DataPage.d.ts.map +1 -1
  8. package/dist/esm/DataPage/DataPage.js.map +1 -1
  9. package/dist/esm/DataTables/DataTableForObservableQuery.d.ts +2 -0
  10. package/dist/esm/DataTables/DataTableForObservableQuery.d.ts.map +1 -1
  11. package/dist/esm/DataTables/DataTableForObservableQuery.js +1 -1
  12. package/dist/esm/DataTables/DataTableForObservableQuery.js.map +1 -1
  13. package/dist/esm/DataTables/DataTableForQuery.d.ts +2 -0
  14. package/dist/esm/DataTables/DataTableForQuery.d.ts.map +1 -1
  15. package/dist/esm/DataTables/DataTableForQuery.js +1 -1
  16. package/dist/esm/DataTables/DataTableForQuery.js.map +1 -1
  17. package/dist/esm/DataTables/PaginatorPageItem.d.ts +13 -0
  18. package/dist/esm/DataTables/PaginatorPageItem.d.ts.map +1 -0
  19. package/dist/esm/DataTables/PaginatorPageItem.js +4 -0
  20. package/dist/esm/DataTables/PaginatorPageItem.js.map +1 -0
  21. package/dist/esm/DataTables/TablePaginator.d.ts +22 -2
  22. package/dist/esm/DataTables/TablePaginator.d.ts.map +1 -1
  23. package/dist/esm/DataTables/TablePaginator.js.map +1 -1
  24. package/dist/esm/DataTables/TablePaginatorImplementation.d.ts +1 -1
  25. package/dist/esm/DataTables/TablePaginatorImplementation.d.ts.map +1 -1
  26. package/dist/esm/DataTables/TablePaginatorImplementation.js +8 -3
  27. package/dist/esm/DataTables/TablePaginatorImplementation.js.map +1 -1
  28. package/dist/esm/DataTables/TablePaginatorPages.d.ts +9 -0
  29. package/dist/esm/DataTables/TablePaginatorPages.d.ts.map +1 -0
  30. package/dist/esm/DataTables/TablePaginatorPages.js +20 -0
  31. package/dist/esm/DataTables/TablePaginatorPages.js.map +1 -0
  32. package/dist/esm/DataTables/index.d.ts +2 -0
  33. package/dist/esm/DataTables/index.d.ts.map +1 -1
  34. package/dist/esm/DataTables/index.js +1 -0
  35. package/dist/esm/DataTables/index.js.map +1 -1
  36. package/dist/esm/DataTables/paginatorPages.d.ts +15 -0
  37. package/dist/esm/DataTables/paginatorPages.d.ts.map +1 -0
  38. package/dist/esm/DataTables/paginatorPages.js +40 -0
  39. package/dist/esm/DataTables/paginatorPages.js.map +1 -0
  40. package/dist/esm/Filter/FilterGroup.d.ts +23 -0
  41. package/dist/esm/Filter/FilterGroup.d.ts.map +1 -0
  42. package/dist/esm/Filter/FilterGroup.js +51 -0
  43. package/dist/esm/Filter/FilterGroup.js.map +1 -0
  44. package/dist/esm/Filter/FilterPanel.d.ts.map +1 -1
  45. package/dist/esm/Filter/FilterPanel.js +24 -257
  46. package/dist/esm/Filter/FilterPanel.js.map +1 -1
  47. package/dist/esm/Filter/FilterSearchBox.d.ts +10 -0
  48. package/dist/esm/Filter/FilterSearchBox.d.ts.map +1 -0
  49. package/dist/esm/Filter/FilterSearchBox.js +10 -0
  50. package/dist/esm/Filter/FilterSearchBox.js.map +1 -0
  51. package/dist/esm/Filter/measurePanelPlacement.d.ts +31 -0
  52. package/dist/esm/Filter/measurePanelPlacement.d.ts.map +1 -0
  53. package/dist/esm/Filter/measurePanelPlacement.js +132 -0
  54. package/dist/esm/Filter/measurePanelPlacement.js.map +1 -0
  55. package/dist/esm/Filter/usePanelDismissal.d.ts +32 -0
  56. package/dist/esm/Filter/usePanelDismissal.d.ts.map +1 -0
  57. package/dist/esm/Filter/usePanelDismissal.js +63 -0
  58. package/dist/esm/Filter/usePanelDismissal.js.map +1 -0
  59. package/dist/esm/Filter/usePanelPosition.d.ts +24 -0
  60. package/dist/esm/Filter/usePanelPosition.d.ts.map +1 -0
  61. package/dist/esm/Filter/usePanelPosition.js +85 -0
  62. package/dist/esm/Filter/usePanelPosition.js.map +1 -0
  63. package/dist/esm/Filter/useResolvedAnchor.d.ts +17 -0
  64. package/dist/esm/Filter/useResolvedAnchor.d.ts.map +1 -0
  65. package/dist/esm/Filter/useResolvedAnchor.js +16 -0
  66. package/dist/esm/Filter/useResolvedAnchor.js.map +1 -0
  67. package/dist/esm/PivotViewer/PivotViewer.d.ts.map +1 -1
  68. package/dist/esm/PivotViewer/PivotViewer.js +47 -207
  69. package/dist/esm/PivotViewer/PivotViewer.js.map +1 -1
  70. package/dist/esm/PivotViewer/components/pivot/visibility.d.ts +1 -1
  71. package/dist/esm/PivotViewer/components/pivot/visibility.d.ts.map +1 -1
  72. package/dist/esm/PivotViewer/components/pivot/visibility.js +24 -222
  73. package/dist/esm/PivotViewer/components/pivot/visibility.js.map +1 -1
  74. package/dist/esm/PivotViewer/components/pivot/visibilityHelpers.d.ts +114 -0
  75. package/dist/esm/PivotViewer/components/pivot/visibilityHelpers.d.ts.map +1 -0
  76. package/dist/esm/PivotViewer/components/pivot/visibilityHelpers.js +306 -0
  77. package/dist/esm/PivotViewer/components/pivot/visibilityHelpers.js.map +1 -0
  78. package/dist/esm/PivotViewer/hooks/useColorCssVariables.d.ts +9 -0
  79. package/dist/esm/PivotViewer/hooks/useColorCssVariables.d.ts.map +1 -0
  80. package/dist/esm/PivotViewer/hooks/useColorCssVariables.js +32 -0
  81. package/dist/esm/PivotViewer/hooks/useColorCssVariables.js.map +1 -0
  82. package/dist/esm/PivotViewer/hooks/usePivotViewerGrouping.d.ts +27 -0
  83. package/dist/esm/PivotViewer/hooks/usePivotViewerGrouping.d.ts.map +1 -0
  84. package/dist/esm/PivotViewer/hooks/usePivotViewerGrouping.js +40 -0
  85. package/dist/esm/PivotViewer/hooks/usePivotViewerGrouping.js.map +1 -0
  86. package/dist/esm/PivotViewer/hooks/usePivotViewerInteractions.d.ts +27 -0
  87. package/dist/esm/PivotViewer/hooks/usePivotViewerInteractions.d.ts.map +1 -0
  88. package/dist/esm/PivotViewer/hooks/usePivotViewerInteractions.js +46 -0
  89. package/dist/esm/PivotViewer/hooks/usePivotViewerInteractions.js.map +1 -0
  90. package/dist/esm/PivotViewer/hooks/useRawGrouping.d.ts +22 -0
  91. package/dist/esm/PivotViewer/hooks/useRawGrouping.d.ts.map +1 -0
  92. package/dist/esm/PivotViewer/hooks/useRawGrouping.js +56 -0
  93. package/dist/esm/PivotViewer/hooks/useRawGrouping.js.map +1 -0
  94. package/dist/esm/PivotViewer/hooks/useScrollPositionTracking.d.ts +12 -0
  95. package/dist/esm/PivotViewer/hooks/useScrollPositionTracking.d.ts.map +1 -0
  96. package/dist/esm/PivotViewer/hooks/useScrollPositionTracking.js +20 -0
  97. package/dist/esm/PivotViewer/hooks/useScrollPositionTracking.js.map +1 -0
  98. package/dist/esm/PivotViewer/hooks/useViewerLayout.d.ts +16 -0
  99. package/dist/esm/PivotViewer/hooks/useViewerLayout.d.ts.map +1 -0
  100. package/dist/esm/PivotViewer/hooks/useViewerLayout.js +29 -0
  101. package/dist/esm/PivotViewer/hooks/useViewerLayout.js.map +1 -0
  102. package/dist/esm/PivotViewer/hooks/useVisibleIds.d.ts +21 -0
  103. package/dist/esm/PivotViewer/hooks/useVisibleIds.d.ts.map +1 -0
  104. package/dist/esm/PivotViewer/hooks/useVisibleIds.js +34 -0
  105. package/dist/esm/PivotViewer/hooks/useVisibleIds.js.map +1 -0
  106. package/dist/esm/configuration/CratisComponentsDefaults.d.ts.map +1 -1
  107. package/dist/esm/configuration/CratisComponentsDefaults.js +2 -0
  108. package/dist/esm/configuration/CratisComponentsDefaults.js.map +1 -1
  109. package/dist/esm/styles.DataPage.css +51 -6
  110. package/dist/esm/styles.DataTables.css +51 -6
  111. package/dist/esm/styles.SchemaEditor.css +51 -6
  112. package/dist/esm/styles.css +51 -6
  113. package/dist/esm/styles.renderer.builtin.css +51 -6
  114. package/dist/esm/types/parts.d.ts +4 -1
  115. package/dist/esm/types/parts.d.ts.map +1 -1
  116. package/dist/esm/types/parts.js +4 -1
  117. package/dist/esm/types/parts.js.map +1 -1
  118. package/package.json +1 -1
@@ -1,14 +1,15 @@
1
1
  import { jsx, jsxs } from 'react/jsx-runtime';
2
- import { useSyncExternalStore, useState, useLayoutEffect, useRef, useMemo, useEffect, Children, isValidElement } from 'react';
2
+ import { useSyncExternalStore, useRef, useMemo, useEffect, Children, isValidElement } from 'react';
3
3
  import { createPortal } from 'react-dom';
4
4
  import { AnimatePresence, motion } from 'framer-motion';
5
- import { useCratisIcon } from '../configuration/useCratisIcon.js';
6
5
  import { useCratisComponentsConfig } from '../configuration/CratisComponentsContext.js';
7
6
  import { unstable_useOverlayEnvironment } from '../renderer/RendererContext.js';
8
- import { resolveDropdownPosition } from './utils.js';
9
7
  import { FilterEditor } from './FilterEditor.js';
10
- import { RangeHistogramFilter } from './RangeHistogramFilter.js';
11
- import { CheckboxListFilter } from './CheckboxListFilter.js';
8
+ import { FilterGroup } from './FilterGroup.js';
9
+ import { usePanelPosition } from './usePanelPosition.js';
10
+ import { useResolvedAnchor } from './useResolvedAnchor.js';
11
+ import { FilterSearchBox } from './FilterSearchBox.js';
12
+ import { useDismissOnOutsideClick, focusPanelUnlessFocused, useCloseOnAnchorEscape, closeOnPanelEscape } from './usePanelDismissal.js';
12
13
 
13
14
  function buildEditorMap(children) {
14
15
  const map = {};
@@ -22,281 +23,47 @@ function buildEditorMap(children) {
22
23
  });
23
24
  return map;
24
25
  }
25
- function OptionList({ filter, selections, onFilterToggle, searchPlaceholder, isExpanded, }) {
26
- return (jsx(CheckboxListFilter, { options: filter.options ?? [], selected: selections, multi: filter.multi, searchable: filter.searchable, searchPlaceholder: filter.searchPlaceholder ?? searchPlaceholder, searchAriaLabel: filter.searchAriaLabel, autoFocusSearch: filter.autoFocus === true && isExpanded, name: `filter-${filter.key}`, onToggle: (optionKey) => onFilterToggle(filter.key, optionKey, filter.multi ?? false) }));
26
+ function resolvePortalContainer(environmentContainer, modalRoot) {
27
+ return environmentContainer && modalRoot && !modalRoot.contains(environmentContainer)
28
+ ? modalRoot
29
+ : environmentContainer;
27
30
  }
28
31
  const subscribeToBrowserState = () => () => undefined;
29
32
  const browserSnapshot = () => true;
30
33
  const serverSnapshot = () => false;
31
34
  function FilterPanel({ isOpen, filters, filterValues, rangeValues, customValues, search, searchPlaceholder = 'Search…', 'aria-label': ariaLabel, searchAriaLabel, clearFilterAriaLabel = 'Clear filter', clearRangeAriaLabel = 'Clear range', expandedFilterKey, anchorRef, onClose, onSearchChange, onFilterToggle, onFilterClear, onRangeChange, onExpandedFilterChange, onCustomValueChange, children, }) {
32
35
  const isBrowser = useSyncExternalStore(subscribeToBrowserState, browserSnapshot, serverSnapshot);
33
- const icon = useCratisIcon();
34
36
  const { messages } = useCratisComponentsConfig();
35
37
  const overlayEnvironment = unstable_useOverlayEnvironment();
36
38
  const environmentContainer = isBrowser ? overlayEnvironment.getContainer() : null;
37
- const [resolvedAnchor, setResolvedAnchor] = useState(null);
38
- useLayoutEffect(() => {
39
- const anchor = anchorRef.current;
40
- const modalRoot = anchor?.closest('.cratis-dialog[data-cratis-part="root"]') ?? null;
41
- setResolvedAnchor((previous) => previous?.anchor === anchor && previous.modalRoot === modalRoot
42
- ? previous
43
- : { anchor, modalRoot });
44
- });
39
+ const resolvedAnchor = useResolvedAnchor(anchorRef);
45
40
  const modalRoot = resolvedAnchor?.modalRoot;
46
- const portalContainer = environmentContainer && modalRoot && !modalRoot.contains(environmentContainer)
47
- ? modalRoot
48
- : environmentContainer;
41
+ const portalContainer = resolvePortalContainer(environmentContainer, modalRoot);
49
42
  const panelRef = useRef(null);
50
- const [position, setPosition] = useState({
51
- top: 0,
52
- left: 0,
53
- maxHeight: 0,
43
+ const position = usePanelPosition({
44
+ isOpen,
45
+ portalContainer,
46
+ modalRoot,
47
+ anchorRef,
48
+ panelRef,
49
+ resolvedAnchor: resolvedAnchor?.anchor,
54
50
  });
55
51
  const editorMap = useMemo(() => buildEditorMap(children), [children]);
56
- useLayoutEffect(() => {
57
- if (!isOpen || !portalContainer)
58
- return;
59
- const probeStyle = 'position:fixed;left:0;top:0;width:1px;height:1px;margin:0;padding:0;border:0;visibility:hidden;pointer-events:none';
60
- const probe = document.createElement('div');
61
- probe.style.cssText = probeStyle;
62
- portalContainer.appendChild(probe);
63
- const rootProbe = modalRoot && modalRoot.contains(portalContainer) && portalContainer !== modalRoot
64
- ? document.createElement('div') : null;
65
- if (rootProbe && modalRoot) {
66
- rootProbe.style.cssText = probeStyle;
67
- modalRoot.appendChild(rootProbe);
68
- }
69
- const measureProbe = (element) => {
70
- element.style.top = '0';
71
- element.style.bottom = 'auto';
72
- const origin = element.getBoundingClientRect();
73
- element.style.top = 'auto';
74
- element.style.bottom = '0';
75
- const bottom = element.getBoundingClientRect().bottom;
76
- element.style.bottom = 'auto';
77
- element.style.left = 'auto';
78
- element.style.right = '0';
79
- const right = element.getBoundingClientRect().right;
80
- element.style.left = '0';
81
- element.style.right = 'auto';
82
- return { origin, bottom, right };
83
- };
84
- const updatePosition = () => {
85
- if (!anchorRef.current || !panelRef.current)
86
- return;
87
- const { origin, bottom: containingBlockBottom, right: containingBlockRight } = measureProbe(probe);
88
- const scaleX = origin.width || 1;
89
- const scaleY = origin.height || 1;
90
- const viewportElement = document.compatMode === 'BackCompat' ? document.body : document.documentElement;
91
- const viewport = {
92
- width: viewportElement.clientWidth || window.innerWidth,
93
- height: viewportElement.clientHeight || window.innerHeight,
94
- };
95
- const rootRect = modalRoot && modalRoot.contains(portalContainer)
96
- ? modalRoot.getBoundingClientRect() : null;
97
- const rootEdges = rootRect && (rootProbe ? measureProbe(rootProbe) :
98
- { origin, bottom: containingBlockBottom, right: containingBlockRight });
99
- const rootStyle = rootRect && getComputedStyle(modalRoot);
100
- const rootScaleX = rootEdges?.origin.width ?? 1;
101
- const rootScaleY = rootEdges?.origin.height ?? 1;
102
- const rootHasFixedContainingStyle = Boolean(rootStyle && (rootStyle.transform !== 'none' || rootStyle.translate !== 'none' ||
103
- rootStyle.scale !== 'none' || rootStyle.rotate !== 'none' ||
104
- rootStyle.perspective !== 'none' || rootStyle.filter !== 'none' ||
105
- rootStyle.getPropertyValue('backdrop-filter') !== 'none' &&
106
- rootStyle.getPropertyValue('backdrop-filter') !== '' ||
107
- /\b(?:layout|paint|strict|content)\b/u.test(rootStyle.contain) ||
108
- /^(?:size|inline-size)$/u.test(rootStyle.getPropertyValue('container-type')) ||
109
- /\b(?:transform|translate|scale|rotate|perspective|filter|backdrop-filter|contain|container-type)\b/u
110
- .test(rootStyle.willChange)));
111
- const rootOwnsBlock = rootHasFixedContainingStyle || Boolean(rootRect && rootEdges && rootStyle &&
112
- Math.abs(rootEdges.origin.left - (rootRect.left + parseFloat(rootStyle.borderLeftWidth) * rootScaleX)) <= 0.5 &&
113
- Math.abs(rootEdges.origin.top - (rootRect.top + parseFloat(rootStyle.borderTopWidth) * rootScaleY)) <= 0.5 &&
114
- Math.abs(rootEdges.right - (rootRect.right - parseFloat(rootStyle.borderRightWidth) * rootScaleX)) <= 0.5 &&
115
- Math.abs(rootEdges.bottom - (rootRect.bottom - parseFloat(rootStyle.borderBottomWidth) * rootScaleY)) <= 0.5);
116
- const descendantOwnsBlock = Boolean(rootEdges && rootProbe && (Math.abs(origin.left - rootEdges.origin.left) > 0.5 ||
117
- Math.abs(origin.top - rootEdges.origin.top) > 0.5 ||
118
- Math.abs(containingBlockRight - rootEdges.right) > 0.5 ||
119
- Math.abs(containingBlockBottom - rootEdges.bottom) > 0.5));
120
- const modalRect = rootOwnsBlock || descendantOwnsBlock ? rootRect : null;
121
- const clip = modalRect && modalRect.right > modalRect.left && modalRect.bottom > modalRect.top
122
- ? modalRect : null;
123
- const bounds = {
124
- left: Math.max(0, clip?.left ?? 0),
125
- top: Math.max(0, clip?.top ?? 0),
126
- right: Math.min(viewport.width, clip?.right ?? viewport.width),
127
- bottom: Math.min(viewport.height, clip?.bottom ?? viewport.height),
128
- };
129
- const visible = {
130
- width: Math.max(0, bounds.right - bounds.left),
131
- height: Math.max(0, bounds.bottom - bounds.top),
132
- };
133
- const panel = panelRef.current;
134
- const previousWidth = panel.style.width;
135
- if (previousWidth)
136
- panel.style.width = '';
137
- const cssWidth = panel.offsetWidth;
138
- if (previousWidth)
139
- panel.style.width = previousWidth;
140
- const width = cssWidth * scaleX > visible.width - 32
141
- ? Math.max(0, (visible.width - 32) / scaleX)
142
- : undefined;
143
- const anchor = anchorRef.current.getBoundingClientRect();
144
- const viewportPosition = resolveDropdownPosition({
145
- left: anchor.left - bounds.left,
146
- top: anchor.top - bounds.top,
147
- bottom: anchor.bottom - bounds.top,
148
- }, visible, (width ?? cssWidth) * scaleX);
149
- const nextPosition = {
150
- left: (bounds.left + viewportPosition.left - origin.left) / scaleX,
151
- top: viewportPosition.top === undefined ? undefined : (bounds.top + viewportPosition.top - origin.top) / scaleY,
152
- bottom: viewportPosition.bottom === undefined ? undefined
153
- : (containingBlockBottom - (bounds.bottom - viewportPosition.bottom)) / scaleY,
154
- maxHeight: viewportPosition.maxHeight / scaleY,
155
- width,
156
- };
157
- setPosition((previous) => previous.left === nextPosition.left &&
158
- previous.top === nextPosition.top && previous.bottom === nextPosition.bottom &&
159
- previous.maxHeight === nextPosition.maxHeight && previous.width === nextPosition.width
160
- ? previous : nextPosition);
161
- };
162
- let animationFrame;
163
- const schedulePosition = () => {
164
- if (animationFrame !== undefined)
165
- return;
166
- animationFrame = requestAnimationFrame(() => {
167
- animationFrame = undefined;
168
- updatePosition();
169
- });
170
- };
171
- const handleScroll = (event) => {
172
- if (event.target instanceof Node && panelRef.current?.contains(event.target))
173
- return;
174
- schedulePosition();
175
- };
176
- const positioner = modalRoot?.parentElement?.matches('[data-cratis-part="positioner"]')
177
- ? modalRoot.parentElement : null;
178
- const handleModalMotionEnd = (event) => {
179
- if (event.target === modalRoot || event.target === positioner)
180
- schedulePosition();
181
- };
182
- const motionEvents = ['animationend', 'animationcancel', 'transitionend'];
183
- updatePosition();
184
- window.addEventListener('resize', schedulePosition);
185
- window.addEventListener('scroll', handleScroll, true);
186
- for (const eventName of motionEvents) {
187
- modalRoot?.addEventListener(eventName, handleModalMotionEnd);
188
- positioner?.addEventListener(eventName, handleModalMotionEnd);
189
- }
190
- return () => {
191
- if (animationFrame !== undefined)
192
- cancelAnimationFrame(animationFrame);
193
- window.removeEventListener('resize', schedulePosition);
194
- window.removeEventListener('scroll', handleScroll, true);
195
- for (const eventName of motionEvents) {
196
- modalRoot?.removeEventListener(eventName, handleModalMotionEnd);
197
- positioner?.removeEventListener(eventName, handleModalMotionEnd);
198
- }
199
- probe.remove();
200
- rootProbe?.remove();
201
- };
202
- }, [anchorRef, isOpen, modalRoot, portalContainer, resolvedAnchor?.anchor]);
203
- useEffect(() => {
204
- if (!isOpen)
205
- return;
206
- const handleClickOutside = (event) => {
207
- const target = event.target;
208
- const panel = panelRef.current;
209
- const anchor = anchorRef.current;
210
- if (panel && !panel.contains(target) && anchor && !anchor.contains(target)) {
211
- onClose();
212
- }
213
- };
214
- const timeoutId = setTimeout(() => {
215
- document.addEventListener('mousedown', handleClickOutside, true);
216
- }, 0);
217
- return () => {
218
- clearTimeout(timeoutId);
219
- document.removeEventListener('mousedown', handleClickOutside, true);
220
- };
221
- }, [isOpen, anchorRef, onClose]);
52
+ useDismissOnOutsideClick({ isOpen, anchorRef, panelRef, onClose });
222
53
  useEffect(() => {
223
- if (isOpen && panelRef.current && !panelRef.current.contains(document.activeElement)) {
224
- panelRef.current.focus();
225
- }
54
+ focusPanelUnlessFocused(isOpen, panelRef.current);
226
55
  }, [isOpen, portalContainer, resolvedAnchor]);
227
- useEffect(() => {
228
- if (!isOpen)
229
- return;
230
- const handleAnchorEscape = (event) => {
231
- if (event.key !== 'Escape' || event.defaultPrevented || event.isComposing)
232
- return;
233
- const focused = document.activeElement;
234
- if (focused && anchorRef.current?.contains(focused)) {
235
- event.preventDefault();
236
- event.stopPropagation();
237
- onClose();
238
- anchorRef.current?.focus();
239
- }
240
- };
241
- document.addEventListener('keydown', handleAnchorEscape, true);
242
- return () => {
243
- document.removeEventListener('keydown', handleAnchorEscape, true);
244
- };
245
- }, [isOpen, anchorRef, onClose]);
56
+ useCloseOnAnchorEscape({ isOpen, anchorRef, onClose });
246
57
  if (!portalContainer || !resolvedAnchor || resolvedAnchor.anchor !== anchorRef.current)
247
58
  return null;
248
- return createPortal(jsx(AnimatePresence, { initial: Boolean(modalRoot), children: isOpen && (jsx(motion.div, { ref: panelRef, role: 'dialog', "aria-label": ariaLabel ?? messages?.filter?.label ?? 'Filters', tabIndex: -1, onKeyDown: (event) => {
249
- if (event.key !== 'Escape' || event.defaultPrevented || event.nativeEvent.isComposing)
250
- return;
251
- event.preventDefault();
252
- event.stopPropagation();
253
- onClose();
254
- anchorRef.current?.focus();
255
- }, className: 'pv-filter-dropdown', style: {
59
+ return createPortal(jsx(AnimatePresence, { initial: Boolean(modalRoot), children: isOpen && (jsx(motion.div, { ref: panelRef, role: 'dialog', "aria-label": ariaLabel ?? messages?.filter?.label ?? 'Filters', tabIndex: -1, onKeyDown: (event) => closeOnPanelEscape(event, onClose, anchorRef), className: 'pv-filter-dropdown', style: {
256
60
  position: 'fixed',
257
61
  left: position.left,
258
62
  top: position.top,
259
63
  bottom: position.bottom,
260
64
  maxHeight: position.maxHeight,
261
65
  width: position.width,
262
- }, initial: { opacity: 0, y: -8 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: -8 }, transition: { duration: 0.15 }, "data-open": isOpen || undefined, children: jsxs("div", { className: 'pv-filter-dropdown-content', children: [onSearchChange && (jsx("div", { className: 'pv-search', children: jsx("input", { type: 'search', placeholder: searchPlaceholder, "aria-label": (searchAriaLabel ?? messages?.filter?.searchAriaLabel ?? searchPlaceholder) || 'Search', value: search ?? '', onChange: (event) => onSearchChange(event.target.value) }) })), jsx("div", { className: 'pv-filter-groups', children: filters.map((filter) => {
263
- const selections = filterValues[filter.key] ?? new Set();
264
- const rangeSelection = rangeValues[filter.key];
265
- const customValue = customValues?.[filter.key];
266
- const isExpanded = expandedFilterKey === filter.key;
267
- const isDate = filter.type === 'date';
268
- const isNumeric = filter.type === 'number' || isDate;
269
- const editorRender = editorMap[filter.key];
270
- const isCustom = filter.type === 'custom' ||
271
- editorRender !== undefined;
272
- const hasCustomValue = customValue !== undefined && customValue !== null;
273
- const canClear = isNumeric
274
- ? Boolean(rangeSelection)
275
- : isCustom
276
- ? hasCustomValue
277
- : selections.size > 0;
278
- const clearLabel = isNumeric
279
- ? clearRangeAriaLabel
280
- : clearFilterAriaLabel;
281
- const clearValue = () => {
282
- if (isNumeric)
283
- onRangeChange(filter.key, null);
284
- else if (isCustom) {
285
- onCustomValueChange?.(filter.key, undefined);
286
- }
287
- else
288
- onFilterClear(filter.key);
289
- };
290
- const formatRangeValue = isDate
291
- ? (value) => new Date(value).toLocaleString()
292
- : undefined;
293
- return (jsxs("div", { className: `pv-filter ${isExpanded ? 'expanded' : ''}`, "data-selected": canClear || undefined, "data-open": isExpanded || undefined, children: [jsxs("div", { className: 'pv-filter-trigger', "data-selected": canClear || undefined, "data-open": isExpanded || undefined, children: [jsxs("button", { type: 'button', className: 'pv-filter-toggle', "aria-expanded": isExpanded, "data-selected": canClear || undefined, "data-open": isExpanded || undefined, "data-pressed": isExpanded || undefined, onClick: () => onExpandedFilterChange(isExpanded ? null : filter.key), children: [jsx("span", { className: 'pv-filter-label', children: filter.label }), jsxs("span", { className: 'pv-filter-trigger-meta', children: [!isNumeric &&
294
- !isCustom &&
295
- selections.size > 0 && (jsx("span", { className: 'pv-filter-count', children: selections.size })), isNumeric && rangeSelection && (jsx("span", { className: 'pv-filter-count', children: "Range" })), isCustom && hasCustomValue && (jsx("span", { className: 'pv-filter-count', children: "\u2022" })), jsx("span", { className: 'pv-filter-chevron' })] })] }), canClear && (jsx("button", { type: 'button', className: 'pv-filter-clear-header', title: clearLabel, "aria-label": clearLabel, onClick: clearValue, children: icon('clear', '×') }))] }), jsx("div", { className: `pv-filter-content ${isExpanded ? 'expanded' : ''}`, "data-open": isExpanded || undefined, children: isCustom && editorRender ? (editorRender({
296
- value: customValue,
297
- onChange: (value) => onCustomValueChange?.(filter.key, value),
298
- })) : isNumeric && filter.numericRange ? (jsx(RangeHistogramFilter, { values: filter.numericRange.values, histogram: filter.numericRange.histogram, min: filter.numericRange.min, max: filter.numericRange.max, buckets: filter.buckets ?? 20, selectedRange: rangeSelection ?? null, onChange: (range) => onRangeChange(filter.key, range), formatValue: formatRangeValue })) : (jsx(OptionList, { filter: filter, selections: selections, onFilterToggle: onFilterToggle, searchPlaceholder: searchPlaceholder, isExpanded: isExpanded })) })] }, filter.key));
299
- }) })] }) })) }), portalContainer);
66
+ }, initial: { opacity: 0, y: -8 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: -8 }, transition: { duration: 0.15 }, "data-open": isOpen || undefined, children: jsxs("div", { className: 'pv-filter-dropdown-content', children: [onSearchChange && (jsx(FilterSearchBox, { search: search, placeholder: searchPlaceholder, ariaLabel: searchAriaLabel, onSearchChange: onSearchChange })), jsx("div", { className: 'pv-filter-groups', children: filters.map((filter) => (jsx(FilterGroup, { filter: filter, selections: filterValues[filter.key] ?? new Set(), rangeSelection: rangeValues[filter.key], customValue: customValues?.[filter.key], isExpanded: expandedFilterKey === filter.key, editorRender: editorMap[filter.key], searchPlaceholder: searchPlaceholder, clearFilterAriaLabel: clearFilterAriaLabel, clearRangeAriaLabel: clearRangeAriaLabel, onFilterToggle: onFilterToggle, onFilterClear: onFilterClear, onRangeChange: onRangeChange, onExpandedFilterChange: onExpandedFilterChange, onCustomValueChange: onCustomValueChange }, filter.key))) })] }) })) }), portalContainer);
300
67
  }
301
68
 
302
69
  export { FilterPanel };
@@ -1 +1 @@
1
- {"version":3,"file":"FilterPanel.js","sources":["../../../Filter/FilterPanel.tsx"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport {\n Children,\n isValidElement,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n} from 'react';\nimport type { ReactNode } from 'react';\nimport { createPortal } from 'react-dom';\nimport { AnimatePresence, motion } from 'framer-motion';\nimport { useCratisIcon } from '../configuration/useCratisIcon';\nimport { useCratisComponentsConfig } from '../configuration/CratisComponentsContext';\nimport { unstable_useOverlayEnvironment } from '../renderer/RendererContext';\nimport type {\n FilterDefinition,\n FilterValues,\n RangeValues,\n CustomFilterValues,\n} from './types';\nimport { resolveDropdownPosition, type DropdownPosition } from './utils';\nimport type { FilterEditorProps } from './FilterEditorProps';\nimport { FilterEditor } from './FilterEditor';\nimport { RangeHistogramFilter } from './RangeHistogramFilter';\nimport { CheckboxListFilter } from './CheckboxListFilter';\n\n/**\n * Props for {@link FilterPanel}.\n *\n * Describes the filter definitions to render, the current state\n * (selections, ranges, custom values), and the callbacks that fire when\n * the user changes a filter or expands a filter group.\n */\nexport interface FilterPanelProps {\n /** Whether the panel is visible. */\n isOpen: boolean;\n /** Filter definitions, each describing one filter group. */\n filters: FilterDefinition[];\n /** Current string/option selections, keyed by FilterDefinition.key. */\n filterValues: FilterValues;\n /** Current numeric range selections, keyed by FilterDefinition.key. */\n rangeValues: RangeValues;\n /** Current values for filters using a custom `<FilterEditor>` child, keyed by FilterDefinition.key. */\n customValues?: CustomFilterValues;\n /** Current search text shown in the search box. */\n search?: string;\n /** Placeholder text for the search input. Defaults to 'Search…'. */\n searchPlaceholder?: string;\n /** Accessible name for the non-modal dialog. Overrides `messages.filter.label`, then 'Filters'. */\n 'aria-label'?: string;\n /** Accessible name for the panel search. Falls back to `messages.filter.searchAriaLabel`, its placeholder, then 'Search'. */\n searchAriaLabel?: string;\n /** Accessible name for a clear-filter button. Override to localize. Defaults to 'Clear filter'. */\n clearFilterAriaLabel?: string;\n /** Accessible name for a clear-range button. Override to localize. Defaults to 'Clear range'. */\n clearRangeAriaLabel?: string;\n /** Which filter group is currently expanded. */\n expandedFilterKey?: string | null;\n /** The button element the panel anchors below. */\n anchorRef: React.RefObject<HTMLButtonElement | null>;\n /** Called when the panel should close (e.g. click outside). */\n onClose: () => void;\n /** Called when the search text changes. If omitted, the search box is hidden. */\n onSearchChange?: (value: string) => void;\n /** Called when a string option is toggled. */\n onFilterToggle: (filterKey: string, optionKey: string, multi: boolean) => void;\n /** Called when all selections for a filter are cleared. */\n onFilterClear: (filterKey: string) => void;\n /** Called when a numeric range changes. */\n onRangeChange: (filterKey: string, range: [number, number] | null) => void;\n /** Called when the expanded filter group changes. */\n onExpandedFilterChange: (key: string | null) => void;\n /** Called when a custom-editor value changes. */\n onCustomValueChange?: (filterKey: string, value: unknown) => void;\n /**\n * `<FilterEditor>` elements that provide custom UI for specific filter groups.\n *\n * See the {@link FilterPanel} example below for the complete prop mapping\n * and a custom editor.\n */\n children?: ReactNode;\n}\n\n/** Build a map of filterKey → render function from any <FilterEditor> children. */\nfunction buildEditorMap(\n children: ReactNode | undefined,\n): Record<string, (props: FilterEditorProps) => ReactNode> {\n const map: Record<string, (props: FilterEditorProps) => ReactNode> = {};\n Children.forEach(children, (child) => {\n if (isValidElement(child) && child.type === FilterEditor) {\n const { filterKey, children: renderFn } = child.props as {\n filterKey: string;\n children: (props: FilterEditorProps) => ReactNode;\n };\n if (filterKey && typeof renderFn === 'function') {\n map[filterKey] = renderFn;\n }\n }\n });\n return map;\n}\n\n/**\n * Renders a filter dropdown panel anchored below a trigger button. The panel\n * appears as a portal at a fixed position on the page and includes search,\n * collapsible filter groups, and per-filter editors (option lists, numeric\n * range histograms, or custom components).\n *\n * Use with {@link useFilterState} for turnkey state management, or wire the\n * props to external state when the filter state lives elsewhere (e.g. in a\n * query param reducer).\n *\n * ## Filter types\n *\n * - **String/option filters** render as a {@link CheckboxListFilter} - a bounded,\n * scrollable list of checkboxes or radio buttons. Controlled through `filterValues`.\n * A search box grows out of the list automatically once it has more options than\n * fit in its box; see {@link FilterDefinition.searchable} to force it always on\n * or always off instead.\n * - **Numeric/date range filters** render as a {@link RangeHistogramFilter}\n * with a draggable range selector over a histogram. Controlled through\n * `rangeValues`.\n * - **Custom filters** slot in a `<FilterEditor>` child whose `filterKey`\n * matches the filter's `key`. Controlled through `customValues`.\n *\n * ```tsx\n * import { useRef, useState } from 'react';\n * import {\n * FilterEditor, FilterPanel, useFilterState, type FilterDefinition,\n * } from '@cratis/components/Filter';\n *\n * const filters: FilterDefinition[] = [{ key: 'category', label: 'Category', type: 'custom' }];\n *\n * function FilterExample() {\n * const state = useFilterState(filters);\n * const [isOpen, setIsOpen] = useState(false);\n * const anchorRef = useRef<HTMLButtonElement>(null);\n *\n * return (\n * <>\n * <button ref={anchorRef} onClick={() => setIsOpen(!isOpen)}>\n * Filters\n * </button>\n * <FilterPanel\n * isOpen={isOpen}\n * filters={filters}\n * anchorRef={anchorRef}\n * onClose={() => setIsOpen(false)}\n * filterValues={state.filterValues}\n * rangeValues={state.rangeValues}\n * customValues={state.customValues}\n * expandedFilterKey={state.expandedFilterKey}\n * onFilterToggle={state.handleToggleFilter}\n * onFilterClear={state.handleClearFilter}\n * onRangeChange={state.handleRangeChange}\n * onExpandedFilterChange={state.setExpandedFilterKey}\n * onCustomValueChange={state.handleCustomValueChange}\n * >\n * <FilterEditor filterKey=\"category\">\n * {({ value, onChange }) => (\n * <input value={String(value ?? '')} onChange={(event) => onChange(event.target.value)} />\n * )}\n * </FilterEditor>\n * </FilterPanel>\n * </>\n * );\n * }\n * ```\n *\n * @param props - {@link FilterPanelProps}.\n */\ninterface OptionListProps {\n filter: FilterDefinition;\n selections: Set<string>;\n onFilterToggle: (filterKey: string, optionKey: string, multi: boolean) => void;\n /** Falls back to the panel-level search placeholder when the filter group has none of its own. */\n searchPlaceholder?: string;\n isExpanded: boolean;\n}\n\n/** Adapts a `FilterDefinition`'s string/option shape onto the reusable {@link CheckboxListFilter}. */\nfunction OptionList({\n filter,\n selections,\n onFilterToggle,\n searchPlaceholder,\n isExpanded,\n}: OptionListProps) {\n return (\n <CheckboxListFilter\n options={filter.options ?? []}\n selected={selections}\n multi={filter.multi}\n searchable={filter.searchable}\n searchPlaceholder={filter.searchPlaceholder ?? searchPlaceholder}\n searchAriaLabel={filter.searchAriaLabel}\n autoFocusSearch={filter.autoFocus === true && isExpanded}\n name={`filter-${filter.key}`}\n onToggle={(optionKey) =>\n onFilterToggle(filter.key, optionKey, filter.multi ?? false)\n }\n />\n );\n}\n\nconst subscribeToBrowserState = () => () => undefined;\nconst browserSnapshot = () => true;\nconst serverSnapshot = () => false;\n\n/**\n * A filter dropdown panel anchored below a trigger button. Renders filter\n * groups, search, numeric range histograms, and custom editors. The panel\n * appears as a portal at a fixed position on the page and closes when the\n * user clicks outside.\n *\n * See the full documentation comment at line 169 for usage examples.\n */\nexport function FilterPanel({\n isOpen,\n filters,\n filterValues,\n rangeValues,\n customValues,\n search,\n searchPlaceholder = 'Search…',\n 'aria-label': ariaLabel,\n searchAriaLabel,\n clearFilterAriaLabel = 'Clear filter',\n clearRangeAriaLabel = 'Clear range',\n expandedFilterKey,\n anchorRef,\n onClose,\n onSearchChange,\n onFilterToggle,\n onFilterClear,\n onRangeChange,\n onExpandedFilterChange,\n onCustomValueChange,\n children,\n}: FilterPanelProps) {\n const isBrowser = useSyncExternalStore(\n subscribeToBrowserState,\n browserSnapshot,\n serverSnapshot,\n );\n const icon = useCratisIcon();\n const { messages } = useCratisComponentsConfig();\n const overlayEnvironment = unstable_useOverlayEnvironment();\n const environmentContainer = isBrowser ? overlayEnvironment.getContainer() : null;\n const [resolvedAnchor, setResolvedAnchor] = useState<{\n anchor: HTMLButtonElement | null;\n modalRoot: HTMLElement | null;\n } | null>(null);\n // Refs attach during commit, after render. Resolve the modal before mounting any open\n // panel so its first portal never lands in a shared root hidden by the modal.\n // Ref attachment/identity can change on any commit; the state updater bails out if unchanged.\n // eslint-disable-next-line @eslint-react/exhaustive-deps\n useLayoutEffect(() => {\n const anchor = anchorRef.current;\n const modalRoot = anchor?.closest<HTMLElement>('.cratis-dialog[data-cratis-part=\"root\"]') ?? null;\n setResolvedAnchor((previous) => previous?.anchor === anchor && previous.modalRoot === modalRoot\n ? previous\n : { anchor, modalRoot });\n });\n // A shared overlay root sits outside a modal's focus scope and is hidden from assistive\n // technology. Keep a panel anchored inside a Cratis Dialog inside that modal instead.\n // An explicitly unavailable container still defers the portal; it is never a body fallback.\n const modalRoot = resolvedAnchor?.modalRoot;\n const portalContainer = environmentContainer && modalRoot && !modalRoot.contains(environmentContainer)\n ? modalRoot\n : environmentContainer;\n const panelRef = useRef<HTMLDivElement>(null);\n const [position, setPosition] = useState<DropdownPosition & { width?: number }>({\n top: 0,\n left: 0,\n maxHeight: 0,\n });\n\n const editorMap = useMemo(() => buildEditorMap(children), [children]);\n\n // Keep the fixed-position portal attached to its anchor and clamped inside the viewport.\n // A transform (or other fixed containing-block property) on the portal container OR any\n // ancestor changes the meaning of CSS left/top/bottom. A fixed probe in the portal sees\n // exactly the same containing block as the panel, without guessing which ancestor owns it.\n // Capture-phase scroll observation also covers nested scrolling containers.\n useLayoutEffect(() => {\n if (!isOpen || !portalContainer) return;\n\n const probeStyle = 'position:fixed;left:0;top:0;width:1px;height:1px;margin:0;padding:0;border:0;visibility:hidden;pointer-events:none';\n const probe = document.createElement('div');\n probe.style.cssText = probeStyle;\n portalContainer.appendChild(probe);\n // A portal inside (rather than directly on) the root can have a different fixed\n // containing block. A second probe distinguishes that descendant block from one\n // owned by the positioner outside the root.\n const rootProbe = modalRoot && modalRoot.contains(portalContainer) && portalContainer !== modalRoot\n ? document.createElement('div') : null;\n if (rootProbe && modalRoot) {\n rootProbe.style.cssText = probeStyle;\n modalRoot.appendChild(rootProbe);\n }\n\n const measureProbe = (element: HTMLDivElement) => {\n element.style.top = '0';\n element.style.bottom = 'auto';\n const origin = element.getBoundingClientRect();\n element.style.top = 'auto';\n element.style.bottom = '0';\n const bottom = element.getBoundingClientRect().bottom;\n element.style.bottom = 'auto';\n element.style.left = 'auto';\n element.style.right = '0';\n const right = element.getBoundingClientRect().right;\n element.style.left = '0';\n element.style.right = 'auto';\n return { origin, bottom, right };\n };\n\n const updatePosition = () => {\n if (!anchorRef.current || !panelRef.current) return;\n\n // The probe stays mounted while open, but its origin must be read on every\n // update: scrolling a transformed ancestor changes its viewport coordinates.\n const { origin, bottom: containingBlockBottom, right: containingBlockRight } = measureProbe(probe);\n\n // offsetWidth is in local CSS pixels; the probe's rect supplies the viewport scale.\n // Preserve the viewport gutter even when a containing block scales the dropdown.\n const scaleX = origin.width || 1;\n const scaleY = origin.height || 1;\n // Layoutless renderers report zero; browser layout uses the scrollbar-free viewport.\n // In quirks mode the viewport's client size is reported on the body, not the root.\n const viewportElement = document.compatMode === 'BackCompat' ? document.body : document.documentElement;\n const viewport = {\n width: viewportElement.clientWidth || window.innerWidth,\n height: viewportElement.clientHeight || window.innerHeight,\n };\n const rootRect = modalRoot && modalRoot.contains(portalContainer)\n ? modalRoot.getBoundingClientRect() : null;\n const rootEdges = rootRect && (rootProbe ? measureProbe(rootProbe) :\n { origin, bottom: containingBlockBottom, right: containingBlockRight });\n const rootStyle = rootRect && getComputedStyle(modalRoot!);\n const rootScaleX = rootEdges?.origin.width ?? 1;\n const rootScaleY = rootEdges?.origin.height ?? 1;\n // Axis-aligned bounding rects lose the padding origin under rotation/skew.\n // The root's own containing-block properties cover those cases; the probe\n // comparison below still handles nested portal containers geometrically.\n const rootHasFixedContainingStyle = Boolean(rootStyle && (\n rootStyle.transform !== 'none' || rootStyle.translate !== 'none' ||\n rootStyle.scale !== 'none' || rootStyle.rotate !== 'none' ||\n rootStyle.perspective !== 'none' || rootStyle.filter !== 'none' ||\n rootStyle.getPropertyValue('backdrop-filter') !== 'none' &&\n rootStyle.getPropertyValue('backdrop-filter') !== '' ||\n /\\b(?:layout|paint|strict|content)\\b/u.test(rootStyle.contain) ||\n /^(?:size|inline-size)$/u.test(rootStyle.getPropertyValue('container-type')) ||\n /\\b(?:transform|translate|scale|rotate|perspective|filter|backdrop-filter|contain|container-type)\\b/u\n .test(rootStyle.willChange)\n ));\n // A fixed probe spans its containing block, not the Dialog's overflow box.\n // Compare all four edges to the root's padding box: a side dialog can share\n // the positioner's top-left origin without sharing its containing block.\n // Comparing to innerWidth/innerHeight instead would mistake classic scrollbars\n // (excluded from the layout viewport) for a transformed containing block.\n const rootOwnsBlock = rootHasFixedContainingStyle || Boolean(rootRect && rootEdges && rootStyle &&\n Math.abs(rootEdges.origin.left - (rootRect.left + parseFloat(rootStyle.borderLeftWidth) * rootScaleX)) <= 0.5 &&\n Math.abs(rootEdges.origin.top - (rootRect.top + parseFloat(rootStyle.borderTopWidth) * rootScaleY)) <= 0.5 &&\n Math.abs(rootEdges.right - (rootRect.right - parseFloat(rootStyle.borderRightWidth) * rootScaleX)) <= 0.5 &&\n Math.abs(rootEdges.bottom - (rootRect.bottom - parseFloat(rootStyle.borderBottomWidth) * rootScaleY)) <= 0.5);\n const descendantOwnsBlock = Boolean(rootEdges && rootProbe && (\n Math.abs(origin.left - rootEdges.origin.left) > 0.5 ||\n Math.abs(origin.top - rootEdges.origin.top) > 0.5 ||\n Math.abs(containingBlockRight - rootEdges.right) > 0.5 ||\n Math.abs(containingBlockBottom - rootEdges.bottom) > 0.5));\n const modalRect = rootOwnsBlock || descendantOwnsBlock ? rootRect : null;\n // Layoutless environments cannot supply a modal clip rect.\n const clip = modalRect && modalRect.right > modalRect.left && modalRect.bottom > modalRect.top\n ? modalRect : null;\n const bounds = {\n left: Math.max(0, clip?.left ?? 0),\n top: Math.max(0, clip?.top ?? 0),\n right: Math.min(viewport.width, clip?.right ?? viewport.width),\n bottom: Math.min(viewport.height, clip?.bottom ?? viewport.height),\n };\n const visible = {\n width: Math.max(0, bounds.right - bounds.left),\n height: Math.max(0, bounds.bottom - bounds.top),\n };\n // Measure the CSS width without a previous resize's inline clamp applied.\n const panel = panelRef.current;\n const previousWidth = panel.style.width;\n if (previousWidth) panel.style.width = '';\n const cssWidth = panel.offsetWidth;\n if (previousWidth) panel.style.width = previousWidth;\n const width = cssWidth * scaleX > visible.width - 32\n ? Math.max(0, (visible.width - 32) / scaleX)\n : undefined;\n const anchor = anchorRef.current.getBoundingClientRect();\n const viewportPosition = resolveDropdownPosition({\n left: anchor.left - bounds.left,\n top: anchor.top - bounds.top,\n bottom: anchor.bottom - bounds.top,\n }, visible, (width ?? cssWidth) * scaleX);\n const nextPosition = {\n left: (bounds.left + viewportPosition.left - origin.left) / scaleX,\n top: viewportPosition.top === undefined ? undefined : (bounds.top + viewportPosition.top - origin.top) / scaleY,\n bottom: viewportPosition.bottom === undefined ? undefined\n : (containingBlockBottom - (bounds.bottom - viewportPosition.bottom)) / scaleY,\n maxHeight: viewportPosition.maxHeight / scaleY,\n width,\n };\n setPosition((previous) => previous.left === nextPosition.left &&\n previous.top === nextPosition.top && previous.bottom === nextPosition.bottom &&\n previous.maxHeight === nextPosition.maxHeight && previous.width === nextPosition.width\n ? previous : nextPosition);\n };\n\n // A Dialog's entry motion may change the fixed-position containing block.\n // Measure again when its root or positioner stops moving.\n let animationFrame: number | undefined;\n const schedulePosition = () => {\n if (animationFrame !== undefined) return;\n animationFrame = requestAnimationFrame(() => {\n animationFrame = undefined;\n updatePosition();\n });\n };\n const handleScroll = (event: Event) => {\n if (event.target instanceof Node && panelRef.current?.contains(event.target)) return;\n schedulePosition();\n };\n // Consumers may replace the Dialog's entry animation or animate its positioner.\n // Ignore events bubbling from controls inside the dialog; one frame is enough for\n // any number of animations or transitions ending in the same rendering frame.\n const positioner = modalRoot?.parentElement?.matches('[data-cratis-part=\"positioner\"]')\n ? modalRoot.parentElement : null;\n const handleModalMotionEnd = (event: Event) => {\n if (event.target === modalRoot || event.target === positioner) schedulePosition();\n };\n const motionEvents = ['animationend', 'animationcancel', 'transitionend'] as const;\n\n updatePosition();\n window.addEventListener('resize', schedulePosition);\n window.addEventListener('scroll', handleScroll, true);\n for (const eventName of motionEvents) {\n modalRoot?.addEventListener(eventName, handleModalMotionEnd);\n positioner?.addEventListener(eventName, handleModalMotionEnd);\n }\n\n return () => {\n if (animationFrame !== undefined) cancelAnimationFrame(animationFrame);\n window.removeEventListener('resize', schedulePosition);\n window.removeEventListener('scroll', handleScroll, true);\n for (const eventName of motionEvents) {\n modalRoot?.removeEventListener(eventName, handleModalMotionEnd);\n positioner?.removeEventListener(eventName, handleModalMotionEnd);\n }\n probe.remove();\n rootProbe?.remove();\n };\n }, [anchorRef, isOpen, modalRoot, portalContainer, resolvedAnchor?.anchor]);\n\n // Handle click outside to close\n useEffect(() => {\n if (!isOpen) return;\n\n const handleClickOutside = (event: MouseEvent) => {\n const target = event.target as Node;\n const panel = panelRef.current;\n const anchor = anchorRef.current;\n\n if (panel && !panel.contains(target) && anchor && !anchor.contains(target)) {\n onClose();\n }\n };\n\n // Use capture phase to ensure we catch the event before any other handlers.\n // Use timeout to avoid closing immediately when clicking the button to open.\n const timeoutId = setTimeout(() => {\n document.addEventListener('mousedown', handleClickOutside, true);\n }, 0);\n\n return () => {\n clearTimeout(timeoutId);\n document.removeEventListener('mousedown', handleClickOutside, true);\n };\n }, [isOpen, anchorRef, onClose]);\n\n useEffect(() => {\n if (isOpen && panelRef.current && !panelRef.current.contains(document.activeElement)) {\n panelRef.current.focus();\n }\n }, [isOpen, portalContainer, resolvedAnchor]);\n\n useEffect(() => {\n if (!isOpen) return;\n\n const handleAnchorEscape = (event: KeyboardEvent) => {\n if (event.key !== 'Escape' || event.defaultPrevented || event.isComposing) return;\n const focused = document.activeElement;\n if (focused && anchorRef.current?.contains(focused)) {\n event.preventDefault();\n event.stopPropagation();\n onClose();\n anchorRef.current?.focus();\n }\n };\n document.addEventListener('keydown', handleAnchorEscape, true);\n return () => {\n document.removeEventListener('keydown', handleAnchorEscape, true);\n };\n }, [isOpen, anchorRef, onClose]);\n\n if (!portalContainer || !resolvedAnchor || resolvedAnchor.anchor !== anchorRef.current) return null;\n\n return createPortal(\n <AnimatePresence initial={Boolean(modalRoot)}>\n {isOpen && (\n <motion.div\n ref={panelRef}\n role='dialog'\n aria-label={ariaLabel ?? messages?.filter?.label ?? 'Filters'}\n tabIndex={-1}\n onKeyDown={(event) => {\n if (event.key !== 'Escape' || event.defaultPrevented || event.nativeEvent.isComposing) return;\n event.preventDefault();\n event.stopPropagation();\n onClose();\n anchorRef.current?.focus();\n }}\n className='pv-filter-dropdown'\n style={{\n position: 'fixed',\n left: position.left,\n top: position.top,\n bottom: position.bottom,\n maxHeight: position.maxHeight,\n width: position.width,\n }}\n initial={{ opacity: 0, y: -8 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -8 }}\n transition={{ duration: 0.15 }}\n data-open={isOpen || undefined}\n >\n <div className='pv-filter-dropdown-content'>\n {onSearchChange && (\n <div className='pv-search'>\n <input\n type='search'\n placeholder={searchPlaceholder}\n aria-label={(searchAriaLabel ?? messages?.filter?.searchAriaLabel ?? searchPlaceholder) || 'Search'}\n value={search ?? ''}\n onChange={(event) =>\n onSearchChange(event.target.value)\n }\n />\n </div>\n )}\n <div className='pv-filter-groups'>\n {filters.map((filter) => {\n const selections =\n filterValues[filter.key] ?? new Set<string>();\n const rangeSelection = rangeValues[filter.key];\n const customValue = customValues?.[filter.key];\n const isExpanded = expandedFilterKey === filter.key;\n const isDate = filter.type === 'date';\n const isNumeric = filter.type === 'number' || isDate;\n const editorRender = editorMap[filter.key];\n const isCustom =\n filter.type === 'custom' ||\n editorRender !== undefined;\n const hasCustomValue =\n customValue !== undefined && customValue !== null;\n const canClear = isNumeric\n ? Boolean(rangeSelection)\n : isCustom\n ? hasCustomValue\n : selections.size > 0;\n const clearLabel = isNumeric\n ? clearRangeAriaLabel\n : clearFilterAriaLabel;\n const clearValue = () => {\n if (isNumeric) onRangeChange(filter.key, null);\n else if (isCustom) {\n onCustomValueChange?.(filter.key, undefined);\n } else onFilterClear(filter.key);\n };\n const formatRangeValue = isDate\n ? (value: number) => new Date(value).toLocaleString()\n : undefined;\n\n return (\n <div\n key={filter.key}\n className={`pv-filter ${isExpanded ? 'expanded' : ''}`}\n data-selected={canClear || undefined}\n data-open={isExpanded || undefined}\n >\n <div\n className='pv-filter-trigger'\n data-selected={canClear || undefined}\n data-open={isExpanded || undefined}\n >\n <button\n type='button'\n className='pv-filter-toggle'\n aria-expanded={isExpanded}\n data-selected={canClear || undefined}\n data-open={isExpanded || undefined}\n data-pressed={isExpanded || undefined}\n onClick={() =>\n onExpandedFilterChange(\n isExpanded ? null : filter.key,\n )\n }\n >\n <span className='pv-filter-label'>\n {filter.label}\n </span>\n <span className='pv-filter-trigger-meta'>\n {!isNumeric &&\n !isCustom &&\n selections.size > 0 && (\n <span className='pv-filter-count'>\n {selections.size}\n </span>\n )}\n {isNumeric && rangeSelection && (\n <span className='pv-filter-count'>\n Range\n </span>\n )}\n {isCustom && hasCustomValue && (\n <span className='pv-filter-count'>\n •\n </span>\n )}\n <span className='pv-filter-chevron' />\n </span>\n </button>\n {canClear && (\n <button\n type='button'\n className='pv-filter-clear-header'\n title={clearLabel}\n aria-label={clearLabel}\n onClick={clearValue}\n >\n {icon('clear', '×')}\n </button>\n )}\n </div>\n <div\n className={`pv-filter-content ${isExpanded ? 'expanded' : ''}`}\n data-open={isExpanded || undefined}\n >\n {isCustom && editorRender ? (\n editorRender({\n value: customValue,\n onChange: (value) =>\n onCustomValueChange?.(\n filter.key,\n value,\n ),\n })\n ) : isNumeric && filter.numericRange ? (\n <RangeHistogramFilter\n values={filter.numericRange.values}\n histogram={\n filter.numericRange.histogram\n }\n min={filter.numericRange.min}\n max={filter.numericRange.max}\n buckets={filter.buckets ?? 20}\n selectedRange={rangeSelection ?? null}\n onChange={(range) =>\n onRangeChange(filter.key, range)\n }\n formatValue={formatRangeValue}\n />\n ) : (\n <OptionList\n filter={filter}\n selections={selections}\n onFilterToggle={onFilterToggle}\n searchPlaceholder={searchPlaceholder}\n isExpanded={isExpanded}\n />\n )}\n </div>\n </div>\n );\n })}\n </div>\n </div>\n </motion.div>\n )}\n </AnimatePresence>,\n portalContainer,\n );\n}\n"],"names":["_jsx","_jsxs"],"mappings":"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"}
1
+ {"version":3,"file":"FilterPanel.js","sources":["../../../Filter/FilterPanel.tsx"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport {\n Children,\n isValidElement,\n useEffect,\n useMemo,\n useRef,\n useSyncExternalStore,\n} from 'react';\nimport type { ReactNode } from 'react';\nimport { createPortal } from 'react-dom';\nimport { AnimatePresence, motion } from 'framer-motion';\nimport { useCratisComponentsConfig } from '../configuration/CratisComponentsContext';\nimport { unstable_useOverlayEnvironment } from '../renderer/RendererContext';\nimport type {\n FilterDefinition,\n FilterValues,\n RangeValues,\n CustomFilterValues,\n} from './types';\nimport type { FilterEditorProps } from './FilterEditorProps';\nimport { FilterEditor } from './FilterEditor';\nimport { FilterGroup } from './FilterGroup';\nimport { usePanelPosition } from './usePanelPosition';\nimport { useResolvedAnchor } from './useResolvedAnchor';\nimport { FilterSearchBox } from './FilterSearchBox';\nimport { useDismissOnOutsideClick, useCloseOnAnchorEscape, closeOnPanelEscape, focusPanelUnlessFocused } from './usePanelDismissal';\n\n/**\n * Props for {@link FilterPanel}.\n *\n * Describes the filter definitions to render, the current state\n * (selections, ranges, custom values), and the callbacks that fire when\n * the user changes a filter or expands a filter group.\n */\nexport interface FilterPanelProps {\n /** Whether the panel is visible. */\n isOpen: boolean;\n /** Filter definitions, each describing one filter group. */\n filters: FilterDefinition[];\n /** Current string/option selections, keyed by FilterDefinition.key. */\n filterValues: FilterValues;\n /** Current numeric range selections, keyed by FilterDefinition.key. */\n rangeValues: RangeValues;\n /** Current values for filters using a custom `<FilterEditor>` child, keyed by FilterDefinition.key. */\n customValues?: CustomFilterValues;\n /** Current search text shown in the search box. */\n search?: string;\n /** Placeholder text for the search input. Defaults to 'Search…'. */\n searchPlaceholder?: string;\n /** Accessible name for the non-modal dialog. Overrides `messages.filter.label`, then 'Filters'. */\n 'aria-label'?: string;\n /** Accessible name for the panel search. Falls back to `messages.filter.searchAriaLabel`, its placeholder, then 'Search'. */\n searchAriaLabel?: string;\n /** Accessible name for a clear-filter button. Override to localize. Defaults to 'Clear filter'. */\n clearFilterAriaLabel?: string;\n /** Accessible name for a clear-range button. Override to localize. Defaults to 'Clear range'. */\n clearRangeAriaLabel?: string;\n /** Which filter group is currently expanded. */\n expandedFilterKey?: string | null;\n /** The button element the panel anchors below. */\n anchorRef: React.RefObject<HTMLButtonElement | null>;\n /** Called when the panel should close (e.g. click outside). */\n onClose: () => void;\n /** Called when the search text changes. If omitted, the search box is hidden. */\n onSearchChange?: (value: string) => void;\n /** Called when a string option is toggled. */\n onFilterToggle: (filterKey: string, optionKey: string, multi: boolean) => void;\n /** Called when all selections for a filter are cleared. */\n onFilterClear: (filterKey: string) => void;\n /** Called when a numeric range changes. */\n onRangeChange: (filterKey: string, range: [number, number] | null) => void;\n /** Called when the expanded filter group changes. */\n onExpandedFilterChange: (key: string | null) => void;\n /** Called when a custom-editor value changes. */\n onCustomValueChange?: (filterKey: string, value: unknown) => void;\n /**\n * `<FilterEditor>` elements that provide custom UI for specific filter groups.\n *\n * See the {@link FilterPanel} example below for the complete prop mapping\n * and a custom editor.\n */\n children?: ReactNode;\n}\n\n/** Build a map of filterKey → render function from any <FilterEditor> children. */\nfunction buildEditorMap(\n children: ReactNode | undefined,\n): Record<string, (props: FilterEditorProps) => ReactNode> {\n const map: Record<string, (props: FilterEditorProps) => ReactNode> = {};\n Children.forEach(children, (child) => {\n if (isValidElement(child) && child.type === FilterEditor) {\n const { filterKey, children: renderFn } = child.props as {\n filterKey: string;\n children: (props: FilterEditorProps) => ReactNode;\n };\n if (filterKey && typeof renderFn === 'function') {\n map[filterKey] = renderFn;\n }\n }\n });\n return map;\n}\n\n/**\n * A shared overlay root sits outside a modal's focus scope and is hidden from assistive\n * technology. Keep a panel anchored inside a Cratis Dialog inside that modal instead.\n * An explicitly unavailable container still defers the portal; it is never a body fallback.\n */\nfunction resolvePortalContainer(\n environmentContainer: HTMLElement | null,\n modalRoot: HTMLElement | null | undefined,\n): HTMLElement | null {\n return environmentContainer && modalRoot && !modalRoot.contains(environmentContainer)\n ? modalRoot\n : environmentContainer;\n}\n\n/**\n * Renders a filter dropdown panel anchored below a trigger button. The panel\n * appears as a portal at a fixed position on the page and includes search,\n * collapsible filter groups, and per-filter editors (option lists, numeric\n * range histograms, or custom components).\n *\n * Use with {@link useFilterState} for turnkey state management, or wire the\n * props to external state when the filter state lives elsewhere (e.g. in a\n * query param reducer).\n *\n * ## Filter types\n *\n * - **String/option filters** render as a {@link CheckboxListFilter} - a bounded,\n * scrollable list of checkboxes or radio buttons. Controlled through `filterValues`.\n * A search box grows out of the list automatically once it has more options than\n * fit in its box; see {@link FilterDefinition.searchable} to force it always on\n * or always off instead.\n * - **Numeric/date range filters** render as a {@link RangeHistogramFilter}\n * with a draggable range selector over a histogram. Controlled through\n * `rangeValues`.\n * - **Custom filters** slot in a `<FilterEditor>` child whose `filterKey`\n * matches the filter's `key`. Controlled through `customValues`.\n *\n * ```tsx\n * import { useRef, useState } from 'react';\n * import {\n * FilterEditor, FilterPanel, useFilterState, type FilterDefinition,\n * } from '@cratis/components/Filter';\n *\n * const filters: FilterDefinition[] = [{ key: 'category', label: 'Category', type: 'custom' }];\n *\n * function FilterExample() {\n * const state = useFilterState(filters);\n * const [isOpen, setIsOpen] = useState(false);\n * const anchorRef = useRef<HTMLButtonElement>(null);\n *\n * return (\n * <>\n * <button ref={anchorRef} onClick={() => setIsOpen(!isOpen)}>\n * Filters\n * </button>\n * <FilterPanel\n * isOpen={isOpen}\n * filters={filters}\n * anchorRef={anchorRef}\n * onClose={() => setIsOpen(false)}\n * filterValues={state.filterValues}\n * rangeValues={state.rangeValues}\n * customValues={state.customValues}\n * expandedFilterKey={state.expandedFilterKey}\n * onFilterToggle={state.handleToggleFilter}\n * onFilterClear={state.handleClearFilter}\n * onRangeChange={state.handleRangeChange}\n * onExpandedFilterChange={state.setExpandedFilterKey}\n * onCustomValueChange={state.handleCustomValueChange}\n * >\n * <FilterEditor filterKey=\"category\">\n * {({ value, onChange }) => (\n * <input value={String(value ?? '')} onChange={(event) => onChange(event.target.value)} />\n * )}\n * </FilterEditor>\n * </FilterPanel>\n * </>\n * );\n * }\n * ```\n *\n * @param props - {@link FilterPanelProps}.\n */\nconst subscribeToBrowserState = () => () => undefined;\nconst browserSnapshot = () => true;\nconst serverSnapshot = () => false;\n\n/**\n * A filter dropdown panel anchored below a trigger button. Renders filter\n * groups, search, numeric range histograms, and custom editors. The panel\n * appears as a portal at a fixed position on the page and closes when the\n * user clicks outside.\n *\n * See the full documentation comment at line 169 for usage examples.\n */\nexport function FilterPanel({\n isOpen,\n filters,\n filterValues,\n rangeValues,\n customValues,\n search,\n searchPlaceholder = 'Search…',\n 'aria-label': ariaLabel,\n searchAriaLabel,\n clearFilterAriaLabel = 'Clear filter',\n clearRangeAriaLabel = 'Clear range',\n expandedFilterKey,\n anchorRef,\n onClose,\n onSearchChange,\n onFilterToggle,\n onFilterClear,\n onRangeChange,\n onExpandedFilterChange,\n onCustomValueChange,\n children,\n}: FilterPanelProps) {\n const isBrowser = useSyncExternalStore(\n subscribeToBrowserState,\n browserSnapshot,\n serverSnapshot,\n );\n const { messages } = useCratisComponentsConfig();\n const overlayEnvironment = unstable_useOverlayEnvironment();\n const environmentContainer = isBrowser ? overlayEnvironment.getContainer() : null;\n const resolvedAnchor = useResolvedAnchor(anchorRef);\n const modalRoot = resolvedAnchor?.modalRoot;\n const portalContainer = resolvePortalContainer(environmentContainer, modalRoot);\n const panelRef = useRef<HTMLDivElement>(null);\n const position = usePanelPosition({\n isOpen,\n portalContainer,\n modalRoot,\n anchorRef,\n panelRef,\n resolvedAnchor: resolvedAnchor?.anchor,\n });\n\n const editorMap = useMemo(() => buildEditorMap(children), [children]);\n\n useDismissOnOutsideClick({ isOpen, anchorRef, panelRef, onClose });\n\n useEffect(() => {\n focusPanelUnlessFocused(isOpen, panelRef.current);\n }, [isOpen, portalContainer, resolvedAnchor]);\n\n useCloseOnAnchorEscape({ isOpen, anchorRef, onClose });\n\n if (!portalContainer || !resolvedAnchor || resolvedAnchor.anchor !== anchorRef.current) return null;\n\n return createPortal(\n <AnimatePresence initial={Boolean(modalRoot)}>\n {isOpen && (\n <motion.div\n ref={panelRef}\n role='dialog'\n aria-label={ariaLabel ?? messages?.filter?.label ?? 'Filters'}\n tabIndex={-1}\n onKeyDown={(event) => closeOnPanelEscape(event, onClose, anchorRef)}\n className='pv-filter-dropdown'\n style={{\n position: 'fixed',\n left: position.left,\n top: position.top,\n bottom: position.bottom,\n maxHeight: position.maxHeight,\n width: position.width,\n }}\n initial={{ opacity: 0, y: -8 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -8 }}\n transition={{ duration: 0.15 }}\n data-open={isOpen || undefined}\n >\n <div className='pv-filter-dropdown-content'>\n {onSearchChange && (\n <FilterSearchBox\n search={search}\n placeholder={searchPlaceholder}\n ariaLabel={searchAriaLabel}\n onSearchChange={onSearchChange}\n />\n )}\n <div className='pv-filter-groups'>\n {filters.map((filter) => (\n <FilterGroup\n key={filter.key}\n filter={filter}\n selections={filterValues[filter.key] ?? new Set<string>()}\n rangeSelection={rangeValues[filter.key]}\n customValue={customValues?.[filter.key]}\n isExpanded={expandedFilterKey === filter.key}\n editorRender={editorMap[filter.key]}\n searchPlaceholder={searchPlaceholder}\n clearFilterAriaLabel={clearFilterAriaLabel}\n clearRangeAriaLabel={clearRangeAriaLabel}\n onFilterToggle={onFilterToggle}\n onFilterClear={onFilterClear}\n onRangeChange={onRangeChange}\n onExpandedFilterChange={onExpandedFilterChange}\n onCustomValueChange={onCustomValueChange}\n />\n ))}\n </div>\n </div>\n </motion.div>\n )}\n </AnimatePresence>,\n portalContainer,\n );\n}\n"],"names":["_jsx","_jsxs"],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ interface FilterSearchBoxProps {
2
+ search: string | undefined;
3
+ placeholder: string;
4
+ ariaLabel: string | undefined;
5
+ onSearchChange: (value: string) => void;
6
+ }
7
+ /** The panel's search input. */
8
+ export declare function FilterSearchBox({ search, placeholder, ariaLabel, onSearchChange }: FilterSearchBoxProps): import("react").JSX.Element;
9
+ export {};
10
+ //# sourceMappingURL=FilterSearchBox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilterSearchBox.d.ts","sourceRoot":"","sources":["../../../Filter/FilterSearchBox.tsx"],"names":[],"mappings":"AAKA,UAAU,oBAAoB;IAC1B,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3C;AAED,gCAAgC;AAChC,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,cAAc,EAAE,EAAE,oBAAoB,+BAavG"}
@@ -0,0 +1,10 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { useCratisComponentsConfig } from '../configuration/CratisComponentsContext.js';
3
+
4
+ function FilterSearchBox({ search, placeholder, ariaLabel, onSearchChange }) {
5
+ const { messages } = useCratisComponentsConfig();
6
+ return (jsx("div", { className: 'pv-search', children: jsx("input", { type: 'search', placeholder: placeholder, "aria-label": (ariaLabel ?? messages?.filter?.searchAriaLabel ?? placeholder) || 'Search', value: search ?? '', onChange: (event) => onSearchChange(event.target.value) }) }));
7
+ }
8
+
9
+ export { FilterSearchBox };
10
+ //# sourceMappingURL=FilterSearchBox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilterSearchBox.js","sources":["../../../Filter/FilterSearchBox.tsx"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { useCratisComponentsConfig } from '../configuration/CratisComponentsContext';\n\ninterface FilterSearchBoxProps {\n search: string | undefined;\n placeholder: string;\n ariaLabel: string | undefined;\n onSearchChange: (value: string) => void;\n}\n\n/** The panel's search input. */\nexport function FilterSearchBox({ search, placeholder, ariaLabel, onSearchChange }: FilterSearchBoxProps) {\n const { messages } = useCratisComponentsConfig();\n return (\n <div className='pv-search'>\n <input\n type='search'\n placeholder={placeholder}\n aria-label={(ariaLabel ?? messages?.filter?.searchAriaLabel ?? placeholder) || 'Search'}\n value={search ?? ''}\n onChange={(event) => onSearchChange(event.target.value)}\n />\n </div>\n );\n}\n"],"names":["_jsx"],"mappings":";;;AAaM,SAAU,eAAe,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,cAAc,EAAwB,EAAA;AACpG,IAAA,MAAM,EAAE,QAAQ,EAAE,GAAG,yBAAyB,EAAE;IAChD,QACIA,GAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAC,WAAW,YACtBA,GAAA,CAAA,OAAA,EAAA,EACI,IAAI,EAAC,QAAQ,EACb,WAAW,EAAE,WAAW,EAAA,YAAA,EACZ,CAAC,SAAS,IAAI,QAAQ,EAAE,MAAM,EAAE,eAAe,IAAI,WAAW,KAAK,QAAQ,EACvF,KAAK,EAAE,MAAM,IAAI,EAAE,EACnB,QAAQ,EAAE,CAAC,KAAK,KAAK,cAAc,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,EAAA,CACzD,EAAA,CACA;AAEd;;"}
@@ -0,0 +1,31 @@
1
+ import { type DropdownPosition } from './utils.js';
2
+ /** Fixed-position panel placement, optionally with a clamped CSS width. */
3
+ export type PanelPosition = DropdownPosition & {
4
+ width?: number;
5
+ };
6
+ /** Edges of a fixed-position probe's containing block. */
7
+ interface ProbeEdges {
8
+ origin: DOMRect;
9
+ bottom: number;
10
+ right: number;
11
+ }
12
+ /** The probes (and the elements they sit in) the panel placement is measured against. */
13
+ export interface PlacementTargets {
14
+ probe: HTMLDivElement;
15
+ rootProbe: HTMLDivElement | null;
16
+ modalRoot: HTMLElement | null | undefined;
17
+ portalContainer: HTMLElement;
18
+ panel: HTMLDivElement;
19
+ anchor: HTMLButtonElement;
20
+ }
21
+ /** Inline style that makes an element a fixed, invisible, 1px probe. */
22
+ export declare const probeStyle = "position:fixed;left:0;top:0;width:1px;height:1px;margin:0;padding:0;border:0;visibility:hidden;pointer-events:none";
23
+ /** Measures the origin, bottom and right edges of a fixed probe's containing block. */
24
+ export declare function measureProbe(element: HTMLDivElement): ProbeEdges;
25
+ /**
26
+ * Computes where the fixed-position panel belongs so it stays attached to its anchor
27
+ * and clamped inside the viewport (or the modal that clips it).
28
+ */
29
+ export declare function measurePanelPlacement(targets: PlacementTargets): PanelPosition;
30
+ export {};
31
+ //# sourceMappingURL=measurePanelPlacement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"measurePanelPlacement.d.ts","sourceRoot":"","sources":["../../../Filter/measurePanelPlacement.ts"],"names":[],"mappings":"AAGA,OAAO,EAA2B,KAAK,gBAAgB,EAAE,MAAM,YAAS,CAAC;AAEzE,2EAA2E;AAC3E,MAAM,MAAM,aAAa,GAAG,gBAAgB,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAElE,0DAA0D;AAC1D,UAAU,UAAU;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,yFAAyF;AACzF,MAAM,WAAW,gBAAgB;IAC7B,KAAK,EAAE,cAAc,CAAC;IACtB,SAAS,EAAE,cAAc,GAAG,IAAI,CAAC;IACjC,SAAS,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,CAAC;IAC1C,eAAe,EAAE,WAAW,CAAC;IAC7B,KAAK,EAAE,cAAc,CAAC;IACtB,MAAM,EAAE,iBAAiB,CAAC;CAC7B;AAED,wEAAwE;AACxE,eAAO,MAAM,UAAU,uHAAuH,CAAC;AAK/I,uFAAuF;AACvF,wBAAgB,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG,UAAU,CAchE;AAkHD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,gBAAgB,GAAG,aAAa,CAoC9E"}