@cccsaurora/howler-ui 2.17.0-dev.600 → 2.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/api/index.d.ts +0 -2
  2. package/api/index.js +2 -4
  3. package/api/search/index.d.ts +1 -2
  4. package/api/search/index.js +1 -2
  5. package/commons/components/leftnav/LeftNavDrawer.js +1 -1
  6. package/components/app/App.js +0 -14
  7. package/components/app/providers/FavouritesProvider.js +2 -2
  8. package/components/app/providers/HitSearchProvider.d.ts +1 -0
  9. package/components/app/providers/HitSearchProvider.js +11 -6
  10. package/components/app/providers/HitSearchProvider.test.js +32 -11
  11. package/components/app/providers/LocalStorageProvider.js +1 -1
  12. package/components/app/providers/ParameterProvider.d.ts +2 -9
  13. package/components/app/providers/ParameterProvider.js +240 -165
  14. package/components/app/providers/ParameterProvider.test.js +14 -307
  15. package/components/elements/EditRow.d.ts +4 -1
  16. package/components/elements/EditRow.js +4 -4
  17. package/components/elements/PluginTypography.d.ts +1 -2
  18. package/components/elements/PluginTypography.js +2 -3
  19. package/components/elements/UserList.d.ts +2 -5
  20. package/components/elements/UserList.js +5 -14
  21. package/components/elements/addons/search/phrase/Phrase.js +1 -1
  22. package/components/elements/display/ChipPopper.d.ts +1 -1
  23. package/components/elements/display/ChipPopper.js +1 -1
  24. package/components/elements/display/HowlerCard.js +1 -1
  25. package/components/elements/display/Modal.js +0 -1
  26. package/components/elements/display/icons/BundleButton.d.ts +6 -0
  27. package/components/elements/display/icons/BundleButton.js +32 -0
  28. package/components/elements/hit/HitBanner.js +48 -28
  29. package/components/elements/hit/HitCard.js +1 -1
  30. package/components/elements/{ObjectDetails.js → hit/HitDetails.js} +17 -17
  31. package/components/elements/hit/HitOutline.js +7 -3
  32. package/components/elements/hit/{HitPreview.d.ts → HitQuickSearch.d.ts} +3 -3
  33. package/components/elements/hit/{HitPreview.js → HitQuickSearch.js} +4 -10
  34. package/components/elements/hit/HitRelated.d.ts +1 -1
  35. package/components/elements/hit/HitRelated.js +3 -30
  36. package/components/elements/hit/outlines/DefaultOutline.js +1 -1
  37. package/components/elements/hit/related/PivotLink.js +1 -1
  38. package/components/elements/hit/related/RelatedLink.d.ts +1 -0
  39. package/components/elements/hit/related/RelatedLink.js +2 -2
  40. package/components/elements/view/ViewTitle.js +1 -1
  41. package/components/hooks/useHitActions.d.ts +1 -1
  42. package/components/hooks/useHitActions.js +2 -2
  43. package/components/hooks/useHitSelection.js +24 -3
  44. package/components/hooks/useLocalStorage.d.ts +13 -0
  45. package/components/hooks/useLocalStorage.js +53 -0
  46. package/components/hooks/useLocalStorageItem.d.ts +18 -0
  47. package/components/hooks/useLocalStorageItem.js +78 -0
  48. package/components/hooks/useLocalStorageItem.test.d.ts +1 -0
  49. package/components/hooks/useLocalStorageItem.test.js +144 -0
  50. package/components/hooks/useMyLocalStorage.js +2 -2
  51. package/components/hooks/useMyPreferences.js +1 -10
  52. package/components/hooks/useMySearch.js +2 -2
  53. package/components/hooks/useMySitemap.js +1 -4
  54. package/components/hooks/useMyTheme.js +2 -9
  55. package/components/routes/action/view/ActionSearch.js +1 -1
  56. package/components/routes/advanced/QueryBuilder.js +1 -1
  57. package/components/routes/analytics/AnalyticDetails.js +2 -2
  58. package/components/routes/analytics/AnalyticSearch.js +1 -1
  59. package/components/routes/help/ApiDocumentation.js +1 -1
  60. package/components/routes/help/BundleDocumentation.d.ts +3 -0
  61. package/components/routes/help/BundleDocumentation.js +12 -0
  62. package/components/routes/help/HitDocumentation.js +3 -1
  63. package/components/routes/help/markdown/en/bundles.md.js +1 -0
  64. package/components/routes/help/markdown/fr/bundles.md.js +1 -0
  65. package/components/routes/hits/search/BundleParentMenu.d.ts +6 -0
  66. package/components/routes/hits/search/BundleParentMenu.js +32 -0
  67. package/components/routes/hits/search/HitContextMenu.js +2 -3
  68. package/components/routes/hits/search/InformationPane.d.ts +0 -1
  69. package/components/routes/hits/search/InformationPane.js +28 -6
  70. package/components/routes/hits/search/LayoutSettings.d.ts +3 -0
  71. package/components/routes/hits/search/LayoutSettings.js +18 -0
  72. package/components/routes/hits/search/QuerySettings.js +1 -2
  73. package/components/routes/hits/search/QuerySettings.test.js +9 -14
  74. package/components/routes/hits/search/SearchPane.js +37 -13
  75. package/components/routes/hits/search/ViewLink.js +1 -1
  76. package/components/routes/hits/search/grid/EnhancedCell.js +1 -1
  77. package/components/routes/hits/view/HitViewer.js +4 -3
  78. package/components/routes/home/AnalyticCard.d.ts +2 -3
  79. package/components/routes/home/AnalyticCard.js +2 -2
  80. package/components/routes/home/ViewCard.js +1 -1
  81. package/components/routes/home/ViewRefresh.d.ts +23 -0
  82. package/components/routes/home/ViewRefresh.js +67 -0
  83. package/components/routes/home/index.js +9 -46
  84. package/components/{elements/MarkdownEditor.js → routes/overviews/OverviewEditor.js} +3 -3
  85. package/components/routes/overviews/OverviewViewer.js +2 -2
  86. package/components/routes/settings/LocalSection.js +2 -1
  87. package/locales/en/translation.json +6 -42
  88. package/locales/fr/translation.json +4 -35
  89. package/models/WithMetadata.d.ts +1 -2
  90. package/models/entities/generated/{ThreatEnrichment.d.ts → Enrichment.d.ts} +1 -1
  91. package/models/entities/generated/Howler.d.ts +4 -0
  92. package/models/entities/generated/Rule.d.ts +10 -2
  93. package/models/entities/generated/Threat.d.ts +2 -2
  94. package/package.json +1 -16
  95. package/plugins/clue/components/ClueTypography.js +2 -2
  96. package/plugins/clue/utils.d.ts +1 -2
  97. package/utils/constants.d.ts +4 -3
  98. package/utils/constants.js +1 -0
  99. package/api/search/case.d.ts +0 -4
  100. package/api/search/case.js +0 -8
  101. package/api/v2/case/index.d.ts +0 -6
  102. package/api/v2/case/index.js +0 -18
  103. package/api/v2/index.d.ts +0 -4
  104. package/api/v2/index.js +0 -6
  105. package/api/v2/search/facet.d.ts +0 -3
  106. package/api/v2/search/facet.js +0 -12
  107. package/api/v2/search/index.d.ts +0 -5
  108. package/api/v2/search/index.js +0 -24
  109. package/components/elements/ObjectDetails.d.ts +0 -6
  110. package/components/elements/case/CaseCard.d.ts +0 -8
  111. package/components/elements/case/CaseCard.js +0 -39
  112. package/components/elements/case/CasePreview.d.ts +0 -6
  113. package/components/elements/case/CasePreview.js +0 -17
  114. package/components/elements/case/StatusIcon.d.ts +0 -5
  115. package/components/elements/case/StatusIcon.js +0 -13
  116. package/components/elements/hit/elements/AnalyticLink.d.ts +0 -8
  117. package/components/elements/hit/elements/AnalyticLink.js +0 -22
  118. package/components/elements/hit/related/RelatedRecords.js +0 -63
  119. package/components/elements/observable/ObservableCard.d.ts +0 -5
  120. package/components/elements/observable/ObservableCard.js +0 -7
  121. package/components/elements/observable/ObservablePreview.d.ts +0 -6
  122. package/components/elements/observable/ObservablePreview.js +0 -12
  123. package/components/hooks/useRelatedRecords.d.ts +0 -13
  124. package/components/hooks/useRelatedRecords.js +0 -32
  125. package/components/routes/cases/CaseViewer.d.ts +0 -2
  126. package/components/routes/cases/CaseViewer.js +0 -24
  127. package/components/routes/cases/Cases.d.ts +0 -2
  128. package/components/routes/cases/Cases.js +0 -101
  129. package/components/routes/cases/constants.d.ts +0 -5
  130. package/components/routes/cases/constants.js +0 -5
  131. package/components/routes/cases/detail/AlertPanel.d.ts +0 -6
  132. package/components/routes/cases/detail/AlertPanel.js +0 -32
  133. package/components/routes/cases/detail/CaseDashboard.d.ts +0 -7
  134. package/components/routes/cases/detail/CaseDashboard.js +0 -49
  135. package/components/routes/cases/detail/CaseDetails.d.ts +0 -6
  136. package/components/routes/cases/detail/CaseDetails.js +0 -61
  137. package/components/routes/cases/detail/CaseOverview.d.ts +0 -7
  138. package/components/routes/cases/detail/CaseOverview.js +0 -43
  139. package/components/routes/cases/detail/CaseSidebar.d.ts +0 -6
  140. package/components/routes/cases/detail/CaseSidebar.js +0 -36
  141. package/components/routes/cases/detail/CaseTask.d.ts +0 -11
  142. package/components/routes/cases/detail/CaseTask.js +0 -57
  143. package/components/routes/cases/detail/ItemPage.d.ts +0 -6
  144. package/components/routes/cases/detail/ItemPage.js +0 -93
  145. package/components/routes/cases/detail/RelatedCasePanel.d.ts +0 -6
  146. package/components/routes/cases/detail/RelatedCasePanel.js +0 -31
  147. package/components/routes/cases/detail/TaskPanel.d.ts +0 -7
  148. package/components/routes/cases/detail/TaskPanel.js +0 -52
  149. package/components/routes/cases/detail/aggregates/CaseAggregate.d.ts +0 -12
  150. package/components/routes/cases/detail/aggregates/CaseAggregate.js +0 -19
  151. package/components/routes/cases/detail/aggregates/SourceAggregate.d.ts +0 -6
  152. package/components/routes/cases/detail/aggregates/SourceAggregate.js +0 -27
  153. package/components/routes/cases/detail/sidebar/CaseFolder.d.ts +0 -12
  154. package/components/routes/cases/detail/sidebar/CaseFolder.js +0 -179
  155. package/components/routes/cases/detail/sidebar/types.d.ts +0 -3
  156. package/components/routes/cases/hooks/useCase.d.ts +0 -13
  157. package/components/routes/cases/hooks/useCase.js +0 -38
  158. package/components/routes/cases/modals/ResolveModal.d.ts +0 -7
  159. package/components/routes/cases/modals/ResolveModal.js +0 -59
  160. package/components/routes/hits/search/shared/IndexPicker.d.ts +0 -2
  161. package/components/routes/hits/search/shared/IndexPicker.js +0 -20
  162. package/components/routes/observables/ObservableViewer.d.ts +0 -7
  163. package/components/routes/observables/ObservableViewer.js +0 -27
  164. package/models/entities/generated/AttachmentsFile.d.ts +0 -12
  165. package/models/entities/generated/Case.d.ts +0 -28
  166. package/models/entities/generated/DestinationOriginal.d.ts +0 -19
  167. package/models/entities/generated/EmailAttachment.d.ts +0 -8
  168. package/models/entities/generated/EmailParent.d.ts +0 -19
  169. package/models/entities/generated/Enrichments.d.ts +0 -7
  170. package/models/entities/generated/EnrichmentsIndicator.d.ts +0 -21
  171. package/models/entities/generated/HttpResponse.d.ts +0 -11
  172. package/models/entities/generated/Item.d.ts +0 -9
  173. package/models/entities/generated/Observable.d.ts +0 -84
  174. package/models/entities/generated/ObservableCloud.d.ts +0 -20
  175. package/models/entities/generated/ObservableDestination.d.ts +0 -23
  176. package/models/entities/generated/ObservableEmail.d.ts +0 -30
  177. package/models/entities/generated/ObservableFile.d.ts +0 -36
  178. package/models/entities/generated/ObservableHowler.d.ts +0 -44
  179. package/models/entities/generated/ObservableHttp.d.ts +0 -11
  180. package/models/entities/generated/ObservableObserver.d.ts +0 -21
  181. package/models/entities/generated/ObservableOrganization.d.ts +0 -7
  182. package/models/entities/generated/ObservableProcess.d.ts +0 -34
  183. package/models/entities/generated/ObservableSource.d.ts +0 -23
  184. package/models/entities/generated/ObservableThreat.d.ts +0 -21
  185. package/models/entities/generated/ObservableTls.d.ts +0 -12
  186. package/models/entities/generated/ObserverIngress.d.ts +0 -9
  187. package/models/entities/generated/Task.d.ts +0 -10
  188. package/utils/typeUtils.d.ts +0 -7
  189. package/utils/typeUtils.js +0 -18
  190. /package/components/elements/hit/{related/RelatedRecords.d.ts → HitDetails.d.ts} +0 -0
  191. /package/components/{elements/MarkdownEditor.d.ts → routes/overviews/OverviewEditor.d.ts} +0 -0
