@cratis/components 2.0.1 → 2.1.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 (139) hide show
  1. package/dist/cjs/Filter/FilterEditor.js +28 -0
  2. package/dist/cjs/Filter/FilterEditor.js.map +1 -0
  3. package/dist/cjs/Filter/FilterPanel.css +412 -0
  4. package/dist/cjs/Filter/FilterPanel.js +87 -0
  5. package/dist/cjs/Filter/FilterPanel.js.map +1 -0
  6. package/dist/cjs/Filter/RangeHistogramFilter.js.map +1 -0
  7. package/dist/cjs/Filter/index.js +18 -0
  8. package/dist/cjs/Filter/index.js.map +1 -0
  9. package/dist/cjs/Filter/useFilterState.js +104 -0
  10. package/dist/cjs/Filter/useFilterState.js.map +1 -0
  11. package/dist/cjs/Filter/utils.js +28 -0
  12. package/dist/cjs/Filter/utils.js.map +1 -0
  13. package/dist/cjs/PivotViewer/components/FilterPanel.js +20 -54
  14. package/dist/cjs/PivotViewer/components/FilterPanel.js.map +1 -1
  15. package/dist/cjs/PivotViewer/types.js.map +1 -1
  16. package/dist/cjs/PivotViewer/utils/utils.js +0 -2
  17. package/dist/cjs/PivotViewer/utils/utils.js.map +1 -1
  18. package/dist/cjs/index.js +16 -14
  19. package/dist/cjs/index.js.map +1 -1
  20. package/dist/esm/CommandDialog/CommandDialog.d.ts +2 -2
  21. package/dist/esm/CommandDialog/CommandStepper.d.ts +2 -2
  22. package/dist/esm/CommandDialog/CommandStepper.d.ts.map +1 -1
  23. package/dist/esm/CommandDialog/StepperCommandDialog.d.ts +1 -1
  24. package/dist/esm/CommandDialog/StepperCommandDialog.d.ts.map +1 -1
  25. package/dist/esm/Common/CratisComponentsProvider.d.ts +1 -1
  26. package/dist/esm/Common/CratisComponentsProvider.d.ts.map +1 -1
  27. package/dist/esm/Common/ErrorBoundary.d.ts +1 -1
  28. package/dist/esm/Common/FormElement.d.ts +1 -1
  29. package/dist/esm/Common/FormElement.d.ts.map +1 -1
  30. package/dist/esm/Common/Icon.d.ts +1 -1
  31. package/dist/esm/Common/Icon.d.ts.map +1 -1
  32. package/dist/esm/Common/Page.d.ts +1 -1
  33. package/dist/esm/Common/Page.d.ts.map +1 -1
  34. package/dist/esm/DataPage/DataPage.d.ts +5 -5
  35. package/dist/esm/DataPage/DataPage.d.ts.map +1 -1
  36. package/dist/esm/DataTables/DataTableForObservableQuery.d.ts +1 -1
  37. package/dist/esm/DataTables/DataTableForObservableQuery.d.ts.map +1 -1
  38. package/dist/esm/DataTables/DataTableForQuery.d.ts +1 -1
  39. package/dist/esm/DataTables/DataTableForQuery.d.ts.map +1 -1
  40. package/dist/esm/Dialogs/BusyIndicatorDialog.d.ts +1 -1
  41. package/dist/esm/Dialogs/BusyIndicatorDialog.d.ts.map +1 -1
  42. package/dist/esm/Dialogs/ConfirmationDialog.d.ts +1 -1
  43. package/dist/esm/Dialogs/ConfirmationDialog.d.ts.map +1 -1
  44. package/dist/esm/Dialogs/Dialog.d.ts +1 -1
  45. package/dist/esm/Dialogs/Dialog.d.ts.map +1 -1
  46. package/dist/esm/Filter/FilterEditor.d.ts +8 -0
  47. package/dist/esm/Filter/FilterEditor.d.ts.map +1 -0
  48. package/dist/esm/Filter/FilterEditor.js +26 -0
  49. package/dist/esm/Filter/FilterEditor.js.map +1 -0
  50. package/dist/esm/Filter/FilterPanel.css +412 -0
  51. package/dist/esm/Filter/FilterPanel.d.ts +24 -0
  52. package/dist/esm/Filter/FilterPanel.d.ts.map +1 -0
  53. package/dist/esm/Filter/FilterPanel.js +85 -0
  54. package/dist/esm/Filter/FilterPanel.js.map +1 -0
  55. package/dist/esm/Filter/RangeHistogramFilter.d.ts +11 -0
  56. package/dist/esm/Filter/RangeHistogramFilter.d.ts.map +1 -0
  57. package/dist/esm/Filter/RangeHistogramFilter.js +122 -0
  58. package/dist/esm/Filter/RangeHistogramFilter.js.map +1 -0
  59. package/dist/esm/Filter/index.d.ts +11 -0
  60. package/dist/esm/Filter/index.d.ts.map +1 -0
  61. package/dist/esm/Filter/index.js +9 -0
  62. package/dist/esm/Filter/index.js.map +1 -0
  63. package/dist/esm/Filter/types.d.ts +28 -0
  64. package/dist/esm/Filter/types.d.ts.map +1 -0
  65. package/dist/esm/Filter/types.js +2 -0
  66. package/dist/esm/Filter/types.js.map +1 -0
  67. package/dist/esm/Filter/useFilterState.d.ts +14 -0
  68. package/dist/esm/Filter/useFilterState.d.ts.map +1 -0
  69. package/dist/esm/Filter/useFilterState.js +102 -0
  70. package/dist/esm/Filter/useFilterState.js.map +1 -0
  71. package/dist/esm/Filter/utils.d.ts +4 -0
  72. package/dist/esm/Filter/utils.d.ts.map +1 -0
  73. package/dist/esm/Filter/utils.js +25 -0
  74. package/dist/esm/Filter/utils.js.map +1 -0
  75. package/dist/esm/ObjectContentEditor/ObjectContentEditor.d.ts +2 -1
  76. package/dist/esm/ObjectContentEditor/ObjectContentEditor.d.ts.map +1 -1
  77. package/dist/esm/ObjectNavigationalBar/ObjectNavigationalBar.d.ts +1 -1
  78. package/dist/esm/ObjectNavigationalBar/ObjectNavigationalBar.d.ts.map +1 -1
  79. package/dist/esm/PivotViewer/PivotViewer.d.ts +1 -1
  80. package/dist/esm/PivotViewer/PivotViewer.d.ts.map +1 -1
  81. package/dist/esm/PivotViewer/components/AxisLabels.d.ts +1 -1
  82. package/dist/esm/PivotViewer/components/AxisLabels.d.ts.map +1 -1
  83. package/dist/esm/PivotViewer/components/DetailPanel.d.ts +1 -1
  84. package/dist/esm/PivotViewer/components/DetailPanel.d.ts.map +1 -1
  85. package/dist/esm/PivotViewer/components/FilterPanel.d.ts +1 -1
  86. package/dist/esm/PivotViewer/components/FilterPanel.d.ts.map +1 -1
  87. package/dist/esm/PivotViewer/components/FilterPanel.js +21 -55
  88. package/dist/esm/PivotViewer/components/FilterPanel.js.map +1 -1
  89. package/dist/esm/PivotViewer/components/FilterPanelContainer.d.ts +1 -1
  90. package/dist/esm/PivotViewer/components/FilterPanelContainer.d.ts.map +1 -1
  91. package/dist/esm/PivotViewer/components/PivotViewerMain.d.ts +1 -1
  92. package/dist/esm/PivotViewer/components/PivotViewerMain.d.ts.map +1 -1
  93. package/dist/esm/PivotViewer/components/RangeHistogramFilter.d.ts +1 -1
  94. package/dist/esm/PivotViewer/components/RangeHistogramFilter.d.ts.map +1 -1
  95. package/dist/esm/PivotViewer/components/RangeHistogramFilter.js +8 -11
  96. package/dist/esm/PivotViewer/components/RangeHistogramFilter.js.map +1 -1
  97. package/dist/esm/PivotViewer/components/Spinner.d.ts +1 -1
  98. package/dist/esm/PivotViewer/components/Spinner.d.ts.map +1 -1
  99. package/dist/esm/PivotViewer/components/Toolbar.d.ts +1 -1
  100. package/dist/esm/PivotViewer/components/Toolbar.d.ts.map +1 -1
  101. package/dist/esm/PivotViewer/components/ToolbarContainer.d.ts +1 -1
  102. package/dist/esm/PivotViewer/components/ToolbarContainer.d.ts.map +1 -1
  103. package/dist/esm/PivotViewer/types.d.ts +3 -1
  104. package/dist/esm/PivotViewer/types.d.ts.map +1 -1
  105. package/dist/esm/PivotViewer/types.js.map +1 -1
  106. package/dist/esm/PivotViewer/utils/utils.js +1 -2
  107. package/dist/esm/PivotViewer/utils/utils.js.map +1 -1
  108. package/dist/esm/SchemaEditor/NameCell.d.ts +1 -1
  109. package/dist/esm/SchemaEditor/NameCell.d.ts.map +1 -1
  110. package/dist/esm/SchemaEditor/SchemaEditor.d.ts +1 -1
  111. package/dist/esm/SchemaEditor/SchemaEditor.d.ts.map +1 -1
  112. package/dist/esm/SchemaEditor/TypeCell.d.ts +1 -1
  113. package/dist/esm/SchemaEditor/TypeCell.d.ts.map +1 -1
  114. package/dist/esm/Toolbar/Toolbar.d.ts +1 -1
  115. package/dist/esm/Toolbar/Toolbar.d.ts.map +1 -1
  116. package/dist/esm/Toolbar/ToolbarButton.d.ts +1 -1
  117. package/dist/esm/Toolbar/ToolbarButton.d.ts.map +1 -1
  118. package/dist/esm/Toolbar/ToolbarFanOutItem.d.ts +1 -1
  119. package/dist/esm/Toolbar/ToolbarFanOutItem.d.ts.map +1 -1
  120. package/dist/esm/Toolbar/ToolbarFolder.d.ts +1 -1
  121. package/dist/esm/Toolbar/ToolbarFolder.d.ts.map +1 -1
  122. package/dist/esm/Toolbar/ToolbarGroup.d.ts +1 -1
  123. package/dist/esm/Toolbar/ToolbarGroup.d.ts.map +1 -1
  124. package/dist/esm/Toolbar/ToolbarLayout.d.ts +1 -1
  125. package/dist/esm/Toolbar/ToolbarLayout.d.ts.map +1 -1
  126. package/dist/esm/Toolbar/ToolbarSection.d.ts +1 -1
  127. package/dist/esm/Toolbar/ToolbarSection.d.ts.map +1 -1
  128. package/dist/esm/Toolbar/ToolbarSeparator.d.ts +1 -1
  129. package/dist/esm/Toolbar/ToolbarSeparator.d.ts.map +1 -1
  130. package/dist/esm/Toolbar/ToolbarSlot.d.ts +1 -1
  131. package/dist/esm/Toolbar/ToolbarSlot.d.ts.map +1 -1
  132. package/dist/esm/index.d.ts +2 -1
  133. package/dist/esm/index.d.ts.map +1 -1
  134. package/dist/esm/index.js +16 -14
  135. package/dist/esm/index.js.map +1 -1
  136. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  137. package/package.json +6 -1
  138. package/dist/cjs/PivotViewer/components/RangeHistogramFilter.js.map +0 -1
  139. /package/dist/cjs/{PivotViewer/components → Filter}/RangeHistogramFilter.js +0 -0
