@gravity-ui/dashkit 8.5.0 → 8.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +11 -4
  2. package/build/cjs/components/DashKit/DashKit.d.ts +3 -2
  3. package/build/cjs/components/DashKitView/DashKitView.js +2 -2
  4. package/build/cjs/components/GridItem/GridItem.js +2 -4
  5. package/build/cjs/components/GridLayout/GridLayout.d.ts +16 -1
  6. package/build/cjs/components/GridLayout/GridLayout.js +81 -10
  7. package/build/cjs/components/Item/Item.d.ts +4 -0
  8. package/build/cjs/components/OverlayControls/OverlayControls.d.ts +8 -6
  9. package/build/cjs/components/OverlayControls/OverlayControls.js +58 -42
  10. package/build/cjs/constants/common.d.ts +1 -0
  11. package/build/cjs/constants/common.js +2 -1
  12. package/build/cjs/context/DashKitContext.d.ts +1 -0
  13. package/build/cjs/context/DashKitContext.js +3 -1
  14. package/build/cjs/hocs/prepareItem.d.ts +4 -0
  15. package/build/cjs/hocs/prepareItem.js +33 -22
  16. package/build/cjs/hocs/withContext.js +129 -21
  17. package/build/cjs/hooks/useDeepEqualMemo.d.ts +2 -0
  18. package/build/cjs/hooks/useDeepEqualMemo.js +17 -0
  19. package/build/cjs/typings/config.d.ts +3 -1
  20. package/build/esm/components/DashKit/DashKit.d.ts +3 -2
  21. package/build/esm/components/DashKitView/DashKitView.js +2 -2
  22. package/build/esm/components/GridItem/GridItem.js +2 -4
  23. package/build/esm/components/GridLayout/GridLayout.d.ts +16 -1
  24. package/build/esm/components/GridLayout/GridLayout.js +82 -11
  25. package/build/esm/components/Item/Item.d.ts +4 -0
  26. package/build/esm/components/OverlayControls/OverlayControls.d.ts +8 -6
  27. package/build/esm/components/OverlayControls/OverlayControls.js +59 -43
  28. package/build/esm/constants/common.d.ts +1 -0
  29. package/build/esm/constants/common.js +1 -0
  30. package/build/esm/context/DashKitContext.d.ts +1 -0
  31. package/build/esm/context/DashKitContext.js +2 -1
  32. package/build/esm/hocs/prepareItem.d.ts +4 -0
  33. package/build/esm/hocs/prepareItem.js +33 -22
  34. package/build/esm/hocs/withContext.js +132 -23
  35. package/build/esm/hooks/useDeepEqualMemo.d.ts +2 -0
  36. package/build/esm/hooks/useDeepEqualMemo.js +12 -0
  37. package/build/esm/typings/config.d.ts +3 -1
  38. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import { ButtonSize, ButtonView, IconProps, MenuItemProps } from '@gravity-ui/uikit';
3
- import type { Config, ConfigItem, ItemState, PluginBase, StringParams } from '../../shared';
4
- import type { RegisterManager } from '../../utils/register-manager';
3
+ import type { ConfigItem, ConfigLayout, ItemState, PluginBase, StringParams } from '../../shared';
4
+ import { MenuItem } from '../../typings';
5
5
  import './OverlayControls.css';
