@gravity-ui/dashkit 8.11.0 → 8.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
- import type { Config, ConfigItem, GlobalParams, ItemDropProps, ItemsStateAndParams } from '../../shared';
3
- import { ContextProps, DashKitGroup, MenuItem, Plugin, SetConfigItem, SetNewItemOptions, Settings, SettingsProps } from '../../typings';
2
+ import type { Config, ConfigItem, ConfigLayout, GlobalParams, ItemDropProps, ItemsStateAndParams } from '../../shared';
3
+ import { AddNewItemOptions, ContextProps, DashKitGroup, MenuItem, Plugin, SetConfigItem, Settings, SettingsProps } from '../../typings';
4
4
  import GridLayout from '../GridLayout/GridLayout';
5
5
  import { OverlayControlItem } from '../OverlayControls/OverlayControls';
6
6
  interface DashKitGeneralProps {
@@ -39,11 +39,12 @@ export declare class DashKit extends React.PureComponent<DashKitInnerProps> {
39
39
  static defaultProps: DashKitDefaultProps;
40
40
  static registerPlugins(...plugins: Plugin[]): void;
41
41
  static setSettings(settings: Settings): void;
42
- static setItem({ item: setItem, namespace, config, options, }: {
42
+ static setItem({ item: setItem, namespace, config, options, groups, }: {
43
43
  item: SetConfigItem;
44
44
  namespace?: string;
45
45
  config: Config;
46
- options?: SetNewItemOptions;
46
+ options?: Omit<AddNewItemOptions, 'reflowLayoutOptions'>;
47
+ groups?: DashKitGroup[];
47
48
  }): Config;
48
49
  static removeItem({ id, config, itemsStateAndParams, }: {
49
50
  id: string;
@@ -53,6 +54,7 @@ export declare class DashKit extends React.PureComponent<DashKitInnerProps> {
53
54
  config: Config;
54
55
  itemsStateAndParams: ItemsStateAndParams;
55
56
  };
57
+ static reflowLayout(newLayoutItem: ConfigLayout, layout: ConfigLayout[], groups?: DashKitGroup[]): ConfigLayout[];
56
58
  metaRef: React.RefObject<GridLayout>;
57
59
  render(): JSX.Element;
58
60
  getItemsMeta(): any;
@@ -4,10 +4,25 @@ exports.DashKit = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const react_1 = tslib_1.__importDefault(require("react"));
6
6
  const noop_1 = tslib_1.__importDefault(require("lodash/noop"));
7
+ const pick_1 = tslib_1.__importDefault(require("lodash/pick"));
7
8
  const constants_1 = require("../../constants");
8
9
  const utils_1 = require("../../utils");
9
10
  const DashKitView_1 = tslib_1.__importDefault(require("../DashKitView/DashKitView"));
10
11
  const registerManager = new utils_1.RegisterManager();
12
+ const getReflowProps = (props) => Object.assign({ compactType: 'vertical', cols: 36 }, (0, pick_1.default)(props, 'cols', 'maxRows', 'compactType'));
13
+ const getReflowGroupsConfig = (groups = []) => {
14
+ const defaultGridProps = getReflowProps(registerManager.gridLayout);
15
+ return {
16
+ defaultProps: defaultGridProps,
17
+ groups: groups.reduce((memo, g) => {
18
+ const groupId = g.id || constants_1.DEFAULT_GROUP;
19
+ memo[groupId] = g.gridProperties
20
+ ? getReflowProps(g.gridProperties(defaultGridProps))
21
+ : defaultGridProps;
22
+ return memo;
23
+ }, {}),
24
+ };
25
+ };
11
26
  class DashKit extends react_1.default.PureComponent {
12
27
  constructor() {
13
28
  super(...arguments);
@@ -21,28 +36,39 @@ class DashKit extends react_1.default.PureComponent {
21
36
  static setSettings(settings) {
22
37
  registerManager.setSettings(settings);
23
38
  }
24
- static setItem({ item: setItem, namespace = constants_1.DEFAULT_NAMESPACE, config, options = {}, }) {
39
+ static setItem({ item: setItem, namespace = constants_1.DEFAULT_NAMESPACE, config, options = {}, groups = [], }) {
25
40
  if (setItem.id) {
26
- return utils_1.UpdateManager.editItem({ item: setItem, namespace, config, options });
41
+ return utils_1.UpdateManager.editItem({
42
+ item: setItem,
43
+ namespace,
44
+ config,
45
+ options,
46
+ });
27
47
  }
28
48
  else {
29
49
  const item = setItem;
30
50
  const layout = Object.assign({}, registerManager.getItem(item.type).defaultLayout);
31
- if (item.layout) {
32
- Object.assign(layout, item.layout);
51
+ const reflowLayoutOptions = getReflowGroupsConfig(groups);
52
+ const copyItem = Object.assign({}, item);
53
+ if (copyItem.layout) {
54
+ Object.assign(layout, copyItem.layout);
55
+ delete copyItem.layout;
33
56
  }
34
57
  return utils_1.UpdateManager.addItem({
35
- item,
58
+ item: copyItem,
36
59
  namespace,
37
60
  config,
38
61
  layout,
39
- options,
62
+ options: Object.assign(Object.assign({}, options), { reflowLayoutOptions }),
40
63
  });
41
64
  }
42
65
  }
43
66
  static removeItem({ id, config, itemsStateAndParams, }) {
44
67
  return utils_1.UpdateManager.removeItem({ id, config, itemsStateAndParams });
45
68
  }
69
+ static reflowLayout(newLayoutItem, layout, groups) {
70
+ return (0, utils_1.reflowLayout)(newLayoutItem, layout, getReflowGroupsConfig(groups));
71
+ }
46
72
  render() {
47
73
  return react_1.default.createElement(DashKitView_1.default, Object.assign({ registerManager: registerManager, ref: this.metaRef }, this.props));
48
74
  }
@@ -38,7 +38,7 @@ export default class GridLayout extends React.PureComponent<any, any, any> {
38
38
  getMemoForwardRefCallback: (refIndex: any) => any;
39
39
  mergeGroupsLayout(group: any, newLayout: any, temporaryItem: any): any;
40
40
  reloadItems(): void;
41
- _onDragStart: (group: any, _newLayout: any, layoutItem: any) => void;
41
+ _onDragStart: (group: any, _newLayout: any, layoutItem: any, _newItem: any, _placeholder: any, e: any) => void;
42
42
  _onResizeStart: () => void;
43
43
  _onTargetRestore: () => void;
44
44
  _onStop: (group: any, newLayout: any) => void;
@@ -94,7 +94,7 @@ class GridLayout extends react_1.default.PureComponent {
94
94
  }
95
95
  return this._memoForwardedPluginRef[refIndex];
96
96
  };
97
- this._onDragStart = (group, _newLayout, layoutItem) => {
97
+ this._onDragStart = (group, _newLayout, layoutItem, _newItem, _placeholder, e) => {
98
98
  if (this.temporaryLayout)
99
99
  return;
100
100
  if (this.context.dragOverPlugin) {
@@ -103,9 +103,15 @@ class GridLayout extends react_1.default.PureComponent {
103
103
  else {
104
104
  let currentDraggingElement = this.state.currentDraggingElement;
105
105
  if (!currentDraggingElement) {
106
- const _id = layoutItem.i;
107
- const item = this.context.config.items.find(({ id }) => id === _id);
108
- currentDraggingElement = [group, layoutItem, item];
106
+ const layoutId = layoutItem.i;
107
+ const item = this.context.config.items.find(({ id }) => id === layoutId);
108
+ const { offsetX, offsetY } = e.nativeEvent;
109
+ currentDraggingElement = {
110
+ group,
111
+ layoutItem,
112
+ item,
113
+ cursorPosition: { offsetX, offsetY },
114
+ };
109
115
  }
110
116
  this.setState({
111
117
  isDragging: true,
@@ -123,7 +129,7 @@ class GridLayout extends react_1.default.PureComponent {
123
129
  const { currentDraggingElement } = this.state;
124
130
  if (currentDraggingElement) {
125
131
  this.setState({
126
- draggedOverGroup: currentDraggingElement[0],
132
+ draggedOverGroup: currentDraggingElement.group,
127
133
  });
128
134
  }
129
135
  };
@@ -132,7 +138,7 @@ class GridLayout extends react_1.default.PureComponent {
132
138
  const { draggedOverGroup, currentDraggingElement } = this.state;
133
139
  if (currentDraggingElement &&
134
140
  draggedOverGroup !== null &&
135
- draggedOverGroup !== currentDraggingElement[0]) {
141
+ draggedOverGroup !== currentDraggingElement.group) {
136
142
  // Skipping layout update when change event called for source grid
137
143
  // and waiting _onDrop
138
144
  return;
@@ -156,15 +162,15 @@ class GridLayout extends react_1.default.PureComponent {
156
162
  if (!currentDraggingElement) {
157
163
  return;
158
164
  }
159
- const [, sourceItem] = currentDraggingElement;
165
+ const sourceLayoutItem = currentDraggingElement.layoutItem;
160
166
  const groupedLayout = this.mergeGroupsLayout(targetGroup, newLayout, tempItem).map((item) => {
161
- if (item.i === sourceItem.i) {
167
+ if (item.i === sourceLayoutItem.i) {
162
168
  const copy = Object.assign({}, tempItem);
163
169
  delete copy.parent;
164
170
  if (targetGroup !== constants_1.DEFAULT_GROUP) {
165
171
  copy.parent = targetGroup;
166
172
  }
167
- copy.i = sourceItem.i;
173
+ copy.i = sourceLayoutItem.i;
168
174
  return copy;
169
175
  }
170
176
  return item;
@@ -205,7 +211,8 @@ class GridLayout extends react_1.default.PureComponent {
205
211
  }
206
212
  const { properties, layout } = this.getLayoutAndPropsByGroup(group);
207
213
  if (currentDraggingElement) {
208
- const [, { h, w, i }, { type }] = currentDraggingElement;
214
+ const { h, w, i } = currentDraggingElement.layoutItem;
215
+ const { type } = currentDraggingElement.item;
209
216
  return onDropDragOver(e, properties, layout, { h, w, i, type });
210
217
  }
211
218
  if (dragOverPlugin) {
@@ -297,12 +304,12 @@ class GridLayout extends react_1.default.PureComponent {
297
304
  compactType = 'horizontal';
298
305
  }
299
306
  const { callbacks, layout } = this.getMemoGroupProps(group, renderLayout, properties);
300
- const hasSharedDragItem = Boolean(currentDraggingElement && currentDraggingElement[0] !== group);
307
+ const hasSharedDragItem = Boolean(currentDraggingElement && currentDraggingElement.group !== group);
301
308
  const isDragCaptured = currentDraggingElement &&
302
- group === currentDraggingElement[0] &&
309
+ group === currentDraggingElement.group &&
303
310
  draggedOverGroup !== null &&
304
311
  draggedOverGroup !== group;
305
- return (react_1.default.createElement(ReactGridLayout_1.Layout, Object.assign({}, properties, { compactType: compactType, layout: layout, key: `group_${group}`, isDraggable: editMode, isResizable: editMode, onResizeStart: this._onResizeStart, onDragStart: callbacks.onDragStart, onDragStop: callbacks.onDragStop, onResizeStop: callbacks.onResizeStop, onDragTargetRestore: callbacks.onDragTargetRestore, onDropDragOver: callbacks.onDropDragOver, onDrop: callbacks.onDrop, hasSharedDragItem: hasSharedDragItem, isDragCaptured: isDragCaptured }, (draggableHandleClassName
312
+ return (react_1.default.createElement(ReactGridLayout_1.Layout, Object.assign({}, properties, { compactType: compactType, layout: layout, key: `group_${group}`, isDraggable: editMode, isResizable: editMode, onResizeStart: this._onResizeStart, onDragStart: callbacks.onDragStart, onDragStop: callbacks.onDragStop, onResizeStop: callbacks.onResizeStop, onDragTargetRestore: callbacks.onDragTargetRestore, onDropDragOver: callbacks.onDropDragOver, onDrop: callbacks.onDrop, hasSharedDragItem: hasSharedDragItem, sharedDragPosition: currentDraggingElement === null || currentDraggingElement === void 0 ? void 0 : currentDraggingElement.cursorPosition, isDragCaptured: isDragCaptured }, (draggableHandleClassName
306
313
  ? { draggableHandle: `.${draggableHandleClassName}` }
307
314
  : null), (outerDnDEnable
308
315
  ? {
@@ -170,15 +170,23 @@ class DragOverLayout extends react_grid_layout_1.default {
170
170
  const { props: itemProps } = gridItem;
171
171
  if (isDroppingItem) {
172
172
  const { containerWidth, cols, w, h, rowHeight, margin, transformScale, droppingPosition, } = itemProps;
173
- const leftOffset = (((containerWidth / cols) * w) / 2 || 0) * transformScale;
174
- const topOffset = ((h * rowHeight + (h - 1) * margin[1]) / 2 || 0) * transformScale;
173
+ const { sharedDragPosition, hasSharedDragItem } = this.props;
174
+ let offsetX, offsetY;
175
+ if (sharedDragPosition) {
176
+ offsetX = sharedDragPosition.offsetX;
177
+ offsetY = sharedDragPosition.offsetY;
178
+ }
179
+ else {
180
+ offsetX = (((containerWidth / cols) * w) / 2 || 0) * transformScale;
181
+ offsetY = ((h * rowHeight + (h - 1) * margin[1]) / 2 || 0) * transformScale;
182
+ }
175
183
  const style = gridItem.props.style || null;
176
184
  // React.cloneElement is just cleaner then copy-paste whole processGridItem method
177
185
  return react_1.default.cloneElement(gridItem, {
178
186
  // hiding previre if dragging shared item
179
- style: this.props.hasSharedDragItem ? Object.assign(Object.assign({}, style), { opacity: 0 }) : style,
187
+ style: hasSharedDragItem ? Object.assign(Object.assign({}, style), { opacity: 0 }) : style,
180
188
  className: constants_1.OVERLAY_CLASS_NAME,
181
- droppingPosition: Object.assign(Object.assign({}, droppingPosition), { left: droppingPosition.left - leftOffset, top: droppingPosition.top - topOffset }),
189
+ droppingPosition: Object.assign(Object.assign({}, droppingPosition), { left: droppingPosition.left - offsetX, top: droppingPosition.top - offsetY }),
182
190
  });
183
191
  }
184
192
  return gridItem;
@@ -128,7 +128,7 @@ function useMemoStateContext(props) {
128
128
  react_1.default.useMemo(() => {
129
129
  const groups = props.groups;
130
130
  const layout = props.layout;
131
- const defaultProps = props.registerManager._gridLayout || {};
131
+ const defaultProps = props.registerManager.gridLayout || {};
132
132
  const nowrapGroups = {};
133
133
  let hasNowrapGroups = false;
134
134
  if (defaultProps.compactType === common_1.COMPACT_TYPE_HORIZONTAL_NOWRAP) {
@@ -4,14 +4,26 @@ import type { Config, ConfigItem, ConfigLayout } from '../shared';
4
4
  export interface AddConfigItem extends Omit<ConfigItem, 'id' | 'namespace'> {
5
5
  id?: null;
6
6
  namespace?: string;
7
- layout?: ConfigLayout;
7
+ layout?: Omit<ConfigLayout, 'i'>;
8
8
  }
9
9
  export type SetConfigItem = ConfigItem | AddConfigItem;
10
10
  export type SetItemOptions = {
11
11
  excludeIds?: string[];
12
+ };
13
+ export type GridReflowOptions = {
14
+ cols: number;
15
+ maxRows?: number;
16
+ compactType?: CompactType;
17
+ };
18
+ export type CompactType = ReactGridLayout.ReactGridLayoutProps['compactType'] | 'horizontal-nowrap';
19
+ export type ReflowLayoutOptions = {
20
+ defaultProps: GridReflowOptions;
21
+ groups?: Record<string, GridReflowOptions>;
22
+ };
23
+ export type AddNewItemOptions = SetItemOptions & {
12
24
  updateLayout?: ConfigLayout[];
25
+ reflowLayoutOptions?: ReflowLayoutOptions;
13
26
  };
14
- export type SetNewItemOptions = SetItemOptions;
15
27
  export interface DashkitGroupRenderProps {
16
28
  config: Config;
17
29
  editMode: boolean;
@@ -19,7 +31,7 @@ export interface DashkitGroupRenderProps {
19
31
  layout: ConfigLayout[];
20
32
  }
21
33
  export type ReactGridLayoutProps = Omit<ReactGridLayout.ReactGridLayoutProps, 'children' | 'compactType' | 'innerRef' | 'key' | 'layout' | 'isDraggable' | 'isResizable' | 'onDragStart' | 'onDragStop' | 'onResizeStart' | 'onResizeStop' | 'draggableHandle' | 'isDroppable' | 'onDropDragOver' | 'onDrop' | 'draggableCancel'> & {
22
- compactType?: ReactGridLayout.ReactGridLayoutProps['compactType'] | 'horizontal-nowrap';
34
+ compactType?: CompactType;
23
35
  };
24
36
  export interface DashKitGroup {
25
37
  id?: string;
@@ -196,7 +196,7 @@ describe('UpdateManager', () => {
196
196
  type: 'widget',
197
197
  },
198
198
  namespace: 'default',
199
- layout: { h: 4, w: 4, x: 0, y: 0 },
199
+ layout: { h: 4, w: 4, x: 0, y: Infinity },
200
200
  config: config,
201
201
  })).toEqual(Object.assign(Object.assign({}, config), { salt: '0.09852189776033704', counter: 15, items: [
202
202
  ...config.items,
@@ -215,7 +215,7 @@ describe('UpdateManager', () => {
215
215
  type: 'widget',
216
216
  },
217
217
  namespace: 'default',
218
- layout: { h: 4, w: 4, x: 0, y: 0 },
218
+ layout: { h: 4, w: 4, x: 0, y: Infinity },
219
219
  config: config,
220
220
  options: {
221
221
  excludeIds: ['EZ'],
@@ -260,6 +260,67 @@ describe('UpdateManager', () => {
260
260
  },
261
261
  ], layout: [...updateLayout, { h: 4, w: 12, x: 0, y: 0, i: 'EZ' }] }));
262
262
  });
263
+ it('prepend new item', () => {
264
+ expect(update_manager_1.UpdateManager.addItem({
265
+ item: {
266
+ data: {},
267
+ type: 'widget',
268
+ },
269
+ namespace: 'default',
270
+ layout: { h: 4, w: 36, x: 0, y: 0 },
271
+ config: config,
272
+ options: {
273
+ reflowLayoutOptions: {
274
+ defaultProps: {
275
+ cols: 36,
276
+ },
277
+ },
278
+ },
279
+ })).toEqual(Object.assign(Object.assign({}, config), { salt: '0.09852189776033704', counter: 15, items: [
280
+ ...config.items,
281
+ {
282
+ id: 'EZ',
283
+ data: {},
284
+ type: 'widget',
285
+ namespace: 'default',
286
+ },
287
+ ], layout: [
288
+ { i: 'al', h: 4, w: 4, x: 8, y: 4 },
289
+ { i: 'Q8', h: 16, w: 12, x: 12, y: 4 },
290
+ { i: 'L5', h: 2, w: 8, x: 0, y: 4 },
291
+ { i: 'C8', h: 2, w: 8, x: 10, y: 30 },
292
+ { i: 'lko', h: 2, w: 8, x: 0, y: 12 },
293
+ { i: 'qY', h: 2, w: 8, x: 0, y: 24 },
294
+ { i: 'EZ', h: 4, w: 36, x: 0, y: 0 },
295
+ ] }));
296
+ });
297
+ it('add item with no compactType', () => {
298
+ expect(update_manager_1.UpdateManager.addItem({
299
+ item: {
300
+ data: {},
301
+ type: 'widget',
302
+ },
303
+ namespace: 'default',
304
+ layout: { h: 4, w: 36, x: 0, y: 0 },
305
+ config: config,
306
+ options: {
307
+ reflowLayoutOptions: {
308
+ defaultProps: {
309
+ cols: 36,
310
+ compactType: null,
311
+ },
312
+ },
313
+ },
314
+ })).toEqual(Object.assign(Object.assign({}, config), { salt: '0.09852189776033704', counter: 15, items: [
315
+ ...config.items,
316
+ {
317
+ id: 'EZ',
318
+ data: {},
319
+ type: 'widget',
320
+ namespace: 'default',
321
+ },
322
+ ], layout: [...config.layout, { i: 'EZ', h: 4, w: 36, x: 0, y: 0 }] }));
323
+ });
263
324
  });
264
325
  describe('removeItem', () => {
265
326
  it('remove item by id', () => {
@@ -0,0 +1,4 @@
1
+ import { Layout } from 'react-grid-layout';
2
+ import type { CompactType } from '../';
3
+ export declare const compact: (layout: Layout[], compactType: CompactType, cols: number) => Layout[];
4
+ export declare const bottom: (layout: Layout[]) => number;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.bottom = exports.compact = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const react_grid_layout_1 = tslib_1.__importDefault(require("react-grid-layout"));
6
+ const { utils } = react_grid_layout_1.default;
7
+ const compact = (layout, compactType, cols) => {
8
+ if (compactType === 'horizontal-nowrap') {
9
+ compactType = 'horizontal';
10
+ }
11
+ return utils.compact(layout, compactType, cols);
12
+ };
13
+ exports.compact = compact;
14
+ const bottom = (layout) => {
15
+ return utils.bottom(layout);
16
+ };
17
+ exports.bottom = bottom;
@@ -1,5 +1,6 @@
1
+ import { ConfigLayout } from '../shared';
1
2
  import type { Config, ConfigItem, ConfigItemGroup, ItemStateAndParams, ItemStateAndParamsChangeOptions, ItemsStateAndParams } from '../shared';
2
- import type { AddConfigItem, SetItemOptions, SetNewItemOptions, WidgetLayout } from '../typings';
3
+ import type { AddConfigItem, AddNewItemOptions, ReflowLayoutOptions, SetItemOptions, WidgetLayout } from '../typings';
3
4
  import { RegisterManagerPluginLayout } from './register-manager';
4
5
  interface RemoveItemArg {
5
6
  id: string;
@@ -20,13 +21,14 @@ export declare const getChangedParams: ({ initiatorItem, stateAndParams, itemsSt
20
21
  }) => {
21
22
  [x: string]: string | string[];
22
23
  };
24
+ export declare function reflowLayout(newLayoutItem: ConfigLayout, layout: ConfigLayout[], reflowLayoutOptions: ReflowLayoutOptions): ConfigLayout[];
23
25
  export declare class UpdateManager {
24
26
  static addItem({ item, namespace, layout, config, options, }: {
25
27
  item: AddConfigItem;
26
28
  namespace: string;
27
29
  layout: RegisterManagerPluginLayout;
28
30
  config: Config;
29
- options?: SetNewItemOptions;
31
+ options?: AddNewItemOptions;
30
32
  }): Config;
31
33
  static editItem({ item, namespace, config, options, }: {
32
34
  item: ConfigItem;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.UpdateManager = exports.getChangedParams = void 0;
3
+ exports.UpdateManager = exports.reflowLayout = exports.getChangedParams = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const immutability_helper_1 = tslib_1.__importStar(require("immutability-helper"));
6
6
  const omit_1 = tslib_1.__importDefault(require("lodash/omit"));
@@ -8,6 +8,7 @@ const pick_1 = tslib_1.__importDefault(require("lodash/pick"));
8
8
  const constants_1 = require("../constants");
9
9
  const shared_1 = require("../shared");
10
10
  const get_new_id_1 = require("./get-new-id");
11
+ const grid_layout_1 = require("./grid-layout");
11
12
  (0, immutability_helper_1.extend)('$auto', (value, object) => (object ? (0, immutability_helper_1.default)(object, value) : (0, immutability_helper_1.default)({}, value)));
12
13
  function removeItemVersion1({ id, config, itemsStateAndParams }) {
13
14
  const itemIndex = config.items.findIndex((item) => item.id === id);
@@ -226,6 +227,49 @@ function changeGroupParams({ groupItemIds, initiatorId, initiatorItem, itemsStat
226
227
  };
227
228
  return (0, immutability_helper_1.default)(itemsStateAndParams, obj);
228
229
  }
230
+ function reflowLayout(newLayoutItem, layout, reflowLayoutOptions) {
231
+ const byGroup = {};
232
+ const reducer = (memo, item, i, _items, isNewItem) => {
233
+ memo[item.i] = i;
234
+ const parent = item.parent || constants_1.DEFAULT_GROUP;
235
+ if (byGroup[parent]) {
236
+ if (isNewItem) {
237
+ byGroup[parent].unshift(item);
238
+ }
239
+ else {
240
+ byGroup[parent].push(item);
241
+ }
242
+ }
243
+ else {
244
+ byGroup[parent] = [item];
245
+ }
246
+ return memo;
247
+ };
248
+ const orderById = layout.reduce(reducer, {});
249
+ reducer(orderById, newLayoutItem, layout.length, layout, true);
250
+ const { defaultProps } = reflowLayoutOptions;
251
+ return Object.entries(byGroup)
252
+ .reduce((memo, [groupId, layoutItems]) => {
253
+ var _a;
254
+ let reflowOptions = defaultProps;
255
+ if ((_a = reflowLayoutOptions === null || reflowLayoutOptions === void 0 ? void 0 : reflowLayoutOptions.groups) === null || _a === void 0 ? void 0 : _a[groupId]) {
256
+ reflowOptions = reflowLayoutOptions === null || reflowLayoutOptions === void 0 ? void 0 : reflowLayoutOptions.groups[groupId];
257
+ }
258
+ if (reflowOptions.compactType === null) {
259
+ return memo.concat(layoutItems);
260
+ }
261
+ const compactedList = (0, grid_layout_1.compact)(layoutItems, reflowOptions.compactType, reflowOptions.cols).map((item) => {
262
+ const cleanCopy = (0, pick_1.default)(item, ['i', 'h', 'w', 'x', 'y', 'parent']);
263
+ if (groupId === constants_1.DEFAULT_GROUP) {
264
+ return cleanCopy;
265
+ }
266
+ return Object.assign(Object.assign({}, cleanCopy), { parent: groupId });
267
+ });
268
+ return memo.concat(compactedList);
269
+ }, [])
270
+ .sort((a, b) => orderById[a.i] - orderById[b.i]);
271
+ }
272
+ exports.reflowLayout = reflowLayout;
229
273
  class UpdateManager {
230
274
  static addItem({ item, namespace = constants_1.DEFAULT_NAMESPACE, layout, config, options = {}, }) {
231
275
  const salt = config.salt;
@@ -240,10 +284,32 @@ class UpdateManager {
240
284
  memo[t.i] = t;
241
285
  return memo;
242
286
  }, {});
243
- const newLayout = [
244
- ...config.layout.map((t) => (Object.assign(Object.assign({}, t), (byId[t.i] || {})))),
245
- Object.assign(Object.assign({}, saveDefaultLayout), { i: newItem.id }),
246
- ];
287
+ let newLayout;
288
+ const newLayoutItem = Object.assign(Object.assign({}, saveDefaultLayout), { i: newItem.id });
289
+ if (options.reflowLayoutOptions) {
290
+ newLayout = reflowLayout(newLayoutItem, config.layout.map((t) => (Object.assign(Object.assign({}, t), (byId[t.i] || {})))), options.reflowLayoutOptions);
291
+ }
292
+ else {
293
+ newLayout = [
294
+ ...config.layout.map((t) => (Object.assign(Object.assign({}, t), (byId[t.i] || {})))),
295
+ Object.assign(Object.assign({}, saveDefaultLayout), { i: newItem.id }),
296
+ ];
297
+ }
298
+ return (0, immutability_helper_1.default)(config, {
299
+ items: { $push: [newItem] },
300
+ layout: { $set: newLayout },
301
+ counter: { $set: counter },
302
+ });
303
+ }
304
+ else if (isFinite(layout.y)) {
305
+ let newLayout;
306
+ const newLayoutItem = Object.assign(Object.assign({}, saveDefaultLayout), { i: newItem.id });
307
+ if (options.reflowLayoutOptions) {
308
+ newLayout = reflowLayout(newLayoutItem, config.layout, options.reflowLayoutOptions);
309
+ }
310
+ else {
311
+ newLayout = [...config.layout, newLayoutItem];
312
+ }
247
313
  return (0, immutability_helper_1.default)(config, {
248
314
  items: { $push: [newItem] },
249
315
  layout: { $set: newLayout },
@@ -251,7 +317,7 @@ class UpdateManager {
251
317
  });
252
318
  }
253
319
  else {
254
- const layoutY = Math.max(0, ...config.layout.map(({ h, y }) => h + y));
320
+ const layoutY = (0, grid_layout_1.bottom)(config.layout);
255
321
  const newLayoutItem = Object.assign(Object.assign({}, saveDefaultLayout), { y: layoutY, i: newItem.id });
256
322
  return (0, immutability_helper_1.default)(config, {
257
323
  items: { $push: [newItem] },
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
- import type { Config, ConfigItem, GlobalParams, ItemDropProps, ItemsStateAndParams } from '../../shared';
3
- import { ContextProps, DashKitGroup, MenuItem, Plugin, SetConfigItem, SetNewItemOptions, Settings, SettingsProps } from '../../typings';
2
+ import type { Config, ConfigItem, ConfigLayout, GlobalParams, ItemDropProps, ItemsStateAndParams } from '../../shared';
3
+ import { AddNewItemOptions, ContextProps, DashKitGroup, MenuItem, Plugin, SetConfigItem, Settings, SettingsProps } from '../../typings';
4
4
  import GridLayout from '../GridLayout/GridLayout';
5
5
  import { OverlayControlItem } from '../OverlayControls/OverlayControls';
6
6
  interface DashKitGeneralProps {
@@ -39,11 +39,12 @@ export declare class DashKit extends React.PureComponent<DashKitInnerProps> {
39
39
  static defaultProps: DashKitDefaultProps;
40
40
  static registerPlugins(...plugins: Plugin[]): void;
41
41
  static setSettings(settings: Settings): void;
42
- static setItem({ item: setItem, namespace, config, options, }: {
42
+ static setItem({ item: setItem, namespace, config, options, groups, }: {
43
43
  item: SetConfigItem;
44
44
  namespace?: string;
45
45
  config: Config;
46
- options?: SetNewItemOptions;
46
+ options?: Omit<AddNewItemOptions, 'reflowLayoutOptions'>;
47
+ groups?: DashKitGroup[];
47
48
  }): Config;
48
49
  static removeItem({ id, config, itemsStateAndParams, }: {
49
50
  id: string;
@@ -53,6 +54,7 @@ export declare class DashKit extends React.PureComponent<DashKitInnerProps> {
53
54
  config: Config;
54
55
  itemsStateAndParams: ItemsStateAndParams;
55
56
  };
57
+ static reflowLayout(newLayoutItem: ConfigLayout, layout: ConfigLayout[], groups?: DashKitGroup[]): ConfigLayout[];
56
58
  metaRef: React.RefObject<GridLayout>;
57
59
  render(): JSX.Element;
58
60
  getItemsMeta(): any;
@@ -1,9 +1,24 @@
1
1
  import React from 'react';
2
2
  import noop from 'lodash/noop';
3
- import { DEFAULT_NAMESPACE } from '../../constants';
4
- import { RegisterManager, UpdateManager } from '../../utils';
3
+ import pick from 'lodash/pick';
4
+ import { DEFAULT_GROUP, DEFAULT_NAMESPACE } from '../../constants';
5
+ import { RegisterManager, UpdateManager, reflowLayout } from '../../utils';
5
6
  import DashKitView from '../DashKitView/DashKitView';
6
7
  const registerManager = new RegisterManager();
8
+ const getReflowProps = (props) => Object.assign({ compactType: 'vertical', cols: 36 }, pick(props, 'cols', 'maxRows', 'compactType'));
9
+ const getReflowGroupsConfig = (groups = []) => {
10
+ const defaultGridProps = getReflowProps(registerManager.gridLayout);
11
+ return {
12
+ defaultProps: defaultGridProps,
13
+ groups: groups.reduce((memo, g) => {
14
+ const groupId = g.id || DEFAULT_GROUP;
15
+ memo[groupId] = g.gridProperties
16
+ ? getReflowProps(g.gridProperties(defaultGridProps))
17
+ : defaultGridProps;
18
+ return memo;
19
+ }, {}),
20
+ };
21
+ };
7
22
  export class DashKit extends React.PureComponent {
8
23
  constructor() {
9
24
  super(...arguments);
@@ -17,28 +32,39 @@ export class DashKit extends React.PureComponent {
17
32
  static setSettings(settings) {
18
33
  registerManager.setSettings(settings);
19
34
  }
20
- static setItem({ item: setItem, namespace = DEFAULT_NAMESPACE, config, options = {}, }) {
35
+ static setItem({ item: setItem, namespace = DEFAULT_NAMESPACE, config, options = {}, groups = [], }) {
21
36
  if (setItem.id) {
22
- return UpdateManager.editItem({ item: setItem, namespace, config, options });
37
+ return UpdateManager.editItem({
38
+ item: setItem,
39
+ namespace,
40
+ config,
41
+ options,
42
+ });
23
43
  }
24
44
  else {
25
45
  const item = setItem;
26
46
  const layout = Object.assign({}, registerManager.getItem(item.type).defaultLayout);
27
- if (item.layout) {
28
- Object.assign(layout, item.layout);
47
+ const reflowLayoutOptions = getReflowGroupsConfig(groups);
48
+ const copyItem = Object.assign({}, item);
49
+ if (copyItem.layout) {
50
+ Object.assign(layout, copyItem.layout);
51
+ delete copyItem.layout;
29
52
  }
30
53
  return UpdateManager.addItem({
31
- item,
54
+ item: copyItem,
32
55
  namespace,
33
56
  config,
34
57
  layout,
35
- options,
58
+ options: Object.assign(Object.assign({}, options), { reflowLayoutOptions }),
36
59
  });
37
60
  }
38
61
  }
39
62
  static removeItem({ id, config, itemsStateAndParams, }) {
40
63
  return UpdateManager.removeItem({ id, config, itemsStateAndParams });
41
64
  }
65
+ static reflowLayout(newLayoutItem, layout, groups) {
66
+ return reflowLayout(newLayoutItem, layout, getReflowGroupsConfig(groups));
67
+ }
42
68
  render() {
43
69
  return React.createElement(DashKitView, Object.assign({ registerManager: registerManager, ref: this.metaRef }, this.props));
44
70
  }
@@ -38,7 +38,7 @@ export default class GridLayout extends React.PureComponent<any, any, any> {
38
38
  getMemoForwardRefCallback: (refIndex: any) => any;
39
39
  mergeGroupsLayout(group: any, newLayout: any, temporaryItem: any): any;
40
40
  reloadItems(): void;
41
- _onDragStart: (group: any, _newLayout: any, layoutItem: any) => void;
41
+ _onDragStart: (group: any, _newLayout: any, layoutItem: any, _newItem: any, _placeholder: any, e: any) => void;
42
42
  _onResizeStart: () => void;
43
43
  _onTargetRestore: () => void;
44
44
  _onStop: (group: any, newLayout: any) => void;
@@ -91,7 +91,7 @@ class GridLayout extends React.PureComponent {
91
91
  }
92
92
  return this._memoForwardedPluginRef[refIndex];
93
93
  };
94
- this._onDragStart = (group, _newLayout, layoutItem) => {
94
+ this._onDragStart = (group, _newLayout, layoutItem, _newItem, _placeholder, e) => {
95
95
  if (this.temporaryLayout)
96
96
  return;
97
97
  if (this.context.dragOverPlugin) {
@@ -100,9 +100,15 @@ class GridLayout extends React.PureComponent {
100
100
  else {
101
101
  let currentDraggingElement = this.state.currentDraggingElement;
102
102
  if (!currentDraggingElement) {
103
- const _id = layoutItem.i;
104
- const item = this.context.config.items.find(({ id }) => id === _id);
105
- currentDraggingElement = [group, layoutItem, item];
103
+ const layoutId = layoutItem.i;
104
+ const item = this.context.config.items.find(({ id }) => id === layoutId);
105
+ const { offsetX, offsetY } = e.nativeEvent;
106
+ currentDraggingElement = {
107
+ group,
108
+ layoutItem,
109
+ item,
110
+ cursorPosition: { offsetX, offsetY },
111
+ };
106
112
  }
107
113
  this.setState({
108
114
  isDragging: true,
@@ -120,7 +126,7 @@ class GridLayout extends React.PureComponent {
120
126
  const { currentDraggingElement } = this.state;
121
127
  if (currentDraggingElement) {
122
128
  this.setState({
123
- draggedOverGroup: currentDraggingElement[0],
129
+ draggedOverGroup: currentDraggingElement.group,
124
130
  });
125
131
  }
126
132
  };
@@ -129,7 +135,7 @@ class GridLayout extends React.PureComponent {
129
135
  const { draggedOverGroup, currentDraggingElement } = this.state;
130
136
  if (currentDraggingElement &&
131
137
  draggedOverGroup !== null &&
132
- draggedOverGroup !== currentDraggingElement[0]) {
138
+ draggedOverGroup !== currentDraggingElement.group) {
133
139
  // Skipping layout update when change event called for source grid
134
140
  // and waiting _onDrop
135
141
  return;
@@ -153,15 +159,15 @@ class GridLayout extends React.PureComponent {
153
159
  if (!currentDraggingElement) {
154
160
  return;
155
161
  }
156
- const [, sourceItem] = currentDraggingElement;
162
+ const sourceLayoutItem = currentDraggingElement.layoutItem;
157
163
  const groupedLayout = this.mergeGroupsLayout(targetGroup, newLayout, tempItem).map((item) => {
158
- if (item.i === sourceItem.i) {
164
+ if (item.i === sourceLayoutItem.i) {
159
165
  const copy = Object.assign({}, tempItem);
160
166
  delete copy.parent;
161
167
  if (targetGroup !== DEFAULT_GROUP) {
162
168
  copy.parent = targetGroup;
163
169
  }
164
- copy.i = sourceItem.i;
170
+ copy.i = sourceLayoutItem.i;
165
171
  return copy;
166
172
  }
167
173
  return item;
@@ -202,7 +208,8 @@ class GridLayout extends React.PureComponent {
202
208
  }
203
209
  const { properties, layout } = this.getLayoutAndPropsByGroup(group);
204
210
  if (currentDraggingElement) {
205
- const [, { h, w, i }, { type }] = currentDraggingElement;
211
+ const { h, w, i } = currentDraggingElement.layoutItem;
212
+ const { type } = currentDraggingElement.item;
206
213
  return onDropDragOver(e, properties, layout, { h, w, i, type });
207
214
  }
208
215
  if (dragOverPlugin) {
@@ -294,12 +301,12 @@ class GridLayout extends React.PureComponent {
294
301
  compactType = 'horizontal';
295
302
  }
296
303
  const { callbacks, layout } = this.getMemoGroupProps(group, renderLayout, properties);
297
- const hasSharedDragItem = Boolean(currentDraggingElement && currentDraggingElement[0] !== group);
304
+ const hasSharedDragItem = Boolean(currentDraggingElement && currentDraggingElement.group !== group);
298
305
  const isDragCaptured = currentDraggingElement &&
299
- group === currentDraggingElement[0] &&
306
+ group === currentDraggingElement.group &&
300
307
  draggedOverGroup !== null &&
301
308
  draggedOverGroup !== group;
302
- return (React.createElement(Layout, Object.assign({}, properties, { compactType: compactType, layout: layout, key: `group_${group}`, isDraggable: editMode, isResizable: editMode, onResizeStart: this._onResizeStart, onDragStart: callbacks.onDragStart, onDragStop: callbacks.onDragStop, onResizeStop: callbacks.onResizeStop, onDragTargetRestore: callbacks.onDragTargetRestore, onDropDragOver: callbacks.onDropDragOver, onDrop: callbacks.onDrop, hasSharedDragItem: hasSharedDragItem, isDragCaptured: isDragCaptured }, (draggableHandleClassName
309
+ return (React.createElement(Layout, Object.assign({}, properties, { compactType: compactType, layout: layout, key: `group_${group}`, isDraggable: editMode, isResizable: editMode, onResizeStart: this._onResizeStart, onDragStart: callbacks.onDragStart, onDragStop: callbacks.onDragStop, onResizeStop: callbacks.onResizeStop, onDragTargetRestore: callbacks.onDragTargetRestore, onDropDragOver: callbacks.onDropDragOver, onDrop: callbacks.onDrop, hasSharedDragItem: hasSharedDragItem, sharedDragPosition: currentDraggingElement === null || currentDraggingElement === void 0 ? void 0 : currentDraggingElement.cursorPosition, isDragCaptured: isDragCaptured }, (draggableHandleClassName
303
310
  ? { draggableHandle: `.${draggableHandleClassName}` }
304
311
  : null), (outerDnDEnable
305
312
  ? {
@@ -166,15 +166,23 @@ class DragOverLayout extends ReactGridLayout {
166
166
  const { props: itemProps } = gridItem;
167
167
  if (isDroppingItem) {
168
168
  const { containerWidth, cols, w, h, rowHeight, margin, transformScale, droppingPosition, } = itemProps;
169
- const leftOffset = (((containerWidth / cols) * w) / 2 || 0) * transformScale;
170
- const topOffset = ((h * rowHeight + (h - 1) * margin[1]) / 2 || 0) * transformScale;
169
+ const { sharedDragPosition, hasSharedDragItem } = this.props;
170
+ let offsetX, offsetY;
171
+ if (sharedDragPosition) {
172
+ offsetX = sharedDragPosition.offsetX;
173
+ offsetY = sharedDragPosition.offsetY;
174
+ }
175
+ else {
176
+ offsetX = (((containerWidth / cols) * w) / 2 || 0) * transformScale;
177
+ offsetY = ((h * rowHeight + (h - 1) * margin[1]) / 2 || 0) * transformScale;
178
+ }
171
179
  const style = gridItem.props.style || null;
172
180
  // React.cloneElement is just cleaner then copy-paste whole processGridItem method
173
181
  return React.cloneElement(gridItem, {
174
182
  // hiding previre if dragging shared item
175
- style: this.props.hasSharedDragItem ? Object.assign(Object.assign({}, style), { opacity: 0 }) : style,
183
+ style: hasSharedDragItem ? Object.assign(Object.assign({}, style), { opacity: 0 }) : style,
176
184
  className: OVERLAY_CLASS_NAME,
177
- droppingPosition: Object.assign(Object.assign({}, droppingPosition), { left: droppingPosition.left - leftOffset, top: droppingPosition.top - topOffset }),
185
+ droppingPosition: Object.assign(Object.assign({}, droppingPosition), { left: droppingPosition.left - offsetX, top: droppingPosition.top - offsetY }),
178
186
  });
179
187
  }
180
188
  return gridItem;
@@ -125,7 +125,7 @@ function useMemoStateContext(props) {
125
125
  React.useMemo(() => {
126
126
  const groups = props.groups;
127
127
  const layout = props.layout;
128
- const defaultProps = props.registerManager._gridLayout || {};
128
+ const defaultProps = props.registerManager.gridLayout || {};
129
129
  const nowrapGroups = {};
130
130
  let hasNowrapGroups = false;
131
131
  if (defaultProps.compactType === COMPACT_TYPE_HORIZONTAL_NOWRAP) {
@@ -4,14 +4,26 @@ import type { Config, ConfigItem, ConfigLayout } from '../shared';
4
4
  export interface AddConfigItem extends Omit<ConfigItem, 'id' | 'namespace'> {
5
5
  id?: null;
6
6
  namespace?: string;
7
- layout?: ConfigLayout;
7
+ layout?: Omit<ConfigLayout, 'i'>;
8
8
  }
9
9
  export type SetConfigItem = ConfigItem | AddConfigItem;
10
10
  export type SetItemOptions = {
11
11
  excludeIds?: string[];
12
+ };
13
+ export type GridReflowOptions = {
14
+ cols: number;
15
+ maxRows?: number;
16
+ compactType?: CompactType;
17
+ };
18
+ export type CompactType = ReactGridLayout.ReactGridLayoutProps['compactType'] | 'horizontal-nowrap';
19
+ export type ReflowLayoutOptions = {
20
+ defaultProps: GridReflowOptions;
21
+ groups?: Record<string, GridReflowOptions>;
22
+ };
23
+ export type AddNewItemOptions = SetItemOptions & {
12
24
  updateLayout?: ConfigLayout[];
25
+ reflowLayoutOptions?: ReflowLayoutOptions;
13
26
  };
14
- export type SetNewItemOptions = SetItemOptions;
15
27
  export interface DashkitGroupRenderProps {
16
28
  config: Config;
17
29
  editMode: boolean;
@@ -19,7 +31,7 @@ export interface DashkitGroupRenderProps {
19
31
  layout: ConfigLayout[];
20
32
  }
21
33
  export type ReactGridLayoutProps = Omit<ReactGridLayout.ReactGridLayoutProps, 'children' | 'compactType' | 'innerRef' | 'key' | 'layout' | 'isDraggable' | 'isResizable' | 'onDragStart' | 'onDragStop' | 'onResizeStart' | 'onResizeStop' | 'draggableHandle' | 'isDroppable' | 'onDropDragOver' | 'onDrop' | 'draggableCancel'> & {
22
- compactType?: ReactGridLayout.ReactGridLayoutProps['compactType'] | 'horizontal-nowrap';
34
+ compactType?: CompactType;
23
35
  };
24
36
  export interface DashKitGroup {
25
37
  id?: string;
@@ -194,7 +194,7 @@ describe('UpdateManager', () => {
194
194
  type: 'widget',
195
195
  },
196
196
  namespace: 'default',
197
- layout: { h: 4, w: 4, x: 0, y: 0 },
197
+ layout: { h: 4, w: 4, x: 0, y: Infinity },
198
198
  config: config,
199
199
  })).toEqual(Object.assign(Object.assign({}, config), { salt: '0.09852189776033704', counter: 15, items: [
200
200
  ...config.items,
@@ -213,7 +213,7 @@ describe('UpdateManager', () => {
213
213
  type: 'widget',
214
214
  },
215
215
  namespace: 'default',
216
- layout: { h: 4, w: 4, x: 0, y: 0 },
216
+ layout: { h: 4, w: 4, x: 0, y: Infinity },
217
217
  config: config,
218
218
  options: {
219
219
  excludeIds: ['EZ'],
@@ -258,6 +258,67 @@ describe('UpdateManager', () => {
258
258
  },
259
259
  ], layout: [...updateLayout, { h: 4, w: 12, x: 0, y: 0, i: 'EZ' }] }));
260
260
  });
261
+ it('prepend new item', () => {
262
+ expect(UpdateManager.addItem({
263
+ item: {
264
+ data: {},
265
+ type: 'widget',
266
+ },
267
+ namespace: 'default',
268
+ layout: { h: 4, w: 36, x: 0, y: 0 },
269
+ config: config,
270
+ options: {
271
+ reflowLayoutOptions: {
272
+ defaultProps: {
273
+ cols: 36,
274
+ },
275
+ },
276
+ },
277
+ })).toEqual(Object.assign(Object.assign({}, config), { salt: '0.09852189776033704', counter: 15, items: [
278
+ ...config.items,
279
+ {
280
+ id: 'EZ',
281
+ data: {},
282
+ type: 'widget',
283
+ namespace: 'default',
284
+ },
285
+ ], layout: [
286
+ { i: 'al', h: 4, w: 4, x: 8, y: 4 },
287
+ { i: 'Q8', h: 16, w: 12, x: 12, y: 4 },
288
+ { i: 'L5', h: 2, w: 8, x: 0, y: 4 },
289
+ { i: 'C8', h: 2, w: 8, x: 10, y: 30 },
290
+ { i: 'lko', h: 2, w: 8, x: 0, y: 12 },
291
+ { i: 'qY', h: 2, w: 8, x: 0, y: 24 },
292
+ { i: 'EZ', h: 4, w: 36, x: 0, y: 0 },
293
+ ] }));
294
+ });
295
+ it('add item with no compactType', () => {
296
+ expect(UpdateManager.addItem({
297
+ item: {
298
+ data: {},
299
+ type: 'widget',
300
+ },
301
+ namespace: 'default',
302
+ layout: { h: 4, w: 36, x: 0, y: 0 },
303
+ config: config,
304
+ options: {
305
+ reflowLayoutOptions: {
306
+ defaultProps: {
307
+ cols: 36,
308
+ compactType: null,
309
+ },
310
+ },
311
+ },
312
+ })).toEqual(Object.assign(Object.assign({}, config), { salt: '0.09852189776033704', counter: 15, items: [
313
+ ...config.items,
314
+ {
315
+ id: 'EZ',
316
+ data: {},
317
+ type: 'widget',
318
+ namespace: 'default',
319
+ },
320
+ ], layout: [...config.layout, { i: 'EZ', h: 4, w: 36, x: 0, y: 0 }] }));
321
+ });
261
322
  });
262
323
  describe('removeItem', () => {
263
324
  it('remove item by id', () => {
@@ -0,0 +1,4 @@
1
+ import { Layout } from 'react-grid-layout';
2
+ import type { CompactType } from '../';
3
+ export declare const compact: (layout: Layout[], compactType: CompactType, cols: number) => Layout[];
4
+ export declare const bottom: (layout: Layout[]) => number;
@@ -0,0 +1,11 @@
1
+ import gridLayout from 'react-grid-layout';
2
+ const { utils } = gridLayout;
3
+ export const compact = (layout, compactType, cols) => {
4
+ if (compactType === 'horizontal-nowrap') {
5
+ compactType = 'horizontal';
6
+ }
7
+ return utils.compact(layout, compactType, cols);
8
+ };
9
+ export const bottom = (layout) => {
10
+ return utils.bottom(layout);
11
+ };
@@ -1,5 +1,6 @@
1
+ import { ConfigLayout } from '../shared';
1
2
  import type { Config, ConfigItem, ConfigItemGroup, ItemStateAndParams, ItemStateAndParamsChangeOptions, ItemsStateAndParams } from '../shared';
2
- import type { AddConfigItem, SetItemOptions, SetNewItemOptions, WidgetLayout } from '../typings';
3
+ import type { AddConfigItem, AddNewItemOptions, ReflowLayoutOptions, SetItemOptions, WidgetLayout } from '../typings';
3
4
  import { RegisterManagerPluginLayout } from './register-manager';
4
5
  interface RemoveItemArg {
5
6
  id: string;
@@ -20,13 +21,14 @@ export declare const getChangedParams: ({ initiatorItem, stateAndParams, itemsSt
20
21
  }) => {
21
22
  [x: string]: string | string[];
22
23
  };
24
+ export declare function reflowLayout(newLayoutItem: ConfigLayout, layout: ConfigLayout[], reflowLayoutOptions: ReflowLayoutOptions): ConfigLayout[];
23
25
  export declare class UpdateManager {
24
26
  static addItem({ item, namespace, layout, config, options, }: {
25
27
  item: AddConfigItem;
26
28
  namespace: string;
27
29
  layout: RegisterManagerPluginLayout;
28
30
  config: Config;
29
- options?: SetNewItemOptions;
31
+ options?: AddNewItemOptions;
30
32
  }): Config;
31
33
  static editItem({ item, namespace, config, options, }: {
32
34
  item: ConfigItem;
@@ -1,9 +1,10 @@
1
1
  import update, { extend } from 'immutability-helper';
2
2
  import omit from 'lodash/omit';
3
3
  import pick from 'lodash/pick';
4
- import { DEFAULT_NAMESPACE } from '../constants';
4
+ import { DEFAULT_GROUP, DEFAULT_NAMESPACE } from '../constants';
5
5
  import { META_KEY, addGroupToQueue, addToQueue, deleteFromQueue, getCurrentVersion, getInitialItemsStateAndParamsMeta, getItemsStateAndParamsMeta, isItemWithGroup, isItemWithTabs, mergeParamsWithAliases, pickActionParamsFromParams, resolveItemInnerId, transformParamsToActionParams, } from '../shared';
6
6
  import { getNewId } from './get-new-id';
7
+ import { bottom, compact } from './grid-layout';
7
8
  extend('$auto', (value, object) => (object ? update(object, value) : update({}, value)));
8
9
  function removeItemVersion1({ id, config, itemsStateAndParams }) {
9
10
  const itemIndex = config.items.findIndex((item) => item.id === id);
@@ -221,6 +222,48 @@ function changeGroupParams({ groupItemIds, initiatorId, initiatorItem, itemsStat
221
222
  };
222
223
  return update(itemsStateAndParams, obj);
223
224
  }
225
+ export function reflowLayout(newLayoutItem, layout, reflowLayoutOptions) {
226
+ const byGroup = {};
227
+ const reducer = (memo, item, i, _items, isNewItem) => {
228
+ memo[item.i] = i;
229
+ const parent = item.parent || DEFAULT_GROUP;
230
+ if (byGroup[parent]) {
231
+ if (isNewItem) {
232
+ byGroup[parent].unshift(item);
233
+ }
234
+ else {
235
+ byGroup[parent].push(item);
236
+ }
237
+ }
238
+ else {
239
+ byGroup[parent] = [item];
240
+ }
241
+ return memo;
242
+ };
243
+ const orderById = layout.reduce(reducer, {});
244
+ reducer(orderById, newLayoutItem, layout.length, layout, true);
245
+ const { defaultProps } = reflowLayoutOptions;
246
+ return Object.entries(byGroup)
247
+ .reduce((memo, [groupId, layoutItems]) => {
248
+ var _a;
249
+ let reflowOptions = defaultProps;
250
+ if ((_a = reflowLayoutOptions === null || reflowLayoutOptions === void 0 ? void 0 : reflowLayoutOptions.groups) === null || _a === void 0 ? void 0 : _a[groupId]) {
251
+ reflowOptions = reflowLayoutOptions === null || reflowLayoutOptions === void 0 ? void 0 : reflowLayoutOptions.groups[groupId];
252
+ }
253
+ if (reflowOptions.compactType === null) {
254
+ return memo.concat(layoutItems);
255
+ }
256
+ const compactedList = compact(layoutItems, reflowOptions.compactType, reflowOptions.cols).map((item) => {
257
+ const cleanCopy = pick(item, ['i', 'h', 'w', 'x', 'y', 'parent']);
258
+ if (groupId === DEFAULT_GROUP) {
259
+ return cleanCopy;
260
+ }
261
+ return Object.assign(Object.assign({}, cleanCopy), { parent: groupId });
262
+ });
263
+ return memo.concat(compactedList);
264
+ }, [])
265
+ .sort((a, b) => orderById[a.i] - orderById[b.i]);
266
+ }
224
267
  export class UpdateManager {
225
268
  static addItem({ item, namespace = DEFAULT_NAMESPACE, layout, config, options = {}, }) {
226
269
  const salt = config.salt;
@@ -235,10 +278,32 @@ export class UpdateManager {
235
278
  memo[t.i] = t;
236
279
  return memo;
237
280
  }, {});
238
- const newLayout = [
239
- ...config.layout.map((t) => (Object.assign(Object.assign({}, t), (byId[t.i] || {})))),
240
- Object.assign(Object.assign({}, saveDefaultLayout), { i: newItem.id }),
241
- ];
281
+ let newLayout;
282
+ const newLayoutItem = Object.assign(Object.assign({}, saveDefaultLayout), { i: newItem.id });
283
+ if (options.reflowLayoutOptions) {
284
+ newLayout = reflowLayout(newLayoutItem, config.layout.map((t) => (Object.assign(Object.assign({}, t), (byId[t.i] || {})))), options.reflowLayoutOptions);
285
+ }
286
+ else {
287
+ newLayout = [
288
+ ...config.layout.map((t) => (Object.assign(Object.assign({}, t), (byId[t.i] || {})))),
289
+ Object.assign(Object.assign({}, saveDefaultLayout), { i: newItem.id }),
290
+ ];
291
+ }
292
+ return update(config, {
293
+ items: { $push: [newItem] },
294
+ layout: { $set: newLayout },
295
+ counter: { $set: counter },
296
+ });
297
+ }
298
+ else if (isFinite(layout.y)) {
299
+ let newLayout;
300
+ const newLayoutItem = Object.assign(Object.assign({}, saveDefaultLayout), { i: newItem.id });
301
+ if (options.reflowLayoutOptions) {
302
+ newLayout = reflowLayout(newLayoutItem, config.layout, options.reflowLayoutOptions);
303
+ }
304
+ else {
305
+ newLayout = [...config.layout, newLayoutItem];
306
+ }
242
307
  return update(config, {
243
308
  items: { $push: [newItem] },
244
309
  layout: { $set: newLayout },
@@ -246,7 +311,7 @@ export class UpdateManager {
246
311
  });
247
312
  }
248
313
  else {
249
- const layoutY = Math.max(0, ...config.layout.map(({ h, y }) => h + y));
314
+ const layoutY = bottom(config.layout);
250
315
  const newLayoutItem = Object.assign(Object.assign({}, saveDefaultLayout), { y: layoutY, i: newItem.id });
251
316
  return update(config, {
252
317
  items: { $push: [newItem] },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/dashkit",
3
- "version": "8.11.0",
3
+ "version": "8.12.1",
4
4
  "description": "Library for rendering dashboard grid layout",
5
5
  "license": "MIT",
6
6
  "repository": {