@cratis/components 0.1.16 → 0.1.18

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 (137) hide show
  1. package/dist/cjs/PivotViewer/PivotViewer.css +70 -21
  2. package/dist/cjs/PivotViewer/PivotViewer.js +15 -5
  3. package/dist/cjs/PivotViewer/PivotViewer.js.map +1 -1
  4. package/dist/cjs/PivotViewer/components/AxisLabels.js +5 -8
  5. package/dist/cjs/PivotViewer/components/AxisLabels.js.map +1 -1
  6. package/dist/cjs/PivotViewer/components/DetailPanel.js +9 -2
  7. package/dist/cjs/PivotViewer/components/DetailPanel.js.map +1 -1
  8. package/dist/cjs/PivotViewer/components/PivotCanvas.js +62 -55
  9. package/dist/cjs/PivotViewer/components/PivotCanvas.js.map +1 -1
  10. package/dist/cjs/PivotViewer/components/PivotViewerMain.js +16 -5
  11. package/dist/cjs/PivotViewer/components/PivotViewerMain.js.map +1 -1
  12. package/dist/cjs/PivotViewer/components/Toolbar.js +34 -2
  13. package/dist/cjs/PivotViewer/components/Toolbar.js.map +1 -1
  14. package/dist/cjs/PivotViewer/components/pivot/animation.js +4 -3
  15. package/dist/cjs/PivotViewer/components/pivot/animation.js.map +1 -1
  16. package/dist/cjs/PivotViewer/components/pivot/constants.js +5 -5
  17. package/dist/cjs/PivotViewer/components/pivot/constants.js.map +1 -1
  18. package/dist/cjs/PivotViewer/components/pivot/groups.js +151 -0
  19. package/dist/cjs/PivotViewer/components/pivot/groups.js.map +1 -0
  20. package/dist/cjs/PivotViewer/components/pivot/sprites.js +11 -42
  21. package/dist/cjs/PivotViewer/components/pivot/sprites.js.map +1 -1
  22. package/dist/cjs/PivotViewer/components/pivot/visibility.js +57 -26
  23. package/dist/cjs/PivotViewer/components/pivot/visibility.js.map +1 -1
  24. package/dist/cjs/PivotViewer/constants.js +0 -2
  25. package/dist/cjs/PivotViewer/constants.js.map +1 -1
  26. package/dist/cjs/PivotViewer/engine/layout.js +11 -7
  27. package/dist/cjs/PivotViewer/engine/layout.js.map +1 -1
  28. package/dist/cjs/PivotViewer/hooks/useCardSelection.js +2 -1
  29. package/dist/cjs/PivotViewer/hooks/useCardSelection.js.map +1 -1
  30. package/dist/cjs/PivotViewer/hooks/useCurrentFilters.js.map +1 -1
  31. package/dist/cjs/PivotViewer/hooks/usePanning.js +8 -4
  32. package/dist/cjs/PivotViewer/hooks/usePanning.js.map +1 -1
  33. package/dist/cjs/PivotViewer/hooks/useViewModeScrollHandling.js +4 -3
  34. package/dist/cjs/PivotViewer/hooks/useViewModeScrollHandling.js.map +1 -1
  35. package/dist/cjs/PivotViewer/hooks/useZoomState.js +4 -0
  36. package/dist/cjs/PivotViewer/hooks/useZoomState.js.map +1 -1
  37. package/dist/cjs/PivotViewer/types.js.map +1 -1
  38. package/dist/cjs/PivotViewer/utils/animations.js +11 -3
  39. package/dist/cjs/PivotViewer/utils/animations.js.map +1 -1
  40. package/dist/cjs/PivotViewer/utils/cardPosition.js +0 -3
  41. package/dist/cjs/PivotViewer/utils/cardPosition.js.map +1 -1
  42. package/dist/cjs/PivotViewer/utils/constants.js +3 -3
  43. package/dist/cjs/PivotViewer/utils/constants.js.map +1 -1
  44. package/dist/cjs/PivotViewer/utils/selection.js +8 -1
  45. package/dist/cjs/PivotViewer/utils/selection.js.map +1 -1
  46. package/dist/esm/PivotViewer/PivotViewer.css +70 -21
  47. package/dist/esm/PivotViewer/PivotViewer.d.ts.map +1 -1
  48. package/dist/esm/PivotViewer/PivotViewer.js +16 -6
  49. package/dist/esm/PivotViewer/PivotViewer.js.map +1 -1
  50. package/dist/esm/PivotViewer/PivotViewer.stories.d.ts +0 -1
  51. package/dist/esm/PivotViewer/PivotViewer.stories.d.ts.map +1 -1
  52. package/dist/esm/PivotViewer/PivotViewer.stories.js +10 -9
  53. package/dist/esm/PivotViewer/PivotViewer.stories.js.map +1 -1
  54. package/dist/esm/PivotViewer/components/AxisLabels.d.ts +2 -1
  55. package/dist/esm/PivotViewer/components/AxisLabels.d.ts.map +1 -1
  56. package/dist/esm/PivotViewer/components/AxisLabels.js +6 -9
  57. package/dist/esm/PivotViewer/components/AxisLabels.js.map +1 -1
  58. package/dist/esm/PivotViewer/components/DetailPanel.d.ts +3 -1
  59. package/dist/esm/PivotViewer/components/DetailPanel.d.ts.map +1 -1
  60. package/dist/esm/PivotViewer/components/DetailPanel.js +10 -3
  61. package/dist/esm/PivotViewer/components/DetailPanel.js.map +1 -1
  62. package/dist/esm/PivotViewer/components/PivotCanvas.d.ts +5 -2
  63. package/dist/esm/PivotViewer/components/PivotCanvas.d.ts.map +1 -1
  64. package/dist/esm/PivotViewer/components/PivotCanvas.js +63 -56
  65. package/dist/esm/PivotViewer/components/PivotCanvas.js.map +1 -1
  66. package/dist/esm/PivotViewer/components/PivotViewerMain.d.ts +5 -1
  67. package/dist/esm/PivotViewer/components/PivotViewerMain.d.ts.map +1 -1
  68. package/dist/esm/PivotViewer/components/PivotViewerMain.js +16 -5
  69. package/dist/esm/PivotViewer/components/PivotViewerMain.js.map +1 -1
  70. package/dist/esm/PivotViewer/components/Toolbar.d.ts +3 -1
  71. package/dist/esm/PivotViewer/components/Toolbar.d.ts.map +1 -1
  72. package/dist/esm/PivotViewer/components/Toolbar.js +34 -2
  73. package/dist/esm/PivotViewer/components/Toolbar.js.map +1 -1
  74. package/dist/esm/PivotViewer/components/pivot/animation.d.ts.map +1 -1
  75. package/dist/esm/PivotViewer/components/pivot/animation.js +4 -3
  76. package/dist/esm/PivotViewer/components/pivot/animation.js.map +1 -1
  77. package/dist/esm/PivotViewer/components/pivot/constants.d.ts.map +1 -1
  78. package/dist/esm/PivotViewer/components/pivot/constants.js +5 -5
  79. package/dist/esm/PivotViewer/components/pivot/constants.js.map +1 -1
  80. package/dist/esm/PivotViewer/components/pivot/groups.d.ts +6 -0
  81. package/dist/esm/PivotViewer/components/pivot/groups.d.ts.map +1 -0
  82. package/dist/esm/PivotViewer/components/pivot/groups.js +129 -0
  83. package/dist/esm/PivotViewer/components/pivot/groups.js.map +1 -0
  84. package/dist/esm/PivotViewer/components/pivot/sprites.d.ts +10 -2
  85. package/dist/esm/PivotViewer/components/pivot/sprites.d.ts.map +1 -1
  86. package/dist/esm/PivotViewer/components/pivot/sprites.js +12 -42
  87. package/dist/esm/PivotViewer/components/pivot/sprites.js.map +1 -1
  88. package/dist/esm/PivotViewer/components/pivot/visibility.d.ts +4 -0
  89. package/dist/esm/PivotViewer/components/pivot/visibility.d.ts.map +1 -1
  90. package/dist/esm/PivotViewer/components/pivot/visibility.js +57 -26
  91. package/dist/esm/PivotViewer/components/pivot/visibility.js.map +1 -1
  92. package/dist/esm/PivotViewer/constants.js +1 -2
  93. package/dist/esm/PivotViewer/constants.js.map +1 -1
  94. package/dist/esm/PivotViewer/engine/layout.js +11 -7
  95. package/dist/esm/PivotViewer/engine/layout.js.map +1 -1
  96. package/dist/esm/PivotViewer/engine/types.d.ts +1 -0
  97. package/dist/esm/PivotViewer/engine/types.d.ts.map +1 -1
  98. package/dist/esm/PivotViewer/hooks/useCardSelection.d.ts.map +1 -1
  99. package/dist/esm/PivotViewer/hooks/useCardSelection.js +2 -1
  100. package/dist/esm/PivotViewer/hooks/useCardSelection.js.map +1 -1
  101. package/dist/esm/PivotViewer/hooks/useCurrentFilters.js.map +1 -1
  102. package/dist/esm/PivotViewer/hooks/useFilteredData.d.ts +1 -1
  103. package/dist/esm/PivotViewer/hooks/useFilteredData.js +4 -4
  104. package/dist/esm/PivotViewer/hooks/useFilteredData.js.map +1 -1
  105. package/dist/esm/PivotViewer/hooks/usePanning.d.ts.map +1 -1
  106. package/dist/esm/PivotViewer/hooks/usePanning.js +8 -4
  107. package/dist/esm/PivotViewer/hooks/usePanning.js.map +1 -1
  108. package/dist/esm/PivotViewer/hooks/useViewModeScrollHandling.d.ts.map +1 -1
  109. package/dist/esm/PivotViewer/hooks/useViewModeScrollHandling.js +4 -3
  110. package/dist/esm/PivotViewer/hooks/useViewModeScrollHandling.js.map +1 -1
  111. package/dist/esm/PivotViewer/hooks/useZoomState.d.ts +1 -0
  112. package/dist/esm/PivotViewer/hooks/useZoomState.d.ts.map +1 -1
  113. package/dist/esm/PivotViewer/hooks/useZoomState.js +4 -0
  114. package/dist/esm/PivotViewer/hooks/useZoomState.js.map +1 -1
  115. package/dist/esm/PivotViewer/types.d.ts +5 -1
  116. package/dist/esm/PivotViewer/types.d.ts.map +1 -1
  117. package/dist/esm/PivotViewer/types.js.map +1 -1
  118. package/dist/esm/PivotViewer/utils/animations.d.ts.map +1 -1
  119. package/dist/esm/PivotViewer/utils/animations.js +11 -3
  120. package/dist/esm/PivotViewer/utils/animations.js.map +1 -1
  121. package/dist/esm/PivotViewer/utils/cardPosition.js +1 -4
  122. package/dist/esm/PivotViewer/utils/cardPosition.js.map +1 -1
  123. package/dist/esm/PivotViewer/utils/constants.d.ts +5 -5
  124. package/dist/esm/PivotViewer/utils/constants.d.ts.map +1 -1
  125. package/dist/esm/PivotViewer/utils/constants.js +3 -3
  126. package/dist/esm/PivotViewer/utils/constants.js.map +1 -1
  127. package/dist/esm/PivotViewer/utils/selection.d.ts.map +1 -1
  128. package/dist/esm/PivotViewer/utils/selection.js +8 -1
  129. package/dist/esm/PivotViewer/utils/selection.js.map +1 -1
  130. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  131. package/package.json +1 -1
  132. package/dist/cjs/PivotViewer/components/pivot/buckets.js +0 -124
  133. package/dist/cjs/PivotViewer/components/pivot/buckets.js.map +0 -1
  134. package/dist/esm/PivotViewer/components/pivot/buckets.d.ts +0 -6
  135. package/dist/esm/PivotViewer/components/pivot/buckets.d.ts.map +0 -1
  136. package/dist/esm/PivotViewer/components/pivot/buckets.js +0 -102
  137. package/dist/esm/PivotViewer/components/pivot/buckets.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"useZoomState.js","sources":["../../../../PivotViewer/hooks/useZoomState.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 { useState, useCallback } from 'react';\nimport { ZOOM_MIN, ZOOM_MAX } from '../utils/utils';\n\nexport function useZoomState(initialZoom = 1) {\n const [zoomLevel, setZoomLevel] = useState(initialZoom);\n\n const handleZoomChange = useCallback((newZoom: number) => {\n setZoomLevel(Math.max(ZOOM_MIN, Math.min(ZOOM_MAX, newZoom)));\n }, []);\n\n const handleZoomIn = useCallback(() => {\n handleZoomChange(zoomLevel + 0.2);\n }, [zoomLevel, handleZoomChange]);\n\n const handleZoomOut = useCallback(() => {\n handleZoomChange(zoomLevel - 0.2);\n }, [zoomLevel, handleZoomChange]);\n\n const handleZoomSlider = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {\n handleZoomChange(parseFloat(e.target.value));\n }, [handleZoomChange]);\n\n return {\n zoomLevel,\n setZoomLevel,\n handleZoomChange,\n handleZoomIn,\n handleZoomOut,\n handleZoomSlider,\n };\n}\n"],"names":["useState","useCallback","ZOOM_MIN","ZOOM_MAX"],"mappings":";;;;;AAMM,SAAU,YAAY,CAAC,WAAW,GAAG,CAAC,EAAA;IAC1C,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAGA,cAAQ,CAAC,WAAW,CAAC;AAEvD,IAAA,MAAM,gBAAgB,GAAGC,iBAAW,CAAC,CAAC,OAAe,KAAI;AACvD,QAAA,YAAY,CAAC,IAAI,CAAC,GAAG,CAACC,cAAQ,EAAE,IAAI,CAAC,GAAG,CAACC,cAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;IAC/D,CAAC,EAAE,EAAE,CAAC;AAEN,IAAA,MAAM,YAAY,GAAGF,iBAAW,CAAC,MAAK;AACpC,QAAA,gBAAgB,CAAC,SAAS,GAAG,GAAG,CAAC;AACnC,IAAA,CAAC,EAAE,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAC;AAEjC,IAAA,MAAM,aAAa,GAAGA,iBAAW,CAAC,MAAK;AACrC,QAAA,gBAAgB,CAAC,SAAS,GAAG,GAAG,CAAC;AACnC,IAAA,CAAC,EAAE,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAC;AAEjC,IAAA,MAAM,gBAAgB,GAAGA,iBAAW,CAAC,CAAC,CAAsC,KAAI;QAC9E,gBAAgB,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC9C,IAAA,CAAC,EAAE,CAAC,gBAAgB,CAAC,CAAC;IAEtB,OAAO;QACL,SAAS;QACT,YAAY;QACZ,gBAAgB;QAChB,YAAY;QACZ,aAAa;QACb,gBAAgB;KACjB;AACH;;;;"}