6
6
  export declare enum OverlayControlsPosition {
7
7
  TopRight = "top_right",
@@ -14,6 +14,7 @@ export interface OverlayControlItem {
14
14
  icon?: IconProps['data'];
15
15
  iconSize?: number | string;
16
16
  handler?: (item: ConfigItem) => void;
17
+ visible?: (item: ConfigItem) => boolean;
17
18
  allWidgetsControls?: boolean;
18
19
  excludeWidgetsTypes?: Array<PluginBase['type']>;
19
20
  id?: string;
@@ -36,8 +37,6 @@ interface OverlayControlsDefaultProps {
36
37
  }
37
38
  interface OverlayControlsProps extends OverlayControlsDefaultProps {
38
39
  configItem: ConfigItem;
39
- items?: OverlayControlItem[];
40
- overlayControls?: Record<string, OverlayControlItem[]>;
41
40
  }
42
41
  type PreparedCopyItemOptionsArg = Pick<ConfigItem, 'data' | 'type' | 'defaults' | 'namespace'> & {
43
42
  timestamp: number;
@@ -50,21 +49,24 @@ export type PreparedCopyItemOptions<C extends object = {}> = PreparedCopyItemOpt
50
49
  copyContext?: C;
51
50
  };
52
51
  type DashKitCtx = React.Context<{
52
+ overlayControls?: Record<string, OverlayControlItem[]>;
53
53
  context: Record<string, any>;
54
- registerManager: RegisterManager;
54
+ menu: MenuItem[];
55
55
  itemsParams: Record<string, StringParams>;
56
56
  itemsState: Record<string, ItemState>;
57
57
  editItem: (item: ConfigItem) => void;
58
58
  removeItem: (id: string) => void;
59
- config: Config;
59
+ getLayoutItem: (id: string) => ConfigLayout | void;
60
60
  }>;
61
61
  declare class OverlayControls extends React.Component<OverlayControlsProps> {
62
62
  static contextType: React.Context<any>;
63
63
  static defaultProps: OverlayControlsDefaultProps;
64
64
  context: React.ContextType<DashKitCtx>;
65
65
  render(): JSX.Element;
66
+ private getItems;
66
67
  private renderControlsItem;
67
68
  private getDropDownMenuItemConfig;
69
+ private getDefaultControls;
68
70
  private renderControls;
69
71
  private renderMenu;
70
72
  private isDefaultMenu;
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { Button, DropdownMenu, Icon, } from '@gravity-ui/uikit';
3
3
  import noop from 'lodash/noop';
4
4
  import { COPIED_WIDGET_STORE_KEY, MenuItems, OVERLAY_CONTROLS_CLASS_NAME } from '../../constants';
5
- import { DashKitContext } from '../../context/DashKitContext';
5
+ import { DashkitOvelayControlsContext } from '../../context/DashKitContext';
6
6
  import { i18n } from '../../i18n';
7
7
  import { CloseIcon } from '../../icons/CloseIcon';
8
8
  import { CogIcon } from '../../icons/CogIcon';
@@ -21,6 +21,11 @@ const DEFAULT_DROPDOWN_MENU = [MenuItems.Copy, MenuItems.Delete];
21
21
  class OverlayControls extends React.Component {
22
22
  constructor() {
23
23
  super(...arguments);
24
+ this.getItems = () => {
25
+ const { overlayControls } = this.context;
26
+ const { configItem } = this.props;
27
+ return (overlayControls && overlayControls[configItem.type]) || [];
28
+ };
24
29
  this.renderControlsItem = (item, index, length) => {
25
30
  const { view, size } = this.props;
26
31
  const { title, handler, icon, iconSize, qa } = item;
@@ -28,18 +33,28 @@ class OverlayControls extends React.Component {
28
33
  return (React.createElement(Button, { key: index, view: view, size: size, title: title, pin: this.getControlItemPinStyle(index, length), onClick: () => onItemClickHandler(this.props.configItem), qa: qa },
29
34
  React.createElement(Icon, { data: icon || CogIcon, size: icon ? iconSize : 24 })));
30
35
  };
36
+ this.getDefaultControls = () => {
37
+ const { view, size } = this.props;
38
+ if (this.context.overlayControls === null) {
39
+ return null;
40
+ }
41
+ return (React.createElement(Button, { view: view, size: size, title: i18n('label_settings'), pin: "round-brick", onClick: this.onEditItem, qa: "dashkit-overlay-control-settings" },
42
+ React.createElement(Icon, { data: CogIcon, size: "24" })));
43
+ };
31
44
  this.getCustomLeftOverlayControls = () => {
32
45
  // выбираем только items-ы у которых проставлено поле `allWidgetsControls:true`
33
46
  // те контролы, которые будут показываться слева от меню
34
47
  let controls = [];
35
- for (const controlItem of Object.values(this.props.overlayControls || {})) {
48
+ for (const controlItem of Object.values(this.context.overlayControls || {})) {
36
49
  controls = controls.concat((controlItem.filter((item) => {
37
50
  var _a;
38
51
  // если тип виджета-плагина в списке исключаемых, то не показываем такой контрол
39
52
  if ((_a = item.excludeWidgetsTypes) === null || _a === void 0 ? void 0 : _a.includes(this.props.configItem.type)) {
40
53
  return false;
41
54
  }
42
- return item.allWidgetsControls;
55
+ return item.visible
56
+ ? item.visible(this.props.configItem)
57
+ : item.allWidgetsControls;
43
58
  }) || []).map((item) => {
44
59
  var _a;
45
60
  if (!(item === null || item === void 0 ? void 0 : item.id)) {
@@ -54,15 +69,14 @@ class OverlayControls extends React.Component {
54
69
  };
55
70
  this.onCopyItem = () => {
56
71
  var _a;
57
- const correspondedItemLayout = this.context.config.layout.find((item) => {
58
- return item.i === this.props.configItem.id;
59
- });
72
+ const { configItem } = this.props;
73
+ const correspondedItemLayout = this.context.getLayoutItem(configItem.id);
60
74
  let options = {
61
75
  timestamp: Date.now(),
62
- data: this.props.configItem.data,
63
- type: this.props.configItem.type,
64
- defaults: this.props.configItem.defaults,
65
- namespace: this.props.configItem.namespace,
76
+ data: configItem.data,
77
+ type: configItem.type,
78
+ defaults: configItem.defaults,
79
+ namespace: configItem.namespace,
66
80
  layout: {
67
81
  w: correspondedItemLayout.w,
68
82
  h: correspondedItemLayout.h,
@@ -84,7 +98,8 @@ class OverlayControls extends React.Component {
84
98
  };
85
99
  }
86
100
  render() {
87
- const { items = [], position } = this.props;
101
+ const { position } = this.props;
102
+ const items = this.getItems();
88
103
  const hasCustomControlsWithWidgets = items.length > 0;
89
104
  const controls = hasCustomControlsWithWidgets
90
105
  ? this.getCustomControlsWithWidgets()
@@ -119,11 +134,9 @@ class OverlayControls extends React.Component {
119
134
  return null;
120
135
  }
121
136
  renderControls() {
122
- const { view, size } = this.props;
123
137
  const customLeftControls = this.getCustomLeftOverlayControls();
124
138
  const hasCustomOverlayLeftControls = Boolean(customLeftControls.length);
125
- const defaultControl = (React.createElement(Button, { view: view, size: size, title: i18n('label_settings'), pin: "round-brick", onClick: this.onEditItem, qa: "dashkit-overlay-control-settings" },
126
- React.createElement(Icon, { data: CogIcon, size: "24" })));
139
+ const defaultControl = this.getDefaultControls();
127
140
  const controls = hasCustomOverlayLeftControls
128
141
  ? customLeftControls.map((item, index, items) => this.renderControlsItem(item, index, items.length + 1))
129
142
  : defaultControl;
@@ -136,50 +149,58 @@ class OverlayControls extends React.Component {
136
149
  }
137
150
  renderMenu() {
138
151
  const { view, size } = this.props;
139
- const { registerManager } = this.context;
140
- const withMenu = Array.isArray(registerManager.settings.menu) && registerManager.settings.menu.length;
141
- return (React.createElement(React.Fragment, null, withMenu ? (this.renderDropdownMenu()) : (React.createElement(Button, { view: view, size: size, title: i18n('label_delete'), pin: "brick-round", onClick: this.onRemoveItem, qa: "dashkit-overlay-control-menu" },
142
- React.createElement(Icon, { data: CloseIcon, size: "12" })))));
152
+ const dropdown = this.renderDropdownMenu();
153
+ if (dropdown) {
154
+ return dropdown;
155
+ }
156
+ return (React.createElement(Button, { key: 'delete-control', view: view, size: size, title: i18n('label_delete'), pin: "brick-round", onClick: this.onRemoveItem, qa: "dashkit-overlay-control-menu" },
157
+ React.createElement(Icon, { data: CloseIcon, size: "12" })));
143
158
  }
144
159
  isDefaultMenu(menu) {
145
160
  return menu === null || menu === void 0 ? void 0 : menu.every((item) => Object.values(MenuItems).includes(String(item)));
146
161
  }
147
162
  renderDropdownMenu() {
148
163
  const { view, size } = this.props;
149
- const { registerManager, itemsParams, itemsState } = this.context;
164
+ const { menu: contextMenu, itemsParams, itemsState } = this.context;
150
165
  const configItem = this.props.configItem;
151
166
  const itemParams = itemsParams[configItem.id];
152
167
  const itemState = itemsState[configItem.id];
153
- let menu = registerManager.settings.menu;
154
- if (!menu.length) {
155
- menu = DEFAULT_DROPDOWN_MENU;
156
- }
168
+ const menu = (contextMenu === null || contextMenu === void 0 ? void 0 : contextMenu.length) > 0 ? contextMenu : DEFAULT_DROPDOWN_MENU;
157
169
  const isDefaultMenu = this.isDefaultMenu(menu);
158
- let items = isDefaultMenu
159
- ? (menu || []).map((name) => this.getDropDownMenuItemConfig(name, true))
160
- : menu.map((item) => {
170
+ const items = isDefaultMenu
171
+ ? (menu || []).reduce((memo, name) => {
172
+ const item = this.getDropDownMenuItemConfig(name, true);
173
+ if (item) {
174
+ memo.push(item);
175
+ }
176
+ return memo;
177
+ }, [])
178
+ : menu.reduce((memo, item) => {
161
179
  var _a;
162
180
  if (typeof item === 'string') {
163
- return null;
181
+ return memo;
164
182
  }
165
183
  // custom menu dropdown item filter
166
184
  if (item.visible && !item.visible(configItem)) {
167
- return null;
185
+ return memo;
168
186
  }
169
187
  const itemHandler = item.handler;
170
188
  const itemAction = typeof itemHandler === 'function'
171
189
  ? () => itemHandler(configItem, itemParams, itemState)
172
190
  : ((_a = this.getDropDownMenuItemConfig(item.id)) === null || _a === void 0 ? void 0 : _a.action) || (() => { });
173
- return {
191
+ memo.push({
174
192
  // @ts-expect-error
175
193
  text: item.title || i18n(item.id),
176
194
  icon: item.icon,
177
195
  action: itemAction,
178
196
  className: item.className,
179
197
  qa: item.qa,
180
- };
181
- });
182
- items = items.filter(Boolean);
198
+ });
199
+ return memo;
200
+ }, []);
201
+ if (items.length === 0) {
202
+ return null;
203
+ }
183
204
  return (React.createElement(DropdownMenu, { items: items, renderSwitcher: (props) => (React.createElement(Button, Object.assign({}, props, { view: view, size: size, pin: "brick-round", qa: "dashkit-overlay-control-menu" }),
184
205
  React.createElement(Icon, { data: DotsIcon, size: "16" }))) }));
185
206
  }
@@ -199,19 +220,14 @@ class OverlayControls extends React.Component {
199
220
  return 'brick-brick';
200
221
  }
201
222
  getCustomControlsWithWidgets() {
202
- // Добавляем контрол удаления виджета по умолчанию
203
- const deleteControl = {
204
- title: i18n('label_delete'),
205
- icon: CloseIcon,
206
- iconSize: 12,
207
- handler: this.onRemoveItem,
208
- };
209
- const { items = [] } = this.props;
210
- const customOverlayControls = [...items, deleteControl];
211
- return customOverlayControls.map((item, index, controlItems) => this.renderControlsItem(item, index, controlItems.length));
223
+ const items = this.getItems();
224
+ const result = items.map((item, index, controlItems) => this.renderControlsItem(item, index, controlItems.length));
225
+ // Добавляем контрол удаления или меню виджета по умолчанию
226
+ result.push(this.renderMenu());
227
+ return result;
212
228
  }
213
229
  }
214
- OverlayControls.contextType = DashKitContext;
230
+ OverlayControls.contextType = DashkitOvelayControlsContext;
215
231
  OverlayControls.defaultProps = {
216
232
  position: OverlayControlsPosition.TopRight,
217
233
  view: 'flat',
@@ -11,3 +11,4 @@ export declare const MenuItems: {
11
11
  export declare const DEFAULT_WIDGET_HEIGHT = 3;
12
12
  export declare const DEFAULT_WIDGET_WIDTH = 3;
13
13
  export declare const DEFAULT_GROUP = "__default";
14
+ export declare const COMPACT_TYPE_HORIZONTAL_NOWRAP = "horizontal-nowrap";
@@ -11,3 +11,4 @@ export const MenuItems = {
11
11
  export const DEFAULT_WIDGET_HEIGHT = 3;
12
12
  export const DEFAULT_WIDGET_WIDTH = 3;
13
13
  export const DEFAULT_GROUP = '__default';
14
+ export const COMPACT_TYPE_HORIZONTAL_NOWRAP = 'horizontal-nowrap';
@@ -1,3 +1,4 @@
1
1
  export const DashKitContext: React.Context<any>;
2
+ export const DashkitOvelayControlsContext: React.Context<any>;
2
3
  export const DashKitDnDContext: React.Context<any>;
3
4
  import React from 'react';
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
2
  const DashKitContext = React.createContext();
3
+ const DashkitOvelayControlsContext = React.createContext();
3
4
  const DashKitDnDContext = React.createContext();
4
- export { DashKitContext, DashKitDnDContext };
5
+ export { DashKitContext, DashkitOvelayControlsContext, DashKitDnDContext };
@@ -2,6 +2,8 @@ export function prepareItem(Component: any): {
2
2
  new (props: any): {
3
3
  shouldComponentUpdate(nextProps: any): boolean;
4
4
  _onStateAndParamsChange: (stateAndParams: any, options: any) => void;
5
+ _currentRenderProps: {};
6
+ getRenderProps: () => {};
5
7
  render(): JSX.Element;
6
8
  context: unknown;
7
9
  setState<K extends string | number | symbol>(state: any, callback?: (() => void) | undefined): void;
@@ -26,6 +28,8 @@ export function prepareItem(Component: any): {
26
28
  new (props: any, context: any): {
27
29
  shouldComponentUpdate(nextProps: any): boolean;
28
30
  _onStateAndParamsChange: (stateAndParams: any, options: any) => void;
31
+ _currentRenderProps: {};
32
+ getRenderProps: () => {};
29
33
  render(): JSX.Element;
30
34
  context: unknown;
31
35
  setState<K extends string | number | symbol>(state: any, callback?: (() => void) | undefined): void;
@@ -9,6 +9,35 @@ export function prepareItem(Component) {
9
9
  this._onStateAndParamsChange = (stateAndParams, options) => {
10
10
  this.context.onItemStateAndParamsChange(this.props.id, stateAndParams, options);
11
11
  };
12
+ this._currentRenderProps = {};
13
+ this.getRenderProps = () => {
14
+ const { id, width, height, item, adjustWidgetLayout, layout, isPlaceholder } = this.props;
15
+ const { itemsState, itemsParams, registerManager, settings, context, editMode } = this.context;
16
+ const { data, defaults, namespace } = item;
17
+ const rendererProps = {
18
+ data,
19
+ editMode,
20
+ params: itemsParams[id],
21
+ state: itemsState[id],
22
+ onStateAndParamsChange: this._onStateAndParamsChange,
23
+ width,
24
+ height,
25
+ id,
26
+ defaults,
27
+ namespace,
28
+ settings,
29
+ context,
30
+ layout,
31
+ gridLayout: registerManager.gridLayout,
32
+ adjustWidgetLayout,
33
+ isPlaceholder,
34
+ };
35
+ const changedProp = Object.entries(rendererProps).find(([key, value]) => this._currentRenderProps[key] !== value);
36
+ if (changedProp) {
37
+ this._currentRenderProps = rendererProps;
38
+ }
39
+ return this._currentRenderProps;
40
+ };
12
41
  }
13
42
  shouldComponentUpdate(nextProps) {
14
43
  const { width, height, transform } = this.props;
@@ -22,28 +51,10 @@ export function prepareItem(Component) {
22
51
  return true;
23
52
  }
24
53
  render() {
25
- const { id, width, height, item, adjustWidgetLayout, layout, isPlaceholder } = this.props;
26
- const { itemsState, itemsParams, registerManager, settings, context, editMode } = this.context;
27
- const { type, data, defaults, namespace } = item;
28
- const rendererProps = {
29
- data,
30
- editMode,
31
- params: itemsParams[id],
32
- state: itemsState[id],
33
- onStateAndParamsChange: this._onStateAndParamsChange,
34
- width,
35
- height,
36
- id,
37
- defaults,
38
- namespace,
39
- settings,
40
- context,
41
- layout,
42
- gridLayout: registerManager.gridLayout,
43
- adjustWidgetLayout,
44
- isPlaceholder,
45
- };
46
- return (React.createElement(Component, { forwardedPluginRef: this.props.forwardedPluginRef, rendererProps: rendererProps, registerManager: registerManager, type: type, isPlaceholder: isPlaceholder }));
54
+ const { item, isPlaceholder } = this.props;
55
+ const { registerManager } = this.context;
56
+ const { type } = item;
57
+ return (React.createElement(Component, { forwardedPluginRef: this.props.forwardedPluginRef, rendererProps: this.getRenderProps(), registerManager: registerManager, type: type, isPlaceholder: isPlaceholder }));
47
58
  }
48
59
  },
49
60
  _a.propTypes = {
@@ -1,10 +1,13 @@
1
+ import { __rest } from "tslib";
1
2
  import React from 'react';
2
3
  import isEqual from 'lodash/isEqual';
3
4
  import pick from 'lodash/pick';
4
- import { DEFAULT_WIDGET_HEIGHT, DEFAULT_WIDGET_WIDTH, TEMPORARY_ITEM_ID } from '../constants/common';
5
- import { DashKitContext, DashKitDnDContext } from '../context/DashKitContext';
5
+ import { COMPACT_TYPE_HORIZONTAL_NOWRAP, DEFAULT_GROUP, DEFAULT_WIDGET_HEIGHT, DEFAULT_WIDGET_WIDTH, TEMPORARY_ITEM_ID, } from '../constants/common';
6
+ import { DashKitContext, DashKitDnDContext, DashkitOvelayControlsContext, } from '../context/DashKitContext';
7
+ import { useDeepEqualMemo } from '../hooks/useDeepEqualMemo';
6
8
  import { getItemsParams, getItemsState } from '../shared';
7
9
  import { UpdateManager } from '../utils';
10
+ const ITEM_PROPS = ['i', 'h', 'w', 'x', 'y', 'parent'];
8
11
  function useMemoStateContext(props) {
9
12
  // так как мы не хотим хранить параметры виджета с активированной автовысотой в сторе и на сервере, актуальный
10
13
  // (видимый юзером в конкретный момент времени) лэйаут (массив объектов с данными о ширине, высоте,
@@ -13,6 +16,7 @@ function useMemoStateContext(props) {
13
16
  // (высота) из конфига будет запоминаться в originalLayouts, новое значение высоты в adjustedLayouts
14
17
  const originalLayouts = React.useRef({});
15
18
  const adjustedLayouts = React.useRef({});
19
+ const nowrapAdjustedLayouts = React.useRef({});
16
20
  const [temporaryLayout, setTemporaryLayout] = React.useState(null);
17
21
  const resetTemporaryLayout = React.useCallback(() => setTemporaryLayout(null), [setTemporaryLayout]);
18
22
  const dndContext = React.useContext(DashKitDnDContext);
@@ -47,7 +51,13 @@ function useMemoStateContext(props) {
47
51
  onChange({ config: newConfig });
48
52
  }
49
53
  }, [props.config, onChange]);
54
+ const getLayoutItem = React.useCallback((id) => {
55
+ return props.config.layout.find(({ i }) => i === id);
56
+ }, [props.config.layout]);
50
57
  const onItemRemove = React.useCallback((id) => {
58
+ delete nowrapAdjustedLayouts.current[id];
59
+ delete adjustedLayouts.current[id];
60
+ delete originalLayouts.current[id];
51
61
  if (id === TEMPORARY_ITEM_ID) {
52
62
  resetTemporaryLayout();
53
63
  }
@@ -102,7 +112,55 @@ function useMemoStateContext(props) {
102
112
  forceUpdateLayout((prev) => prev + 1);
103
113
  }
104
114
  }, []);
105
- const itemsParams = React.useMemo(() => getItemsParams({
115
+ React.useMemo(() => {
116
+ const groups = props.groups;
117
+ const layout = props.layout;
118
+ const defaultProps = props.registerManager._gridLayout || {};
119
+ const nowrapGroups = {};
120
+ let hasNowrapGroups = false;
121
+ if (defaultProps.compactType === COMPACT_TYPE_HORIZONTAL_NOWRAP) {
122
+ nowrapGroups[DEFAULT_GROUP] = {
123
+ items: [],
124
+ leftSpace: defaultProps.cols,
125
+ };
126
+ hasNowrapGroups = true;
127
+ }
128
+ if (groups) {
129
+ groups.forEach((group) => {
130
+ var _a;
131
+ const resultProps = ((_a = group.gridProperties) === null || _a === void 0 ? void 0 : _a.call(group, defaultProps)) || {};
132
+ if (resultProps.compactType === COMPACT_TYPE_HORIZONTAL_NOWRAP) {
133
+ nowrapGroups[group.id] = {
134
+ items: [],
135
+ leftSpace: resultProps.cols,
136
+ };
137
+ hasNowrapGroups = true;
138
+ }
139
+ });
140
+ }
141
+ if (hasNowrapGroups) {
142
+ layout.forEach((item) => {
143
+ const widgetId = item.i;
144
+ const parentId = item.parent || DEFAULT_GROUP;
145
+ if (nowrapGroups[parentId]) {
146
+ // Collecting nowrap elements
147
+ nowrapGroups[parentId].items.push(item);
148
+ nowrapGroups[parentId].leftSpace -= item.w;
149
+ }
150
+ else if (nowrapAdjustedLayouts.current[widgetId]) {
151
+ // If element is not in horizontal-nowrap cleaning up and reverting adjustLayout values
152
+ delete nowrapAdjustedLayouts.current[widgetId];
153
+ }
154
+ });
155
+ Object.entries(nowrapGroups).forEach(([, { items, leftSpace }]) => {
156
+ items.forEach((item) => {
157
+ // setting maxW with adjustLayout fields and saving previous
158
+ nowrapAdjustedLayouts.current[item.i] = item.w + leftSpace;
159
+ });
160
+ });
161
+ }
162
+ }, [props.registerManager, props.groups, props.layout]);
163
+ const itemsParams = useDeepEqualMemo(() => getItemsParams({
106
164
  defaultGlobalParams: props.defaultGlobalParams,
107
165
  globalParams: props.globalParams,
108
166
  config: props.config,
@@ -115,7 +173,7 @@ function useMemoStateContext(props) {
115
173
  props.itemsStateAndParams,
116
174
  props.registerManager,
117
175
  ]);
118
- const itemsState = React.useMemo(() => getItemsState({
176
+ const itemsState = useDeepEqualMemo(() => getItemsState({
119
177
  config: props.config,
120
178
  itemsStateAndParams: props.itemsStateAndParams,
121
179
  }), [props.config, props.itemsStateAndParams]);
@@ -130,12 +188,31 @@ function useMemoStateContext(props) {
130
188
  .filter(Boolean);
131
189
  }, []);
132
190
  const resultLayout = React.useMemo(() => {
191
+ const adjusted = adjustedLayouts.current;
192
+ const original = originalLayouts.current;
193
+ const nowrapAdjust = nowrapAdjustedLayouts.current;
133
194
  return props.layout.map((item) => {
134
- if (item.i in adjustedLayouts.current) {
135
- return Object.assign(Object.assign({}, adjustedLayouts.current[item.i]), { w: item.w, x: item.x, y: item.y });
195
+ const widgetId = item.i;
196
+ if (widgetId in adjusted || widgetId in nowrapAdjust) {
197
+ original[widgetId] = item;
198
+ // eslint-disable-next-line no-unused-vars
199
+ const _a = adjusted[widgetId] || item, { parent } = _a, adjustedItem = __rest(_a, ["parent"]);
200
+ adjustedItem.w = item.w;
201
+ adjustedItem.x = item.x;
202
+ adjustedItem.y = item.y;
203
+ if (item.parent) {
204
+ adjustedItem.parent = item.parent;
205
+ }
206
+ if (widgetId in nowrapAdjust) {
207
+ adjustedItem.maxW = nowrapAdjust[widgetId];
208
+ }
209
+ return adjustedItem;
136
210
  }
137
211
  else {
138
- return Object.assign({}, item);
212
+ if (widgetId in original) {
213
+ delete original[widgetId];
214
+ }
215
+ return item;
139
216
  }
140
217
  });
141
218
  }, [props.layout, layoutUpdateCounter]);
@@ -157,17 +234,31 @@ function useMemoStateContext(props) {
157
234
  return null;
158
235
  }
159
236
  }, [dragProps, props.registerManager]);
160
- const onDropDragOver = React.useCallback(() => {
237
+ const onDropDragOver = React.useCallback((_e, gridProps, groupLayout) => {
161
238
  if (temporaryLayout) {
162
239
  resetTemporaryLayout();
163
240
  return false;
164
241
  }
165
- if (dragOverPlugin) {
166
- const { defaultLayout } = dragOverPlugin;
167
- const { h = (defaultLayout === null || defaultLayout === void 0 ? void 0 : defaultLayout.h) || DEFAULT_WIDGET_HEIGHT, w = (defaultLayout === null || defaultLayout === void 0 ? void 0 : defaultLayout.w) || DEFAULT_WIDGET_WIDTH, } = dragProps.layout || {};
168
- return { h, w };
242
+ if (!dragOverPlugin) {
243
+ return false;
244
+ }
245
+ let maxW = gridProps.cols;
246
+ const { defaultLayout } = dragOverPlugin;
247
+ const maxH = Math.min(gridProps.maxRows || Infinity, defaultLayout.maxH || Infinity);
248
+ if (gridProps.compactType === COMPACT_TYPE_HORIZONTAL_NOWRAP) {
249
+ maxW = groupLayout.reduce((memo, item) => memo - item.w, gridProps.cols);
250
+ }
251
+ if (maxW === 0 ||
252
+ maxH === 0 ||
253
+ maxW < defaultLayout.minW ||
254
+ maxH < defaultLayout.minH) {
255
+ return false;
169
256
  }
170
- return false;
257
+ const { h = (defaultLayout === null || defaultLayout === void 0 ? void 0 : defaultLayout.h) || DEFAULT_WIDGET_HEIGHT, w = (defaultLayout === null || defaultLayout === void 0 ? void 0 : defaultLayout.w) || DEFAULT_WIDGET_WIDTH, } = dragProps.layout || {};
258
+ return {
259
+ h: maxH ? Math.min(h, maxH) : h,
260
+ w: maxW ? Math.min(w, maxW) : w,
261
+ };
171
262
  }, [resetTemporaryLayout, temporaryLayout, dragOverPlugin, dragProps]);
172
263
  const onDropProp = props.onDrop;
173
264
  const onDrop = React.useCallback((newLayout, item) => {
@@ -181,16 +272,16 @@ function useMemoStateContext(props) {
181
272
  onDropProp({
182
273
  newLayout: newLayout.reduce((memo, l) => {
183
274
  if (l.i !== item.i) {
184
- memo.push(pick(l, ['i', 'h', 'w', 'x', 'y', 'parent']));
275
+ memo.push(pick(l, ITEM_PROPS));
185
276
  }
186
277
  return memo;
187
278
  }, []),
188
- itemLayout: pick(item, ['i', 'h', 'w', 'x', 'y', 'parent']),
279
+ itemLayout: pick(item, ITEM_PROPS),
189
280
  commit: resetTemporaryLayout,
190
281
  dragProps,
191
282
  });
192
283
  }, [dragProps, onDropProp, setTemporaryLayout, resetTemporaryLayout]);
193
- return React.useMemo(() => ({
284
+ const dashkitContextValue = React.useMemo(() => ({
194
285
  layout: resultLayout,
195
286
  temporaryLayout,
196
287
  config: props.config,
@@ -206,10 +297,8 @@ function useMemoStateContext(props) {
206
297
  itemsParams,
207
298
  registerManager: props.registerManager,
208
299
  onItemStateAndParamsChange,
209
- removeItem: onItemRemove,
210
300
  onDrop,
211
301
  onDropDragOver,
212
- editItem: props.onItemEdit,
213
302
  layoutChange: onLayoutChange,
214
303
  getItemsMeta,
215
304
  reloadItems,
@@ -234,10 +323,8 @@ function useMemoStateContext(props) {
234
323
  itemsParams,
235
324
  props.registerManager,
236
325
  onItemStateAndParamsChange,
237
- onItemRemove,
238
326
  onDrop,
239
327
  onDropDragOver,
240
- props.onItemEdit,
241
328
  onLayoutChange,
242
329
  getItemsMeta,
243
330
  reloadItems,
@@ -248,12 +335,34 @@ function useMemoStateContext(props) {
248
335
  outerDnDEnable,
249
336
  dragOverPlugin,
250
337
  ]);
338
+ const overlayMenuItems = props.overlayMenuItems || props.registerManager.settings.menu;
339
+ const controlsContextValue = React.useMemo(() => ({
340
+ overlayControls: props.overlayControls,
341
+ context: props.context,
342
+ menu: overlayMenuItems,
343
+ itemsParams: itemsParams,
344
+ itemsState: itemsState,
345
+ editItem: props.onItemEdit,
346
+ removeItem: onItemRemove,
347
+ getLayoutItem: getLayoutItem,
348
+ }), [
349
+ itemsParams,
350
+ itemsState,
351
+ props.context,
352
+ props.onItemEdit,
353
+ onItemRemove,
354
+ props.overlayControls,
355
+ overlayMenuItems,
356
+ getLayoutItem,
357
+ ]);
358
+ return { controlsContextValue, dashkitContextValue };
251
359
  }
252
360
  export function withContext(Component) {
253
361
  const WithContext = (props) => {
254
- const contextValue = useMemoStateContext(props);
255
- return (React.createElement(DashKitContext.Provider, { value: contextValue },
256
- React.createElement(Component, { overlayControls: props.overlayControls })));
362
+ const { dashkitContextValue, controlsContextValue } = useMemoStateContext(props);
363
+ return (React.createElement(DashKitContext.Provider, { value: dashkitContextValue },
364
+ React.createElement(DashkitOvelayControlsContext.Provider, { value: controlsContextValue },
365
+ React.createElement(Component, null))));
257
366
  };
258
367
  WithContext.displayName = `withContext(${Component.displayName || Component.name || 'Component'}`;
259
368
  return WithContext;
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare const useDeepEqualMemo: <T extends unknown>(predicate: () => T, deps: React.DependencyList) => T;
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import isEqual from 'lodash/isEqual';
3
+ export const useDeepEqualMemo = (predicate, deps) => {
4
+ const previousValueRef = React.useRef({});
5
+ return React.useMemo(() => {
6
+ const value = predicate();
7
+ if (!isEqual(previousValueRef.current, value)) {
8
+ previousValueRef.current = value;
9
+ }
10
+ return previousValueRef.current;
11
+ }, [previousValueRef, ...deps]);
12
+ };
@@ -19,7 +19,9 @@ export interface DashkitGroupRenderProps {
19
19
  items: ConfigItem[];
20
20
  layout: ConfigLayout[];
21
21
  }
22
- export type ReactGridLayoutProps = Omit<ReactGridLayout.ReactGridLayoutProps, 'children' | 'innerRef' | 'key' | 'layout' | 'isDraggable' | 'isResizable' | 'onDragStart' | 'onDragStop' | 'onResizeStart' | 'onResizeStop' | 'draggableHandle' | 'isDroppable' | 'onDropDragOver' | 'onDrop' | 'draggableCancel'>;
22
+ export type ReactGridLayoutProps = Omit<ReactGridLayout.ReactGridLayoutProps, 'children' | 'compactType' | 'innerRef' | 'key' | 'layout' | 'isDraggable' | 'isResizable' | 'onDragStart' | 'onDragStop' | 'onResizeStart' | 'onResizeStop' | 'draggableHandle' | 'isDroppable' | 'onDropDragOver' | 'onDrop' | 'draggableCancel'> & {
23
+ compactType?: ReactGridLayout.ReactGridLayoutProps['compactType'] | 'horizontal-nowrap';
24
+ };
23
25
  export interface DashKitGroup {
24
26
  id?: string;
25
27
  render?: (id: string, children: React.ReactNode, props: DashkitGroupRenderProps) => React.ReactNode;