@@ -0,0 +1,412 @@
1
+ /* -----------------------------------------------------------------------
2
+ * Standalone FilterPanel styles.
3
+ *
4
+ * These styles are shared by the standalone FilterPanel component and the
5
+ * PivotViewer which embeds it. All class names use the .pv-filter-* prefix
6
+ * so they can coexist with PivotViewer's other .pv-* rules.
7
+ * ----------------------------------------------------------------------- */
8
+
9
+ .pv-filter-panel {
10
+ position: fixed;
11
+ width: min(280px, calc(100% - 2rem));
12
+ max-height: calc(100vh - 6.5rem);
13
+ background: linear-gradient(180deg, var(--cratis-surface-card), var(--cratis-surface-ground));
14
+ border: 1px solid var(--cratis-surface-border);
15
+ border-radius: 1rem;
16
+ box-shadow: 0 24px 60px rgba(0, 0, 0, 0.3);
17
+ backdrop-filter: blur(14px);
18
+ overflow: hidden;
19
+ z-index: 12;
20
+ display: flex;
21
+ flex-direction: column;
22
+ }
23
+
24
+ .pv-filter-panel.dragging {
25
+ cursor: grabbing;
26
+ user-select: none;
27
+ }
28
+
29
+ .pv-filter-panel.minimized {
30
+ max-height: none;
31
+ }
32
+
33
+ .pv-filter-panel-header {
34
+ display: flex;
35
+ align-items: center;
36
+ gap: 0.5rem;
37
+ padding: 0.75rem 1rem;
38
+ cursor: grab;
39
+ background: var(--cratis-surface-section);
40
+ border-bottom: 1px solid var(--cratis-surface-border);
41
+ flex-shrink: 0;
42
+ }
43
+
44
+ .pv-filter-panel.dragging .pv-filter-panel-header {
45
+ cursor: grabbing;
46
+ }
47
+
48
+ .pv-filter-panel-header h2 {
49
+ margin: 0;
50
+ font-size: 0.85rem;
51
+ letter-spacing: 0.04em;
52
+ text-transform: uppercase;
53
+ color: var(--cratis-text-color);
54
+ flex: 1;
55
+ pointer-events: none;
56
+ }
57
+
58
+ .pv-minimize-btn,
59
+ .pv-close-btn {
60
+ appearance: none;
61
+ border: none;
62
+ border-radius: 0.4rem;
63
+ width: 1.5rem;
64
+ height: 1.5rem;
65
+ display: flex;
66
+ align-items: center;
67
+ justify-content: center;
68
+ background: var(--cratis-highlight-bg);
69
+ color: var(--cratis-text-color);
70
+ font-size: 1rem;
71
+ font-weight: 500;
72
+ cursor: pointer;
73
+ transition: background 120ms ease;
74
+ }
75
+
76
+ .pv-minimize-btn:hover,
77
+ .pv-close-btn:hover {
78
+ background: var(--cratis-surface-hover);
79
+ }
80
+
81
+ .pv-close-btn {
82
+ font-size: 1.2rem;
83
+ }
84
+
85
+ .pv-filter-panel-content {
86
+ overflow: hidden;
87
+ max-height: 0;
88
+ opacity: 0;
89
+ padding: 0 1.25rem;
90
+ transition: max-height 150ms ease-out, opacity 100ms ease-out, padding 150ms ease-out;
91
+ }
92
+
93
+ .pv-filter-panel-content.expanded {
94
+ overflow-y: auto;
95
+ flex: 1;
96
+ max-height: 600px;
97
+ opacity: 1;
98
+ padding: 1rem 1.25rem 1.25rem;
99
+ }
100
+
101
+ .pv-search {
102
+ padding-bottom: 1rem;
103
+ }
104
+
105
+ .pv-search input {
106
+ width: 100%;
107
+ padding: 0.65rem 0.85rem;
108
+ border-radius: 0.75rem;
109
+ border: 1px solid var(--cratis-surface-border);
110
+ background: var(--cratis-surface-ground);
111
+ color: inherit;
112
+ font-size: 0.95rem;
113
+ box-shadow: inset 0 1px 0 var(--cratis-surface-border);
114
+ }
115
+
116
+ .pv-search input:focus {
117
+ outline: none;
118
+ border-color: var(--cratis-primary-color);
119
+ box-shadow: var(--cratis-focus-ring);
120
+ }
121
+
122
+ .pv-filter-groups {
123
+ display: flex;
124
+ flex-direction: column;
125
+ gap: 0.85rem;
126
+ }
127
+
128
+ .pv-filter {
129
+ background: var(--cratis-surface-overlay);
130
+ border: 1px solid var(--cratis-surface-border);
131
+ border-radius: 0.85rem;
132
+ padding: 0.75rem 0.85rem;
133
+ transition: border-color 180ms ease, box-shadow 180ms ease;
134
+ }
135
+
136
+ .pv-filter.expanded {
137
+ border-color: var(--cratis-primary-color);
138
+ box-shadow: 0 15px 38px rgba(0, 0, 0, 0.3);
139
+ }
140
+
141
+ .pv-filter-trigger {
142
+ display: flex;
143
+ width: 100%;
144
+ align-items: center;
145
+ justify-content: space-between;
146
+ background: none;
147
+ border: none;
148
+ color: inherit;
149
+ padding: 0;
150
+ cursor: pointer;
151
+ font-size: 0.82rem;
152
+ font-weight: 600;
153
+ text-transform: uppercase;
154
+ letter-spacing: 0.05em;
155
+ }
156
+
157
+ .pv-filter-label {
158
+ display: flex;
159
+ align-items: center;
160
+ gap: 0.4rem;
161
+ }
162
+
163
+ .pv-filter-trigger-meta {
164
+ display: inline-flex;
165
+ align-items: center;
166
+ gap: 0.5rem;
167
+ }
168
+
169
+ .pv-filter-count {
170
+ display: inline-flex;
171
+ align-items: center;
172
+ justify-content: center;
173
+ min-width: 1.5rem;
174
+ padding: 0.15rem 0.45rem;
175
+ border-radius: 999px;
176
+ background: var(--cratis-highlight-bg);
177
+ color: var(--cratis-text-color);
178
+ font-size: 0.7rem;
179
+ }
180
+
181
+ .pv-filter-chevron {
182
+ width: 0.65rem;
183
+ height: 0.65rem;
184
+ border-right: 2px solid var(--cratis-text-color);
185
+ border-bottom: 2px solid var(--cratis-text-color);
186
+ transform: rotate(45deg);
187
+ transition: transform 180ms ease;
188
+ }
189
+
190
+ .pv-filter.expanded .pv-filter-chevron {
191
+ transform: rotate(225deg);
192
+ }
193
+
194
+ .pv-filter-content {
195
+ overflow: hidden;
196
+ max-height: 0;
197
+ opacity: 0;
198
+ margin-top: 0;
199
+ padding-top: 0;
200
+ border-top: 1px solid transparent;
201
+ transition: max-height 120ms ease-out, opacity 80ms ease-out, margin-top 120ms ease-out, padding-top 120ms ease-out, border-color 120ms ease-out;
202
+ }
203
+
204
+ .pv-filter-content.expanded {
205
+ max-height: 350px;
206
+ opacity: 1;
207
+ margin-top: 0.75rem;
208
+ padding-top: 0.75rem;
209
+ border-top-color: var(--cratis-surface-border);
210
+ overflow-y: auto;
211
+ }
212
+
213
+ .pv-filter-content ul {
214
+ list-style: none;
215
+ margin: 0;
216
+ padding: 0;
217
+ display: grid;
218
+ gap: 0.45rem;
219
+ }
220
+
221
+ .pv-filter-content li label {
222
+ display: grid;
223
+ grid-template-columns: auto 1fr auto;
224
+ gap: 0.6rem;
225
+ align-items: center;
226
+ padding: 0.45rem 0.5rem;
227
+ border-radius: 0.55rem;
228
+ border: 1px solid transparent;
229
+ cursor: pointer;
230
+ transition: background 160ms ease, border-color 160ms ease;
231
+ }
232
+
233
+ .pv-filter-content li label:hover {
234
+ background: var(--cratis-surface-hover);
235
+ }
236
+
237
+ .pv-filter-content input[type='checkbox'],
238
+ .pv-filter-content input[type='radio'] {
239
+ accent-color: var(--cratis-primary-color);
240
+ }
241
+
242
+ .pv-filter-content li label span {
243
+ font-size: 0.85rem;
244
+ }
245
+
246
+ .pv-filter-clear {
247
+ margin-top: 0.75rem;
248
+ appearance: none;
249
+ border: none;
250
+ border-radius: 0.55rem;
251
+ padding: 0.4rem 0.75rem;
252
+ background: var(--cratis-highlight-bg);
253
+ color: var(--cratis-text-color);
254
+ font-size: 0.72rem;
255
+ text-transform: uppercase;
256
+ letter-spacing: 0.08em;
257
+ cursor: pointer;
258
+ }
259
+
260
+ .pv-option-count {
261
+ font-feature-settings: "tnum";
262
+ font-variant-numeric: tabular-nums;
263
+ color: var(--cratis-text-color-secondary);
264
+ font-size: 0.75rem;
265
+ }
266
+
267
+ /* Filter Dropdown (anchor-positioned panel) */
268
+ .pv-filter-dropdown {
269
+ position: fixed;
270
+ width: min(320px, calc(100vw - 2rem));
271
+ max-height: calc(100vh - 8rem);
272
+ background: linear-gradient(180deg, var(--cratis-surface-card), var(--cratis-surface-ground));
273
+ border: 1px solid var(--cratis-surface-border);
274
+ border-radius: 0.85rem;
275
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.4);
276
+ backdrop-filter: blur(16px);
277
+ overflow: hidden;
278
+ z-index: 1000;
279
+ display: flex;
280
+ flex-direction: column;
281
+ will-change: opacity, transform;
282
+ contain: layout;
283
+ }
284
+
285
+ .pv-filter-dropdown-content {
286
+ padding: 1rem;
287
+ overflow-y: auto;
288
+ flex: 1;
289
+ }
290
+
291
+ /* Range Histogram Filter */
292
+ .pv-range-histogram {
293
+ padding: 0.5rem 0;
294
+ }
295
+
296
+ .pv-histogram-bars {
297
+ display: flex;
298
+ align-items: flex-end;
299
+ gap: 2px;
300
+ height: 80px;
301
+ padding: 0 2px;
302
+ margin-bottom: 0.5rem;
303
+ }
304
+
305
+ .pv-histogram-bar {
306
+ flex: 1;
307
+ min-width: 8px;
308
+ background: var(--cratis-highlight-bg);
309
+ border: none;
310
+ border-radius: 2px 2px 0 0;
311
+ cursor: pointer;
312
+ transition: background 160ms ease, transform 120ms ease;
313
+ padding: 0;
314
+ }
315
+
316
+ .pv-histogram-bar:hover {
317
+ background: var(--cratis-surface-hover);
318
+ transform: scaleY(1.02);
319
+ }
320
+
321
+ .pv-histogram-bar.in-range {
322
+ background: var(--cratis-primary-color);
323
+ }
324
+
325
+ .pv-histogram-bar.partial {
326
+ background: var(--cratis-highlight-bg);
327
+ }
328
+
329
+ .pv-range-slider {
330
+ position: relative;
331
+ height: 24px;
332
+ margin: 0.5rem 0;
333
+ }
334
+
335
+ .pv-range-track {
336
+ position: absolute;
337
+ top: 50%;
338
+ left: 0;
339
+ right: 0;
340
+ height: 4px;
341
+ background: var(--cratis-surface-border);
342
+ border-radius: 2px;
343
+ transform: translateY(-50%);
344
+ }
345
+
346
+ .pv-range-selection {
347
+ position: absolute;
348
+ top: 50%;
349
+ height: 4px;
350
+ background: var(--cratis-primary-color);
351
+ border-radius: 2px;
352
+ transform: translateY(-50%);
353
+ cursor: grab;
354
+ }
355
+
356
+ .pv-range-selection:active {
357
+ cursor: grabbing;
358
+ }
359
+
360
+ .pv-range-handle {
361
+ position: absolute;
362
+ top: 50%;
363
+ width: 16px;
364
+ height: 16px;
365
+ background: var(--cratis-primary-color);
366
+ border: 2px solid var(--cratis-text-color);
367
+ border-radius: 50%;
368
+ transform: translate(-50%, -50%);
369
+ cursor: ew-resize;
370
+ transition: transform 120ms ease, box-shadow 120ms ease;
371
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
372
+ }
373
+
374
+ .pv-range-handle:hover {
375
+ transform: translate(-50%, -50%) scale(1.15);
376
+ box-shadow: 0 4px 12px var(--cratis-primary-color);
377
+ }
378
+
379
+ .pv-range-labels {
380
+ display: flex;
381
+ justify-content: space-between;
382
+ font-size: 0.75rem;
383
+ color: var(--cratis-text-color-secondary);
384
+ font-variant-numeric: tabular-nums;
385
+ }
386
+
387
+ .pv-range-clear {
388
+ margin-top: 0.75rem;
389
+ appearance: none;
390
+ border: none;
391
+ border-radius: 0.55rem;
392
+ padding: 0.4rem 0.75rem;
393
+ background: var(--cratis-highlight-bg);
394
+ color: var(--cratis-text-color);
395
+ font-size: 0.72rem;
396
+ text-transform: uppercase;
397
+ letter-spacing: 0.08em;
398
+ cursor: pointer;
399
+ width: 100%;
400
+ }
401
+
402
+ .pv-range-clear:hover {
403
+ background: var(--cratis-surface-hover);
404
+ }
405
+
406
+ @media (max-width: 900px) {
407
+ .pv-filter-panel {
408
+ width: calc(100% - 2.5rem);
409
+ left: 1.25rem;
410
+ right: 1.25rem;
411
+ }
412
+ }
@@ -0,0 +1,24 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { FilterDefinition, FilterValues, RangeValues, CustomFilterValues } from './types';
3
+ import './FilterPanel.css';
4
+ export interface FilterPanelProps {
5
+ isOpen: boolean;
6
+ filters: FilterDefinition[];
7
+ filterValues: FilterValues;
8
+ rangeValues: RangeValues;
9
+ customValues?: CustomFilterValues;
10
+ search?: string;
11
+ searchPlaceholder?: string;
12
+ expandedFilterKey?: string | null;
13
+ anchorRef: React.RefObject<HTMLButtonElement | null>;
14
+ onClose: () => void;
15
+ onSearchChange?: (value: string) => void;
16
+ onFilterToggle: (filterKey: string, optionKey: string, multi: boolean) => void;
17
+ onFilterClear: (filterKey: string) => void;
18
+ onRangeChange: (filterKey: string, range: [number, number] | null) => void;
19
+ onExpandedFilterChange: (key: string | null) => void;
20
+ onCustomValueChange?: (filterKey: string, value: unknown) => void;
21
+ children?: ReactNode;
22
+ }
23
+ export declare function FilterPanel({ isOpen, filters, filterValues, rangeValues, customValues, search, searchPlaceholder, expandedFilterKey, anchorRef, onClose, onSearchChange, onFilterToggle, onFilterClear, onRangeChange, onExpandedFilterChange, onCustomValueChange, children, }: FilterPanelProps): import("react").ReactPortal;
24
+ //# sourceMappingURL=FilterPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilterPanel.d.ts","sourceRoot":"","sources":["../../../Filter/FilterPanel.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,OAAO,KAAK,EACV,gBAAgB,EAEhB,YAAY,EACZ,WAAW,EACX,kBAAkB,EACnB,MAAM,SAAS,CAAC;AAGjB,OAAO,mBAAmB,CAAC;AAE3B,MAAM,WAAW,gBAAgB;IAE/B,MAAM,EAAE,OAAO,CAAC;IAEhB,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAE5B,YAAY,EAAE,YAAY,CAAC;IAE3B,WAAW,EAAE,WAAW,CAAC;IAEzB,YAAY,CAAC,EAAE,kBAAkB,CAAC;IAElC,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAElC,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IAErD,OAAO,EAAE,MAAM,IAAI,CAAC;IAEpB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAEzC,cAAc,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAE/E,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAE3C,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,KAAK,IAAI,CAAC;IAE3E,sBAAsB,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAErD,mBAAmB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAYlE,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAyBD,wBAAgB,WAAW,CAAC,EAC1B,MAAM,EACN,OAAO,EACP,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,MAAM,EACN,iBAA6B,EAC7B,iBAAiB,EACjB,SAAS,EACT,OAAO,EACP,cAAc,EACd,cAAc,EACd,aAAa,EACb,aAAa,EACb,sBAAsB,EACtB,mBAAmB,EACnB,QAAQ,GACT,EAAE,gBAAgB,+BAkKlB"}
@@ -0,0 +1,85 @@
1
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
+ import { useRef, useState, useEffect, Children, isValidElement } from 'react';
3
+ import { createPortal } from 'react-dom';
4
+ import { AnimatePresence, motion } from 'framer-motion';
5
+ import { FilterEditor } from './FilterEditor.js';
6
+ import { RangeHistogramFilter } from './RangeHistogramFilter.js';
7
+ import './FilterPanel.css';
8
+
9
+ /** Build a map of filterKey → render function from any <FilterEditor> children. */
10
+ function buildEditorMap(children) {
11
+ const map = {};
12
+ Children.forEach(children, (child) => {
13
+ if (isValidElement(child) && child.type === FilterEditor) {
14
+ const { filterKey, children: renderFn } = child.props;
15
+ if (filterKey && typeof renderFn === 'function') {
16
+ map[filterKey] = renderFn;
17
+ }
18
+ }
19
+ });
20
+ return map;
21
+ }
22
+ function renderOptionCount(count) {
23
+ return typeof count === 'number' ? count : '';
24
+ }
25
+ function FilterPanel({ isOpen, filters, filterValues, rangeValues, customValues, search, searchPlaceholder = 'Search…', expandedFilterKey, anchorRef, onClose, onSearchChange, onFilterToggle, onFilterClear, onRangeChange, onExpandedFilterChange, onCustomValueChange, children, }) {
26
+ const panelRef = useRef(null);
27
+ const [position, setPosition] = useState({ top: 0, left: 0 });
28
+ const editorMap = buildEditorMap(children);
29
+ // Calculate position when opening
30
+ useEffect(() => {
31
+ if (isOpen && anchorRef.current) {
32
+ const rect = anchorRef.current.getBoundingClientRect();
33
+ setPosition({
34
+ top: rect.bottom + 8,
35
+ left: rect.left,
36
+ });
37
+ }
38
+ }, [isOpen, anchorRef]);
39
+ // Handle click outside to close
40
+ useEffect(() => {
41
+ if (!isOpen)
42
+ return;
43
+ const handleClickOutside = (event) => {
44
+ const target = event.target;
45
+ const panel = panelRef.current;
46
+ const anchor = anchorRef.current;
47
+ if (panel && !panel.contains(target) && anchor && !anchor.contains(target)) {
48
+ onClose();
49
+ }
50
+ };
51
+ // Use capture phase to ensure we catch the event before any other handlers.
52
+ // Use timeout to avoid closing immediately when clicking the button to open.
53
+ const timeoutId = setTimeout(() => {
54
+ document.addEventListener('mousedown', handleClickOutside, true);
55
+ }, 0);
56
+ return () => {
57
+ clearTimeout(timeoutId);
58
+ document.removeEventListener('mousedown', handleClickOutside, true);
59
+ };
60
+ }, [isOpen, anchorRef, onClose]);
61
+ return createPortal(jsx(AnimatePresence, { initial: false, children: isOpen && (jsx(motion.aside, { ref: panelRef, className: "pv-filter-dropdown", style: {
62
+ position: 'fixed',
63
+ left: position.left,
64
+ top: position.top,
65
+ }, initial: { opacity: 0, y: -8 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: -8 }, transition: { duration: 0.15 }, children: jsxs("div", { className: "pv-filter-dropdown-content", children: [onSearchChange && (jsx("div", { className: "pv-search", children: jsx("input", { type: "search", placeholder: searchPlaceholder, value: search ?? '', onChange: (event) => onSearchChange(event.target.value) }) })), jsx("div", { className: "pv-filter-groups", children: filters.map((filter) => {
66
+ const selections = filterValues[filter.key] ?? new Set();
67
+ const rangeSelection = rangeValues[filter.key];
68
+ const customValue = customValues?.[filter.key];
69
+ const isExpanded = expandedFilterKey === filter.key;
70
+ const isNumeric = filter.type === 'number';
71
+ const editorRender = editorMap[filter.key];
72
+ const isCustom = filter.type === 'custom' || editorRender !== undefined;
73
+ return (jsxs("div", { className: `pv-filter ${isExpanded ? 'expanded' : ''}`, children: [jsxs("button", { type: "button", className: "pv-filter-trigger", 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 && !isCustom && selections.size > 0 && (jsx("span", { className: "pv-filter-count", children: selections.size })), isNumeric && rangeSelection && (jsx("span", { className: "pv-filter-count", children: "Range" })), isCustom && customValue !== undefined && customValue !== null && (jsx("span", { className: "pv-filter-count", children: "\u2022" })), jsx("span", { className: "pv-filter-chevron" })] })] }), jsx("div", { className: `pv-filter-content ${isExpanded ? 'expanded' : ''}`, children: isCustom && editorRender ? (editorRender({
74
+ value: customValue,
75
+ onChange: (value) => onCustomValueChange?.(filter.key, value),
76
+ })) : isNumeric && filter.numericRange ? (jsx(RangeHistogramFilter, { values: filter.numericRange.values, min: filter.numericRange.min, max: filter.numericRange.max, buckets: filter.buckets ?? 20, selectedRange: rangeSelection ?? null, onChange: (range) => onRangeChange(filter.key, range) })) : (jsxs(Fragment, { children: [jsx("ul", { children: (filter.options ?? []).map((option) => {
77
+ const optionKey = option.key;
78
+ const checked = selections.has(optionKey);
79
+ return (jsx("li", { children: jsxs("label", { children: [jsx("input", { type: filter.multi ? 'checkbox' : 'radio', name: `filter-${filter.key}`, checked: checked, onChange: () => onFilterToggle(filter.key, optionKey, filter.multi ?? false) }), jsx("span", { children: option.label }), jsx("span", { className: "pv-option-count", children: renderOptionCount(option.count) })] }) }, option.key));
80
+ }) }), selections.size > 0 && (jsx("button", { type: "button", className: "pv-filter-clear", onClick: () => onFilterClear(filter.key), children: "Clear" }))] })) })] }, filter.key));
81
+ }) })] }) })) }), document.body);
82
+ }
83
+
84
+ export { FilterPanel };
85
+ //# sourceMappingURL=FilterPanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilterPanel.js","sources":["../../../Filter/FilterPanel.tsx"],"sourcesContent":[null],"names":["_jsx","_jsxs","_Fragment"],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { FilterValue } from './types';
2
+ export interface RangeHistogramFilterProps {
3
+ values: FilterValue[];
4
+ min: number;
5
+ max: number;
6
+ buckets?: number;
7
+ selectedRange: [number, number] | null;
8
+ onChange: (range: [number, number] | null) => void;
9
+ }
10
+ export declare function RangeHistogramFilter({ values, min, max, buckets, selectedRange, onChange, }: RangeHistogramFilterProps): import("react").JSX.Element;
11
+ //# sourceMappingURL=RangeHistogramFilter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RangeHistogramFilter.d.ts","sourceRoot":"","sources":["../../../Filter/RangeHistogramFilter.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IACvC,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,KAAK,IAAI,CAAC;CACpD;AASD,wBAAgB,oBAAoB,CAAC,EACnC,MAAM,EACN,GAAG,EACH,GAAG,EACH,OAAY,EACZ,aAAa,EACb,QAAQ,GACT,EAAE,yBAAyB,+BA8L3B"}
@@ -0,0 +1,122 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { useRef, useState, useMemo, useCallback, useEffect } from 'react';
3
+
4
+ function RangeHistogramFilter({ values, min, max, buckets = 20, selectedRange, onChange, }) {
5
+ const containerRef = useRef(null);
6
+ const [isDragging, setIsDragging] = useState(null);
7
+ const [dragStart, setDragStart] = useState(null);
8
+ const numericValues = useMemo(() => {
9
+ return values
10
+ .map((v) => {
11
+ if (typeof v === 'number')
12
+ return v;
13
+ if (v instanceof Date)
14
+ return v.getTime();
15
+ const parsed = Number(v);
16
+ return Number.isNaN(parsed) ? null : parsed;
17
+ })
18
+ .filter((v) => v !== null);
19
+ }, [values]);
20
+ const histogram = useMemo(() => {
21
+ const range = max - min;
22
+ if (range <= 0 || numericValues.length === 0) {
23
+ return [];
24
+ }
25
+ const bucketSize = range / buckets;
26
+ const bucketCounts = Array(buckets).fill(0);
27
+ numericValues.forEach((value) => {
28
+ const bucketIndex = Math.min(Math.floor((value - min) / bucketSize), buckets - 1);
29
+ if (bucketIndex >= 0 && bucketIndex < buckets) {
30
+ bucketCounts[bucketIndex]++;
31
+ }
32
+ });
33
+ const maxCount = Math.max(...bucketCounts, 1);
34
+ return bucketCounts.map((count, i) => ({
35
+ start: min + i * bucketSize,
36
+ end: min + (i + 1) * bucketSize,
37
+ count,
38
+ maxCount,
39
+ }));
40
+ }, [numericValues, min, max, buckets]);
41
+ const currentRange = selectedRange ?? [min, max];
42
+ const getPositionFromValue = useCallback((value) => {
43
+ const range = max - min;
44
+ if (range <= 0)
45
+ return 0;
46
+ return ((value - min) / range) * 100;
47
+ }, [min, max]);
48
+ const handleMouseDown = (e, handle) => {
49
+ e.preventDefault?.();
50
+ setIsDragging(handle);
51
+ setDragStart({ x: e.clientX, range: [...currentRange] });
52
+ };
53
+ useEffect(() => {
54
+ if (!isDragging || !dragStart || !containerRef.current)
55
+ return;
56
+ const container = containerRef.current;
57
+ const rect = container.getBoundingClientRect();
58
+ const range = max - min;
59
+ const handleMouseMove = (e) => {
60
+ const deltaX = e.clientX - dragStart.x;
61
+ const deltaPercent = (deltaX / rect.width) * 100;
62
+ const deltaValue = (deltaPercent / 100) * range;
63
+ let newRange = [...dragStart.range];
64
+ if (isDragging === 'left') {
65
+ newRange[0] = Math.max(min, Math.min(dragStart.range[0] + deltaValue, newRange[1] - range * 0.01));
66
+ }
67
+ else if (isDragging === 'right') {
68
+ newRange[1] = Math.min(max, Math.max(dragStart.range[1] + deltaValue, newRange[0] + range * 0.01));
69
+ }
70
+ else if (isDragging === 'range') {
71
+ const rangeWidth = dragStart.range[1] - dragStart.range[0];
72
+ let newStart = dragStart.range[0] + deltaValue;
73
+ let newEnd = dragStart.range[1] + deltaValue;
74
+ if (newStart < min) {
75
+ newStart = min;
76
+ newEnd = min + rangeWidth;
77
+ }
78
+ if (newEnd > max) {
79
+ newEnd = max;
80
+ newStart = max - rangeWidth;
81
+ }
82
+ newRange = [newStart, newEnd];
83
+ }
84
+ onChange(newRange);
85
+ };
86
+ const handleMouseUp = () => {
87
+ setIsDragging(null);
88
+ setDragStart(null);
89
+ };
90
+ document.addEventListener('mousemove', handleMouseMove);
91
+ document.addEventListener('mouseup', handleMouseUp);
92
+ return () => {
93
+ document.removeEventListener('mousemove', handleMouseMove);
94
+ document.removeEventListener('mouseup', handleMouseUp);
95
+ };
96
+ }, [isDragging, dragStart, min, max, onChange]);
97
+ const handleBarClick = (bucket) => {
98
+ onChange([bucket.start, bucket.end]);
99
+ };
100
+ const handleClear = () => {
101
+ onChange(null);
102
+ };
103
+ const leftPos = getPositionFromValue(currentRange[0]);
104
+ const rightPos = getPositionFromValue(currentRange[1]);
105
+ const formatValue = (value) => {
106
+ if (Number.isInteger(value))
107
+ return value.toString();
108
+ return value.toFixed(1);
109
+ };
110
+ return (jsxs("div", { className: "pv-range-histogram", ref: containerRef, children: [jsx("div", { className: "pv-histogram-bars", children: histogram.map((bucket, i) => {
111
+ const heightPercent = (bucket.count / bucket.maxCount) * 100;
112
+ const isInRange = bucket.start >= currentRange[0] && bucket.end <= currentRange[1];
113
+ const isPartiallyInRange = bucket.end > currentRange[0] && bucket.start < currentRange[1];
114
+ return (jsx("button", { className: `pv-histogram-bar ${isInRange ? 'in-range' : ''} ${isPartiallyInRange && !isInRange ? 'partial' : ''}`, style: { height: `${heightPercent}%` }, onClick: () => handleBarClick(bucket), title: `${formatValue(bucket.start)} - ${formatValue(bucket.end)}: ${bucket.count} items`, type: "button" }, i));
115
+ }) }), jsxs("div", { className: "pv-range-slider", children: [jsx("div", { className: "pv-range-track" }), jsx("div", { className: "pv-range-selection", style: {
116
+ left: `${leftPos}%`,
117
+ width: `${rightPos - leftPos}%`,
118
+ }, onMouseDown: (e) => handleMouseDown(e, 'range') }), jsx("div", { className: "pv-range-handle pv-range-handle-left", style: { left: `${leftPos}%` }, onMouseDown: (e) => handleMouseDown(e, 'left') }), jsx("div", { className: "pv-range-handle pv-range-handle-right", style: { left: `${rightPos}%` }, onMouseDown: (e) => handleMouseDown(e, 'right') })] }), jsxs("div", { className: "pv-range-labels", children: [jsx("span", { className: "pv-range-value", children: formatValue(currentRange[0]) }), jsx("span", { className: "pv-range-value", children: formatValue(currentRange[1]) })] }), selectedRange && (jsx("button", { type: "button", className: "pv-range-clear", onClick: handleClear, children: "Clear Range" }))] }));
119
+ }
120
+
121
+ export { RangeHistogramFilter };
122
+ //# sourceMappingURL=RangeHistogramFilter.js.map