1
+ {"version":3,"file":"useZoomState.js","sources":["../../../../PivotViewer/hooks/useZoomState.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 { useState, useCallback } from 'react';\nimport { ZOOM_MIN, ZOOM_MAX } from '../utils/utils';\n\nexport function useZoomState(initialZoom = 1) {\n const [zoomLevel, setZoomLevel] = useState(initialZoom);\n\n const handleZoomChange = useCallback((newZoom: number) => {\n setZoomLevel(Math.max(ZOOM_MIN, Math.min(ZOOM_MAX, newZoom)));\n }, []);\n\n const handleZoomIn = useCallback(() => {\n handleZoomChange(zoomLevel + 0.2);\n }, [zoomLevel, handleZoomChange]);\n\n const handleZoomOut = useCallback(() => {\n handleZoomChange(zoomLevel - 0.2);\n }, [zoomLevel, handleZoomChange]);\n\n const handleZoomSlider = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {\n handleZoomChange(parseFloat(e.target.value));\n }, [handleZoomChange]);\n\n const handleZoomReset = useCallback(() => {\n handleZoomChange(1);\n }, [handleZoomChange]);\n\n return {\n zoomLevel,\n setZoomLevel,\n handleZoomChange,\n handleZoomIn,\n handleZoomOut,\n handleZoomSlider,\n handleZoomReset,\n };\n}\n"],"names":["useState","useCallback","ZOOM_MIN","ZOOM_MAX"],"mappings":";;;;;AAMM,SAAU,YAAY,CAAC,WAAW,GAAG,CAAC,EAAA;IAC1C,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAGA,cAAQ,CAAC,WAAW,CAAC;AAEvD,IAAA,MAAM,gBAAgB,GAAGC,iBAAW,CAAC,CAAC,OAAe,KAAI;AACvD,QAAA,YAAY,CAAC,IAAI,CAAC,GAAG,CAACC,cAAQ,EAAE,IAAI,CAAC,GAAG,CAACC,cAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;IAC/D,CAAC,EAAE,EAAE,CAAC;AAEN,IAAA,MAAM,YAAY,GAAGF,iBAAW,CAAC,MAAK;AACpC,QAAA,gBAAgB,CAAC,SAAS,GAAG,GAAG,CAAC;AACnC,IAAA,CAAC,EAAE,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAC;AAEjC,IAAA,MAAM,aAAa,GAAGA,iBAAW,CAAC,MAAK;AACrC,QAAA,gBAAgB,CAAC,SAAS,GAAG,GAAG,CAAC;AACnC,IAAA,CAAC,EAAE,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAC;AAEjC,IAAA,MAAM,gBAAgB,GAAGA,iBAAW,CAAC,CAAC,CAAsC,KAAI;QAC9E,gBAAgB,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC9C,IAAA,CAAC,EAAE,CAAC,gBAAgB,CAAC,CAAC;AAEtB,IAAA,MAAM,eAAe,GAAGA,iBAAW,CAAC,MAAK;QACvC,gBAAgB,CAAC,CAAC,CAAC;AACrB,IAAA,CAAC,EAAE,CAAC,gBAAgB,CAAC,CAAC;IAEtB,OAAO;QACL,SAAS;QACT,YAAY;QACZ,gBAAgB;QAChB,YAAY;QACZ,aAAa;QACb,gBAAgB;QAChB,eAAe;KAChB;AACH;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sources":["../../../PivotViewer/types.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 type { ReactNode } from 'react';\n\nexport type PivotPrimitive = string | number | boolean | Date | null | undefined;\n\n/**\n * Type-safe property accessor for accessing properties, including nested ones\n */\nexport type PropertyAccessor<TItem> = (item: TItem) => unknown;\n\n/**\n * Extract property path from a property accessor function\n * Supports nested properties like item => item.address.city\n */\nexport function getPropertyPath<TItem>(accessor: PropertyAccessor<TItem>): string {\n const fnStr = accessor.toString();\n // Match patterns like: item => item.prop or item => item.prop.nested or (item) => item.prop\n const match = fnStr.match(/(?:=>|return)\\s*[a-zA-Z_$][a-zA-Z0-9_$]*\\.([a-zA-Z_$][a-zA-Z0-9_$.]*)/);\n return match ? match[1] : '';\n}\n\n/**\n * Get the value from an item using a property path string\n * Supports nested properties like \"address.city\"\n */\nexport function getValueByPath<TItem>(item: TItem, path: string): unknown {\n const parts = path.split('.');\n let value: unknown = item;\n for (const part of parts) {\n if (value === null || value === undefined) {\n return undefined;\n }\n value = value[part];\n }\n return value;\n}\n\nexport interface PivotGroup<TItem extends object> {\n key: string;\n label: string;\n value: PivotPrimitive;\n items: TItem[];\n count?: number;\n}\n\nexport interface PivotDimension<TItem extends object> {\n key: string;\n label: string;\n getValue: (item: TItem) => PivotPrimitive;\n formatValue?: (value: PivotPrimitive) => string;\n sort?: (a: PivotGroup<TItem>, b: PivotGroup<TItem>) => number;\n}\n\nexport interface PivotFilterOption {\n key: string;\n label: string;\n value: PivotPrimitive;\n count: number;\n}\n\nexport interface PivotFilter<TItem extends object> {\n key: string;\n label: string;\n getValue: (item: TItem) => PivotPrimitive;\n multi?: boolean;\n options?: PivotFilterOption[];\n sort?: (a: PivotFilterOption, b: PivotFilterOption) => number;\n /** For numeric filters, enables range picker with histogram */\n type?: 'string' | 'number' | 'date';\n /** Number of buckets for the histogram in range filters */\n buckets?: number;\n}\n\nexport interface PivotViewerProps<TItem extends object> {\n data: TItem[];\n dimensions: PivotDimension<TItem>[];\n filters?: PivotFilter<TItem>[];\n defaultDimensionKey?: string;\n cardRenderer?: (item: TItem) => ReactNode;\n /** Optional renderer for the slide-in detail panel when a card is selected */\n detailRenderer?: (item: TItem, onClose: () => void) => ReactNode;\n getItemId?: (item: TItem, index: number) => string | number;\n searchFields?: PropertyAccessor<TItem>[];\n className?: string;\n emptyContent?: ReactNode;\n isLoading?: boolean;\n /**\n * Optional color overrides mapped to PrimeReact-like CSS variables.\n * If omitted, values are taken from the global theme (PrimeReact defaults).\n */\n colors?: Partial<{\n primaryColor: string;\n primaryColorText: string;\n primary500: string;\n surfaceGround: string;\n surfaceCard: string;\n surfaceSection: string;\n surfaceOverlay: string;\n surfaceBorder: string;\n textColor: string;\n textColorSecondary: string;\n highlightBg: string;\n maskbg: string;\n focusRing: string;\n }>;\n}\n\nexport type FilterState = Record<string, Set<string>>;\n\nexport type RangeFilterState = Record<string, [number, number] | null>;\n"],"names":[],"mappings":";;AAgBM,SAAU,eAAe,CAAQ,QAAiC,EAAA;AACpE,IAAA,MAAM,KAAK,GAAG,QAAQ,CAAC,QAAQ,EAAE;IAEjC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,uEAAuE,CAAC;AAClG,IAAA,OAAO,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,EAAE;AAChC;AAMM,SAAU,cAAc,CAAQ,IAAW,EAAE,IAAY,EAAA;IAC3D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC;IAC7B,IAAI,KAAK,GAAY,IAAI;AACzB,IAAA,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE;QACtB,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE;AACvC,YAAA,OAAO,SAAS;QACpB;AACA,QAAA,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC;IACvB;AACA,IAAA,OAAO,KAAK;AAChB;;;;;"}
1
+ {"version":3,"file":"types.js","sources":["../../../PivotViewer/types.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 type { ReactNode } from 'react';\n\nexport type PivotPrimitive = string | number | boolean | Date | null | undefined;\n\n/**\n * Type-safe property accessor for accessing properties, including nested ones\n */\nexport type PropertyAccessor<TItem> = (item: TItem) => unknown;\n\n/**\n * Extract property path from a property accessor function\n * Supports nested properties like item => item.address.city\n */\nexport function getPropertyPath<TItem>(accessor: PropertyAccessor<TItem>): string {\n const fnStr = accessor.toString();\n // Match patterns like: item => item.prop or item => item.prop.nested or (item) => item.prop\n const match = fnStr.match(/(?:=>|return)\\s*[a-zA-Z_$][a-zA-Z0-9_$]*\\.([a-zA-Z_$][a-zA-Z0-9_$.]*)/);\n return match ? match[1] : '';\n}\n\n/**\n * Get the value from an item using a property path string\n * Supports nested properties like \"address.city\"\n */\nexport function getValueByPath<TItem>(item: TItem, path: string): unknown {\n const parts = path.split('.');\n let value: unknown = item;\n for (const part of parts) {\n if (value === null || value === undefined) {\n return undefined;\n }\n value = value[part];\n }\n return value;\n}\n\nexport interface PivotGroup<TItem extends object> {\n key: string;\n label: string;\n value: PivotPrimitive;\n items: TItem[];\n count?: number;\n}\n\nexport interface PivotDimension<TItem extends object> {\n key: string;\n label: string;\n getValue: (item: TItem) => PivotPrimitive;\n formatValue?: (value: PivotPrimitive) => string;\n sort?: (a: PivotGroup<TItem>, b: PivotGroup<TItem>) => number;\n}\n\nexport interface PivotFilterOption {\n key: string;\n label: string;\n value: PivotPrimitive;\n count: number;\n}\n\nexport interface PivotFilter<TItem extends object> {\n key: string;\n label: string;\n getValue: (item: TItem) => PivotPrimitive;\n multi?: boolean;\n options?: PivotFilterOption[];\n sort?: (a: PivotFilterOption, b: PivotFilterOption) => number;\n /** For numeric filters, enables range picker with histogram */\n type?: 'string' | 'number' | 'date';\n /** Number of buckets for the histogram in range filters */\n buckets?: number;\n}\n\nexport interface PivotViewerProps<TItem extends object> {\n data: TItem[];\n dimensions: PivotDimension<TItem>[];\n filters?: PivotFilter<TItem>[];\n defaultDimensionKey?: string;\n /** Renderer for card content; returns structured text data for display */\n cardRenderer: (item: TItem) => { title: string; labels: string[]; values: string[] };\n /** Optional renderer for the detail content area; the drawer shell (header/close/layout) stays component-owned */\n detailRenderer?: (item: TItem, onClose: () => void) => ReactNode;\n getItemId?: (item: TItem, index: number) => string | number;\n searchFields?: PropertyAccessor<TItem>[];\n className?: string;\n emptyContent?: ReactNode;\n isLoading?: boolean;\n /**\n * Optional color overrides mapped to PrimeReact-like CSS variables.\n * If omitted, values are taken from the global theme (PrimeReact defaults).\n */\n colors?: Partial<{\n primaryColor: string;\n primaryColorText: string;\n primary500: string;\n surfaceGround: string;\n surfaceCard: string;\n surfaceSection: string;\n surfaceOverlay: string;\n surfaceBorder: string;\n textColor: string;\n textColorSecondary: string;\n highlightBg: string;\n maskbg: string;\n focusRing: string;\n }>;\n}\n\nexport type FilterState = Record<string, Set<string>>;\n\nexport type RangeFilterState = Record<string, [number, number] | null>;\n"],"names":[],"mappings":";;AAgBM,SAAU,eAAe,CAAQ,QAAiC,EAAA;AACpE,IAAA,MAAM,KAAK,GAAG,QAAQ,CAAC,QAAQ,EAAE;IAEjC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,uEAAuE,CAAC;AAClG,IAAA,OAAO,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,EAAE;AAChC;AAMM,SAAU,cAAc,CAAQ,IAAW,EAAE,IAAY,EAAA;IAC3D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC;IAC7B,IAAI,KAAK,GAAY,IAAI;AACzB,IAAA,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE;QACtB,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE;AACvC,YAAA,OAAO,SAAS;QACpB;AACA,QAAA,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC;IACvB;AACA,IAAA,OAAO,KAAK;AAChB;;;;;"}
@@ -100,14 +100,22 @@ function animateZoomAndScroll({ container, cardPosition, targetCardPosition, get
100
100
  function calculateCenterScrollPosition(container, cardPosition, zoomLevel, detailPanelWidth = 0, totalHeight) {
101
101
  const cardCenterX = cardPosition.x * zoomLevel + (cardPosition.width * zoomLevel) / 2;
102
102
  const cardCenterY = cardPosition.y * zoomLevel + (cardPosition.height * zoomLevel) / 2;
103
+ const cardBottomY = (cardPosition.y + cardPosition.height) * zoomLevel;
103
104
  const availableWidth = container.clientWidth - detailPanelWidth;
104
105
  const targetX = availableWidth / 2;
105
- const targetY = container.clientHeight / 2;
106
+ const viewportHeight = container.clientHeight;
107
+ const targetYCenter = viewportHeight / 2;
108
+ const targetYForBottom = viewportHeight * 0.7;
106
109
  const scrollLeft = Math.max(0, cardCenterX - targetX);
107
- let scrollTop = Math.max(0, cardCenterY - targetY);
110
+ const scrollTopForCenter = cardCenterY - targetYCenter;
111
+ const scrollTopForBottomVisible = cardBottomY - viewportHeight + 50;
112
+ let scrollTop = scrollTopForCenter;
113
+ if (scrollTopForCenter > scrollTopForBottomVisible + (cardPosition.height * zoomLevel / 2)) {
114
+ scrollTop = Math.max(0, cardCenterY - targetYForBottom);
115
+ }
116
+ scrollTop = Math.max(0, scrollTop);
108
117
  if (totalHeight) {
109
118
  const contentHeight = totalHeight * zoomLevel;
110
- const viewportHeight = container.clientHeight;
111
119
  const maxScrollTop = Math.max(0, contentHeight - viewportHeight);
112
120
  scrollTop = Math.min(scrollTop, maxScrollTop);
113
121
  }
@@ -1 +1 @@
1
- {"version":3,"file":"animations.js","sources":["../../../../PivotViewer/utils/animations.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\nexport type EasingFunction = (t: number) => number;\n\nexport const easeOutCubic: EasingFunction = (t: number) => 1 - Math.pow(1 - t, 3);\n\nexport interface ZoomAnimationParams {\n startZoom: number;\n targetZoom: number;\n duration?: number;\n easing?: EasingFunction;\n}\n\nexport interface ScrollAnimationParams {\n targetScrollLeft: number;\n targetScrollTop: number;\n}\n\nexport interface ZoomScrollAnimationParams extends ZoomAnimationParams, ScrollAnimationParams {\n container: HTMLElement;\n cardPosition?: { x: number; y: number; width: number; height: number } | null;\n targetCardPosition?: { x: number; y: number; width: number; height: number } | null;\n getCardPositionAtZoom?: (zoom: number) => { x: number; y: number; width: number; height: number } | null;\n getLayoutSizeAtZoom?: (zoom: number) => { width: number; height: number };\n spacer?: HTMLElement | null;\n onUpdate: (zoom: number) => void;\n onComplete?: () => void;\n startScrollLeft?: number; // Optional explicit start scroll\n startScrollTop?: number; // Optional explicit start scroll\n}\n\n/**\n * Animate zoom and scroll together, keeping a specific element centered\n */\nexport function animateZoomAndScroll({\n container,\n cardPosition,\n targetCardPosition,\n getCardPositionAtZoom,\n getLayoutSizeAtZoom,\n spacer,\n startZoom,\n targetZoom,\n targetScrollLeft,\n targetScrollTop,\n duration = 300,\n easing = easeOutCubic,\n onUpdate,\n onComplete,\n startScrollLeft,\n startScrollTop,\n}: ZoomScrollAnimationParams): () => void {\n container.style.scrollBehavior = 'auto';\n\n let startTime: number | null = null;\n let rafId: number | null = null;\n\n // Capture start scroll if not provided\n const initialScrollLeft = startScrollLeft ?? container.scrollLeft;\n const initialScrollTop = startScrollTop ?? container.scrollTop;\n\n // Calculate initial center point relative to viewport\n const startCardCenterX = cardPosition ? (cardPosition.x * startZoom + (cardPosition.width * startZoom) / 2) : 0;\n const startCardCenterY = cardPosition ? (cardPosition.y * startZoom + (cardPosition.height * startZoom) / 2) : 0;\n\n const startCardScreenX = startCardCenterX - initialScrollLeft;\n const startCardScreenY = startCardCenterY - initialScrollTop;\n\n const animate = (timestamp: number) => {\n if (startTime === null) {\n startTime = timestamp;\n }\n\n const elapsed = timestamp - startTime;\n const progress = Math.min(1, elapsed / duration);\n const easedProgress = easing(progress);\n\n const currentZoom = startZoom + (targetZoom - startZoom) * easedProgress;\n\n // Update spacer size synchronously if possible\n if (spacer && getLayoutSizeAtZoom) {\n const size = getLayoutSizeAtZoom(currentZoom);\n if (size) {\n spacer.style.width = `${size.width * currentZoom}px`;\n spacer.style.height = `${size.height * currentZoom}px`;\n }\n }\n\n onUpdate(currentZoom);\n\n if (cardPosition) {\n let currentCardX: number;\n let currentCardY: number;\n\n if (getCardPositionAtZoom) {\n const pos = getCardPositionAtZoom(currentZoom);\n if (pos) {\n currentCardX = pos.x;\n currentCardY = pos.y;\n } else {\n currentCardX = cardPosition.x;\n currentCardY = cardPosition.y;\n }\n } else {\n // Interpolate card position if target is provided (for layouts that change with zoom)\n currentCardX = targetCardPosition\n ? cardPosition.x + (targetCardPosition.x - cardPosition.x) * easedProgress\n : cardPosition.x;\n currentCardY = targetCardPosition\n ? cardPosition.y + (targetCardPosition.y - cardPosition.y) * easedProgress\n : cardPosition.y;\n }\n\n // Calculate where the card center is at current zoom\n const currentCardCenterX = currentCardX * currentZoom + (cardPosition.width * currentZoom) / 2;\n const currentCardCenterY = currentCardY * currentZoom + (cardPosition.height * currentZoom) / 2;\n\n // Calculate where the card center will be at the end of animation (relative to viewport)\n // Target scroll position is where we want to end up\n const endCardX = targetCardPosition ? targetCardPosition.x : cardPosition.x;\n const endCardY = targetCardPosition ? targetCardPosition.y : cardPosition.y;\n\n const endCardCenterX = endCardX * targetZoom + (cardPosition.width * targetZoom) / 2;\n const endCardCenterY = endCardY * targetZoom + (cardPosition.height * targetZoom) / 2;\n\n const endCardScreenX = endCardCenterX - targetScrollLeft;\n const endCardScreenY = endCardCenterY - targetScrollTop;\n\n // Interpolate the desired screen position\n const desiredScreenX = startCardScreenX + (endCardScreenX - startCardScreenX) * easedProgress;\n const desiredScreenY = startCardScreenY + (endCardScreenY - startCardScreenY) * easedProgress;\n\n // Calculate needed scroll position to put card at desired screen position\n const neededScrollLeft = currentCardCenterX - desiredScreenX;\n const neededScrollTop = currentCardCenterY - desiredScreenY;\n\n let maxScrollLeft = container.scrollWidth - container.clientWidth;\n let maxScrollTop = container.scrollHeight - container.clientHeight;\n\n // If we have explicit layout size, use it for clamping to avoid DOM sync issues\n if (getLayoutSizeAtZoom) {\n const size = getLayoutSizeAtZoom(currentZoom);\n if (size) {\n maxScrollLeft = Math.max(0, size.width * currentZoom - container.clientWidth);\n maxScrollTop = Math.max(0, size.height * currentZoom - container.clientHeight);\n }\n }\n\n // We can't clamp strictly during animation because the scrollWidth/Height might not have updated yet\n // if the spacer hasn't resized. But usually spacer resizes immediately on zoom update.\n // For safety, we just set it.\n container.scrollLeft = Math.min(Math.max(0, neededScrollLeft), maxScrollLeft);\n container.scrollTop = Math.min(Math.max(0, neededScrollTop), maxScrollTop);\n } else {\n // If no card position, just interpolate scroll position\n // Use captured initial scroll positions for linear interpolation\n const currentScrollLeft = initialScrollLeft + (targetScrollLeft - initialScrollLeft) * easedProgress;\n const currentScrollTop = initialScrollTop + (targetScrollTop - initialScrollTop) * easedProgress;\n\n container.scrollLeft = currentScrollLeft;\n container.scrollTop = currentScrollTop;\n }\n\n if (progress < 1) {\n rafId = requestAnimationFrame(animate);\n } else {\n container.style.scrollBehavior = '';\n container.scrollLeft = targetScrollLeft;\n container.scrollTop = targetScrollTop;\n onComplete?.();\n }\n };\n\n rafId = requestAnimationFrame(animate);\n\n return () => {\n if (rafId !== null) {\n cancelAnimationFrame(rafId);\n container.style.scrollBehavior = '';\n }\n };\n}\n\n/**\n * Calculate scroll position to center a card in viewport, accounting for detail panel\n */\nexport function calculateCenterScrollPosition(\n container: HTMLElement,\n cardPosition: { x: number; y: number; width: number; height: number },\n zoomLevel: number,\n detailPanelWidth: number = 0,\n totalHeight?: number\n): { scrollLeft: number; scrollTop: number } {\n const cardCenterX = cardPosition.x * zoomLevel + (cardPosition.width * zoomLevel) / 2;\n const cardCenterY = cardPosition.y * zoomLevel + (cardPosition.height * zoomLevel) / 2;\n\n const availableWidth = container.clientWidth - detailPanelWidth;\n const targetX = availableWidth / 2;\n const targetY = container.clientHeight / 2;\n\n const scrollLeft = Math.max(0, cardCenterX - targetX);\n let scrollTop = Math.max(0, cardCenterY - targetY);\n\n // If totalHeight is provided, clamp to valid scroll range\n if (totalHeight) {\n const contentHeight = totalHeight * zoomLevel;\n const viewportHeight = container.clientHeight;\n const maxScrollTop = Math.max(0, contentHeight - viewportHeight);\n\n scrollTop = Math.min(scrollTop, maxScrollTop);\n }\n\n return { scrollLeft, scrollTop };\n}\n\n/**\n * Smooth scroll to position\n */\nexport function smoothScrollTo(\n container: HTMLElement,\n scrollLeft: number,\n scrollTop: number,\n smooth: boolean = true\n): void {\n container.scrollTo({\n left: scrollLeft,\n top: scrollTop,\n behavior: smooth ? 'smooth' : 'auto',\n });\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"animations.js","sources":["../../../../PivotViewer/utils/animations.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\nexport type EasingFunction = (t: number) => number;\n\nexport const easeOutCubic: EasingFunction = (t: number) => 1 - Math.pow(1 - t, 3);\n\nexport interface ZoomAnimationParams {\n startZoom: number;\n targetZoom: number;\n duration?: number;\n easing?: EasingFunction;\n}\n\nexport interface ScrollAnimationParams {\n targetScrollLeft: number;\n targetScrollTop: number;\n}\n\nexport interface ZoomScrollAnimationParams extends ZoomAnimationParams, ScrollAnimationParams {\n container: HTMLElement;\n cardPosition?: { x: number; y: number; width: number; height: number } | null;\n targetCardPosition?: { x: number; y: number; width: number; height: number } | null;\n getCardPositionAtZoom?: (zoom: number) => { x: number; y: number; width: number; height: number } | null;\n getLayoutSizeAtZoom?: (zoom: number) => { width: number; height: number };\n spacer?: HTMLElement | null;\n onUpdate: (zoom: number) => void;\n onComplete?: () => void;\n startScrollLeft?: number; // Optional explicit start scroll\n startScrollTop?: number; // Optional explicit start scroll\n}\n\n/**\n * Animate zoom and scroll together, keeping a specific element centered\n */\nexport function animateZoomAndScroll({\n container,\n cardPosition,\n targetCardPosition,\n getCardPositionAtZoom,\n getLayoutSizeAtZoom,\n spacer,\n startZoom,\n targetZoom,\n targetScrollLeft,\n targetScrollTop,\n duration = 300,\n easing = easeOutCubic,\n onUpdate,\n onComplete,\n startScrollLeft,\n startScrollTop,\n}: ZoomScrollAnimationParams): () => void {\n container.style.scrollBehavior = 'auto';\n\n let startTime: number | null = null;\n let rafId: number | null = null;\n\n // Capture start scroll if not provided\n const initialScrollLeft = startScrollLeft ?? container.scrollLeft;\n const initialScrollTop = startScrollTop ?? container.scrollTop;\n\n // Calculate initial center point relative to viewport\n const startCardCenterX = cardPosition ? (cardPosition.x * startZoom + (cardPosition.width * startZoom) / 2) : 0;\n const startCardCenterY = cardPosition ? (cardPosition.y * startZoom + (cardPosition.height * startZoom) / 2) : 0;\n\n const startCardScreenX = startCardCenterX - initialScrollLeft;\n const startCardScreenY = startCardCenterY - initialScrollTop;\n\n const animate = (timestamp: number) => {\n if (startTime === null) {\n startTime = timestamp;\n }\n\n const elapsed = timestamp - startTime;\n const progress = Math.min(1, elapsed / duration);\n const easedProgress = easing(progress);\n\n const currentZoom = startZoom + (targetZoom - startZoom) * easedProgress;\n\n // Update spacer size synchronously if possible\n if (spacer && getLayoutSizeAtZoom) {\n const size = getLayoutSizeAtZoom(currentZoom);\n if (size) {\n spacer.style.width = `${size.width * currentZoom}px`;\n spacer.style.height = `${size.height * currentZoom}px`;\n }\n }\n\n onUpdate(currentZoom);\n\n if (cardPosition) {\n let currentCardX: number;\n let currentCardY: number;\n\n if (getCardPositionAtZoom) {\n const pos = getCardPositionAtZoom(currentZoom);\n if (pos) {\n currentCardX = pos.x;\n currentCardY = pos.y;\n } else {\n currentCardX = cardPosition.x;\n currentCardY = cardPosition.y;\n }\n } else {\n // Interpolate card position if target is provided (for layouts that change with zoom)\n currentCardX = targetCardPosition\n ? cardPosition.x + (targetCardPosition.x - cardPosition.x) * easedProgress\n : cardPosition.x;\n currentCardY = targetCardPosition\n ? cardPosition.y + (targetCardPosition.y - cardPosition.y) * easedProgress\n : cardPosition.y;\n }\n\n // Calculate where the card center is at current zoom\n const currentCardCenterX = currentCardX * currentZoom + (cardPosition.width * currentZoom) / 2;\n const currentCardCenterY = currentCardY * currentZoom + (cardPosition.height * currentZoom) / 2;\n\n // Calculate where the card center will be at the end of animation (relative to viewport)\n // Target scroll position is where we want to end up\n const endCardX = targetCardPosition ? targetCardPosition.x : cardPosition.x;\n const endCardY = targetCardPosition ? targetCardPosition.y : cardPosition.y;\n\n const endCardCenterX = endCardX * targetZoom + (cardPosition.width * targetZoom) / 2;\n const endCardCenterY = endCardY * targetZoom + (cardPosition.height * targetZoom) / 2;\n\n const endCardScreenX = endCardCenterX - targetScrollLeft;\n const endCardScreenY = endCardCenterY - targetScrollTop;\n\n // Interpolate the desired screen position\n const desiredScreenX = startCardScreenX + (endCardScreenX - startCardScreenX) * easedProgress;\n const desiredScreenY = startCardScreenY + (endCardScreenY - startCardScreenY) * easedProgress;\n\n // Calculate needed scroll position to put card at desired screen position\n const neededScrollLeft = currentCardCenterX - desiredScreenX;\n const neededScrollTop = currentCardCenterY - desiredScreenY;\n\n let maxScrollLeft = container.scrollWidth - container.clientWidth;\n let maxScrollTop = container.scrollHeight - container.clientHeight;\n\n // If we have explicit layout size, use it for clamping to avoid DOM sync issues\n if (getLayoutSizeAtZoom) {\n const size = getLayoutSizeAtZoom(currentZoom);\n if (size) {\n maxScrollLeft = Math.max(0, size.width * currentZoom - container.clientWidth);\n maxScrollTop = Math.max(0, size.height * currentZoom - container.clientHeight);\n }\n }\n\n // We can't clamp strictly during animation because the scrollWidth/Height might not have updated yet\n // if the spacer hasn't resized. But usually spacer resizes immediately on zoom update.\n // For safety, we just set it.\n container.scrollLeft = Math.min(Math.max(0, neededScrollLeft), maxScrollLeft);\n container.scrollTop = Math.min(Math.max(0, neededScrollTop), maxScrollTop);\n } else {\n // If no card position, just interpolate scroll position\n // Use captured initial scroll positions for linear interpolation\n const currentScrollLeft = initialScrollLeft + (targetScrollLeft - initialScrollLeft) * easedProgress;\n const currentScrollTop = initialScrollTop + (targetScrollTop - initialScrollTop) * easedProgress;\n\n container.scrollLeft = currentScrollLeft;\n container.scrollTop = currentScrollTop;\n }\n\n if (progress < 1) {\n rafId = requestAnimationFrame(animate);\n } else {\n container.style.scrollBehavior = '';\n container.scrollLeft = targetScrollLeft;\n container.scrollTop = targetScrollTop;\n onComplete?.();\n }\n };\n\n rafId = requestAnimationFrame(animate);\n\n return () => {\n if (rafId !== null) {\n cancelAnimationFrame(rafId);\n container.style.scrollBehavior = '';\n }\n };\n}\n\n/**\n * Calculate scroll position to center a card in viewport, accounting for detail panel\n */\nexport function calculateCenterScrollPosition(\n container: HTMLElement,\n cardPosition: { x: number; y: number; width: number; height: number },\n zoomLevel: number,\n detailPanelWidth: number = 0,\n totalHeight?: number\n): { scrollLeft: number; scrollTop: number } {\n const cardCenterX = cardPosition.x * zoomLevel + (cardPosition.width * zoomLevel) / 2;\n const cardCenterY = cardPosition.y * zoomLevel + (cardPosition.height * zoomLevel) / 2;\n const cardBottomY = (cardPosition.y + cardPosition.height) * zoomLevel;\n\n const availableWidth = container.clientWidth - detailPanelWidth;\n const targetX = availableWidth / 2;\n \n // For vertical centering, prefer putting the card center at viewport center.\n // But ensure the card bottom stays visible (at least 20% from viewport bottom).\n const viewportHeight = container.clientHeight;\n const targetYCenter = viewportHeight / 2;\n const targetYForBottom = viewportHeight * 0.7; // Card center should be at most 70% down\n\n const scrollLeft = Math.max(0, cardCenterX - targetX);\n \n // Calculate scroll needed to center the card\n const scrollTopForCenter = cardCenterY - targetYCenter;\n \n // Calculate scroll that ensures card bottom is visible with some margin\n const scrollTopForBottomVisible = cardBottomY - viewportHeight + 50; // 50px margin from bottom\n \n // If centering would push the card bottom out of view, use the bottom-visible scroll\n let scrollTop = scrollTopForCenter;\n if (scrollTopForCenter > scrollTopForBottomVisible + (cardPosition.height * zoomLevel / 2)) {\n // Card is near bottom of content, adjust to keep it visible\n scrollTop = Math.max(0, cardCenterY - targetYForBottom);\n }\n \n scrollTop = Math.max(0, scrollTop);\n\n // If totalHeight is provided, clamp to valid scroll range\n if (totalHeight) {\n const contentHeight = totalHeight * zoomLevel;\n const maxScrollTop = Math.max(0, contentHeight - viewportHeight);\n scrollTop = Math.min(scrollTop, maxScrollTop);\n }\n\n return { scrollLeft, scrollTop };\n}\n\n/**\n * Smooth scroll to position\n */\nexport function smoothScrollTo(\n container: HTMLElement,\n scrollLeft: number,\n scrollTop: number,\n smooth: boolean = true\n): void {\n container.scrollTo({\n left: scrollLeft,\n top: scrollTop,\n behavior: smooth ? 'smooth' : 'auto',\n });\n}\n"],"names":[],"mappings":"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"}
@@ -13,7 +13,6 @@ function getCardPositionAtZoom(zoom, itemId, grouping, viewMode, containerWidth,
13
13
  cardWidth: constants.BASE_CARD_WIDTH,
14
14
  cardHeight: constants.BASE_CARD_HEIGHT,
15
15
  cardsPerColumn: constants.CARDS_PER_COLUMN,
16
- groupSpacing: constants.GROUP_SPACING,
17
16
  containerWidth: scaledContainerWidth,
18
17
  containerHeight: scaledContainerHeight,
19
18
  });
@@ -35,7 +34,6 @@ function getLayoutSizeAtZoom(zoom, grouping, viewMode, containerWidth, container
35
34
  cardWidth: constants.BASE_CARD_WIDTH,
36
35
  cardHeight: constants.BASE_CARD_HEIGHT,
37
36
  cardsPerColumn: constants.CARDS_PER_COLUMN,
38
- groupSpacing: constants.GROUP_SPACING,
39
37
  containerWidth,
40
38
  containerHeight,
41
39
  });
@@ -48,7 +46,6 @@ function getLayoutSizeAtZoom(zoom, grouping, viewMode, containerWidth, container
48
46
  cardWidth: constants.BASE_CARD_WIDTH,
49
47
  cardHeight: constants.BASE_CARD_HEIGHT,
50
48
  cardsPerColumn: constants.CARDS_PER_COLUMN,
51
- groupSpacing: constants.GROUP_SPACING,
52
49
  containerWidth: scaledContainerWidth,
53
50
  containerHeight: scaledContainerHeight,
54
51
  });
@@ -1 +1 @@
1
- {"version":3,"file":"cardPosition.js","sources":["../../../../PivotViewer/utils/cardPosition.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 type { GroupingResult } from '../engine/types';\nimport { computeLayout } from '../engine/layout';\nimport {\n BASE_CARD_WIDTH,\n BASE_CARD_HEIGHT,\n CARDS_PER_COLUMN,\n GROUP_SPACING,\n} from '../constants';\nimport type { ViewMode } from '../components/Toolbar';\nimport type { LayoutResult } from '../engine/types';\nimport type { CardPosition } from './idResolution';\n\nexport type Layout = LayoutResult;\n\n/**\n * Computes the card position at a specific zoom level\n */\nexport function getCardPositionAtZoom(\n zoom: number,\n itemId: string | number,\n grouping: GroupingResult,\n viewMode: ViewMode,\n containerWidth: number,\n containerHeight: number,\n): CardPosition | null {\n const scaledContainerWidth = containerWidth / zoom;\n const scaledContainerHeight = viewMode === 'collection'\n ? containerHeight / zoom\n : containerHeight;\n\n const layout = computeLayout(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n grouping as unknown as any, {\n viewMode,\n cardWidth: BASE_CARD_WIDTH,\n cardHeight: BASE_CARD_HEIGHT,\n cardsPerColumn: CARDS_PER_COLUMN,\n groupSpacing: GROUP_SPACING,\n containerWidth: scaledContainerWidth,\n containerHeight: scaledContainerHeight,\n });\n\n const position = layout.positions.get(itemId);\n if (!position) {\n return null;\n }\n\n return {\n x: position.x,\n y: position.y,\n width: BASE_CARD_WIDTH,\n height: BASE_CARD_HEIGHT,\n };\n}\n\n/**\n * Computes layout size at a specific zoom level\n */\nexport function getLayoutSizeAtZoom(\n zoom: number,\n grouping: GroupingResult,\n viewMode: ViewMode,\n containerWidth: number,\n containerHeight: number,\n): { width: number; height: number } {\n if (viewMode === 'collection') {\n const layout = computeLayout(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n grouping as unknown as any, {\n viewMode,\n cardWidth: BASE_CARD_WIDTH,\n cardHeight: BASE_CARD_HEIGHT,\n cardsPerColumn: CARDS_PER_COLUMN,\n groupSpacing: GROUP_SPACING,\n containerWidth,\n containerHeight,\n });\n return { width: layout.totalWidth, height: layout.totalHeight };\n }\n\n const scaledContainerWidth = containerWidth / zoom;\n const scaledContainerHeight = containerHeight;\n\n const layout = computeLayout(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n grouping as unknown as any, {\n viewMode,\n cardWidth: BASE_CARD_WIDTH,\n cardHeight: BASE_CARD_HEIGHT,\n cardsPerColumn: CARDS_PER_COLUMN,\n groupSpacing: GROUP_SPACING,\n containerWidth: scaledContainerWidth,\n containerHeight: scaledContainerHeight,\n });\n\n return { width: layout.totalWidth, height: layout.totalHeight };\n}\n\n/**\n * Creates callback functions for card position calculations at various zoom levels\n * Used for smooth animations during zoom and pan operations\n */\nexport function createCardPositionCallbacks(\n itemId: string | number,\n grouping: GroupingResult,\n viewMode: ViewMode,\n containerWidth: number,\n containerHeight: number,\n) {\n return {\n getCardPositionAtZoom: (zoom: number) =>\n getCardPositionAtZoom(zoom, itemId, grouping, viewMode, containerWidth, containerHeight),\n \n getLayoutSizeAtZoom: (zoom: number) =>\n getLayoutSizeAtZoom(zoom, grouping, viewMode, containerWidth, containerHeight),\n };\n}\n"],"names":["layout","computeLayout","BASE_CARD_WIDTH","BASE_CARD_HEIGHT","CARDS_PER_COLUMN","GROUP_SPACING"],"mappings":";;;;;AAoBM,SAAU,qBAAqB,CACjC,IAAY,EACZ,MAAuB,EACvB,QAAwB,EACxB,QAAkB,EAClB,cAAsB,EACtB,eAAuB,EAAA;AAEvB,IAAA,MAAM,oBAAoB,GAAG,cAAc,GAAG,IAAI;AAClD,IAAA,MAAM,qBAAqB,GAAG,QAAQ,KAAK;UACrC,eAAe,GAAG;UAClB,eAAe;AAErB,IAAA,MAAMA,QAAM,GAAGC,oBAAa,CAExB,QAA0B,EAAE;QAC5B,QAAQ;AACR,QAAA,SAAS,EAAEC,yBAAe;AAC1B,QAAA,UAAU,EAAEC,0BAAgB;AAC5B,QAAA,cAAc,EAAEC,0BAAgB;AAChC,QAAA,YAAY,EAAEC,uBAAa;AAC3B,QAAA,cAAc,EAAE,oBAAoB;AACpC,QAAA,eAAe,EAAE,qBAAqB;AACzC,KAAA,CAAC;IAEF,MAAM,QAAQ,GAAGL,QAAM,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC;IAC7C,IAAI,CAAC,QAAQ,EAAE;AACX,QAAA,OAAO,IAAI;IACf;IAEA,OAAO;QACH,CAAC,EAAE,QAAQ,CAAC,CAAC;QACb,CAAC,EAAE,QAAQ,CAAC,CAAC;AACb,QAAA,KAAK,EAAEE,yBAAe;AACtB,QAAA,MAAM,EAAEC,0BAAgB;KAC3B;AACL;AAKM,SAAU,mBAAmB,CAC/B,IAAY,EACZ,QAAwB,EACxB,QAAkB,EAClB,cAAsB,EACtB,eAAuB,EAAA;AAEvB,IAAA,IAAI,QAAQ,KAAK,YAAY,EAAE;AAC3B,QAAA,MAAMH,QAAM,GAAGC,oBAAa,CAExB,QAA0B,EAAE;YAC5B,QAAQ;AACR,YAAA,SAAS,EAAEC,yBAAe;AAC1B,YAAA,UAAU,EAAEC,0BAAgB;AAC5B,YAAA,cAAc,EAAEC,0BAAgB;AAChC,YAAA,YAAY,EAAEC,uBAAa;YAC3B,cAAc;YACd,eAAe;AAClB,SAAA,CAAC;AACF,QAAA,OAAO,EAAE,KAAK,EAAEL,QAAM,CAAC,UAAU,EAAE,MAAM,EAAEA,QAAM,CAAC,WAAW,EAAE;IACnE;AAEA,IAAA,MAAM,oBAAoB,GAAG,cAAc,GAAG,IAAI;IAClD,MAAM,qBAAqB,GAAG,eAAe;AAE7C,IAAA,MAAMA,QAAM,GAAGC,oBAAa,CAExB,QAA0B,EAAE;QAC5B,QAAQ;AACR,QAAA,SAAS,EAAEC,yBAAe;AAC1B,QAAA,UAAU,EAAEC,0BAAgB;AAC5B,QAAA,cAAc,EAAEC,0BAAgB;AAChC,QAAA,YAAY,EAAEC,uBAAa;AAC3B,QAAA,cAAc,EAAE,oBAAoB;AACpC,QAAA,eAAe,EAAE,qBAAqB;AACzC,KAAA,CAAC;AAEF,IAAA,OAAO,EAAE,KAAK,EAAEL,QAAM,CAAC,UAAU,EAAE,MAAM,EAAEA,QAAM,CAAC,WAAW,EAAE;AACnE;AAMM,SAAU,2BAA2B,CACvC,MAAuB,EACvB,QAAwB,EACxB,QAAkB,EAClB,cAAsB,EACtB,eAAuB,EAAA;IAEvB,OAAO;AACH,QAAA,qBAAqB,EAAE,CAAC,IAAY,KAChC,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,cAAc,EAAE,eAAe,CAAC;AAE5F,QAAA,mBAAmB,EAAE,CAAC,IAAY,KAC9B,mBAAmB,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,cAAc,EAAE,eAAe,CAAC;KACrF;AACL;;;;;;"}
1
+ {"version":3,"file":"cardPosition.js","sources":["../../../../PivotViewer/utils/cardPosition.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 type { GroupingResult } from '../engine/types';\nimport { computeLayout } from '../engine/layout';\nimport {\n BASE_CARD_WIDTH,\n BASE_CARD_HEIGHT,\n CARDS_PER_COLUMN,\n GROUP_SPACING,\n} from '../constants';\nimport type { ViewMode } from '../components/Toolbar';\nimport type { LayoutResult } from '../engine/types';\nimport type { CardPosition } from './idResolution';\n\nexport type Layout = LayoutResult;\n\n/**\n * Computes the card position at a specific zoom level\n */\nexport function getCardPositionAtZoom(\n zoom: number,\n itemId: string | number,\n grouping: GroupingResult,\n viewMode: ViewMode,\n containerWidth: number,\n containerHeight: number,\n): CardPosition | null {\n const scaledContainerWidth = containerWidth / zoom;\n const scaledContainerHeight = viewMode === 'collection'\n ? containerHeight / zoom\n : containerHeight;\n\n const layout = computeLayout(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n grouping as unknown as any, {\n viewMode,\n cardWidth: BASE_CARD_WIDTH,\n cardHeight: BASE_CARD_HEIGHT,\n cardsPerColumn: CARDS_PER_COLUMN,\n groupSpacing: GROUP_SPACING,\n containerWidth: scaledContainerWidth,\n containerHeight: scaledContainerHeight,\n });\n\n const position = layout.positions.get(itemId);\n if (!position) {\n return null;\n }\n\n return {\n x: position.x,\n y: position.y,\n width: BASE_CARD_WIDTH,\n height: BASE_CARD_HEIGHT,\n };\n}\n\n/**\n * Computes layout size at a specific zoom level\n */\nexport function getLayoutSizeAtZoom(\n zoom: number,\n grouping: GroupingResult,\n viewMode: ViewMode,\n containerWidth: number,\n containerHeight: number,\n): { width: number; height: number } {\n if (viewMode === 'collection') {\n const layout = computeLayout(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n grouping as unknown as any, {\n viewMode,\n cardWidth: BASE_CARD_WIDTH,\n cardHeight: BASE_CARD_HEIGHT,\n cardsPerColumn: CARDS_PER_COLUMN,\n groupSpacing: GROUP_SPACING,\n containerWidth,\n containerHeight,\n });\n return { width: layout.totalWidth, height: layout.totalHeight };\n }\n\n const scaledContainerWidth = containerWidth / zoom;\n const scaledContainerHeight = containerHeight;\n\n const layout = computeLayout(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n grouping as unknown as any, {\n viewMode,\n cardWidth: BASE_CARD_WIDTH,\n cardHeight: BASE_CARD_HEIGHT,\n cardsPerColumn: CARDS_PER_COLUMN,\n groupSpacing: GROUP_SPACING,\n containerWidth: scaledContainerWidth,\n containerHeight: scaledContainerHeight,\n });\n\n return { width: layout.totalWidth, height: layout.totalHeight };\n}\n\n/**\n * Creates callback functions for card position calculations at various zoom levels\n * Used for smooth animations during zoom and pan operations\n */\nexport function createCardPositionCallbacks(\n itemId: string | number,\n grouping: GroupingResult,\n viewMode: ViewMode,\n containerWidth: number,\n containerHeight: number,\n) {\n return {\n getCardPositionAtZoom: (zoom: number) =>\n getCardPositionAtZoom(zoom, itemId, grouping, viewMode, containerWidth, containerHeight),\n \n getLayoutSizeAtZoom: (zoom: number) =>\n getLayoutSizeAtZoom(zoom, grouping, viewMode, containerWidth, containerHeight),\n };\n}\n"],"names":["layout","computeLayout","BASE_CARD_WIDTH","BASE_CARD_HEIGHT","CARDS_PER_COLUMN"],"mappings":";;;;;AAoBM,SAAU,qBAAqB,CACjC,IAAY,EACZ,MAAuB,EACvB,QAAwB,EACxB,QAAkB,EAClB,cAAsB,EACtB,eAAuB,EAAA;AAEvB,IAAA,MAAM,oBAAoB,GAAG,cAAc,GAAG,IAAI;AAClD,IAAA,MAAM,qBAAqB,GAAG,QAAQ,KAAK;UACrC,eAAe,GAAG;UAClB,eAAe;AAErB,IAAA,MAAMA,QAAM,GAAGC,oBAAa,CAExB,QAA0B,EAAE;QAC5B,QAAQ;AACR,QAAA,SAAS,EAAEC,yBAAe;AAC1B,QAAA,UAAU,EAAEC,0BAAgB;AAC5B,QAAA,cAAc,EAAEC,0BAAgB;AAChC,QACA,cAAc,EAAE,oBAAoB;AACpC,QAAA,eAAe,EAAE,qBAAqB;AACzC,KAAA,CAAC;IAEF,MAAM,QAAQ,GAAGJ,QAAM,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC;IAC7C,IAAI,CAAC,QAAQ,EAAE;AACX,QAAA,OAAO,IAAI;IACf;IAEA,OAAO;QACH,CAAC,EAAE,QAAQ,CAAC,CAAC;QACb,CAAC,EAAE,QAAQ,CAAC,CAAC;AACb,QAAA,KAAK,EAAEE,yBAAe;AACtB,QAAA,MAAM,EAAEC,0BAAgB;KAC3B;AACL;AAKM,SAAU,mBAAmB,CAC/B,IAAY,EACZ,QAAwB,EACxB,QAAkB,EAClB,cAAsB,EACtB,eAAuB,EAAA;AAEvB,IAAA,IAAI,QAAQ,KAAK,YAAY,EAAE;AAC3B,QAAA,MAAMH,QAAM,GAAGC,oBAAa,CAExB,QAA0B,EAAE;YAC5B,QAAQ;AACR,YAAA,SAAS,EAAEC,yBAAe;AAC1B,YAAA,UAAU,EAAEC,0BAAgB;AAC5B,YAAA,cAAc,EAAEC,0BAAgB;AAChC,YACA,cAAc;YACd,eAAe;AAClB,SAAA,CAAC;AACF,QAAA,OAAO,EAAE,KAAK,EAAEJ,QAAM,CAAC,UAAU,EAAE,MAAM,EAAEA,QAAM,CAAC,WAAW,EAAE;IACnE;AAEA,IAAA,MAAM,oBAAoB,GAAG,cAAc,GAAG,IAAI;IAClD,MAAM,qBAAqB,GAAG,eAAe;AAE7C,IAAA,MAAMA,QAAM,GAAGC,oBAAa,CAExB,QAA0B,EAAE;QAC5B,QAAQ;AACR,QAAA,SAAS,EAAEC,yBAAe;AAC1B,QAAA,UAAU,EAAEC,0BAAgB;AAC5B,QAAA,cAAc,EAAEC,0BAAgB;AAChC,QACA,cAAc,EAAE,oBAAoB;AACpC,QAAA,eAAe,EAAE,qBAAqB;AACzC,KAAA,CAAC;AAEF,IAAA,OAAO,EAAE,KAAK,EAAEJ,QAAM,CAAC,UAAU,EAAE,MAAM,EAAEA,QAAM,CAAC,WAAW,EAAE;AACnE;AAMM,SAAU,2BAA2B,CACvC,MAAuB,EACvB,QAAwB,EACxB,QAAkB,EAClB,cAAsB,EACtB,eAAuB,EAAA;IAEvB,OAAO;AACH,QAAA,qBAAqB,EAAE,CAAC,IAAY,KAChC,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,cAAc,EAAE,eAAe,CAAC;AAE5F,QAAA,mBAAmB,EAAE,CAAC,IAAY,KAC9B,mBAAmB,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,cAAc,EAAE,eAAe,CAAC;KACrF;AACL;;;;;;"}
@@ -1,10 +1,10 @@
1
1
  'use strict';
2
2
 
3
3
  const ZOOM_MAX = 3;
4
- const BASE_CARD_WIDTH = 180;
5
- const BASE_CARD_HEIGHT = 140;
4
+ const BASE_CARD_WIDTH = 200;
5
+ const BASE_CARD_HEIGHT = 176;
6
6
  const DETAIL_PANEL_WIDTH = 380;
7
- const ZOOM_MULTIPLIER = 1.15;
7
+ const ZOOM_MULTIPLIER = 1.2;
8
8
  const MIN_ZOOM_ON_SELECT = 1.2;
9
9
 
10
10
  exports.BASE_CARD_HEIGHT = BASE_CARD_HEIGHT;
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","sources":["../../../../PivotViewer/utils/constants.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\n// Zoom constants\nexport const ZOOM_MIN = 0.1;\nexport const ZOOM_MAX = 3;\nexport const ZOOM_STEP = 0.05;\n\n// Layout constants\nexport const GROUP_SPACING = 40;\nexport const CARD_GAP = 8;\nexport const CARDS_PER_COLUMN = 5;\nexport const BASE_CARD_WIDTH = 180;\nexport const BASE_CARD_HEIGHT = 140;\nexport const DETAIL_PANEL_WIDTH = 380;\n\n// Animation constants\nexport const ZOOM_ANIMATION_DURATION = 300;\nexport const ZOOM_MULTIPLIER = 1.15;\nexport const MIN_ZOOM_ON_SELECT = 1.2;\n"],"names":[],"mappings":";;AAKO,MAAM,QAAQ,GAAG;AAOjB,MAAM,eAAe,GAAG;AACxB,MAAM,gBAAgB,GAAG;AACzB,MAAM,kBAAkB,GAAG;AAI3B,MAAM,eAAe,GAAG;AACxB,MAAM,kBAAkB,GAAG;;;;;;;;;"}
1
+ {"version":3,"file":"constants.js","sources":["../../../../PivotViewer/utils/constants.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\n// Zoom constants\nexport const ZOOM_MIN = 0.1;\nexport const ZOOM_MAX = 3;\nexport const ZOOM_STEP = 0.05;\n\n// Layout constants - matched to main constants\nexport const GROUP_SPACING = 20;\nexport const CARD_GAP = 10;\nexport const CARDS_PER_COLUMN = 5;\nexport const BASE_CARD_WIDTH = 200;\nexport const BASE_CARD_HEIGHT = 176;\nexport const DETAIL_PANEL_WIDTH = 380;\n\n// Animation constants\nexport const ZOOM_ANIMATION_DURATION = 300;\nexport const ZOOM_MULTIPLIER = 1.2;\nexport const MIN_ZOOM_ON_SELECT = 1.2;\n"],"names":[],"mappings":";;AAKO,MAAM,QAAQ,GAAG;AAOjB,MAAM,eAAe,GAAG;AACxB,MAAM,gBAAgB,GAAG;AACzB,MAAM,kBAAkB,GAAG;AAI3B,MAAM,eAAe,GAAG;AACxB,MAAM,kBAAkB,GAAG;;;;;;;;;"}
@@ -3,8 +3,15 @@
3
3
  var animations = require('./animations.js');
4
4
  var constants = require('./constants.js');
5
5
 
6
+ function isSameItemId(id1, id2) {
7
+ if (id1 === null || id2 === null)
8
+ return false;
9
+ if (id1 === id2)
10
+ return true;
11
+ return String(id1) === String(id2);
12
+ }
6
13
  function handleCardSelection({ itemId, selectedItemId, container, cardPosition, targetCardPosition, getCardPositionAtZoom, getLayoutSizeAtZoom, spacer, viewMode, zoomLevel, totalHeight, preSelectionState, startScrollPosition, setZoomLevel, setIsZooming, setSelectedItem, setPreSelectionState, item, }) {
7
- if (selectedItemId === itemId) {
14
+ if (isSameItemId(selectedItemId, itemId)) {
8
15
  deselectCard({
9
16
  container,
10
17
  cardPosition,
@@ -1 +1 @@
1
- {"version":3,"file":"selection.js","sources":["../../../../PivotViewer/utils/selection.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 { animateZoomAndScroll, calculateCenterScrollPosition, smoothScrollTo } from './animations';\nimport { ZOOM_MAX, MIN_ZOOM_ON_SELECT, ZOOM_MULTIPLIER, DETAIL_PANEL_WIDTH } from './constants';\nimport type { ViewMode } from '../components/Toolbar';\n\nexport interface SelectionState {\n zoom: number;\n scrollLeft: number;\n scrollTop: number;\n}\n\nexport interface CardClickHandlerParams<TItem> {\n item: TItem;\n itemId: string | number;\n selectedItemId: string | number | null;\n container: HTMLElement;\n cardPosition: { x: number; y: number; width: number; height: number } | null;\n targetCardPosition?: { x: number; y: number; width: number; height: number } | null;\n getCardPositionAtZoom?: (zoom: number) => { x: number; y: number; width: number; height: number } | null;\n getLayoutSizeAtZoom?: (zoom: number) => { width: number; height: number };\n spacer?: HTMLElement | null;\n viewMode: ViewMode;\n zoomLevel: number;\n totalHeight?: number;\n preSelectionState: SelectionState | null;\n startScrollPosition?: { x: number; y: number };\n setZoomLevel: (zoom: number) => void;\n setIsZooming: (isZooming: boolean) => void;\n setSelectedItem: (item: TItem | null) => void;\n setPreSelectionState: (state: SelectionState | null) => void;\n}\n\n/**\n * Handle card click for selection with zoom and scroll animation\n */\nexport function handleCardSelection<TItem>({\n itemId,\n selectedItemId,\n container,\n cardPosition,\n targetCardPosition,\n getCardPositionAtZoom,\n getLayoutSizeAtZoom,\n spacer,\n viewMode,\n zoomLevel,\n totalHeight,\n preSelectionState,\n startScrollPosition,\n setZoomLevel,\n setIsZooming,\n setSelectedItem,\n setPreSelectionState,\n item,\n}: CardClickHandlerParams<TItem>): void {\n // Clicking the same card - deselect\n if (selectedItemId === itemId) {\n deselectCard({\n container,\n cardPosition,\n targetCardPosition,\n getCardPositionAtZoom,\n getLayoutSizeAtZoom,\n spacer,\n viewMode,\n zoomLevel,\n preSelectionState,\n setZoomLevel,\n setIsZooming,\n setSelectedItem,\n setPreSelectionState,\n });\n return;\n }\n\n // First selection - save state and zoom in\n const isFirstSelection = selectedItemId === null;\n\n if (isFirstSelection) {\n setPreSelectionState({\n zoom: zoomLevel,\n scrollLeft: container.scrollLeft,\n scrollTop: container.scrollTop,\n });\n }\n\n setSelectedItem(item);\n\n if (isFirstSelection) {\n if (viewMode === 'collection') {\n // Collection mode: just smooth scroll to center, no zoom\n if (cardPosition) {\n // In collection mode, we don't have a detail panel width offset because the panel is an overlay or separate\n // But if we want to center it, we should consider if the detail panel pushes content\n // For now, assume 0 offset as per original code\n const { scrollLeft, scrollTop } = calculateCenterScrollPosition(container, cardPosition, zoomLevel, 0, totalHeight);\n smoothScrollTo(container, scrollLeft, scrollTop, true);\n }\n } else {\n // Grouped mode: animate zoom and scroll\n zoomAndCenterCard({\n container,\n cardPosition,\n targetCardPosition,\n getCardPositionAtZoom,\n getLayoutSizeAtZoom,\n spacer,\n itemId,\n zoomLevel,\n totalHeight,\n startScrollPosition,\n setZoomLevel,\n setIsZooming,\n });\n }\n } else {\n // Subsequent selections: just center the new card\n if (cardPosition) {\n // In collection mode, we don't zoom, so we just center.\n // In grouped mode, we might be zoomed in, so we center with offset.\n const detailWidth = viewMode === 'collection' ? 0 : DETAIL_PANEL_WIDTH;\n const { scrollLeft, scrollTop } = calculateCenterScrollPosition(container, cardPosition, zoomLevel, detailWidth, totalHeight);\n smoothScrollTo(container, scrollLeft, scrollTop, true);\n }\n }\n}\n\ninterface DeselectParams {\n container: HTMLElement;\n cardPosition: { x: number; y: number; width: number; height: number } | null;\n targetCardPosition?: { x: number; y: number; width: number; height: number } | null;\n getCardPositionAtZoom?: (zoom: number) => { x: number; y: number; width: number; height: number } | null;\n getLayoutSizeAtZoom?: (zoom: number) => { width: number; height: number };\n spacer?: HTMLElement | null;\n viewMode: ViewMode;\n zoomLevel: number;\n preSelectionState: SelectionState | null;\n setZoomLevel: (zoom: number) => void;\n setIsZooming: (isZooming: boolean) => void;\n setSelectedItem: (item: null) => void;\n setPreSelectionState: (state: SelectionState | null) => void;\n}\n\n/**\n * Deselect card with zoom-out animation if needed\n */\nfunction deselectCard({\n container,\n cardPosition,\n targetCardPosition,\n getCardPositionAtZoom,\n getLayoutSizeAtZoom,\n spacer,\n viewMode,\n zoomLevel,\n preSelectionState,\n setZoomLevel,\n setIsZooming,\n setSelectedItem,\n setPreSelectionState,\n}: DeselectParams): void {\n if (!preSelectionState) {\n setSelectedItem(null);\n return;\n }\n\n // Collection mode: just scroll back\n if (viewMode === 'collection') {\n setSelectedItem(null);\n smoothScrollTo(container, preSelectionState.scrollLeft, preSelectionState.scrollTop, true);\n setPreSelectionState(null);\n return;\n }\n\n // Grouped mode: check if zoom changed\n const zoomChanged = Math.abs(preSelectionState.zoom - zoomLevel) > 0.001;\n\n if (!zoomChanged || !cardPosition) {\n setSelectedItem(null);\n smoothScrollTo(container, preSelectionState.scrollLeft, preSelectionState.scrollTop, true);\n setPreSelectionState(null);\n return;\n }\n\n // Animate zoom out\n setIsZooming(true);\n\n animateZoomAndScroll({\n container,\n cardPosition,\n targetCardPosition,\n getCardPositionAtZoom,\n getLayoutSizeAtZoom,\n spacer,\n startZoom: zoomLevel,\n targetZoom: preSelectionState.zoom,\n targetScrollLeft: preSelectionState.scrollLeft,\n targetScrollTop: preSelectionState.scrollTop,\n onUpdate: setZoomLevel,\n onComplete: () => {\n setIsZooming(false);\n setSelectedItem(null);\n setPreSelectionState(null);\n },\n });\n}\n\ninterface ZoomAndCenterParams {\n container: HTMLElement;\n cardPosition: { x: number; y: number; width: number; height: number } | null;\n targetCardPosition?: { x: number; y: number; width: number; height: number } | null;\n getCardPositionAtZoom?: (zoom: number) => { x: number; y: number; width: number; height: number } | null;\n getLayoutSizeAtZoom?: (zoom: number) => { width: number; height: number };\n spacer?: HTMLElement | null;\n itemId: string | number;\n zoomLevel: number;\n totalHeight?: number;\n startScrollPosition?: { x: number; y: number };\n setZoomLevel: (zoom: number) => void;\n setIsZooming: (isZooming: boolean) => void;\n}\n\n/**\n * Zoom in and center a card\n */\nfunction zoomAndCenterCard({\n container,\n cardPosition,\n targetCardPosition,\n getCardPositionAtZoom,\n getLayoutSizeAtZoom,\n spacer,\n zoomLevel,\n totalHeight,\n startScrollPosition,\n setZoomLevel,\n setIsZooming,\n}: ZoomAndCenterParams): void {\n const targetZoom = Math.min(ZOOM_MAX, Math.max(MIN_ZOOM_ON_SELECT, zoomLevel * ZOOM_MULTIPLIER));\n const shouldZoom = Math.abs(targetZoom - zoomLevel) > 0.001;\n\n if (!shouldZoom || !cardPosition) {\n if (cardPosition) {\n const { scrollLeft, scrollTop } = calculateCenterScrollPosition(container, cardPosition, zoomLevel, DETAIL_PANEL_WIDTH, totalHeight);\n smoothScrollTo(container, scrollLeft, scrollTop, true);\n }\n return;\n }\n\n setIsZooming(true);\n\n // Use targetCardPosition if available, otherwise fallback to cardPosition\n const { scrollLeft: targetScrollLeft, scrollTop: targetScrollTop } = calculateCenterScrollPosition(\n container,\n targetCardPosition || cardPosition,\n targetZoom,\n DETAIL_PANEL_WIDTH,\n totalHeight\n );\n\n animateZoomAndScroll({\n container,\n cardPosition,\n targetCardPosition,\n getCardPositionAtZoom,\n getLayoutSizeAtZoom,\n spacer,\n startZoom: zoomLevel,\n targetZoom,\n targetScrollLeft,\n targetScrollTop,\n startScrollLeft: startScrollPosition?.x,\n startScrollTop: startScrollPosition?.y,\n onUpdate: setZoomLevel,\n onComplete: () => {\n setIsZooming(false);\n },\n });\n}\n\n/**\n * Get card element by ID from container\n */\nexport function getCardElementById(_container: HTMLElement, _itemId: string | number): HTMLElement | null {\n // Deprecated: Pixi renderer doesn't use DOM elements for cards\n // Keep parameter names prefixed to indicate intentional non-use.\n void _container;\n void _itemId;\n return null;\n}\n"],"names":["calculateCenterScrollPosition","smoothScrollTo","DETAIL_PANEL_WIDTH","animateZoomAndScroll","ZOOM_MAX","MIN_ZOOM_ON_SELECT","ZOOM_MULTIPLIER"],"mappings":"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"}
1
+ {"version":3,"file":"selection.js","sources":["../../../../PivotViewer/utils/selection.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 { animateZoomAndScroll, calculateCenterScrollPosition, smoothScrollTo } from './animations';\nimport { ZOOM_MAX, MIN_ZOOM_ON_SELECT, ZOOM_MULTIPLIER, DETAIL_PANEL_WIDTH } from './constants';\nimport type { ViewMode } from '../components/Toolbar';\n\nexport interface SelectionState {\n zoom: number;\n scrollLeft: number;\n scrollTop: number;\n}\n\nexport interface CardClickHandlerParams<TItem> {\n item: TItem;\n itemId: string | number;\n selectedItemId: string | number | null;\n container: HTMLElement;\n cardPosition: { x: number; y: number; width: number; height: number } | null;\n targetCardPosition?: { x: number; y: number; width: number; height: number } | null;\n getCardPositionAtZoom?: (zoom: number) => { x: number; y: number; width: number; height: number } | null;\n getLayoutSizeAtZoom?: (zoom: number) => { width: number; height: number };\n spacer?: HTMLElement | null;\n viewMode: ViewMode;\n zoomLevel: number;\n totalHeight?: number;\n preSelectionState: SelectionState | null;\n startScrollPosition?: { x: number; y: number };\n setZoomLevel: (zoom: number) => void;\n setIsZooming: (isZooming: boolean) => void;\n setSelectedItem: (item: TItem | null) => void;\n setPreSelectionState: (state: SelectionState | null) => void;\n}\n\n/**\n * Compare two item IDs handling type coercion (string vs number)\n */\nfunction isSameItemId(id1: string | number | null, id2: string | number | null): boolean {\n if (id1 === null || id2 === null) return false;\n // Strict equality\n if (id1 === id2) return true;\n // Compare as strings to handle number/string mismatches\n return String(id1) === String(id2);\n}\n\n/**\n * Handle card click for selection with zoom and scroll animation\n */\nexport function handleCardSelection<TItem>({\n itemId,\n selectedItemId,\n container,\n cardPosition,\n targetCardPosition,\n getCardPositionAtZoom,\n getLayoutSizeAtZoom,\n spacer,\n viewMode,\n zoomLevel,\n totalHeight,\n preSelectionState,\n startScrollPosition,\n setZoomLevel,\n setIsZooming,\n setSelectedItem,\n setPreSelectionState,\n item,\n}: CardClickHandlerParams<TItem>): void {\n // Clicking the same card - deselect (using type-coerced comparison)\n if (isSameItemId(selectedItemId, itemId)) {\n deselectCard({\n container,\n cardPosition,\n targetCardPosition,\n getCardPositionAtZoom,\n getLayoutSizeAtZoom,\n spacer,\n viewMode,\n zoomLevel,\n preSelectionState,\n setZoomLevel,\n setIsZooming,\n setSelectedItem,\n setPreSelectionState,\n });\n return;\n }\n\n // First selection - save state and zoom in\n const isFirstSelection = selectedItemId === null;\n\n if (isFirstSelection) {\n setPreSelectionState({\n zoom: zoomLevel,\n scrollLeft: container.scrollLeft,\n scrollTop: container.scrollTop,\n });\n }\n\n setSelectedItem(item);\n\n if (isFirstSelection) {\n if (viewMode === 'collection') {\n // Collection mode: just smooth scroll to center, no zoom\n if (cardPosition) {\n // In collection mode, we don't have a detail panel width offset because the panel is an overlay or separate\n // But if we want to center it, we should consider if the detail panel pushes content\n // For now, assume 0 offset as per original code\n const { scrollLeft, scrollTop } = calculateCenterScrollPosition(container, cardPosition, zoomLevel, 0, totalHeight);\n smoothScrollTo(container, scrollLeft, scrollTop, true);\n }\n } else {\n // Grouped mode: animate zoom and scroll\n zoomAndCenterCard({\n container,\n cardPosition,\n targetCardPosition,\n getCardPositionAtZoom,\n getLayoutSizeAtZoom,\n spacer,\n itemId,\n zoomLevel,\n totalHeight,\n startScrollPosition,\n setZoomLevel,\n setIsZooming,\n });\n }\n } else {\n // Subsequent selections: just center the new card\n if (cardPosition) {\n // In collection mode, we don't zoom, so we just center.\n // In grouped mode, we might be zoomed in, so we center with offset.\n const detailWidth = viewMode === 'collection' ? 0 : DETAIL_PANEL_WIDTH;\n const { scrollLeft, scrollTop } = calculateCenterScrollPosition(container, cardPosition, zoomLevel, detailWidth, totalHeight);\n smoothScrollTo(container, scrollLeft, scrollTop, true);\n }\n }\n}\n\ninterface DeselectParams {\n container: HTMLElement;\n cardPosition: { x: number; y: number; width: number; height: number } | null;\n targetCardPosition?: { x: number; y: number; width: number; height: number } | null;\n getCardPositionAtZoom?: (zoom: number) => { x: number; y: number; width: number; height: number } | null;\n getLayoutSizeAtZoom?: (zoom: number) => { width: number; height: number };\n spacer?: HTMLElement | null;\n viewMode: ViewMode;\n zoomLevel: number;\n preSelectionState: SelectionState | null;\n setZoomLevel: (zoom: number) => void;\n setIsZooming: (isZooming: boolean) => void;\n setSelectedItem: (item: null) => void;\n setPreSelectionState: (state: SelectionState | null) => void;\n}\n\n/**\n * Deselect card with zoom-out animation if needed\n */\nfunction deselectCard({\n container,\n cardPosition,\n targetCardPosition,\n getCardPositionAtZoom,\n getLayoutSizeAtZoom,\n spacer,\n viewMode,\n zoomLevel,\n preSelectionState,\n setZoomLevel,\n setIsZooming,\n setSelectedItem,\n setPreSelectionState,\n}: DeselectParams): void {\n if (!preSelectionState) {\n setSelectedItem(null);\n return;\n }\n\n // Collection mode: just scroll back\n if (viewMode === 'collection') {\n setSelectedItem(null);\n smoothScrollTo(container, preSelectionState.scrollLeft, preSelectionState.scrollTop, true);\n setPreSelectionState(null);\n return;\n }\n\n // Grouped mode: check if zoom changed\n const zoomChanged = Math.abs(preSelectionState.zoom - zoomLevel) > 0.001;\n\n if (!zoomChanged || !cardPosition) {\n setSelectedItem(null);\n smoothScrollTo(container, preSelectionState.scrollLeft, preSelectionState.scrollTop, true);\n setPreSelectionState(null);\n return;\n }\n\n // Animate zoom out\n setIsZooming(true);\n\n animateZoomAndScroll({\n container,\n cardPosition,\n targetCardPosition,\n getCardPositionAtZoom,\n getLayoutSizeAtZoom,\n spacer,\n startZoom: zoomLevel,\n targetZoom: preSelectionState.zoom,\n targetScrollLeft: preSelectionState.scrollLeft,\n targetScrollTop: preSelectionState.scrollTop,\n onUpdate: setZoomLevel,\n onComplete: () => {\n setIsZooming(false);\n setSelectedItem(null);\n setPreSelectionState(null);\n },\n });\n}\n\ninterface ZoomAndCenterParams {\n container: HTMLElement;\n cardPosition: { x: number; y: number; width: number; height: number } | null;\n targetCardPosition?: { x: number; y: number; width: number; height: number } | null;\n getCardPositionAtZoom?: (zoom: number) => { x: number; y: number; width: number; height: number } | null;\n getLayoutSizeAtZoom?: (zoom: number) => { width: number; height: number };\n spacer?: HTMLElement | null;\n itemId: string | number;\n zoomLevel: number;\n totalHeight?: number;\n startScrollPosition?: { x: number; y: number };\n setZoomLevel: (zoom: number) => void;\n setIsZooming: (isZooming: boolean) => void;\n}\n\n/**\n * Zoom in and center a card\n */\nfunction zoomAndCenterCard({\n container,\n cardPosition,\n targetCardPosition,\n getCardPositionAtZoom,\n getLayoutSizeAtZoom,\n spacer,\n zoomLevel,\n totalHeight,\n startScrollPosition,\n setZoomLevel,\n setIsZooming,\n}: ZoomAndCenterParams): void {\n const targetZoom = Math.min(ZOOM_MAX, Math.max(MIN_ZOOM_ON_SELECT, zoomLevel * ZOOM_MULTIPLIER));\n const shouldZoom = Math.abs(targetZoom - zoomLevel) > 0.001;\n\n if (!shouldZoom || !cardPosition) {\n if (cardPosition) {\n const { scrollLeft, scrollTop } = calculateCenterScrollPosition(container, cardPosition, zoomLevel, DETAIL_PANEL_WIDTH, totalHeight);\n smoothScrollTo(container, scrollLeft, scrollTop, true);\n }\n return;\n }\n\n setIsZooming(true);\n\n // Use targetCardPosition if available, otherwise fallback to cardPosition\n const { scrollLeft: targetScrollLeft, scrollTop: targetScrollTop } = calculateCenterScrollPosition(\n container,\n targetCardPosition || cardPosition,\n targetZoom,\n DETAIL_PANEL_WIDTH,\n totalHeight\n );\n\n animateZoomAndScroll({\n container,\n cardPosition,\n targetCardPosition,\n getCardPositionAtZoom,\n getLayoutSizeAtZoom,\n spacer,\n startZoom: zoomLevel,\n targetZoom,\n targetScrollLeft,\n targetScrollTop,\n startScrollLeft: startScrollPosition?.x,\n startScrollTop: startScrollPosition?.y,\n onUpdate: setZoomLevel,\n onComplete: () => {\n setIsZooming(false);\n },\n });\n}\n\n/**\n * Get card element by ID from container\n */\nexport function getCardElementById(_container: HTMLElement, _itemId: string | number): HTMLElement | null {\n // Deprecated: Pixi renderer doesn't use DOM elements for cards\n // Keep parameter names prefixed to indicate intentional non-use.\n void _container;\n void _itemId;\n return null;\n}\n"],"names":["calculateCenterScrollPosition","smoothScrollTo","DETAIL_PANEL_WIDTH","animateZoomAndScroll","ZOOM_MAX","MIN_ZOOM_ON_SELECT","ZOOM_MULTIPLIER"],"mappings":"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"}
@@ -530,6 +530,33 @@
530
530
  min-width: 3rem;
531
531
  text-align: center;
532
532
  font-variant-numeric: tabular-nums;
533
+ cursor: pointer;
534
+ padding: 0.25rem 0.5rem;
535
+ border-radius: 0.4rem;
536
+ transition: background 120ms ease;
537
+ }
538
+
539
+ .pv-zoom-controls span:hover {
540
+ background: var(--surface-hover);
541
+ }
542
+
543
+ .pv-zoom-level-input {
544
+ width: 3rem;
545
+ font-size: 0.75rem;
546
+ text-align: center;
547
+ background: var(--surface-ground);
548
+ border: 1px solid var(--primary-color);
549
+ border-radius: 0.4rem;
550
+ color: var(--text-color);
551
+ padding: 0.25rem 0.5rem;
552
+ font-variant-numeric: tabular-nums;
553
+ outline: none;
554
+ }
555
+
556
+ .pv-zoom-reset {
557
+ display: flex;
558
+ align-items: center;
559
+ justify-content: center;
533
560
  }
534
561
 
535
562
  .pv-dimension-select {
@@ -579,7 +606,7 @@
579
606
  cursor: grabbing;
580
607
  }
581
608
 
582
- /* Groups wrapper for bucket mode with axis labels */
609
+ /* Groups wrapper for grouped mode with axis labels */
583
610
  .pv-groups-wrapper {
584
611
  display: flex;
585
612
  flex-direction: column;
@@ -594,7 +621,7 @@
594
621
  background: radial-gradient(circle at 25% 20%, var(--primary-500), var(--surface-ground));
595
622
  }
596
623
 
597
- .pv-groups-buckets {
624
+ .pv-groups-grouped {
598
625
  flex: 1;
599
626
  padding-bottom: 0;
600
627
  }
@@ -612,21 +639,21 @@
612
639
  transition: background 180ms ease;
613
640
  }
614
641
 
615
- /* Bucket mode groups need to align cards to bottom and extend full height */
616
- .pv-group-bucket {
642
+ /* Grouped mode groups need to align cards to bottom and extend full height */
643
+ .pv-group-grouped {
617
644
  justify-content: flex-end;
618
645
  position: relative;
619
646
  min-height: 100%; /* Ensure full height for hover */
620
647
  width: var(--column-width); /* Fixed width even when empty */
621
648
  }
622
649
 
623
- /* Zebra striping for bucket mode - every other bucket gets a lighter background */
624
- .pv-group-bucket:nth-child(even)::before {
650
+ /* Zebra striping for grouped mode - every other group gets a lighter background */
651
+ .pv-group-grouped:nth-child(even)::before {
625
652
  background: rgba(255, 255, 255, 0.03);
626
653
  }
627
654
 
628
655
  /* Full-height hover highlight that extends to the top */
629
- .pv-group-bucket::before {
656
+ .pv-group-grouped::before {
630
657
  content: '';
631
658
  position: absolute;
632
659
  inset: 0;
@@ -637,14 +664,14 @@
637
664
  z-index: 0;
638
665
  }
639
666
 
640
- .pv-group-bucket:hover::before,
641
- .pv-group-bucket.highlighted::before {
667
+ .pv-group-grouped:hover::before,
668
+ .pv-group-grouped.highlighted::before {
642
669
  background: var(--surface-hover);
643
670
  }
644
671
 
645
672
  /* Zebra striping hover state - combine with stripe background */
646
- .pv-group-bucket:nth-child(even):hover::before,
647
- .pv-group-bucket:nth-child(even).highlighted::before {
673
+ .pv-group-grouped:nth-child(even):hover::before,
674
+ .pv-group-grouped:nth-child(even).highlighted::before {
648
675
  background: var(--surface-hover);
649
676
  }
650
677
 
@@ -680,33 +707,32 @@
680
707
  width: var(--column-width, auto);
681
708
  }
682
709
 
683
- .pv-cards-bucket {
710
+ .pv-cards-grouped {
684
711
  /* Grid fills left-to-right with fixed columns */
685
712
  grid-template-columns: repeat(var(--cards-per-row, 5), 1fr);
686
713
  grid-auto-flow: row;
687
714
  width: var(--column-width);
688
715
  }
689
716
 
690
- .pv-cards-bucket .pv-card {
691
- /* Cards maintain their direction */
717
+ .pv-cards-grouped .pv-card {
718
+ transform: none;
692
719
  }
693
720
 
694
- /* Empty placeholder cells in bucket grid */
721
+ /* Empty placeholder cells in grouped grid */
695
722
  .pv-card-placeholder {
696
723
  visibility: hidden;
697
724
  width: var(--card-width);
698
725
  height: var(--card-height);
699
726
  }
700
727
 
701
- /* Axis labels at bottom of bucket view */
728
+ /* Axis labels at bottom of grouped view */
702
729
  .pv-axis-labels {
703
730
  display: flex;
704
731
  align-items: stretch;
705
732
  background: var(--surface-section);
706
733
  border-top: 1px solid var(--surface-border);
707
- padding: 0.5rem 1.5rem;
708
- padding-left: calc(1.5rem); /* Match groups container padding */
709
- gap: 0.25rem; /* Match gap between groups */
734
+ padding: 0.5rem 0; /* padding-left set dynamically from JS; no right padding to prevent skew */
735
+ gap: 0; /* No extra gap to avoid skew */
710
736
  overflow-x: hidden; /* Disable independent scroll - synced via JS */
711
737
  flex-shrink: 0;
712
738
  }
@@ -719,6 +745,7 @@
719
745
  padding: 0.5rem 0;
720
746
  text-align: center;
721
747
  cursor: pointer;
748
+ /* Note: opacity transition added later in the file for filter effects */
722
749
  transition: background 180ms ease, border-color 180ms ease;
723
750
  flex: 0 0 auto;
724
751
  border-radius: 0.5rem;
@@ -728,7 +755,7 @@
728
755
  font: inherit;
729
756
  }
730
757
 
731
- /* Zebra striping for axis labels - match the bucket columns */
758
+ /* Zebra striping for axis labels - match the grouped columns */
732
759
  .pv-axis-label:nth-child(even) {
733
760
  background: rgba(255, 255, 255, 0.03);
734
761
  }
@@ -772,6 +799,28 @@
772
799
  font-variant-numeric: tabular-nums;
773
800
  }
774
801
 
802
+ /* Fade axis labels when switching views - matched to sprite animation duration (600ms) */
803
+ .pivot-viewer .pv-axis-labels {
804
+ transition: opacity 600ms ease;
805
+ }
806
+ .pivot-viewer .pv-axis-labels.hidden {
807
+ opacity: 0;
808
+ pointer-events: none;
809
+ }
810
+ .pivot-viewer .pv-axis-labels.visible {
811
+ opacity: 1;
812
+ pointer-events: auto;
813
+ }
814
+
815
+ /* Fade individual axis labels when filtering groups */
816
+ .pv-axis-label {
817
+ transition: opacity 600ms ease, background 180ms ease, border-color 180ms ease;
818
+ }
819
+
820
+ .pv-axis-label.filtered-out {
821
+ opacity: 0.3;
822
+ }
823
+
775
824
  .pv-groups.pv-groups-collection {
776
825
  display: flex;
777
826
  flex-wrap: wrap;
@@ -784,7 +833,7 @@
784
833
 
785
834
  /* Pause flex reflow during zoom animation for smoother effect */
786
835
  .pv-groups.pv-zooming {
787
- /* Disable transitions during zoom */
836
+ pointer-events: none;
788
837
  }
789
838
 
790
839
  /* CRITICAL: Freeze collection layout during zoom - prevent any reflow */
@@ -1 +1 @@
1
- {"version":3,"file":"PivotViewer.d.ts","sourceRoot":"","sources":["../../../PivotViewer/PivotViewer.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAQhD,OAAO,mBAAmB,CAAC;AAe3B,wBAAgB,WAAW,CAAC,KAAK,SAAS,MAAM,EAAE,EAC9C,IAAI,EACJ,UAAU,EACV,OAAO,EACP,mBAAmB,EACnB,YAAY,EACZ,cAAc,EACd,SAAS,EACT,YAAY,EACZ,SAAS,EACT,YAAY,EACZ,SAAiB,EACjB,MAAM,GACT,EAAE,gBAAgB,CAAC,KAAK,CAAC,2CA+WzB"}
1
+ {"version":3,"file":"PivotViewer.d.ts","sourceRoot":"","sources":["../../../PivotViewer/PivotViewer.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAQhD,OAAO,mBAAmB,CAAC;AAe3B,wBAAgB,WAAW,CAAC,KAAK,SAAS,MAAM,EAAE,EAC9C,IAAI,EACJ,UAAU,EACV,OAAO,EACP,mBAAmB,EACnB,YAAY,EACZ,cAAc,EACd,SAAS,EACT,YAAY,EACZ,SAAS,EACT,YAAY,EACZ,SAAiB,EACjB,MAAM,GACT,EAAE,gBAAgB,CAAC,KAAK,CAAC,2CAmYzB"}
@@ -5,7 +5,7 @@ import { computeLayout } from './engine/layout.js';
5
5
  import { useFilterState } from './hooks/useFilterState.js';
6
6
  import { useDimensionState } from './hooks/useDimensionState.js';
7
7
  import { useZoomState } from './hooks/useZoomState.js';
8
- import { BASE_CARD_HEIGHT, BASE_CARD_WIDTH, GROUP_SPACING, CARDS_PER_COLUMN } from './constants.js';
8
+ import { BASE_CARD_HEIGHT, BASE_CARD_WIDTH, CARDS_PER_COLUMN } from './constants.js';
9
9
  import './PivotViewer.css';
10
10
  import { PivotViewerMain } from './components/PivotViewerMain.js';
11
11
  import { FilterPanelContainer } from './components/FilterPanelContainer.js';
@@ -41,7 +41,7 @@ function PivotViewer({ data, dimensions, filters, defaultDimensionKey, cardRende
41
41
  const [scrollPosition, setScrollPosition] = useState({ x: 0, y: 0 });
42
42
  const { filterState, rangeFilterState, expandedFilterKey, setExpandedFilterKey, handleToggleFilter, handleClearFilter, handleRangeChange, } = useFilterState(filters);
43
43
  const { activeDimensionKey, setActiveDimensionKey, activeDimension, dimensionFilter, handleAxisLabelClick, } = useDimensionState(dimensions, defaultDimensionKey);
44
- const { zoomLevel, setZoomLevel, handleZoomIn, handleZoomOut, handleZoomSlider, } = useZoomState(1);
44
+ const { zoomLevel, setZoomLevel, handleZoomIn, handleZoomOut, handleZoomSlider, handleZoomReset, handleZoomChange, } = useZoomState(1);
45
45
  const { isPanning, handlePanStart, handlePanMove, handlePanEnd, } = usePanning(containerRef, undefined, setScrollPosition);
46
46
  useWheelZoom(containerRef, zoomLevel, setZoomLevel);
47
47
  const containerDimensions = useContainerDimensions(containerRef, isLoading);
@@ -70,9 +70,17 @@ function PivotViewer({ data, dimensions, filters, defaultDimensionKey, cardRende
70
70
  if (!ready)
71
71
  return;
72
72
  engineApplyFilters(currentFilters).then((result) => {
73
+ if (result.visibleIds.length === 0 && currentFilters.length === 0 && data.length > 0) {
74
+ const fallbackIds = new Uint32Array(data.length);
75
+ for (let i = 0; i < data.length; i++) {
76
+ fallbackIds[i] = i;
77
+ }
78
+ setVisibleIds(fallbackIds);
79
+ return;
80
+ }
73
81
  setVisibleIds(result.visibleIds);
74
82
  });
75
- }, [ready, currentFilters, engineApplyFilters]);
83
+ }, [ready, currentFilters, engineApplyFilters, data.length]);
76
84
  const lastGroupingRequest = useRef(null);
77
85
  useEffect(() => {
78
86
  if (!ready || visibleIds.length === 0) {
@@ -132,7 +140,6 @@ function PivotViewer({ data, dimensions, filters, defaultDimensionKey, cardRende
132
140
  cardWidth,
133
141
  cardHeight,
134
142
  cardsPerColumn: CARDS_PER_COLUMN,
135
- groupSpacing: GROUP_SPACING,
136
143
  containerWidth,
137
144
  containerHeight,
138
145
  });
@@ -202,10 +209,13 @@ function PivotViewer({ data, dimensions, filters, defaultDimensionKey, cardRende
202
209
  'pivot-viewer',
203
210
  className,
204
211
  hasFilters ? (filtersOpen ? 'filters-open' : 'filters-closed') : 'no-filters',
205
- viewMode === 'grouped' ? 'bucket-mode' : 'collection-mode',
212
+ viewMode === 'grouped' ? 'grouped-mode' : 'collection-mode',
206
213
  ]
207
214
  .filter(Boolean)
208
215
  .join(' ');
216
+ useEffect(() => {
217
+ setSelectedItem(null);
218
+ }, [viewMode]);
209
219
  const cssVariables = useMemo(() => {
210
220
  const vars = {};
211
221
  if (!colors)
@@ -238,7 +248,7 @@ function PivotViewer({ data, dimensions, filters, defaultDimensionKey, cardRende
238
248
  vars['--focus-ring'] = colors.focusRing;
239
249
  return vars;
240
250
  }, [colors]);
241
- return (jsxs("div", { className: viewerClassName, style: cssVariables, children: [jsx(FilterPanelContainer, { isOpen: filtersOpen && hasFilters, search: search, filterState: filterState, rangeFilterState: rangeFilterState, expandedFilterKey: expandedFilterKey, filterOptions: filterOptions, anchorRef: filterButtonRef, onClose: () => setFiltersOpen(false), onSearchChange: setSearch, onFilterToggle: handleToggleFilter, onFilterClear: handleClearFilter, onRangeChange: handleRangeChange, onExpandedFilterChange: setExpandedFilterKey }), jsxs("main", { className: "pv-main", children: [jsx(ToolbarContainer, { hasFilters: hasFilters, filtersOpen: filtersOpen, filteredCount: visibleIds.length, viewMode: viewMode, zoomLevel: zoomLevel, activeDimensionKey: activeDimensionKey, dimensions: dimensions, activeFilterCount: activeFilterCount, onFiltersToggle: () => setFiltersOpen((prev) => !prev), onViewModeChange: setViewMode, onZoomIn: handleZoomIn, onZoomOut: handleZoomOut, onZoomSlider: handleZoomSlider, onDimensionChange: setActiveDimensionKey, filterButtonRef: filterButtonRef }), jsx(PivotViewerMain, { data: data, ready: ready, isLoading: isLoading, visibleIds: visibleIds, grouping: grouping, layout: layout, cardWidth: cardWidth, cardHeight: cardHeight, zoomLevel: zoomLevel, scrollPosition: scrollPosition, containerDimensions: containerDimensions, selectedItem: selectedItem, hoveredGroupIndex: hoveredGroupIndex, isZooming: isZooming, viewMode: viewMode, cardRenderer: cardRenderer, detailRenderer: detailRenderer, resolveId: resolveId, emptyContent: emptyContent, dimensionFilter: dimensionFilter, onCardClick: handleCardClick, onPanStart: handlePanStart, onPanMove: handlePanMove, onPanEnd: handlePanEnd, onGroupHover: setHoveredGroupIndex, onAxisLabelClick: handleAxisLabelClick, onCloseDetail: closeDetail, containerRef: containerRef, axisLabelsRef: axisLabelsRef, spacerRef: spacerRef })] })] }));
251
+ return (jsxs("div", { className: viewerClassName, style: cssVariables, children: [jsx(FilterPanelContainer, { isOpen: filtersOpen && hasFilters, search: search, filterState: filterState, rangeFilterState: rangeFilterState, expandedFilterKey: expandedFilterKey, filterOptions: filterOptions, anchorRef: filterButtonRef, onClose: () => setFiltersOpen(false), onSearchChange: setSearch, onFilterToggle: handleToggleFilter, onFilterClear: handleClearFilter, onRangeChange: handleRangeChange, onExpandedFilterChange: setExpandedFilterKey }), jsxs("main", { className: "pv-main", children: [jsx(ToolbarContainer, { hasFilters: hasFilters, filtersOpen: filtersOpen, filteredCount: visibleIds.length, viewMode: viewMode, zoomLevel: zoomLevel, activeDimensionKey: activeDimensionKey, dimensions: dimensions, activeFilterCount: activeFilterCount, onFiltersToggle: () => setFiltersOpen((prev) => !prev), onViewModeChange: setViewMode, onZoomIn: handleZoomIn, onZoomOut: handleZoomOut, onZoomSlider: handleZoomSlider, onZoomReset: handleZoomReset, onZoomChange: handleZoomChange, onDimensionChange: setActiveDimensionKey, filterButtonRef: filterButtonRef }), jsx(PivotViewerMain, { data: data, ready: ready, isLoading: isLoading, visibleIds: visibleIds, grouping: grouping, layout: layout, cardWidth: cardWidth, cardHeight: cardHeight, zoomLevel: zoomLevel, scrollPosition: scrollPosition, containerDimensions: containerDimensions, selectedItem: selectedItem, hoveredGroupIndex: hoveredGroupIndex, isZooming: isZooming, viewMode: viewMode, cardRenderer: cardRenderer, detailRenderer: detailRenderer, resolveId: resolveId, emptyContent: emptyContent, dimensionFilter: dimensionFilter, onCardClick: handleCardClick, onPanStart: handlePanStart, onPanMove: handlePanMove, onPanEnd: handlePanEnd, onGroupHover: setHoveredGroupIndex, onAxisLabelClick: handleAxisLabelClick, onCloseDetail: closeDetail, containerRef: containerRef, axisLabelsRef: axisLabelsRef, spacerRef: spacerRef })] })] }));
242
252
  }
243
253
 
244
254
  export { PivotViewer };