@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
@@ -0,0 +1,132 @@
1
+ import { resolveDropdownPosition } from './utils.js';
2
+
3
+ const probeStyle = 'position:fixed;left:0;top:0;width:1px;height:1px;margin:0;padding:0;border:0;visibility:hidden;pointer-events:none';
4
+ const edgeTolerance = 0.5;
5
+ const viewportGutter = 32;
6
+ function measureProbe(element) {
7
+ element.style.top = '0';
8
+ element.style.bottom = 'auto';
9
+ const origin = element.getBoundingClientRect();
10
+ element.style.top = 'auto';
11
+ element.style.bottom = '0';
12
+ const bottom = element.getBoundingClientRect().bottom;
13
+ element.style.bottom = 'auto';
14
+ element.style.left = 'auto';
15
+ element.style.right = '0';
16
+ const right = element.getBoundingClientRect().right;
17
+ element.style.left = '0';
18
+ element.style.right = 'auto';
19
+ return { origin, bottom, right };
20
+ }
21
+ function readViewport() {
22
+ const viewportElement = document.compatMode === 'BackCompat' ? document.body : document.documentElement;
23
+ return {
24
+ width: viewportElement.clientWidth || window.innerWidth,
25
+ height: viewportElement.clientHeight || window.innerHeight,
26
+ };
27
+ }
28
+ const fixedContainingProperties = ['transform', 'translate', 'scale', 'rotate', 'perspective', 'filter'];
29
+ const containLayoutPattern = /\b(?:layout|paint|strict|content)\b/u;
30
+ const containerTypePattern = /^(?:size|inline-size)$/u;
31
+ const willChangePattern = /\b(?:transform|translate|scale|rotate|perspective|filter|backdrop-filter|contain|container-type)\b/u;
32
+ function hasBackdropFilter(rootStyle) {
33
+ return rootStyle.getPropertyValue('backdrop-filter') !== 'none' &&
34
+ rootStyle.getPropertyValue('backdrop-filter') !== '';
35
+ }
36
+ function hasFixedContainingStyle(rootStyle) {
37
+ if (fixedContainingProperties.some(property => rootStyle[property] !== 'none'))
38
+ return true;
39
+ if (hasBackdropFilter(rootStyle))
40
+ return true;
41
+ return containLayoutPattern.test(rootStyle.contain) ||
42
+ containerTypePattern.test(rootStyle.getPropertyValue('container-type')) ||
43
+ willChangePattern.test(rootStyle.willChange);
44
+ }
45
+ function edgesMatchPaddingBox(rootEdges, rootRect, rootStyle, scaleX, scaleY) {
46
+ const edgeChecks = [
47
+ () => Math.abs(rootEdges.origin.left - (rootRect.left + parseFloat(rootStyle.borderLeftWidth) * scaleX)) <= edgeTolerance,
48
+ () => Math.abs(rootEdges.origin.top - (rootRect.top + parseFloat(rootStyle.borderTopWidth) * scaleY)) <= edgeTolerance,
49
+ () => Math.abs(rootEdges.right - (rootRect.right - parseFloat(rootStyle.borderRightWidth) * scaleX)) <= edgeTolerance,
50
+ () => Math.abs(rootEdges.bottom - (rootRect.bottom - parseFloat(rootStyle.borderBottomWidth) * scaleY)) <= edgeTolerance,
51
+ ];
52
+ return edgeChecks.every(check => check());
53
+ }
54
+ function edgesDiffer(probeEdges, rootEdges) {
55
+ const edgeChecks = [
56
+ () => Math.abs(probeEdges.origin.left - rootEdges.origin.left) > edgeTolerance,
57
+ () => Math.abs(probeEdges.origin.top - rootEdges.origin.top) > edgeTolerance,
58
+ () => Math.abs(probeEdges.right - rootEdges.right) > edgeTolerance,
59
+ () => Math.abs(probeEdges.bottom - rootEdges.bottom) > edgeTolerance,
60
+ ];
61
+ return edgeChecks.some(check => check());
62
+ }
63
+ function hasLayout(rect) {
64
+ return rect.right > rect.left && rect.bottom > rect.top;
65
+ }
66
+ function resolveModalClip(targets, edges) {
67
+ const { modalRoot, portalContainer, rootProbe } = targets;
68
+ if (!modalRoot || !modalRoot.contains(portalContainer))
69
+ return null;
70
+ const rootRect = modalRoot.getBoundingClientRect();
71
+ const rootEdges = rootProbe ? measureProbe(rootProbe) : edges;
72
+ const rootStyle = getComputedStyle(modalRoot);
73
+ const rootScaleX = rootEdges.origin.width;
74
+ const rootScaleY = rootEdges.origin.height;
75
+ const rootOwnsBlock = hasFixedContainingStyle(rootStyle) ||
76
+ edgesMatchPaddingBox(rootEdges, rootRect, rootStyle, rootScaleX, rootScaleY);
77
+ const descendantOwnsBlock = rootProbe !== null && edgesDiffer(edges, rootEdges);
78
+ const ownsBlock = rootOwnsBlock || descendantOwnsBlock;
79
+ return ownsBlock && hasLayout(rootRect) ? rootRect : null;
80
+ }
81
+ function resolveBounds(viewport, clip) {
82
+ return {
83
+ left: Math.max(0, clip?.left ?? 0),
84
+ top: Math.max(0, clip?.top ?? 0),
85
+ right: Math.min(viewport.width, clip?.right ?? viewport.width),
86
+ bottom: Math.min(viewport.height, clip?.bottom ?? viewport.height),
87
+ };
88
+ }
89
+ function measureCssWidth(panel) {
90
+ const previousWidth = panel.style.width;
91
+ if (previousWidth)
92
+ panel.style.width = '';
93
+ const cssWidth = panel.offsetWidth;
94
+ if (previousWidth)
95
+ panel.style.width = previousWidth;
96
+ return cssWidth;
97
+ }
98
+ function measurePanelPlacement(targets) {
99
+ const { probe, panel, anchor: anchorElement } = targets;
100
+ const edges = measureProbe(probe);
101
+ const { origin, bottom: containingBlockBottom } = edges;
102
+ const scaleX = origin.width || 1;
103
+ const scaleY = origin.height || 1;
104
+ const viewport = readViewport();
105
+ const clip = resolveModalClip(targets, edges);
106
+ const bounds = resolveBounds(viewport, clip);
107
+ const visible = {
108
+ width: Math.max(0, bounds.right - bounds.left),
109
+ height: Math.max(0, bounds.bottom - bounds.top),
110
+ };
111
+ const cssWidth = measureCssWidth(panel);
112
+ const width = cssWidth * scaleX > visible.width - viewportGutter
113
+ ? Math.max(0, (visible.width - viewportGutter) / scaleX)
114
+ : undefined;
115
+ const anchor = anchorElement.getBoundingClientRect();
116
+ const viewportPosition = resolveDropdownPosition({
117
+ left: anchor.left - bounds.left,
118
+ top: anchor.top - bounds.top,
119
+ bottom: anchor.bottom - bounds.top,
120
+ }, visible, (width ?? cssWidth) * scaleX);
121
+ return {
122
+ left: (bounds.left + viewportPosition.left - origin.left) / scaleX,
123
+ top: viewportPosition.top === undefined ? undefined : (bounds.top + viewportPosition.top - origin.top) / scaleY,
124
+ bottom: viewportPosition.bottom === undefined ? undefined
125
+ : (containingBlockBottom - (bounds.bottom - viewportPosition.bottom)) / scaleY,
126
+ maxHeight: viewportPosition.maxHeight / scaleY,
127
+ width,
128
+ };
129
+ }
130
+
131
+ export { measurePanelPlacement, measureProbe, probeStyle };
132
+ //# sourceMappingURL=measurePanelPlacement.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"measurePanelPlacement.js","sources":["../../../Filter/measurePanelPlacement.ts"],"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 { resolveDropdownPosition, type DropdownPosition } from './utils';\n\n/** Fixed-position panel placement, optionally with a clamped CSS width. */\nexport type PanelPosition = DropdownPosition & { width?: number };\n\n/** Edges of a fixed-position probe's containing block. */\ninterface ProbeEdges {\n origin: DOMRect;\n bottom: number;\n right: number;\n}\n\n/** The probes (and the elements they sit in) the panel placement is measured against. */\nexport interface PlacementTargets {\n probe: HTMLDivElement;\n rootProbe: HTMLDivElement | null;\n modalRoot: HTMLElement | null | undefined;\n portalContainer: HTMLElement;\n panel: HTMLDivElement;\n anchor: HTMLButtonElement;\n}\n\n/** Inline style that makes an element a fixed, invisible, 1px probe. */\nexport const probeStyle = 'position:fixed;left:0;top:0;width:1px;height:1px;margin:0;padding:0;border:0;visibility:hidden;pointer-events:none';\n\nconst edgeTolerance = 0.5;\nconst viewportGutter = 32;\n\n/** Measures the origin, bottom and right edges of a fixed probe's containing block. */\nexport function measureProbe(element: HTMLDivElement): ProbeEdges {\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/** Layoutless renderers report zero; browser layout uses the scrollbar-free viewport. */\nfunction readViewport(): { width: number; height: number } {\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 return {\n width: viewportElement.clientWidth || window.innerWidth,\n height: viewportElement.clientHeight || window.innerHeight,\n };\n}\n\n/** Properties whose non-\"none\" value makes an element the containing block of fixed descendants. */\nconst fixedContainingProperties = ['transform', 'translate', 'scale', 'rotate', 'perspective', 'filter'] as const;\n\nconst containLayoutPattern = /\\b(?:layout|paint|strict|content)\\b/u;\nconst containerTypePattern = /^(?:size|inline-size)$/u;\nconst willChangePattern = /\\b(?:transform|translate|scale|rotate|perspective|filter|backdrop-filter|contain|container-type)\\b/u;\n\nfunction hasBackdropFilter(rootStyle: CSSStyleDeclaration): boolean {\n return rootStyle.getPropertyValue('backdrop-filter') !== 'none' &&\n rootStyle.getPropertyValue('backdrop-filter') !== '';\n}\n\nfunction hasFixedContainingStyle(rootStyle: CSSStyleDeclaration): boolean {\n if (fixedContainingProperties.some(property => rootStyle[property] !== 'none')) return true;\n if (hasBackdropFilter(rootStyle)) return true;\n return containLayoutPattern.test(rootStyle.contain) ||\n containerTypePattern.test(rootStyle.getPropertyValue('container-type')) ||\n willChangePattern.test(rootStyle.willChange);\n}\n\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 */\nfunction edgesMatchPaddingBox(\n rootEdges: ProbeEdges,\n rootRect: DOMRect,\n rootStyle: CSSStyleDeclaration,\n scaleX: number,\n scaleY: number,\n): boolean {\n // Lazy checks: each edge is read only when the previous edges matched.\n const edgeChecks: Array<() => boolean> = [\n () => Math.abs(rootEdges.origin.left - (rootRect.left + parseFloat(rootStyle.borderLeftWidth) * scaleX)) <= edgeTolerance,\n () => Math.abs(rootEdges.origin.top - (rootRect.top + parseFloat(rootStyle.borderTopWidth) * scaleY)) <= edgeTolerance,\n () => Math.abs(rootEdges.right - (rootRect.right - parseFloat(rootStyle.borderRightWidth) * scaleX)) <= edgeTolerance,\n () => Math.abs(rootEdges.bottom - (rootRect.bottom - parseFloat(rootStyle.borderBottomWidth) * scaleY)) <= edgeTolerance,\n ];\n return edgeChecks.every(check => check());\n}\n\nfunction edgesDiffer(probeEdges: ProbeEdges, rootEdges: ProbeEdges): boolean {\n // Lazy checks: each edge is read only when the previous edges did not differ.\n const edgeChecks: Array<() => boolean> = [\n () => Math.abs(probeEdges.origin.left - rootEdges.origin.left) > edgeTolerance,\n () => Math.abs(probeEdges.origin.top - rootEdges.origin.top) > edgeTolerance,\n () => Math.abs(probeEdges.right - rootEdges.right) > edgeTolerance,\n () => Math.abs(probeEdges.bottom - rootEdges.bottom) > edgeTolerance,\n ];\n return edgeChecks.some(check => check());\n}\n\n/** Layoutless environments cannot supply a modal clip rect. */\nfunction hasLayout(rect: DOMRect): boolean {\n return rect.right > rect.left && rect.bottom > rect.top;\n}\n\n/** The modal's rect when the modal or the portal's descendant block owns the fixed containing block. */\nfunction resolveModalClip(\n targets: PlacementTargets,\n edges: ProbeEdges,\n): DOMRect | null {\n const { modalRoot, portalContainer, rootProbe } = targets;\n if (!modalRoot || !modalRoot.contains(portalContainer)) return null;\n const rootRect = modalRoot.getBoundingClientRect();\n const rootEdges = rootProbe ? measureProbe(rootProbe) : edges;\n const rootStyle = getComputedStyle(modalRoot);\n const rootScaleX = rootEdges.origin.width;\n const rootScaleY = rootEdges.origin.height;\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 rootOwnsBlock = hasFixedContainingStyle(rootStyle) ||\n edgesMatchPaddingBox(rootEdges, rootRect, rootStyle, rootScaleX, rootScaleY);\n // Evaluated even when the root owns the block, as the probe edges are always compared.\n const descendantOwnsBlock = rootProbe !== null && edgesDiffer(edges, rootEdges);\n const ownsBlock = rootOwnsBlock || descendantOwnsBlock;\n return ownsBlock && hasLayout(rootRect) ? rootRect : null;\n}\n\n/** The viewport rectangle, narrowed to the modal clip when there is one. */\nfunction resolveBounds(viewport: { width: number; height: number }, clip: DOMRect | null) {\n return {\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}\n\n/** Measures the CSS width without a previous resize's inline clamp applied. */\nfunction measureCssWidth(panel: HTMLDivElement): number {\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 return cssWidth;\n}\n\n/**\n * Computes where the fixed-position panel belongs so it stays attached to its anchor\n * and clamped inside the viewport (or the modal that clips it).\n */\nexport function measurePanelPlacement(targets: PlacementTargets): PanelPosition {\n const { probe, panel, anchor: anchorElement } = targets;\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 edges = measureProbe(probe);\n const { origin, bottom: containingBlockBottom } = edges;\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 const viewport = readViewport();\n const clip = resolveModalClip(targets, edges);\n const bounds = resolveBounds(viewport, clip);\n const visible = {\n width: Math.max(0, bounds.right - bounds.left),\n height: Math.max(0, bounds.bottom - bounds.top),\n };\n const cssWidth = measureCssWidth(panel);\n const width = cssWidth * scaleX > visible.width - viewportGutter\n ? Math.max(0, (visible.width - viewportGutter) / scaleX)\n : undefined;\n const anchor = anchorElement.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 return {\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}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import type { KeyboardEvent as ReactKeyboardEvent, RefObject } from 'react';
2
+ /** Inputs for {@link useDismissOnOutsideClick}. */
3
+ export interface UseDismissOnOutsideClickOptions {
4
+ isOpen: boolean;
5
+ anchorRef: RefObject<HTMLButtonElement | null>;
6
+ panelRef: RefObject<HTMLDivElement | null>;
7
+ onClose: () => void;
8
+ }
9
+ /**
10
+ * Closes an open panel when the user presses the pointer outside both the panel and its anchor.
11
+ *
12
+ * @param options - The panel, its anchor and the close callback.
13
+ */
14
+ export declare function useDismissOnOutsideClick({ isOpen, anchorRef, panelRef, onClose, }: UseDismissOnOutsideClickOptions): void;
15
+ /** Inputs for {@link useCloseOnAnchorEscape}. */
16
+ export interface UseCloseOnAnchorEscapeOptions {
17
+ isOpen: boolean;
18
+ anchorRef: RefObject<HTMLButtonElement | null>;
19
+ onClose: () => void;
20
+ }
21
+ /**
22
+ * Closes an open panel when Escape is pressed while focus is inside its anchor,
23
+ * then returns focus to the anchor.
24
+ *
25
+ * @param options - The anchor and the close callback.
26
+ */
27
+ export declare function useCloseOnAnchorEscape({ isOpen, anchorRef, onClose, }: UseCloseOnAnchorEscapeOptions): void;
28
+ /** Moves focus into an open panel unless focus is already inside it. */
29
+ export declare function focusPanelUnlessFocused(isOpen: boolean, panel: HTMLDivElement | null): void;
30
+ /** Closes the panel on an unhandled Escape and returns focus to the anchor. */
31
+ export declare function closeOnPanelEscape(event: ReactKeyboardEvent<HTMLElement>, onClose: () => void, anchorRef: RefObject<HTMLButtonElement | null>): void;
32
+ //# sourceMappingURL=usePanelDismissal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePanelDismissal.d.ts","sourceRoot":"","sources":["../../../Filter/usePanelDismissal.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,IAAI,kBAAkB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAE5E,mDAAmD;AACnD,MAAM,WAAW,+BAA+B;IAC5C,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,SAAS,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IAC/C,QAAQ,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC3C,OAAO,EAAE,MAAM,IAAI,CAAC;CACvB;AAYD;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,EACrC,MAAM,EACN,SAAS,EACT,QAAQ,EACR,OAAO,GACV,EAAE,+BAA+B,GAAG,IAAI,CAqBxC;AAED,iDAAiD;AACjD,MAAM,WAAW,6BAA6B;IAC1C,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,SAAS,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IAC/C,OAAO,EAAE,MAAM,IAAI,CAAC;CACvB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,EACnC,MAAM,EACN,SAAS,EACT,OAAO,GACV,EAAE,6BAA6B,GAAG,IAAI,CAmBtC;AAED,wEAAwE;AACxE,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,cAAc,GAAG,IAAI,QAIpF;AAED,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAC9B,KAAK,EAAE,kBAAkB,CAAC,WAAW,CAAC,EACtC,OAAO,EAAE,MAAM,IAAI,EACnB,SAAS,EAAE,SAAS,CAAC,iBAAiB,GAAG,IAAI,CAAC,QAOjD"}
@@ -0,0 +1,63 @@
1
+ import { useEffect } from 'react';
2
+
3
+ function isOutside(target, element) {
4
+ return !!element && !element.contains(target);
5
+ }
6
+ function isUnhandledEscape(event) {
7
+ return event.key === 'Escape' && !event.defaultPrevented && !event.isComposing;
8
+ }
9
+ function useDismissOnOutsideClick({ isOpen, anchorRef, panelRef, onClose, }) {
10
+ useEffect(() => {
11
+ if (!isOpen)
12
+ return;
13
+ const handleClickOutside = (event) => {
14
+ if (isOutside(event.target, panelRef.current) && isOutside(event.target, anchorRef.current)) {
15
+ onClose();
16
+ }
17
+ };
18
+ const timeoutId = setTimeout(() => {
19
+ document.addEventListener('mousedown', handleClickOutside, true);
20
+ }, 0);
21
+ return () => {
22
+ clearTimeout(timeoutId);
23
+ document.removeEventListener('mousedown', handleClickOutside, true);
24
+ };
25
+ }, [isOpen, anchorRef, panelRef, onClose]);
26
+ }
27
+ function useCloseOnAnchorEscape({ isOpen, anchorRef, onClose, }) {
28
+ useEffect(() => {
29
+ if (!isOpen)
30
+ return;
31
+ const handleAnchorEscape = (event) => {
32
+ if (!isUnhandledEscape(event))
33
+ return;
34
+ const focused = document.activeElement;
35
+ if (focused && anchorRef.current?.contains(focused)) {
36
+ event.preventDefault();
37
+ event.stopPropagation();
38
+ onClose();
39
+ anchorRef.current?.focus();
40
+ }
41
+ };
42
+ document.addEventListener('keydown', handleAnchorEscape, true);
43
+ return () => {
44
+ document.removeEventListener('keydown', handleAnchorEscape, true);
45
+ };
46
+ }, [isOpen, anchorRef, onClose]);
47
+ }
48
+ function focusPanelUnlessFocused(isOpen, panel) {
49
+ if (isOpen && panel && !panel.contains(document.activeElement)) {
50
+ panel.focus();
51
+ }
52
+ }
53
+ function closeOnPanelEscape(event, onClose, anchorRef) {
54
+ if (event.key !== 'Escape' || event.defaultPrevented || event.nativeEvent.isComposing)
55
+ return;
56
+ event.preventDefault();
57
+ event.stopPropagation();
58
+ onClose();
59
+ anchorRef.current?.focus();
60
+ }
61
+
62
+ export { closeOnPanelEscape, focusPanelUnlessFocused, useCloseOnAnchorEscape, useDismissOnOutsideClick };
63
+ //# sourceMappingURL=usePanelDismissal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePanelDismissal.js","sources":["../../../Filter/usePanelDismissal.ts"],"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 { useEffect } from 'react';\nimport type { KeyboardEvent as ReactKeyboardEvent, RefObject } from 'react';\n\n/** Inputs for {@link useDismissOnOutsideClick}. */\nexport interface UseDismissOnOutsideClickOptions {\n isOpen: boolean;\n anchorRef: RefObject<HTMLButtonElement | null>;\n panelRef: RefObject<HTMLDivElement | null>;\n onClose: () => void;\n}\n\n/** True when the element is mounted and does not contain the target. */\nfunction isOutside(target: Node, element: HTMLElement | null): boolean {\n return !!element && !element.contains(target);\n}\n\n/** True for an Escape key press that nobody else has handled. */\nfunction isUnhandledEscape(event: KeyboardEvent): boolean {\n return event.key === 'Escape' && !event.defaultPrevented && !event.isComposing;\n}\n\n/**\n * Closes an open panel when the user presses the pointer outside both the panel and its anchor.\n *\n * @param options - The panel, its anchor and the close callback.\n */\nexport function useDismissOnOutsideClick({\n isOpen,\n anchorRef,\n panelRef,\n onClose,\n}: UseDismissOnOutsideClickOptions): void {\n useEffect(() => {\n if (!isOpen) return;\n\n const handleClickOutside = (event: MouseEvent) => {\n if (isOutside(event.target as Node, panelRef.current) && isOutside(event.target as Node, anchorRef.current)) {\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, panelRef, onClose]);\n}\n\n/** Inputs for {@link useCloseOnAnchorEscape}. */\nexport interface UseCloseOnAnchorEscapeOptions {\n isOpen: boolean;\n anchorRef: RefObject<HTMLButtonElement | null>;\n onClose: () => void;\n}\n\n/**\n * Closes an open panel when Escape is pressed while focus is inside its anchor,\n * then returns focus to the anchor.\n *\n * @param options - The anchor and the close callback.\n */\nexport function useCloseOnAnchorEscape({\n isOpen,\n anchorRef,\n onClose,\n}: UseCloseOnAnchorEscapeOptions): void {\n useEffect(() => {\n if (!isOpen) return;\n\n const handleAnchorEscape = (event: KeyboardEvent) => {\n if (!isUnhandledEscape(event)) 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\n/** Moves focus into an open panel unless focus is already inside it. */\nexport function focusPanelUnlessFocused(isOpen: boolean, panel: HTMLDivElement | null) {\n if (isOpen && panel && !panel.contains(document.activeElement)) {\n panel.focus();\n }\n}\n\n/** Closes the panel on an unhandled Escape and returns focus to the anchor. */\nexport function closeOnPanelEscape(\n event: ReactKeyboardEvent<HTMLElement>,\n onClose: () => void,\n anchorRef: RefObject<HTMLButtonElement | null>,\n) {\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"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { RefObject } from 'react';
2
+ import { type PanelPosition } from './measurePanelPlacement.js';
3
+ /** What the panel position tracks. */
4
+ export interface UsePanelPositionOptions {
5
+ isOpen: boolean;
6
+ portalContainer: HTMLElement | null;
7
+ modalRoot: HTMLElement | null | undefined;
8
+ anchorRef: RefObject<HTMLButtonElement | null>;
9
+ panelRef: RefObject<HTMLDivElement | null>;
10
+ /** The resolved anchor element; re-runs the effect when the anchor changes. */
11
+ resolvedAnchor: HTMLButtonElement | null | undefined;
12
+ }
13
+ /**
14
+ * Keeps the fixed-position portal attached to its anchor and clamped inside the viewport.
15
+ * A transform (or other fixed containing-block property) on the portal container OR any
16
+ * ancestor changes the meaning of CSS left/top/bottom. A fixed probe in the portal sees
17
+ * exactly the same containing block as the panel, without guessing which ancestor owns it.
18
+ * Capture-phase scroll observation also covers nested scrolling containers.
19
+ *
20
+ * @param options - What the position tracks.
21
+ * @returns The current fixed-position placement of the panel.
22
+ */
23
+ export declare function usePanelPosition({ isOpen, portalContainer, modalRoot, anchorRef, panelRef, resolvedAnchor, }: UsePanelPositionOptions): PanelPosition;
24
+ //# sourceMappingURL=usePanelPosition.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePanelPosition.d.ts","sourceRoot":"","sources":["../../../Filter/usePanelPosition.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,EAGH,KAAK,aAAa,EACrB,MAAM,4BAAyB,CAAC;AAIjC,sCAAsC;AACtC,MAAM,WAAW,uBAAuB;IACpC,MAAM,EAAE,OAAO,CAAC;IAChB,eAAe,EAAE,WAAW,GAAG,IAAI,CAAC;IACpC,SAAS,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,CAAC;IAC1C,SAAS,EAAE,SAAS,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IAC/C,QAAQ,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC3C,+EAA+E;IAC/E,cAAc,EAAE,iBAAiB,GAAG,IAAI,GAAG,SAAS,CAAC;CACxD;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,EAC7B,MAAM,EACN,eAAe,EACf,SAAS,EACT,SAAS,EACT,QAAQ,EACR,cAAc,GACjB,EAAE,uBAAuB,GAAG,aAAa,CAuFzC"}
@@ -0,0 +1,85 @@
1
+ import { useState, useLayoutEffect } from 'react';
2
+ import { probeStyle, measurePanelPlacement } from './measurePanelPlacement.js';
3
+
4
+ const motionEvents = ['animationend', 'animationcancel', 'transitionend'];
5
+ function usePanelPosition({ isOpen, portalContainer, modalRoot, anchorRef, panelRef, resolvedAnchor, }) {
6
+ const [position, setPosition] = useState({
7
+ top: 0,
8
+ left: 0,
9
+ maxHeight: 0,
10
+ });
11
+ useLayoutEffect(() => {
12
+ if (!isOpen || !portalContainer)
13
+ return;
14
+ const probe = document.createElement('div');
15
+ probe.style.cssText = probeStyle;
16
+ portalContainer.appendChild(probe);
17
+ const rootProbe = modalRoot && modalRoot.contains(portalContainer) && portalContainer !== modalRoot
18
+ ? document.createElement('div') : null;
19
+ if (rootProbe && modalRoot) {
20
+ rootProbe.style.cssText = probeStyle;
21
+ modalRoot.appendChild(rootProbe);
22
+ }
23
+ const updatePosition = () => {
24
+ const anchor = anchorRef.current;
25
+ const panel = panelRef.current;
26
+ if (!anchor || !panel)
27
+ return;
28
+ const nextPosition = measurePanelPlacement({
29
+ probe,
30
+ rootProbe,
31
+ modalRoot,
32
+ portalContainer,
33
+ panel,
34
+ anchor,
35
+ });
36
+ setPosition((previous) => previous.left === nextPosition.left &&
37
+ previous.top === nextPosition.top && previous.bottom === nextPosition.bottom &&
38
+ previous.maxHeight === nextPosition.maxHeight && previous.width === nextPosition.width
39
+ ? previous : nextPosition);
40
+ };
41
+ let animationFrame;
42
+ const schedulePosition = () => {
43
+ if (animationFrame !== undefined)
44
+ return;
45
+ animationFrame = requestAnimationFrame(() => {
46
+ animationFrame = undefined;
47
+ updatePosition();
48
+ });
49
+ };
50
+ const handleScroll = (event) => {
51
+ if (event.target instanceof Node && panelRef.current?.contains(event.target))
52
+ return;
53
+ schedulePosition();
54
+ };
55
+ const positioner = modalRoot?.parentElement?.matches('[data-cratis-part="positioner"]')
56
+ ? modalRoot.parentElement : null;
57
+ const handleModalMotionEnd = (event) => {
58
+ if (event.target === modalRoot || event.target === positioner)
59
+ schedulePosition();
60
+ };
61
+ updatePosition();
62
+ window.addEventListener('resize', schedulePosition);
63
+ window.addEventListener('scroll', handleScroll, true);
64
+ for (const eventName of motionEvents) {
65
+ modalRoot?.addEventListener(eventName, handleModalMotionEnd);
66
+ positioner?.addEventListener(eventName, handleModalMotionEnd);
67
+ }
68
+ return () => {
69
+ if (animationFrame !== undefined)
70
+ cancelAnimationFrame(animationFrame);
71
+ window.removeEventListener('resize', schedulePosition);
72
+ window.removeEventListener('scroll', handleScroll, true);
73
+ for (const eventName of motionEvents) {
74
+ modalRoot?.removeEventListener(eventName, handleModalMotionEnd);
75
+ positioner?.removeEventListener(eventName, handleModalMotionEnd);
76
+ }
77
+ probe.remove();
78
+ rootProbe?.remove();
79
+ };
80
+ }, [anchorRef, panelRef, isOpen, modalRoot, portalContainer, resolvedAnchor]);
81
+ return position;
82
+ }
83
+
84
+ export { usePanelPosition };
85
+ //# sourceMappingURL=usePanelPosition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePanelPosition.js","sources":["../../../Filter/usePanelPosition.ts"],"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 { useLayoutEffect, useState } from 'react';\nimport type { RefObject } from 'react';\nimport {\n measurePanelPlacement,\n probeStyle,\n type PanelPosition,\n} from './measurePanelPlacement';\n\nconst motionEvents = ['animationend', 'animationcancel', 'transitionend'] as const;\n\n/** What the panel position tracks. */\nexport interface UsePanelPositionOptions {\n isOpen: boolean;\n portalContainer: HTMLElement | null;\n modalRoot: HTMLElement | null | undefined;\n anchorRef: RefObject<HTMLButtonElement | null>;\n panelRef: RefObject<HTMLDivElement | null>;\n /** The resolved anchor element; re-runs the effect when the anchor changes. */\n resolvedAnchor: HTMLButtonElement | null | undefined;\n}\n\n/**\n * Keeps 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 *\n * @param options - What the position tracks.\n * @returns The current fixed-position placement of the panel.\n */\nexport function usePanelPosition({\n isOpen,\n portalContainer,\n modalRoot,\n anchorRef,\n panelRef,\n resolvedAnchor,\n}: UsePanelPositionOptions): PanelPosition {\n const [position, setPosition] = useState<PanelPosition>({\n top: 0,\n left: 0,\n maxHeight: 0,\n });\n\n useLayoutEffect(() => {\n if (!isOpen || !portalContainer) return;\n\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 updatePosition = () => {\n const anchor = anchorRef.current;\n const panel = panelRef.current;\n if (!anchor || !panel) return;\n\n const nextPosition = measurePanelPlacement({\n probe,\n rootProbe,\n modalRoot,\n portalContainer,\n panel,\n anchor,\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\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, panelRef, isOpen, modalRoot, portalContainer, resolvedAnchor]);\n\n return position;\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { RefObject } from 'react';
2
+ /** The anchor element and the Cratis Dialog (if any) that contains it. */
3
+ export interface ResolvedAnchor {
4
+ anchor: HTMLButtonElement | null;
5
+ modalRoot: HTMLElement | null;
6
+ }
7
+ /**
8
+ * Resolves the anchor element and its enclosing modal after commit.
9
+ *
10
+ * Refs attach during commit, after render. Resolving the modal before mounting any open
11
+ * panel keeps its first portal from landing in a shared root hidden by the modal.
12
+ *
13
+ * @param anchorRef - The anchor button's ref.
14
+ * @returns The resolved anchor, or null before the first commit.
15
+ */
16
+ export declare function useResolvedAnchor(anchorRef: RefObject<HTMLButtonElement | null>): ResolvedAnchor | null;
17
+ //# sourceMappingURL=useResolvedAnchor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useResolvedAnchor.d.ts","sourceRoot":"","sources":["../../../Filter/useResolvedAnchor.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,0EAA0E;AAC1E,MAAM,WAAW,cAAc;IAC3B,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACjC,SAAS,EAAE,WAAW,GAAG,IAAI,CAAC;CACjC;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,SAAS,CAAC,iBAAiB,GAAG,IAAI,CAAC,GAAG,cAAc,GAAG,IAAI,CAYvG"}
@@ -0,0 +1,16 @@
1
+ import { useState, useLayoutEffect } from 'react';
2
+
3
+ function useResolvedAnchor(anchorRef) {
4
+ const [resolvedAnchor, setResolvedAnchor] = useState(null);
5
+ useLayoutEffect(() => {
6
+ const anchor = anchorRef.current;
7
+ const modalRoot = anchor?.closest('.cratis-dialog[data-cratis-part="root"]') ?? null;
8
+ setResolvedAnchor((previous) => previous?.anchor === anchor && previous.modalRoot === modalRoot
9
+ ? previous
10
+ : { anchor, modalRoot });
11
+ });
12
+ return resolvedAnchor;
13
+ }
14
+
15
+ export { useResolvedAnchor };
16
+ //# sourceMappingURL=useResolvedAnchor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useResolvedAnchor.js","sources":["../../../Filter/useResolvedAnchor.ts"],"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 { useLayoutEffect, useState } from 'react';\nimport type { RefObject } from 'react';\n\n/** The anchor element and the Cratis Dialog (if any) that contains it. */\nexport interface ResolvedAnchor {\n anchor: HTMLButtonElement | null;\n modalRoot: HTMLElement | null;\n}\n\n/**\n * Resolves the anchor element and its enclosing modal after commit.\n *\n * Refs attach during commit, after render. Resolving the modal before mounting any open\n * panel keeps its first portal from landing in a shared root hidden by the modal.\n *\n * @param anchorRef - The anchor button's ref.\n * @returns The resolved anchor, or null before the first commit.\n */\nexport function useResolvedAnchor(anchorRef: RefObject<HTMLButtonElement | null>): ResolvedAnchor | null {\n const [resolvedAnchor, setResolvedAnchor] = useState<ResolvedAnchor | null>(null);\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 return resolvedAnchor;\n}\n"],"names":[],"mappings":";;AAqBM,SAAU,iBAAiB,CAAC,SAA8C,EAAA;IAC5E,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC,GAAG,QAAQ,CAAwB,IAAI,CAAC;IAGjF,eAAe,CAAC,MAAK;AACjB,QAAA,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO;QAChC,MAAM,SAAS,GAAG,MAAM,EAAE,OAAO,CAAc,yCAAyC,CAAC,IAAI,IAAI;AACjG,QAAA,iBAAiB,CAAC,CAAC,QAAQ,KAAK,QAAQ,EAAE,MAAM,KAAK,MAAM,IAAI,QAAQ,CAAC,SAAS,KAAK;AAClF,cAAE;AACF,cAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AAChC,IAAA,CAAC,CAAC;AACF,IAAA,OAAO,cAAc;AACzB;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"PivotViewer.d.ts","sourceRoot":"","sources":["../../../PivotViewer/PivotViewer.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAS,CAAC;AA8BhD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,CAAC,KAAK,SAAS,MAAM,EAAE,EAC9C,IAAI,EACJ,UAAU,EACV,OAAO,EACP,mBAAmB,EACnB,YAAY,EACZ,cAAc,EACd,SAAS,EACT,YAAY,EACZ,KAAK,EACL,SAAS,EACT,YAAY,EACZ,SAAiB,EACjB,YAAY,EACZ,MAAM,EACN,MAAM,GACT,EAAE,gBAAgB,CAAC,KAAK,CAAC,+BAmbzB"}
1
+ {"version":3,"file":"PivotViewer.d.ts","sourceRoot":"","sources":["../../../PivotViewer/PivotViewer.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAS,CAAC;AAiDhD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,CAAC,KAAK,SAAS,MAAM,EAAE,EAC9C,IAAI,EACJ,UAAU,EACV,OAAO,EACP,mBAAmB,EACnB,YAAY,EACZ,cAAc,EACd,SAAS,EACT,YAAY,EACZ,KAAK,EACL,SAAS,EACT,YAAY,EACZ,SAAiB,EACjB,YAAY,EACZ,MAAM,EACN,MAAM,GACT,EAAE,gBAAgB,CAAC,KAAK,CAAC,+BAiNzB"}