@@ -272,17 +272,17 @@ describe('ParameterContext', () => {
272
272
  });
273
273
  });
274
274
  });
275
- describe('resetFilters', () => {
275
+ describe('clearFilters', () => {
276
276
  it('should clear all filters', async () => {
277
277
  mockSearchParams = new URLSearchParams();
278
278
  mockSearchParams.append('filter', 'filter1');
279
279
  mockSearchParams.append('filter', 'filter2');
280
280
  const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
281
281
  filters: ctx.filters,
282
- resetFilters: ctx.resetFilters
282
+ clearFilters: ctx.clearFilters
283
283
  })), { wrapper: Wrapper });
284
284
  await act(async () => {
285
- hook.result.current.resetFilters();
285
+ hook.result.current.clearFilters();
286
286
  });
287
287
  await waitFor(() => {
288
288
  expect(hook.result.current.filters).toEqual([]);
@@ -291,10 +291,10 @@ describe('ParameterContext', () => {
291
291
  it('should be no-op when already empty', async () => {
292
292
  const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
293
293
  filters: ctx.filters,
294
- resetFilters: ctx.resetFilters
294
+ clearFilters: ctx.clearFilters
295
295
  })), { wrapper: Wrapper });
296
296
  await act(async () => {
297
- hook.result.current.resetFilters();
297
+ hook.result.current.clearFilters();
298
298
  });
299
299
  await waitFor(() => {
300
300
  expect(hook.result.current.filters).toEqual([]);
@@ -441,10 +441,10 @@ describe('ParameterContext', () => {
441
441
  mockSearchParams.append('filter', 'filter1');
442
442
  mockSearchParams.append('filter', 'filter2');
443
443
  const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
444
- resetFilters: ctx.resetFilters
444
+ clearFilters: ctx.clearFilters
445
445
  })), { wrapper: Wrapper });
446
446
  await act(async () => {
447
- hook.result.current.resetFilters();
447
+ hook.result.current.clearFilters();
448
448
  });
449
449
  await waitFor(() => {
450
450
  expect(mockSetParams).toHaveBeenCalled();
@@ -706,299 +706,6 @@ describe('ParameterContext', () => {
706
706
  });
707
707
  });
708
708
  });
709
- describe('indexes (multi-index support)', () => {
710
- it('should initialize with default ["hit"] when no index params are present', async () => {
711
- const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ctx.indexes), { wrapper: Wrapper });
712
- expect(hook.result.current).toEqual(['hit']);
713
- });
714
- it('should initialize with single index from URL', async () => {
715
- mockSearchParams = new URLSearchParams({ index: 'observable' });
716
- const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ctx.indexes), { wrapper: Wrapper });
717
- expect(hook.result.current).toEqual(['observable']);
718
- });
719
- it('should initialize with multiple indexes from URL', async () => {
720
- mockSearchParams = new URLSearchParams();
721
- mockSearchParams.append('index', 'hit');
722
- mockSearchParams.append('index', 'observable');
723
- const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ctx.indexes), { wrapper: Wrapper });
724
- expect(hook.result.current).toEqual(['hit', 'observable']);
725
- });
726
- it('should deduplicate repeated index values from URL', async () => {
727
- mockSearchParams = new URLSearchParams();
728
- mockSearchParams.append('index', 'hit');
729
- mockSearchParams.append('index', 'hit');
730
- const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ctx.indexes), { wrapper: Wrapper });
731
- expect(hook.result.current).toEqual(['hit']);
732
- });
733
- describe('addIndex', () => {
734
- it('should add an index to the default array', async () => {
735
- const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
736
- indexes: ctx.indexes,
737
- addIndex: ctx.addIndex
738
- })), { wrapper: Wrapper });
739
- await act(async () => {
740
- hook.result.current.addIndex('observable');
741
- });
742
- await waitFor(() => {
743
- expect(hook.result.current.indexes).toEqual(['hit', 'observable']);
744
- });
745
- });
746
- it('should not add a duplicate index', async () => {
747
- const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
748
- indexes: ctx.indexes,
749
- addIndex: ctx.addIndex
750
- })), { wrapper: Wrapper });
751
- await act(async () => {
752
- hook.result.current.addIndex('hit');
753
- });
754
- await waitFor(() => {
755
- expect(hook.result.current.indexes).toEqual(['hit']);
756
- });
757
- });
758
- });
759
- describe('removeIndex', () => {
760
- it('should remove an index from the list', async () => {
761
- mockSearchParams = new URLSearchParams();
762
- mockSearchParams.append('index', 'hit');
763
- mockSearchParams.append('index', 'observable');
764
- const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
765
- indexes: ctx.indexes,
766
- removeIndex: ctx.removeIndex
767
- })), { wrapper: Wrapper });
768
- await act(async () => {
769
- hook.result.current.removeIndex('hit');
770
- });
771
- await waitFor(() => {
772
- expect(hook.result.current.indexes).toEqual(['observable']);
773
- });
774
- });
775
- it('should do nothing when removing a nonexistent index', async () => {
776
- const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
777
- indexes: ctx.indexes,
778
- removeIndex: ctx.removeIndex
779
- })), { wrapper: Wrapper });
780
- await act(async () => {
781
- hook.result.current.removeIndex('observable');
782
- });
783
- await waitFor(() => {
784
- expect(hook.result.current.indexes).toEqual(['hit']);
785
- });
786
- });
787
- it('should handle removing from empty array', async () => {
788
- mockSearchParams = new URLSearchParams();
789
- mockSearchParams.append('index', 'hit');
790
- const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
791
- indexes: ctx.indexes,
792
- removeIndex: ctx.removeIndex
793
- })), { wrapper: Wrapper });
794
- await act(async () => {
795
- hook.result.current.removeIndex('hit');
796
- });
797
- await waitFor(() => {
798
- expect(hook.result.current.indexes).toEqual([]);
799
- });
800
- });
801
- });
802
- describe('setIndex', () => {
803
- it('should update the index at the specified position', async () => {
804
- mockSearchParams = new URLSearchParams();
805
- mockSearchParams.append('index', 'hit');
806
- mockSearchParams.append('index', 'observable');
807
- const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
808
- indexes: ctx.indexes,
809
- setIndex: ctx.setIndex
810
- })), { wrapper: Wrapper });
811
- await act(async () => {
812
- hook.result.current.setIndex(0, 'observable');
813
- });
814
- await waitFor(() => {
815
- expect(hook.result.current.indexes).toEqual(['observable', 'observable']);
816
- });
817
- });
818
- it('should do nothing when index is out of bounds', async () => {
819
- const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
820
- indexes: ctx.indexes,
821
- setIndex: ctx.setIndex
822
- })), { wrapper: Wrapper });
823
- await act(async () => {
824
- hook.result.current.setIndex(5, 'observable');
825
- });
826
- await waitFor(() => {
827
- expect(hook.result.current.indexes).toEqual(['hit']);
828
- });
829
- });
830
- it('should do nothing when position is negative', async () => {
831
- const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
832
- indexes: ctx.indexes,
833
- setIndex: ctx.setIndex
834
- })), { wrapper: Wrapper });
835
- await act(async () => {
836
- hook.result.current.setIndex(-1, 'observable');
837
- });
838
- await waitFor(() => {
839
- expect(hook.result.current.indexes).toEqual(['hit']);
840
- });
841
- });
842
- });
843
- describe('setIndexes', () => {
844
- it('should replace all indexes with the provided list', async () => {
845
- const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
846
- indexes: ctx.indexes,
847
- setIndexes: ctx.setIndexes
848
- })), { wrapper: Wrapper });
849
- await act(async () => {
850
- hook.result.current.setIndexes(['observable']);
851
- });
852
- await waitFor(() => {
853
- expect(hook.result.current.indexes).toEqual(['observable']);
854
- });
855
- });
856
- it('should deduplicate values when setting all indexes', async () => {
857
- const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
858
- indexes: ctx.indexes,
859
- setIndexes: ctx.setIndexes
860
- })), { wrapper: Wrapper });
861
- await act(async () => {
862
- hook.result.current.setIndexes(['hit', 'hit', 'observable']);
863
- });
864
- await waitFor(() => {
865
- expect(hook.result.current.indexes).toEqual(['hit', 'observable']);
866
- });
867
- });
868
- it('should set to empty array', async () => {
869
- const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
870
- indexes: ctx.indexes,
871
- setIndexes: ctx.setIndexes
872
- })), { wrapper: Wrapper });
873
- await act(async () => {
874
- hook.result.current.setIndexes([]);
875
- });
876
- await waitFor(() => {
877
- expect(hook.result.current.indexes).toEqual([]);
878
- });
879
- });
880
- });
881
- describe('resetIndexes', () => {
882
- it('should reset indexes to default ["hit"]', async () => {
883
- mockSearchParams = new URLSearchParams();
884
- mockSearchParams.append('index', 'hit');
885
- mockSearchParams.append('index', 'observable');
886
- const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
887
- indexes: ctx.indexes,
888
- resetIndexes: ctx.resetIndexes
889
- })), { wrapper: Wrapper });
890
- await act(async () => {
891
- hook.result.current.resetIndexes();
892
- });
893
- await waitFor(() => {
894
- expect(hook.result.current.indexes).toEqual(['hit']);
895
- });
896
- });
897
- it('should reset to default even when called on empty array', async () => {
898
- mockSearchParams = new URLSearchParams();
899
- mockSearchParams.append('index', 'hit');
900
- const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
901
- indexes: ctx.indexes,
902
- removeIndex: ctx.removeIndex,
903
- resetIndexes: ctx.resetIndexes
904
- })), { wrapper: Wrapper });
905
- // First empty it
906
- await act(async () => {
907
- hook.result.current.removeIndex('hit');
908
- });
909
- await waitFor(() => {
910
- expect(hook.result.current.indexes).toEqual([]);
911
- });
912
- // Resetting always returns to default ['hit']
913
- await act(async () => {
914
- hook.result.current.resetIndexes();
915
- });
916
- await waitFor(() => {
917
- expect(hook.result.current.indexes).toEqual(['hit']);
918
- });
919
- });
920
- });
921
- describe('URL synchronization', () => {
922
- it('should not write the default ["hit"] index to the URL', async () => {
923
- renderHook(() => useContextSelector(ParameterContext, ctx => ctx.indexes), { wrapper: Wrapper });
924
- // Allow any effects to flush
925
- await waitFor(() => {
926
- // If setParams was called, the URL must not contain ?index=hit
927
- if (mockSetParams.mock.calls.length > 0) {
928
- const call = mockSetParams.mock.calls[mockSetParams.mock.calls.length - 1];
929
- const urlParams = typeof call[0] === 'function' ? call[0](mockSearchParams) : call[0];
930
- expect(urlParams.getAll('index')).toEqual([]);
931
- }
932
- else {
933
- // setParams not called at all is also fine
934
- expect(true).toBe(true);
935
- }
936
- });
937
- });
938
- it('should write a non-default index to the URL', async () => {
939
- const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
940
- addIndex: ctx.addIndex,
941
- resetIndexes: ctx.resetIndexes
942
- })), { wrapper: Wrapper });
943
- await act(async () => {
944
- hook.result.current.resetIndexes();
945
- hook.result.current.addIndex('observable');
946
- });
947
- await waitFor(() => {
948
- expect(mockSetParams).toHaveBeenCalled();
949
- const call = mockSetParams.mock.calls[mockSetParams.mock.calls.length - 1];
950
- const urlParams = typeof call[0] === 'function' ? call[0](mockSearchParams) : call[0];
951
- expect(urlParams.getAll('index')).toEqual(['hit', 'observable']);
952
- });
953
- });
954
- it('should sync multiple indexes to URL as multiple index params', async () => {
955
- const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
956
- addIndex: ctx.addIndex
957
- })), { wrapper: Wrapper });
958
- await act(async () => {
959
- hook.result.current.addIndex('observable');
960
- });
961
- await waitFor(() => {
962
- expect(mockSetParams).toHaveBeenCalled();
963
- const call = mockSetParams.mock.calls[mockSetParams.mock.calls.length - 1];
964
- const urlParams = typeof call[0] === 'function' ? call[0](mockSearchParams) : call[0];
965
- expect(urlParams.getAll('index')).toEqual(['hit', 'observable']);
966
- });
967
- });
968
- it('should remove all index params from URL when state resets to default', async () => {
969
- mockSearchParams = new URLSearchParams();
970
- mockSearchParams.append('index', 'hit');
971
- mockSearchParams.append('index', 'observable');
972
- const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
973
- resetIndexes: ctx.resetIndexes
974
- })), { wrapper: Wrapper });
975
- await act(async () => {
976
- hook.result.current.resetIndexes();
977
- });
978
- await waitFor(() => {
979
- expect(mockSetParams).toHaveBeenCalled();
980
- const call = mockSetParams.mock.calls[mockSetParams.mock.calls.length - 1];
981
- const urlParams = typeof call[0] === 'function' ? call[0](mockSearchParams) : call[0];
982
- expect(urlParams.getAll('index')).toEqual([]);
983
- });
984
- });
985
- it('should remove index param from URL when state returns to default', async () => {
986
- mockSearchParams = new URLSearchParams({ index: 'observable' });
987
- const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
988
- setIndexes: ctx.setIndexes
989
- })), { wrapper: Wrapper });
990
- await act(async () => {
991
- hook.result.current.setIndexes(['hit']);
992
- });
993
- await waitFor(() => {
994
- expect(mockSetParams).toHaveBeenCalled();
995
- const call = mockSetParams.mock.calls[mockSetParams.mock.calls.length - 1];
996
- const urlParams = typeof call[0] === 'function' ? call[0](mockSearchParams) : call[0];
997
- expect(urlParams.getAll('index')).toEqual([]);
998
- });
999
- });
1000
- });
1001
- });
1002
709
  describe('views (multi-view support)', () => {
1003
710
  it('should initialize with empty array when no view params present', async () => {
1004
711
  const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ctx.views), { wrapper: Wrapper });
@@ -1132,17 +839,17 @@ describe('ParameterContext', () => {
1132
839
  });
1133
840
  });
