dashboardity 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -79,16 +79,22 @@ type BreakpointWidths = Partial<Record<Exclude<BreakpointKey, "base">, number>>;
79
79
  type ColumnsByBreakpoint = Partial<Record<BreakpointKey, number>> & {
80
80
  base: number;
81
81
  };
82
+ /** 브레이크포인트별 레이아웃. 지정 시 해당 구간에서 스케일 대신 이 레이아웃 사용 */
83
+ type LayoutByBreakpoint = {
84
+ items: GridItem[];
85
+ };
82
86
  /**
83
87
  * 대시보드 공식 스펙. "대시보드 = JSON"
84
88
  * layout.items[].id === panels[].id 1:1. panels에는 좌표 없음.
85
89
  * breakpoints + columnsByBreakpoint 있으면 뷰포트 너비에 따라 columns 해석.
90
+ * layoutsByBreakpoint 있으면 해당 구간에서 layout 대신 그 레이아웃 사용(브레이크포인트별 배치).
86
91
  */
87
92
  type DashboardSpec = {
88
93
  id: string;
89
94
  title: string;
90
95
  /** 현재 저장된 레이아웃의 열 수. columnsByBreakpoint 사용 시에도 직렬화 시 이 값으로 저장 */
91
96
  columns: number;
97
+ /** 기본/캐논칼 레이아웃. layoutsByBreakpoint 미지정 구간은 여기서 스케일 */
92
98
  layout: {
93
99
  items: GridItem[];
94
100
  };
@@ -97,6 +103,8 @@ type DashboardSpec = {
97
103
  breakpoints?: BreakpointWidths;
98
104
  /** 브레이크포인트별 열 수. 지정 시 반응형 columns 적용 */
99
105
  columnsByBreakpoint?: ColumnsByBreakpoint;
106
+ /** 브레이크포인트별 레이아웃. 지정한 구간은 스케일 없이 이 배치 사용 */
107
+ layoutsByBreakpoint?: Partial<Record<BreakpointKey, LayoutByBreakpoint>>;
100
108
  };
101
109
  /** 편집 / 보기 모드 (Grafana 핵심 UX) */
102
110
  type DashboardMode = "view" | "edit";
@@ -271,22 +279,26 @@ declare const DashboardRuntime: React$1.ForwardRefExoticComponent<DashboardRunti
271
279
  declare const createEmptyPanel: (type: string, defaultTitle?: string) => PanelConfig;
272
280
  /**
273
281
  * 라이브러리 레벨: store + panels + meta → JSON 스펙.
274
- * 순서 보존, deterministic, side-effect 없음.
275
- * breakpoints/columnsByBreakpoint가 있으면 그대로 직렬화(저장 현재 columns 사용).
282
+ * breakpoints/columnsByBreakpoint/layoutsByBreakpoint 있으면 그대로 직렬화.
283
+ * layoutCache: 현재 브레이크포인트 포함 편집된 구간별 레이아웃(merge되어 출력).
276
284
  */
277
285
  declare const serializeDashboard: (store: LayoutStore, panels: PanelConfig[], meta: {
278
286
  id: string;
279
287
  title: string;
280
- }, specOverrides?: Pick<DashboardSpec, "breakpoints" | "columnsByBreakpoint">) => DashboardSpec;
288
+ }, specOverrides?: Pick<DashboardSpec, "breakpoints" | "columnsByBreakpoint" | "layoutsByBreakpoint">) => DashboardSpec;
281
289
  type LoadDashboardOptions = {
282
290
  /** 뷰포트/컨테이너 너비(px). columnsByBreakpoint 있을 때 이 너비로 열 수 해석 */
283
291
  width?: number;
284
292
  /** 이미 해석된 열 수. 지정 시 width 무시하고 이 값으로 스토어 생성 */
285
293
  resolvedColumns?: number;
294
+ /** 현재 브레이크포인트. layoutsByBreakpoint[breakpointKey] 있으면 그 레이아웃 사용 */
295
+ breakpointKey?: BreakpointKey;
296
+ /** 런타임 캐시(구간별 레이아웃). breakpointKey에 대한 캐시가 있으면 spec보다 우선 */
297
+ layoutCache?: Partial<Record<BreakpointKey, GridItem[]>>;
286
298
  };