1134
841
  });
1135
- describe('resetViews', () => {
842
+ describe('clearViews', () => {
1136
843
  it('should clear all views', async () => {
1137
844
  mockSearchParams = new URLSearchParams();
1138
845
  mockSearchParams.append('view', 'view_1');
1139
846
  mockSearchParams.append('view', 'view_2');
1140
847
  const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
1141
848
  views: ctx.views,
1142
- resetViews: ctx.resetViews
849
+ clearViews: ctx.clearViews
1143
850
  })), { wrapper: Wrapper });
1144
851
  await act(async () => {
1145
- hook.result.current.resetViews();
852
+ hook.result.current.clearViews();
1146
853
  });
1147
854
  await waitFor(() => {
1148
855
  expect(hook.result.current.views).toEqual([]);
@@ -1151,10 +858,10 @@ describe('ParameterContext', () => {
1151
858
  it('should be no-op when already empty', async () => {
1152
859
  const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
1153
860
  views: ctx.views,
1154
- resetViews: ctx.resetViews
861
+ clearViews: ctx.clearViews
1155
862
  })), { wrapper: Wrapper });
1156
863
  await act(async () => {
1157
- hook.result.current.resetViews();
864
+ hook.result.current.clearViews();
1158
865
  });
1159
866
  await waitFor(() => {
1160
867
  expect(hook.result.current.views).toEqual([]);
@@ -1301,10 +1008,10 @@ describe('ParameterContext', () => {
1301
1008
  mockSearchParams.append('view', 'view_1');
1302
1009
  mockSearchParams.append('view', 'view_2');
1303
1010
  const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
1304
- resetViews: ctx.resetViews
1011
+ clearViews: ctx.clearViews
1305
1012
  })), { wrapper: Wrapper });
1306
1013
  await act(async () => {
1307
- hook.result.current.resetViews();
1014
+ hook.result.current.clearViews();
1308
1015
  });
1309
1016
  await waitFor(() => {
1310
1017
  expect(mockSetParams).toHaveBeenCalled();
@@ -1,3 +1,4 @@
1
+ import { type SliderProps } from '@mui/material';
1
2
  type EditRowTypes<T extends string | number | boolean> = {
2
3
  titleKey: string;
3
4
  descriptionKey?: string;
@@ -8,7 +9,9 @@ type EditRowTypes<T extends string | number | boolean> = {
8
9
  type?: 'password' | 'number' | 'text' | 'checkbox' | 'range';
9
10
  min?: number;
10
11
  max?: number;
12
+ step?: number;
11
13
  optional?: boolean;
14
+ valueLabelFormat?: SliderProps['valueLabelFormat'];
12
15
  };
13
- declare const EditRow: <T extends string | number | boolean>({ titleKey, descriptionKey, value, onEdit, validate, failOnValidate, type, min, max, optional }: EditRowTypes<T>) => import("react/jsx-runtime").JSX.Element;
16
+ declare const EditRow: <T extends string | number | boolean>({ titleKey, descriptionKey, value, onEdit, validate, failOnValidate, type, min, max, step, valueLabelFormat, optional }: EditRowTypes<T>) => import("react/jsx-runtime").JSX.Element;
14
17
  export default EditRow;
@@ -2,10 +2,10 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { Check, Close, Delete, Edit } from '@mui/icons-material';
3
3
  import { Box, Checkbox, CircularProgress, IconButton, Slider, Stack, TableCell, TableRow, TextField, Typography } from '@mui/material';
4
4
  import useMySnackbar from '@cccsaurora/howler-ui/components/hooks/useMySnackbar';
5
- import { isNull, isUndefined } from 'lodash-es';
5
+ import { isNil, isNull, isUndefined } from 'lodash-es';
6
6
  import { useCallback, useEffect, useMemo, useState } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
- const EditRow = ({ titleKey, descriptionKey, value, onEdit, validate, failOnValidate = false, type = 'text', min, max, optional }) => {
8
+ const EditRow = ({ titleKey, descriptionKey, value, onEdit, validate, failOnValidate = false, type = 'text', min, max, step, valueLabelFormat, optional }) => {
9
9
  const { t } = useTranslation();
10
10
  const { showErrorMessage } = useMySnackbar();
11
11
  const [editing, setEditing] = useState(false);
@@ -102,7 +102,7 @@ const EditRow = ({ titleKey, descriptionKey, value, onEdit, validate, failOnVali
102
102
  }
103
103
  }, children: [!['checkbox', 'range'].includes(type) && (_jsx(TextField, { size: "small", value: editValue, onChange: ev => onChange(ev.target.value), onKeyDown: checkForActions, fullWidth: true, label: type === 'password' ? t('password') : null, type: type, error: error, sx: { '& input': { fontSize: '13.5px !important' } }, InputProps: {
104
104
  endAdornment: loading && _jsx(CircularProgress, { size: 24 })
105
- } })), type === 'checkbox' && (_jsx(Checkbox, { sx: { marginRight: 'auto' }, value: editValue, onChange: ev => onChange(ev.target.checked) })), type === 'range' && (_jsx(Slider, { min: min ?? 0, max: max ?? 100, step: Math.pow(10, Math.floor(Math.log10(max ?? 100) - Math.log10(Math.max(min ?? 0, 1)) / 2)), onChange: (__, val) => onChange(val), value: editValue, valueLabelDisplay: "auto", valueLabelFormat: val => `${val}px` })), type === 'password' && (_jsx(TextField, { size: "small", value: confirmPassword, onChange: ev => {
105
+ } })), type === 'checkbox' && (_jsx(Checkbox, { sx: { marginRight: 'auto' }, value: editValue, onChange: ev => onChange(ev.target.checked) })), type === 'range' && (_jsx(Slider, { min: min ?? 0, max: max ?? 100, step: step ?? Math.pow(10, Math.floor(Math.log10(max ?? 100) - Math.log10(Math.max(min ?? 0, 1)) / 2)), onChange: (__, val) => onChange(val), value: editValue, valueLabelDisplay: "auto", valueLabelFormat: valueLabelFormat ?? (val => `${val}px`) })), type === 'password' && (_jsx(TextField, { size: "small", value: confirmPassword, onChange: ev => {
106
106
  setConfirmPassword(ev.target.value);
107
107
  setError(false);
108
108
  }, onKeyDown: checkForActions, fullWidth: true, label: t('password.confirm'), type: "password", error: error, sx: { '& input': { fontSize: '13.5px !important' } }, InputProps: {
@@ -110,6 +110,6 @@ const EditRow = ({ titleKey, descriptionKey, value, onEdit, validate, failOnVali
110
110
  } }))] }), _jsx(IconButton, { onClick: onSubmit, disabled: loading, children: _jsx(Check, { fontSize: "small" }) }), _jsx(IconButton, { onClick: () => setEditing(false), disabled: loading, children: _jsx(Close, { fontSize: "small" }) }), optional && (_jsx(IconButton, { onClick: () => {
111
111
  setEditing(false);
112
112
  onEdit(null);
113
- }, disabled: loading, children: _jsx(Delete, { fontSize: "small" }) }))] }) })) : (_jsxs(TableCell, { sx: cellSx, width: "100%", children: [type === 'checkbox' ? (_jsx(Checkbox, { onChange: ev => onChange(ev.target.checked), checked: value.toString() === 'true' })) : ((value ?? t('none'))), type === 'range' && value && 'px'] })), onEdit && !editing && type !== 'checkbox' && (_jsx(TableCell, { sx: cellSx, align: "right", children: _jsx(IconButton, { onClick: () => setEditing(true), children: _jsx(Edit, { fontSize: "small" }) }) }))] }), descriptionKey && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 3, sx: { paddingTop: '0 !important' }, children: _jsx(Typography, { variant: "caption", color: "text.secondary", children: t(descriptionKey) }) }) }))] }));
113
+ }, disabled: loading, children: _jsx(Delete, { fontSize: "small" }) }))] }) })) : (_jsx(TableCell, { sx: cellSx, width: "100%", children: type === 'checkbox' ? (_jsx(Checkbox, { onChange: ev => onChange(ev.target.checked), checked: value.toString() === 'true' })) : type === 'range' && !isNil(value) ? ((valueLabelFormat ?? (val => `${val}px`))(value)) : ((value ?? t('none'))) })), onEdit && !editing && type !== 'checkbox' && (_jsx(TableCell, { sx: cellSx, align: "right", children: _jsx(IconButton, { onClick: () => setEditing(true), children: _jsx(Edit, { fontSize: "small" }) }) }))] }), descriptionKey && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 3, sx: { paddingTop: '0 !important' }, children: _jsx(Typography, { variant: "caption", color: "text.secondary", children: t(descriptionKey) }) }) }))] }));
114
114
  };
115
115
  export default EditRow;
@@ -1,11 +1,10 @@
1
1
  import { type TypographyProps } from '@mui/material';
2
2
  import type { Hit } from '@cccsaurora/howler-ui/models/entities/generated/Hit';
3
- import type { Observable } from '@cccsaurora/howler-ui/models/entities/generated/Observable';
4
3
  export type PluginTypographyProps = TypographyProps & {
5
4
  value: string;
6
5
  context: string;
7
6
  field?: string;
8
- obj?: Hit | Observable;
7
+ hit?: Hit;
9
8
  };
10
9
  declare const _default: import("react").NamedExoticComponent<PluginTypographyProps>;
11
10
  export default _default;
@@ -3,7 +3,7 @@ import { Typography } from '@mui/material';
3
3
  import howlerPluginStore from '@cccsaurora/howler-ui/plugins/store';
4
4
  import { memo } from 'react';
5
5
  import { usePluginStore } from 'react-pluggable';
6
- const PluginTypography = ({ children, value, context, field, obj, ...props }) => {
6
+ const PluginTypography = ({ children, value, context, field, hit, ...props }) => {
7
7
  const pluginStore = usePluginStore();
8
8
  for (const plugin of howlerPluginStore.plugins) {
9
9
  const component = pluginStore.executeFunction(`${plugin}.typography`, {
@@ -11,8 +11,7 @@ const PluginTypography = ({ children, value, context, field, obj, ...props }) =>
11
11
  value,
12
12
  context,
13
13
  field,
14
- hit: obj,
15
- obj,
14
+ hit,
16
15
  ...props
17
16
  });
18
17
  if (component) {
@@ -2,11 +2,8 @@ import type { SxProps, Theme } from '@mui/material';
2
2
  import type { FC } from 'react';
3
3
  declare const UserList: FC<{
4
4
  buttonSx?: SxProps<Theme>;
5
- userIds: string[];
6
- onChange: (userIds: string[]) => void;
5
+ userId: string;
6
+ onChange: (userId: string) => void;
7
7
  i18nLabel: string;
8
- avatarHeight?: number;
9
- disabled?: boolean;
10
- multiple?: boolean;
11
8
  }>;
12
9
  export default UserList;
@@ -1,20 +1,18 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { Add } from '@mui/icons-material';
3
- import { Autocomplete, AvatarGroup, Box, IconButton, Popover, Stack, TextField, Typography } from '@mui/material';
2
+ import { Autocomplete, Box, IconButton, Popover, TextField, Typography } from '@mui/material';
4
3
  import { UserListContext } from '@cccsaurora/howler-ui/components/app/providers/UserListProvider';
5
- import { uniq } from 'lodash-es';
6
4
  import { useContext, useEffect, useMemo, useState } from 'react';
7
5
  import { useTranslation } from 'react-i18next';
8
6
  import HowlerAvatar from './display/HowlerAvatar';
9
- const UserList = ({ buttonSx = {}, userIds, onChange, i18nLabel, avatarHeight = 32, multiple = false, disabled = false }) => {
7
+ const UserList = ({ buttonSx = {}, userId, onChange, i18nLabel }) => {
10
8
  const { t } = useTranslation();
11
9
  const [anchorEl, setAnchorEl] = useState(null);
12
10
  const { users, searchUsers } = useContext(UserListContext);
13
- const allUserIds = useMemo(() => Object.keys(users), [users]);
11
+ const userIds = useMemo(() => Object.keys(users), [users]);
14
12
  useEffect(() => {
15
13
  searchUsers('uname:*');
16
14
  }, [searchUsers]);
17
- return (_jsxs(_Fragment, { children: [multiple ? (_jsxs(Stack, { direction: "row", spacing: 0.25, alignItems: "center", children: [_jsx(AvatarGroup, { children: uniq(userIds ?? [null]).map(userId => (_jsx(HowlerAvatar, { userId: userId, sx: { height: avatarHeight, width: avatarHeight } }, userId))) }), _jsx(IconButton, { size: "small", sx: buttonSx, disabled: disabled, onClick: e => setAnchorEl(e.currentTarget), children: _jsx(Add, {}) })] })) : (_jsx(IconButton, { sx: buttonSx, disabled: disabled, onClick: e => setAnchorEl(e.currentTarget), children: _jsx(HowlerAvatar, { userId: userIds[0], sx: { height: avatarHeight, width: avatarHeight } }) })), _jsx(Popover, { open: !!anchorEl, onClose: () => setAnchorEl(null), anchorEl: anchorEl, anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, children: _jsx(Box, { sx: { p: 2 }, children: _jsx(Autocomplete, { disabled: disabled, multiple: multiple, sx: { minWidth: '300px' }, options: allUserIds, renderInput: params => _jsx(TextField, { ...params, label: t(i18nLabel), size: "small" }), renderOption: (props, _userId) => {
15
+ return (_jsxs(_Fragment, { children: [_jsx(IconButton, { sx: buttonSx, onClick: e => setAnchorEl(e.currentTarget), children: _jsx(HowlerAvatar, { userId: userId }) }), _jsx(Popover, { open: !!anchorEl, onClose: () => setAnchorEl(null), anchorEl: anchorEl, anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, children: _jsx(Box, { sx: { p: 2 }, children: _jsx(Autocomplete, { sx: { minWidth: '300px' }, options: userIds, renderInput: params => _jsx(TextField, { ...params, label: t(i18nLabel), size: "small" }), renderOption: (props, _userId) => {
18
16
  const user = users[_userId];
19
17
  return (_jsx("li", { ...props, children: _jsxs(Box, { sx: {
20
18
  display: 'grid',
@@ -23,13 +21,6 @@ const UserList = ({ buttonSx = {}, userIds, onChange, i18nLabel, avatarHeight =
23
21
  gridTemplateAreas: `"profile name"\n"profile email"`,
24
22
  columnGap: 1.5
25
23
  }, children: [_jsx(HowlerAvatar, { sx: { gridArea: 'profile', alignSelf: 'center', height: '32px', width: '32px' }, userId: user.username }), _jsx(Typography, { sx: { gridArea: 'name' }, variant: "body1", children: user.name }), _jsx(Typography, { sx: { gridArea: 'email' }, variant: "caption", children: user.email })] }) }));
26
- }, value: userIds, onChange: (__, options) => {
27
- if (multiple) {
28
- onChange(Array.isArray(options) ? options : [options]);
29
- }
30
- else {
31
- onChange([Array.isArray(options) ? (options[0] ?? null) : options]);
32
- }
33
- } }) }) })] }));
24
+ }, value: userId, onChange: (__, option) => onChange(option) }) }) })] }));
34
25
  };
35
26
  export default UserList;
@@ -80,7 +80,7 @@ const Phrase = ({ value = '', variant = 'outlined', suggestions = [], lexer, sug
80
80
  onSelectCapture: _onSelectCapture,
81
81
  startAdornment: startAdornment && _jsx(InputAdornment, { position: "start", children: startAdornment }),
82
82
  endAdornment: endAdornment && _jsx(InputAdornment, { position: "end", children: endAdornment })
83
- } }), _jsx(Popper, { anchorEl: containerRef.current, style: { width: '100%', zIndex: 100 }, open: optionsOpen && (options.length > 0 || (debug && analysisRef.current?.tokens.length > 0)), disablePortal: true, children: _jsx(Paper, { elevation: 1, sx: { maxHeight: 200, overflow: 'auto', borderTopRightRadius: 0, borderTopLeftRadius: 0 }, children: _jsx(MenuList, { ref: menuRef, onKeyDown: _onMenuKeyDown, sx: {
83
+ } }), _jsx(Popper, { anchorEl: containerRef.current, style: { width: '100%', zIndex: 100 }, open: optionsOpen && (options.length > 0 || (debug && analysisRef.current?.tokens.length > 0)), disablePortal: true, children: _jsx(Paper, { elevation: 2, sx: { maxHeight: 200, overflow: 'auto', borderTopRightRadius: 0, borderTopLeftRadius: 0 }, children: _jsx(MenuList, { ref: menuRef, onKeyDown: _onMenuKeyDown, sx: {
84
84
  '&:focus': {
85
85
  outline: 'none'
86
86
  }
@@ -15,8 +15,8 @@ interface ChipPopperProps {
15
15
  onToggle?: (show: boolean) => void;
16
16
  onDelete?: (event?: any) => void;
17
17
  toggleOnDelete?: boolean;
18
- disablePortal?: boolean;
19
18
  closeOnClick?: boolean;
19
+ disablePortal?: boolean;
20
20
  }
21
21
  declare const _default: import("react").NamedExoticComponent<ChipPopperProps>;
22
22
  export default _default;
@@ -21,7 +21,7 @@ const ChipPopper = ({ icon, deleteIcon, label, children, minWidth, placement = '
21
21
  ...(Array.isArray(slotProps.chip?.sx) ? slotProps.chip.sx : [slotProps.chip?.sx])
22
22
  ], ...(slotProps.chip ?? {}) }), _jsx(Popper, { placement: placement, anchorEl: anchorEl.current, disablePortal: disablePortal, open: true, sx: {
23
23
  minWidth: Math.max(typeof minWidth === 'number' ? minWidth : parseInt(minWidth?.replace('px', '')) || 0, anchorEl.current?.clientWidth || 0),
24
- zIndex: 1
24
+ zIndex: 1000
25
25
  }, children: _jsx(Collapse, { in: show, unmountOnExit: true, onClick: () => {
26
26
  if (closeOnClick) {
27
27
  handleToggle(false);
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Card } from '@mui/material';
3
3
  import { memo } from 'react';
4
- const HowlerCard = props => _jsx(Card, { elevation: props.variant !== 'outlined' ? 1 : 0, ...props });
4
+ const HowlerCard = props => (_jsx(Card, { style: { outline: 'none' }, elevation: props.variant !== 'outlined' ? 4 : 0, ...props }));
5
5
  export default memo(HowlerCard);
@@ -15,7 +15,6 @@ const Modal = () => {
15
15
  left: '50%',
16
16
  maxWidth: options.maxWidth || '1200px',
17
17
  maxHeight: options.maxHeight || '400px',
18
- height: '100%',
19
18
  transform: 'translate(-50%, -50%)',
20
19
  backgroundColor: 'background.paper',
21
20
  borderRadius: theme.shape.borderRadius,
@@ -0,0 +1,6 @@
1
+ import type { FC } from 'react';
2
+ declare const BundleButton: FC<{
3
+ ids: string[];
4
+ disabled?: boolean;
5
+ }>;
6
+ export default BundleButton;
@@ -0,0 +1,32 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { AccountTree } from '@mui/icons-material';
3
+ import { ListItemText, Menu, MenuItem, Typography } from '@mui/material';
4
+ import TuiIconButton from '@cccsaurora/howler-ui/components/elements/addons/buttons/CustomIconButton';
5
+ import { useCallback, useState } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { useNavigate } from 'react-router-dom';
8
+ const BundleButton = ({ ids, disabled = false }) => {
9
+ const { t } = useTranslation();
10
+ const navigate = useNavigate();
11
+ const [anchorEl, setAnchorEl] = useState(null);
12
+ const onClick = useCallback((event) => {
13
+ if (ids.length === 1) {
14
+ navigate(`/bundles/${ids[0]}`);
15
+ }
16
+ else {
17
+ setAnchorEl(event.currentTarget);
18
+ }
19
+ }, [ids, navigate]);
20
+ const handleClose = useCallback(() => setAnchorEl(null), []);
21
+ return (_jsxs(_Fragment, { children: [_jsx(TuiIconButton, { size: "small", tooltip: t(`hit.panel.bundles.open${ids.length > 1 ? '' : '.prompt'}`), onClick: onClick, disabled: disabled, "aria-disabled": disabled, "aria-haspopup": "true", "aria-controls": anchorEl ? 'bundle-action-menu' : undefined, "aria-expanded": anchorEl ? 'true' : undefined, children: _jsx(AccountTree, {}) }), _jsx(Menu, { id: "bundle-action-menu", anchorEl: anchorEl, open: !!anchorEl, onClose: handleClose, MenuListProps: {
22
+ dense: true,
23
+ 'aria-labelledby': `bundle-button`
24
+ }, anchorOrigin: {
25
+ vertical: 'bottom',
26
+ horizontal: 'right'
27
+ }, transformOrigin: {
28
+ vertical: 'top',
29
+ horizontal: 'right'
30
+ }, children: ids.map(id => (_jsx(MenuItem, { onClick: () => navigate(`/bundles/${id}`), children: _jsx(ListItemText, { primary: t('hit.panel.bundles.open.prompt'), secondary: _jsx(Typography, { variant: "caption", color: "text.secondary", children: id }) }) }, id))) })] }));
31
+ };
32
+ export default BundleButton;