287
299
  /**
288
300
  * JSON 스펙 → store + panels 복원.
289
- * spec.columnsByBreakpoint + breakpoints 있으면 width 또는 resolvedColumns 해석 레이아웃 스케일.
301
+ * layoutsByBreakpoint 또는 layoutCache해당 구간 레이아웃 사용, 없으면 layout 스케일.
290
302
  */
291
303
  declare const loadDashboard: (spec: DashboardSpec, options?: LoadDashboardOptions) => {
292
304
  store: LayoutStore;
@@ -425,4 +437,4 @@ type WidgetContainerProps = {
425
437
  */
426
438
  declare const WidgetContainer: React$1.FC<WidgetContainerProps>;
427
439
 
428
- export { type BreakpointKey, type BreakpointWidths, type ColumnsByBreakpoint, DEFAULT_BREAKPOINT_WIDTHS, Dashboard, type DashboardGridOptions, type DashboardMode, type DashboardOption, DashboardPicker, type DashboardPickerProps, type DashboardProps, DashboardRuntime, type DashboardRuntimeHandle, type DashboardRuntimeProps, type DashboardSpec, type DataSource, FallbackWidget, type LoadDashboardOptions, Panel, type PanelAction, type PanelConfig, type PanelConfigWithLayout, PanelOptionEditor, type PanelOptionEditorProps, type PanelProps, type ResponsiveGridSize, SideNav, type SideNavItem, type SideNavProps, type Theme, TimeRangePicker, type TimeRangePickerProps, type TimeRangePreset, TopBar, type TopBarProps, type UseResponsiveGridOptions, type WidgetComponent, WidgetContainer, type WidgetContainerProps, type WidgetDefinition, type WidgetOptionSchema, type WidgetOptionSchemaField, type WidgetProps, type WidgetRegistry, buildInitialItemsFromPanelConfigs, cn, colors, createEmptyPanel, createStaticDataSource, createWidgetRegistry, defaultTheme, getBreakpointKey, loadDashboard, resolveColumns, scaleLayout, serializeDashboard, spacing, typography, useContainerWidth, useResponsiveGrid };
440
+ export { type BreakpointKey, type BreakpointWidths, type ColumnsByBreakpoint, DEFAULT_BREAKPOINT_WIDTHS, Dashboard, type DashboardGridOptions, type DashboardMode, type DashboardOption, DashboardPicker, type DashboardPickerProps, type DashboardProps, DashboardRuntime, type DashboardRuntimeHandle, type DashboardRuntimeProps, type DashboardSpec, type DataSource, FallbackWidget, type LayoutByBreakpoint, type LoadDashboardOptions, Panel, type PanelAction, type PanelConfig, type PanelConfigWithLayout, PanelOptionEditor, type PanelOptionEditorProps, type PanelProps, type ResponsiveGridSize, SideNav, type SideNavItem, type SideNavProps, type Theme, TimeRangePicker, type TimeRangePickerProps, type TimeRangePreset, TopBar, type TopBarProps, type UseResponsiveGridOptions, type WidgetComponent, WidgetContainer, type WidgetContainerProps, type WidgetDefinition, type WidgetOptionSchema, type WidgetOptionSchemaField, type WidgetProps, type WidgetRegistry, buildInitialItemsFromPanelConfigs, cn, colors, createEmptyPanel, createStaticDataSource, createWidgetRegistry, defaultTheme, getBreakpointKey, loadDashboard, resolveColumns, scaleLayout, serializeDashboard, spacing, typography, useContainerWidth, useResponsiveGrid };
package/dist/index.d.ts CHANGED
@@ -79,16 +79,22 @@ type BreakpointWidths = Partial<Record<Exclude<BreakpointKey, "base">, number>>;
79
79
  type ColumnsByBreakpoint = Partial<Record<BreakpointKey, number>> & {
80
80
  base: number;
81
81
  };
82
+ /** 브레이크포인트별 레이아웃. 지정 시 해당 구간에서 스케일 대신 이 레이아웃 사용 */
83
+ type LayoutByBreakpoint = {
84
+ items: GridItem[];
85
+ };
82
86
  /**
83
87
  * 대시보드 공식 스펙. "대시보드 = JSON"
84
88
  * layout.items[].id === panels[].id 1:1. panels에는 좌표 없음.
85
89
  * breakpoints + columnsByBreakpoint 있으면 뷰포트 너비에 따라 columns 해석.
90
+ * layoutsByBreakpoint 있으면 해당 구간에서 layout 대신 그 레이아웃 사용(브레이크포인트별 배치).
86
91
  */
87
92
  type DashboardSpec = {
88
93
  id: string;
89
94
  title: string;
90
95
  /** 현재 저장된 레이아웃의 열 수. columnsByBreakpoint 사용 시에도 직렬화 시 이 값으로 저장 */
91
96
  columns: number;
97
+ /** 기본/캐논칼 레이아웃. layoutsByBreakpoint 미지정 구간은 여기서 스케일 */
92
98
  layout: {
93
99
  items: GridItem[];
94
100
  };
@@ -97,6 +103,8 @@ type DashboardSpec = {
97
103
  breakpoints?: BreakpointWidths;
98
104
  /** 브레이크포인트별 열 수. 지정 시 반응형 columns 적용 */
99
105
  columnsByBreakpoint?: ColumnsByBreakpoint;
106
+ /** 브레이크포인트별 레이아웃. 지정한 구간은 스케일 없이 이 배치 사용 */
107
+ layoutsByBreakpoint?: Partial<Record<BreakpointKey, LayoutByBreakpoint>>;
100
108
  };
101
109
  /** 편집 / 보기 모드 (Grafana 핵심 UX) */
102
110
  type DashboardMode = "view" | "edit";
@@ -271,22 +279,26 @@ declare const DashboardRuntime: React$1.ForwardRefExoticComponent<DashboardRunti
271
279
  declare const createEmptyPanel: (type: string, defaultTitle?: string) => PanelConfig;
272
280
  /**
273
281
  * 라이브러리 레벨: store + panels + meta → JSON 스펙.
274
- * 순서 보존, deterministic, side-effect 없음.
275
- * breakpoints/columnsByBreakpoint가 있으면 그대로 직렬화(저장 현재 columns 사용).
282
+ * breakpoints/columnsByBreakpoint/layoutsByBreakpoint 있으면 그대로 직렬화.
283
+ * layoutCache: 현재 브레이크포인트 포함 편집된 구간별 레이아웃(merge되어 출력).
276
284
  */
277
285
  declare const serializeDashboard: (store: LayoutStore, panels: PanelConfig[], meta: {
278
286
  id: string;
279
287
  title: string;
280
- }, specOverrides?: Pick<DashboardSpec, "breakpoints" | "columnsByBreakpoint">) => DashboardSpec;
288
+ }, specOverrides?: Pick<DashboardSpec, "breakpoints" | "columnsByBreakpoint" | "layoutsByBreakpoint">) => DashboardSpec;
281
289
  type LoadDashboardOptions = {
282
290
  /** 뷰포트/컨테이너 너비(px). columnsByBreakpoint 있을 때 이 너비로 열 수 해석 */
283
291
  width?: number;
284
292
  /** 이미 해석된 열 수. 지정 시 width 무시하고 이 값으로 스토어 생성 */
285
293
  resolvedColumns?: number;
294
+ /** 현재 브레이크포인트. layoutsByBreakpoint[breakpointKey] 있으면 그 레이아웃 사용 */
295
+ breakpointKey?: BreakpointKey;
296
+ /** 런타임 캐시(구간별 레이아웃). breakpointKey에 대한 캐시가 있으면 spec보다 우선 */
297
+ layoutCache?: Partial<Record<BreakpointKey, GridItem[]>>;
286
298
  };
287
299
  /**
288
300
  * JSON 스펙 → store + panels 복원.
289
- * spec.columnsByBreakpoint + breakpoints 있으면 width 또는 resolvedColumns 해석 레이아웃 스케일.
301
+ * layoutsByBreakpoint 또는 layoutCache해당 구간 레이아웃 사용, 없으면 layout 스케일.
290
302
  */
291
303
  declare const loadDashboard: (spec: DashboardSpec, options?: LoadDashboardOptions) => {
292
304
  store: LayoutStore;
@@ -425,4 +437,4 @@ type WidgetContainerProps = {
425
437
  */
426
438
  declare const WidgetContainer: React$1.FC<WidgetContainerProps>;
427
439
 
428
- export { type BreakpointKey, type BreakpointWidths, type ColumnsByBreakpoint, DEFAULT_BREAKPOINT_WIDTHS, Dashboard, type DashboardGridOptions, type DashboardMode, type DashboardOption, DashboardPicker, type DashboardPickerProps, type DashboardProps, DashboardRuntime, type DashboardRuntimeHandle, type DashboardRuntimeProps, type DashboardSpec, type DataSource, FallbackWidget, type LoadDashboardOptions, Panel, type PanelAction, type PanelConfig, type PanelConfigWithLayout, PanelOptionEditor, type PanelOptionEditorProps, type PanelProps, type ResponsiveGridSize, SideNav, type SideNavItem, type SideNavProps, type Theme, TimeRangePicker, type TimeRangePickerProps, type TimeRangePreset, TopBar, type TopBarProps, type UseResponsiveGridOptions, type WidgetComponent, WidgetContainer, type WidgetContainerProps, type WidgetDefinition, type WidgetOptionSchema, type WidgetOptionSchemaField, type WidgetProps, type WidgetRegistry, buildInitialItemsFromPanelConfigs, cn, colors, createEmptyPanel, createStaticDataSource, createWidgetRegistry, defaultTheme, getBreakpointKey, loadDashboard, resolveColumns, scaleLayout, serializeDashboard, spacing, typography, useContainerWidth, useResponsiveGrid };
440
+ export { type BreakpointKey, type BreakpointWidths, type ColumnsByBreakpoint, DEFAULT_BREAKPOINT_WIDTHS, Dashboard, type DashboardGridOptions, type DashboardMode, type DashboardOption, DashboardPicker, type DashboardPickerProps, type DashboardProps, DashboardRuntime, type DashboardRuntimeHandle, type DashboardRuntimeProps, type DashboardSpec, type DataSource, FallbackWidget, type LayoutByBreakpoint, type LoadDashboardOptions, Panel, type PanelAction, type PanelConfig, type PanelConfigWithLayout, PanelOptionEditor, type PanelOptionEditorProps, type PanelProps, type ResponsiveGridSize, SideNav, type SideNavItem, type SideNavProps, type Theme, TimeRangePicker, type TimeRangePickerProps, type TimeRangePreset, TopBar, type TopBarProps, type UseResponsiveGridOptions, type WidgetComponent, WidgetContainer, type WidgetContainerProps, type WidgetDefinition, type WidgetOptionSchema, type WidgetOptionSchemaField, type WidgetProps, type WidgetRegistry, buildInitialItemsFromPanelConfigs, cn, colors, createEmptyPanel, createStaticDataSource, createWidgetRegistry, defaultTheme, getBreakpointKey, loadDashboard, resolveColumns, scaleLayout, serializeDashboard, spacing, typography, useContainerWidth, useResponsiveGrid };
package/dist/index.js CHANGED
@@ -35,7 +35,7 @@ __export(index_exports, {
35
35
  cn: () => cn3,
36
36
  colors: () => colors,
37
37
  createEmptyPanel: () => createEmptyPanel,
38
- createLayoutStore: () => import_layout_store3.createLayoutStore,
38
+ createLayoutStore: () => import_layout_store2.createLayoutStore,
39
39
  createStaticDataSource: () => createStaticDataSource,
40
40
  createWidgetRegistry: () => createWidgetRegistry,
41
41
  defaultTheme: () => defaultTheme,
@@ -518,7 +518,6 @@ var Dashboard = ({
518
518
 
519
519
  // src/core/DashboardRuntime.tsx
520
520
  var import_react4 = require("react");
521
- var import_layout_store2 = require("@dashboardity/layout-store");
522
521
 
523
522
  // src/core/DashboardSerializer.ts
524
523
  var import_layout_store = require("@dashboardity/layout-store");
@@ -542,13 +541,32 @@ var serializeDashboard = (store, panels, meta, specOverrides) => {
542
541
  if (specOverrides?.breakpoints) base.breakpoints = specOverrides.breakpoints;
543
542
  if (specOverrides?.columnsByBreakpoint)
544
543
  base.columnsByBreakpoint = specOverrides.columnsByBreakpoint;
544
+ if (specOverrides?.layoutsByBreakpoint != null)
545
+ base.layoutsByBreakpoint = specOverrides.layoutsByBreakpoint;
545
546
  return base;
546
547
  };
548
+ var getItemsForBreakpoint = (spec, breakpointKey, resolvedColumns, layoutCache) => {
549
+ const cached = layoutCache?.[breakpointKey];
550
+ if (cached != null && cached.length > 0) return [...cached];
551
+ const byBp = spec.layoutsByBreakpoint?.[breakpointKey]?.items;
552
+ if (byBp != null && byBp.length > 0) return [...byBp];
553
+ return scaleLayout(
554
+ spec.layout.items,
555
+ spec.columns,
556
+ resolvedColumns
557
+ );
558
+ };
547
559
  var loadDashboard = (spec, options) => {
548
560
  const savedColumns = spec.columns;
549
561
  const colConfig = spec.columnsByBreakpoint ?? savedColumns;
550
562
  const resolved = options?.resolvedColumns ?? (options?.width != null ? resolveColumns(options.width, colConfig, spec.breakpoints) : savedColumns);
551
- const items = resolved !== savedColumns ? scaleLayout(spec.layout.items, savedColumns, resolved) : [...spec.layout.items];
563
+ const breakpointKey = options?.breakpointKey ?? "xl";
564
+ const items = getItemsForBreakpoint(
565
+ spec,
566
+ breakpointKey,
567
+ resolved,
568
+ options?.layoutCache
569
+ );
552
570
  const store = (0, import_layout_store.createLayoutStore)({
553
571
  items,
554
572
  columns: resolved
@@ -749,16 +767,26 @@ var DashboardRuntime = (0, import_react4.forwardRef)((props, ref) => {
749
767
  const isEdit = mode === "edit";
750
768
  const gridWrapRef = (0, import_react4.useRef)(null);
751
769
  const containerWidth = useContainerWidth(gridWrapRef);
770
+ const widthForBreakpoint = containerWidth || (typeof window !== "undefined" ? window.innerWidth : 1024);
771
+ const currentBreakpoint = (0, import_react4.useMemo)(
772
+ () => getBreakpointKey(widthForBreakpoint, initialSpec.breakpoints),
773
+ [widthForBreakpoint, initialSpec.breakpoints]
774
+ );
752
775
  const resolvedColumns = (0, import_react4.useMemo)(() => {
753
776
  const colConfig = initialSpec.columnsByBreakpoint ?? initialSpec.columns;
754
- const width = containerWidth || (typeof window !== "undefined" ? window.innerWidth : 1024);
755
- return resolveColumns(width, colConfig, initialSpec.breakpoints);
777
+ return resolveColumns(
778
+ widthForBreakpoint,
779
+ colConfig,
780
+ initialSpec.breakpoints
781
+ );
756
782
  }, [
757
- containerWidth,
783
+ widthForBreakpoint,
758
784
  initialSpec.columns,
759
785
  initialSpec.columnsByBreakpoint,
760
786
  initialSpec.breakpoints
761
787
  ]);
788
+ const initialSpecRef = (0, import_react4.useRef)(initialSpec);
789
+ initialSpecRef.current = initialSpec;
762
790
  const [store, setStore] = (0, import_react4.useState)(() => loadDashboard(initialSpec).store);
763
791
  const [panels, setPanels] = (0, import_react4.useState)(() => [
764
792
  ...loadDashboard(initialSpec).panels
@@ -767,29 +795,52 @@ var DashboardRuntime = (0, import_react4.forwardRef)((props, ref) => {
767
795
  const specIdRef = (0, import_react4.useRef)(initialSpec.id);
768
796
  const storeRef = (0, import_react4.useRef)(store);
769
797
  storeRef.current = store;
798
+ const prevBreakpointRef = (0, import_react4.useRef)(currentBreakpoint);
799
+ const layoutCacheRef = (0, import_react4.useRef)({});
800
+ const currentBreakpointRef = (0, import_react4.useRef)(currentBreakpoint);
801
+ currentBreakpointRef.current = currentBreakpoint;
770
802
  (0, import_react4.useEffect)(() => {
771
803
  if (resolvedColumns < 1) return;
772
- if (specIdRef.current !== initialSpec.id) {
773
- specIdRef.current = initialSpec.id;
774
- const loaded = loadDashboard(initialSpec, { resolvedColumns });
775
- setStore(loaded.store);
776
- setPanels([...loaded.panels]);
804
+ const spec = initialSpecRef.current;
805
+ if (specIdRef.current !== spec.id) {
806
+ specIdRef.current = spec.id;
807
+ prevBreakpointRef.current = currentBreakpoint;
808
+ layoutCacheRef.current = {};
809
+ Object.keys(spec.layoutsByBreakpoint ?? {}).forEach(
810
+ (k) => {
811
+ const items = spec.layoutsByBreakpoint?.[k]?.items;
812
+ if (items) layoutCacheRef.current[k] = [...items];
813
+ }
814
+ );
815
+ const loaded2 = loadDashboard(spec, {
816
+ resolvedColumns,
817
+ breakpointKey: currentBreakpoint,
818
+ layoutCache: layoutCacheRef.current
819
+ });
820
+ setStore(loaded2.store);
821
+ setPanels([...loaded2.panels]);
822
+ layoutCacheRef.current[currentBreakpoint] = loaded2.store.getState().items;
777
823
  return;
778
824
  }
825
+ const prevBp = prevBreakpointRef.current;
779
826
  const current = storeRef.current;
780
- if (!current) {
781
- const loaded = loadDashboard(initialSpec, { resolvedColumns });
782
- setStore(loaded.store);
783
- setPanels([...loaded.panels]);
827
+ if (prevBp === currentBreakpoint && current?.getState().columns === resolvedColumns) {
784
828
  return;
785
829
  }
786
- const state = current.getState();
787
- if (state.columns === resolvedColumns) return;
788
- const scaled = scaleLayout(state.items, state.columns, resolvedColumns);
789
- setStore(
790
- (0, import_layout_store2.createLayoutStore)({ items: scaled, columns: resolvedColumns })
791
- );
792
- }, [initialSpec, resolvedColumns]);
830
+ if (current) {
831
+ layoutCacheRef.current[prevBp] = [...current.getState().items];
832
+ }
833
+ prevBreakpointRef.current = currentBreakpoint;
834
+ const loaded = loadDashboard(spec, {
835
+ resolvedColumns,
836
+ breakpointKey: currentBreakpoint,
837
+ layoutCache: layoutCacheRef.current
838
+ });
839
+ const loadedItems = loaded.store.getState().items;
840
+ layoutCacheRef.current[currentBreakpoint] = [...loadedItems];
841
+ setStore(loaded.store);
842
+ setPanels([...loaded.panels]);
843
+ }, [initialSpec.id, resolvedColumns, currentBreakpoint]);
793
844
  const meta = (0, import_react4.useMemo)(
794
845
  () => ({ id: initialSpec.id, title: initialSpec.title }),
795
846
  [initialSpec.id, initialSpec.title]
@@ -797,15 +848,32 @@ var DashboardRuntime = (0, import_react4.forwardRef)((props, ref) => {
797
848
  const panelsRef = (0, import_react4.useRef)(panels);
798
849
  panelsRef.current = panels;
799
850
  const notifyChange = (0, import_react4.useCallback)(() => {
851
+ const cache = layoutCacheRef.current;
852
+ const layoutsByBreakpoint = Object.keys(cache).length > 0 ? Object.fromEntries(
853
+ Object.entries(cache).map(([k, v]) => [k, { items: v }])
854
+ ) : initialSpec.layoutsByBreakpoint;
800
855
  onChange?.(
801
856
  serializeDashboard(store, panelsRef.current, meta, {
802
857
  breakpoints: initialSpec.breakpoints,
803
- columnsByBreakpoint: initialSpec.columnsByBreakpoint
858
+ columnsByBreakpoint: initialSpec.columnsByBreakpoint,
859
+ layoutsByBreakpoint
804
860
  })
805
861
  );
806
- }, [onChange, store, meta, initialSpec.breakpoints, initialSpec.columnsByBreakpoint]);
862
+ }, [
863
+ onChange,
864
+ store,
865
+ meta,
866
+ initialSpec.breakpoints,
867
+ initialSpec.columnsByBreakpoint,
868
+ initialSpec.layoutsByBreakpoint
869
+ ]);
807
870
  (0, import_react4.useEffect)(() => {
808
- const unsub = store.subscribe(notifyChange);
871
+ const unsub = store.subscribe(() => {
872
+ layoutCacheRef.current[currentBreakpointRef.current] = [
873
+ ...store.getState().items
874
+ ];
875
+ notifyChange();
876
+ });
809
877
  return unsub;
810
878
  }, [store, notifyChange]);
811
879
  (0, import_react4.useEffect)(() => {
@@ -873,13 +941,23 @@ var DashboardRuntime = (0, import_react4.forwardRef)((props, ref) => {
873
941
  },
874
942
  [store]
875
943
  );
876
- const exportSpec = (0, import_react4.useCallback)(
877
- () => serializeDashboard(store, panelsRef.current, meta, {
944
+ const exportSpec = (0, import_react4.useCallback)(() => {
945
+ const cache = layoutCacheRef.current;
946
+ const layoutsByBreakpoint = Object.keys(cache).length > 0 ? Object.fromEntries(
947
+ Object.entries(cache).map(([k, v]) => [k, { items: v }])
948
+ ) : initialSpec.layoutsByBreakpoint;
949
+ return serializeDashboard(store, panelsRef.current, meta, {
878
950
  breakpoints: initialSpec.breakpoints,
879
- columnsByBreakpoint: initialSpec.columnsByBreakpoint
880
- }),
881
- [store, meta, initialSpec.breakpoints, initialSpec.columnsByBreakpoint]
882
- );
951
+ columnsByBreakpoint: initialSpec.columnsByBreakpoint,
952
+ layoutsByBreakpoint
953
+ });
954
+ }, [
955
+ store,
956
+ meta,
957
+ initialSpec.breakpoints,
958
+ initialSpec.columnsByBreakpoint,
959
+ initialSpec.layoutsByBreakpoint
960
+ ]);
883
961
  (0, import_react4.useImperativeHandle)(
884
962
  ref,
885
963
  () => ({
@@ -956,7 +1034,7 @@ var DashboardRuntime = (0, import_react4.forwardRef)((props, ref) => {
956
1034
  DashboardRuntime.displayName = "DashboardRuntime";
957
1035
 
958
1036
  // src/index.ts
959
- var import_layout_store3 = require("@dashboardity/layout-store");
1037
+ var import_layout_store2 = require("@dashboardity/layout-store");
960
1038
 
961
1039
  // src/app-shell/TopBar.tsx
962
1040
  var import_jsx_runtime7 = require("react/jsx-runtime");