superdesk-ui-framework 7.0.0-dev1 → 7.0.0-dev2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "superdesk-ui-framework",
3
- "version": "7.0.0-dev1",
3
+ "version": "7.0.0-dev2",
4
4
  "license": "AGPL-3.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -5,23 +5,24 @@ export interface IMenuItem {
5
5
  active?: boolean;
6
6
  onSelect(): void;
7
7
  }
8
+ export type IMenuElement = IMenuItem | ISubmenu | IMenuGroup | 'divider';
8
9
  export interface ISubmenu {
9
10
  type: 'submenu';
10
11
  label: string | React.ReactNode;
11
12
  icon?: string;
12
- items: Array<IMenuItem | ISubmenu | IMenuGroup | 'divider'>;
13
+ items: Array<IMenuElement>;
13
14
  }
14
15
  export interface IMenuGroup {
15
16
  type: 'group';
16
17
  label?: string | React.ReactNode;
17
- items: Array<IMenuItem | ISubmenu | IMenuGroup | 'divider'>;
18
+ items: Array<IMenuElement>;
18
19
  }
19
20
  interface IMenu {
20
21
  label?: string | React.ReactNode;
21
22
  align?: 'left' | 'right';
22
- items: Array<IMenuItem | ISubmenu | IMenuGroup | 'divider'>;
23
- header?: Array<IMenuItem | ISubmenu | IMenuGroup | 'divider'>;
24
- footer?: Array<IMenuItem | ISubmenu | IMenuGroup | 'divider'>;
23
+ items: Array<IMenuElement>;
24
+ header?: Array<IMenuElement>;
25
+ footer?: Array<IMenuElement>;
25
26
  children: React.ReactNode;
26
27
  onChange?(event?: any): void;
27
28
  maxHeight?: number;
@@ -51,155 +51,115 @@ var core_1 = require("@popperjs/core");
51
51
  var react_id_generator_1 = require("react-id-generator");
52
52
  var zIndex_1 = require("./../zIndex");
53
53
  var DROPDOWN_ID_CONTAINER = 'sd-dropdown-constainer';
54
+ function ensureDropdownContainer() {
55
+ var placeholder = document.getElementById(DROPDOWN_ID_CONTAINER);
56
+ if (!placeholder) {
57
+ placeholder = document.createElement('div');
58
+ placeholder.id = DROPDOWN_ID_CONTAINER;
59
+ placeholder.style.position = 'absolute';
60
+ placeholder.style.top = '0';
61
+ placeholder.style.left = '0';
62
+ placeholder.style.width = '1px';
63
+ placeholder.style.height = '1px';
64
+ placeholder.setAttribute('data-test-id', 'dropdown-overlay');
65
+ document.body.appendChild(placeholder);
66
+ }
67
+ return placeholder;
68
+ }
54
69
  var Dropdown = function (_a) {
55
70
  var items = _a.items, header = _a.header, footer = _a.footer, children = _a.children, align = _a.align, onChange = _a.onChange, maxHeight = _a.maxHeight;
56
- var _b = React.useState(-1), zIndex = _b[0], setZIndex = _b[1];
57
- if (zIndex === -1) {
58
- setZIndex((0, zIndex_1.getNextZIndex)());
59
- }
60
- var _c = React.useState(false), open = _c[0], setOpen = _c[1];
61
- var _d = React.useState(false), change = _d[0], setChange = _d[1];
71
+ var zIndex = React.useState(zIndex_1.getNextZIndex)[0];
72
+ var _b = React.useState(false), open = _b[0], setOpen = _b[1];
73
+ var _c = React.useState(null), container = _c[0], setContainer = _c[1];
62
74
  var menuID = (0, react_id_generator_1.useId)()[0];
63
- var ref = React.useRef(null);
75
+ var menuRef = React.useRef(null);
64
76
  var buttonRef = React.useRef(null);
65
- var headerElements = header === null || header === void 0 ? void 0 : header.map(function (el, index) {
66
- return each(el, index);
67
- });
68
- var maxHeightStyle = maxHeight ? { maxHeight: maxHeight } : {};
69
- var dropdownElements = items.map(function (el, index) {
70
- return each(el, index);
71
- });
72
- var footerElements = footer === null || footer === void 0 ? void 0 : footer.map(function (el, index) {
73
- return each(el, index);
74
- });
75
- React.useEffect(function () {
76
- var existingElement = document.getElementById(DROPDOWN_ID_CONTAINER);
77
- if (!existingElement) {
78
- var el = document.createElement('div');
79
- el.id = DROPDOWN_ID_CONTAINER;
80
- el.style.position = 'absolute';
81
- el.style.top = '0';
82
- el.style.left = '0';
83
- el.style.width = '1px';
84
- el.style.height = '1px';
85
- el.setAttribute('data-test-id', 'dropdown-overlay');
86
- document.body.appendChild(el);
87
- }
88
- }, [change]);
77
+ // Callback refs so the same ref can be attached to either element type
78
+ // rendered by each branch below (ul/div for the menu, div/button for the toggle).
79
+ var setMenuRef = React.useCallback(function (element) {
80
+ menuRef.current = element;
81
+ }, []);
82
+ var setButtonRef = React.useCallback(function (element) {
83
+ buttonRef.current = element;
84
+ }, []);
85
+ // Created after mount rather than during render, so render stays side-effect free.
86
+ // The menu only opens on user interaction, so the container is always ready by then.
89
87
  React.useLayoutEffect(function () {
90
- if (change) {
91
- addInPlaceholder();
92
- }
93
- setChange(true);
94
- }, [open]);
95
- function createAppendMenu() {
96
- if (header && footer) {
97
- return (React.createElement("div", { className: "dropdown__menu dropdown__menu--has-head-foot", id: menuID, role: "menu", ref: ref, style: { zIndex: zIndex } },
98
- React.createElement("ul", { className: "dropdown__menu-header" }, headerElements),
99
- React.createElement("ul", { className: "dropdown__menu-body" }, dropdownElements),
100
- React.createElement("ul", { className: "dropdown__menu-footer dropdown__menu-footer--has-list " }, footerElements)));
101
- }
102
- else if (header) {
103
- return (React.createElement("div", { className: "dropdown__menu dropdown__menu--has-head-foot", id: menuID, role: "menu", ref: ref, style: { zIndex: zIndex } },
104
- React.createElement("ul", { className: "dropdown__menu-header" }, headerElements),
105
- React.createElement("ul", { className: "dropdown__menu-body" }, dropdownElements)));
106
- }
107
- else if (footer) {
108
- return (React.createElement("div", { className: "dropdown__menu dropdown__menu--has-head-foot", id: menuID, role: "menu", ref: ref, style: { zIndex: zIndex } },
109
- React.createElement("ul", { className: "dropdown__menu-body" }, dropdownElements),
110
- React.createElement("ul", { className: "dropdown__menu-footer dropdown__menu-footer--has-list " }, footerElements)));
111
- }
112
- else {
113
- return (React.createElement("ul", { className: "dropdown__menu ", id: menuID, role: "menu", ref: ref, style: __assign({ zIndex: zIndex, overflowY: 'auto' }, maxHeightStyle) }, dropdownElements));
114
- }
115
- }
116
- function toggleDisplay() {
88
+ setContainer(ensureDropdownContainer());
89
+ }, []);
90
+ // Any click closes the menu — including clicks on menu items, which defer
91
+ // their `onSelect` so the menu is gone by the time it runs.
92
+ React.useEffect(function () {
117
93
  if (!open) {
118
- var menuRef_1;
119
- setOpen(true);
120
- setTimeout(function () {
121
- menuRef_1 = ref.current;
122
- var toggleRef = buttonRef.current;
123
- if (toggleRef && menuRef_1) {
124
- (0, core_1.createPopper)(toggleRef, menuRef_1, {
125
- placement: checkAlign() ? 'bottom-end' : 'bottom-start',
126
- strategy: 'fixed',
127
- });
128
- menuRef_1.style.display = 'block';
129
- }
130
- }, 0);
131
- document.addEventListener('click', closeMenu);
132
- setTimeout(function () {
133
- menuRef_1.getElementsByTagName('button')[0].focus();
134
- });
135
- }
136
- else {
137
- setOpen(false);
138
- }
139
- }
140
- function closeMenu() {
141
- document.removeEventListener('click', closeMenu);
142
- setOpen(false);
143
- }
144
- function checkAlign() {
145
- if (align === 'right') {
146
- return true;
147
- }
148
- else {
149
- return false;
94
+ return;
150
95
  }
151
- }
152
- function addInPlaceholder() {
153
- var placeholder = document.getElementById(DROPDOWN_ID_CONTAINER);
154
- var menu = createAppendMenu();
155
- if (open) {
156
- return ReactDOM.render(menu, placeholder);
96
+ var closeMenu = function () { return setOpen(false); };
97
+ document.addEventListener('click', closeMenu);
98
+ return function () { return document.removeEventListener('click', closeMenu); };
99
+ }, [open]);
100
+ React.useLayoutEffect(function () {
101
+ var _a;
102
+ var toggle = buttonRef.current;
103
+ var menu = menuRef.current;
104
+ if (!open || toggle == null || menu == null) {
105
+ return;
106
+ }
107
+ var popper = (0, core_1.createPopper)(toggle, menu, {
108
+ placement: align === 'right' ? 'bottom-end' : 'bottom-start',
109
+ strategy: 'fixed',
110
+ });
111
+ (_a = menu.getElementsByTagName('button')[0]) === null || _a === void 0 ? void 0 : _a.focus();
112
+ return function () { return popper.destroy(); };
113
+ }, [open, align]);
114
+ function each(item, index) {
115
+ var _a;
116
+ if (item === 'divider') {
117
+ return React.createElement("li", { className: "dropdown__menu-divider", key: index });
157
118
  }
158
- else {
159
- if (placeholder) {
160
- ReactDOM.unmountComponentAtNode(placeholder);
119
+ if ('type' in item && item.type === 'submenu') {
120
+ // Empty submenu definitions are treated as plain items so we never
121
+ // portal an empty menu panel.
122
+ if (item.items.length === 0) {
123
+ var asItem = item;
124
+ return (React.createElement(DropdownItem, { key: index, label: item.label, icon: item.icon, active: asItem.active, onSelect: (_a = asItem.onSelect) !== null && _a !== void 0 ? _a : (function () { return undefined; }), onChange: onChange }));
161
125
  }
126
+ return (React.createElement(DropdownItemWithSubmenu, { key: index, item: item, zIndex: zIndex, subMenuItems: item.items.map(each), onChange: onChange }));
162
127
  }
163
- }
164
- function each(item, index) {
165
- if (item['type'] === 'submenu') {
166
- var submenuItems_1 = [];
167
- item['items'].forEach(function (el, key) {
168
- submenuItems_1.push(each(el, key));
169
- });
170
- return (React.createElement(DropdownItemWithSubmenu, { key: index, index: index, item: item, menuID: menuID, subMenuItems: submenuItems_1, onChange: onChange }));
171
- }
172
- else if (item['type'] === 'group') {
173
- var groupItems_1 = [];
174
- item['items'].forEach(function (el, key) {
175
- groupItems_1.push(each(el, key));
176
- });
128
+ if ('type' in item && item.type === 'group') {
177
129
  return (React.createElement(React.Fragment, { key: index },
178
130
  React.createElement("li", null,
179
- React.createElement("div", { className: "dropdown__menu-label" }, item['label'])),
180
- groupItems_1));
181
- }
182
- else if (item === 'divider') {
183
- return React.createElement("li", { className: "dropdown__menu-divider", key: index });
184
- }
185
- else {
186
- return (React.createElement(DropdownItem, { key: index, label: item['label'], icon: item['icon'], active: item['active'], onSelect: item['onSelect'], onChange: onChange }));
131
+ React.createElement("div", { className: "dropdown__menu-label" }, item.label)),
132
+ item.items.map(each)));
187
133
  }
134
+ return (React.createElement(DropdownItem, { key: index, label: item.label, icon: item.icon, active: item.active, onSelect: item.onSelect, onChange: onChange }));
135
+ }
136
+ function renderMenu() {
137
+ // Only constrain overflow when scrolling is requested. Submenus are portaled
138
+ // to document.body so they are not clipped by this overflow.
139
+ var menuStyle = __assign({ zIndex: zIndex, display: 'block' }, (maxHeight != null ? { maxHeight: maxHeight, overflowY: 'auto' } : {}));
140
+ if (header == null && footer == null) {
141
+ return (React.createElement("ul", { className: "dropdown__menu", id: menuID, role: "menu", ref: setMenuRef, style: menuStyle }, items.map(each)));
142
+ }
143
+ return (React.createElement("div", { className: "dropdown__menu dropdown__menu--has-head-foot", id: menuID, role: "menu", ref: setMenuRef, style: menuStyle },
144
+ header != null && React.createElement("ul", { className: "dropdown__menu-header" }, header.map(each)),
145
+ React.createElement("ul", { className: "dropdown__menu-body" }, items.map(each)),
146
+ footer != null && (React.createElement("ul", { className: "dropdown__menu-footer dropdown__menu-footer--has-list" }, footer.map(each)))));
188
147
  }
189
- return (React.createElement("div", { className: 'dropdown ' + (open ? 'open' : '') }, typeof children === 'object' ? (React.isValidElement(children) ? (React.createElement("div", { ref: buttonRef, style: { display: 'content' } }, (function () {
190
- var attrs = {
191
- className: children.props.className
148
+ var toggleProps = {
149
+ 'aria-haspopup': 'menu',
150
+ 'aria-expanded': open,
151
+ onClick: function () { return setOpen(function (currentlyOpen) { return !currentlyOpen; }); },
152
+ };
153
+ return (React.createElement("div", { className: open ? 'dropdown open' : 'dropdown' },
154
+ React.isValidElement(children) ? (
155
+ // The wrapper (not the cloned child) is the popper anchor, so a plain
156
+ // function component can be used as the toggle without forwarding a ref.
157
+ React.createElement("div", { ref: setButtonRef }, React.cloneElement(children, __assign(__assign({}, toggleProps), { className: children.props.className
192
158
  ? children.props.className + ' dropdown__toggle dropdown-toggle'
193
- : 'dropdown__toggle dropdown-toggle',
194
- 'aria-haspopup': 'menu',
195
- 'aria-expanded': open,
196
- onClick: toggleDisplay,
197
- ref: buttonRef,
198
- };
199
- return React.cloneElement(children, attrs);
200
- })())) : null) : (React.createElement("button", { style: { whiteSpace: 'nowrap' }, ref: buttonRef, className: " dropdown__toggle dropdown-toggle", "aria-haspopup": "menu", tabIndex: 0, "aria-expanded": open, onClick: toggleDisplay },
201
- children,
202
- React.createElement("span", { className: "dropdown__caret" })))));
159
+ : 'dropdown__toggle dropdown-toggle' })))) : (React.createElement("button", __assign({}, toggleProps, { ref: setButtonRef, className: "dropdown__toggle dropdown__toggle--default dropdown-toggle", tabIndex: 0 }),
160
+ children,
161
+ React.createElement("span", { className: "dropdown__caret" }))),
162
+ open && container != null && ReactDOM.createPortal(renderMenu(), container)));
203
163
  };
204
164
  exports.Dropdown = Dropdown;
205
165
  var DropdownItem = function (_a) {
@@ -209,49 +169,84 @@ var DropdownItem = function (_a) {
209
169
  setTimeout(function () {
210
170
  onSelect();
211
171
  });
212
- if (onChange) {
213
- onChange();
214
- }
172
+ onChange === null || onChange === void 0 ? void 0 : onChange();
215
173
  } },
216
174
  React.createElement("i", { className: icon ? 'icon-' + icon : '' }),
217
175
  label)));
218
176
  };
177
+ var SUBMENU_CLOSE_DELAY_MS = 150;
178
+ var SubmenuHoverContext = React.createContext({
179
+ keepParentOpen: function () { return undefined; },
180
+ scheduleParentClose: function () { return undefined; },
181
+ });
182
+ function isMovingToDropdown(relatedTarget) {
183
+ return relatedTarget instanceof Element ? relatedTarget.closest('.dropdown__menu, .dropdown') != null : false;
184
+ }
219
185
  var DropdownItemWithSubmenu = function (_a) {
220
- var index = _a.index, item = _a.item, menuID = _a.menuID, subMenuItems = _a.subMenuItems, onChange = _a.onChange;
221
- var _b = React.useState(undefined), open = _b[0], setOpen = _b[1];
186
+ var item = _a.item, zIndex = _a.zIndex, subMenuItems = _a.subMenuItems, onChange = _a.onChange;
187
+ var _b = React.useState(false), open = _b[0], setOpen = _b[1];
188
+ var _c = React.useState(zIndex + 1), submenuZIndex = _c[0], setSubmenuZIndex = _c[1];
222
189
  var refButtonSubMenu = React.useRef(null);
223
190
  var refSubMenu = React.useRef(null);
224
- var placeholder = document.getElementById(menuID);
225
- React.useEffect(function () {
226
- var subMenuRef = refSubMenu.current;
227
- var subToggleRef = refButtonSubMenu.current;
228
- if (open === true) {
229
- placeholder === null || placeholder === void 0 ? void 0 : placeholder.appendChild(subMenuRef);
230
- subMenuRef.style.display = 'block';
231
- }
232
- else if (open === false) {
233
- placeholder === null || placeholder === void 0 ? void 0 : placeholder.removeChild(subMenuRef);
234
- subMenuRef.style.display = 'none';
191
+ var closeTimeoutRef = React.useRef(null);
192
+ var parentHover = React.useContext(SubmenuHoverContext);
193
+ var onSelect = item.onSelect;
194
+ var clearCloseTimeout = React.useCallback(function () {
195
+ if (closeTimeoutRef.current != null) {
196
+ window.clearTimeout(closeTimeoutRef.current);
197
+ closeTimeoutRef.current = null;
198
+ }
199
+ }, []);
200
+ var openSubmenu = function () {
201
+ if (subMenuItems.length === 0) {
202
+ return;
203
+ }
204
+ clearCloseTimeout();
205
+ parentHover.keepParentOpen();
206
+ if (!open) {
207
+ setSubmenuZIndex((0, zIndex_1.getNextZIndex)());
235
208
  }
236
- if (subMenuRef && subToggleRef) {
237
- (0, core_1.createPopper)(subToggleRef, subMenuRef, {
238
- placement: 'right-start',
239
- });
209
+ setOpen(true);
210
+ };
211
+ var scheduleClose = function (closeAncestors) {
212
+ clearCloseTimeout();
213
+ closeTimeoutRef.current = window.setTimeout(function () {
214
+ setOpen(false);
215
+ closeTimeoutRef.current = null;
216
+ }, SUBMENU_CLOSE_DELAY_MS);
217
+ if (closeAncestors) {
218
+ parentHover.scheduleParentClose();
240
219
  }
220
+ };
221
+ var handleMouseLeave = function (event) {
222
+ scheduleClose(!isMovingToDropdown(event.relatedTarget));
223
+ };
224
+ React.useEffect(function () { return clearCloseTimeout; }, [clearCloseTimeout]);
225
+ React.useLayoutEffect(function () {
226
+ if (!open || refButtonSubMenu.current == null || refSubMenu.current == null) {
227
+ return;
228
+ }
229
+ // Portal to body so parent overflow (e.g. maxHeight) cannot clip the submenu.
230
+ var popper = (0, core_1.createPopper)(refButtonSubMenu.current, refSubMenu.current, {
231
+ placement: 'right-start',
232
+ strategy: 'fixed',
233
+ });
234
+ return function () { return popper.destroy(); };
241
235
  }, [open]);
242
- return (React.createElement("li", { key: index, ref: refButtonSubMenu },
243
- React.createElement("div", { className: "dropdown", onMouseLeave: function () { return setOpen(false); } },
244
- React.createElement("button", { className: "dropdown__toggle dropdown-toggle", "aria-haspopup": "menu", tabIndex: 0, onClick: function () {
245
- if (item.onSelect) {
246
- setTimeout(function () {
247
- item.onSelect();
248
- });
249
- }
250
- if (onChange) {
251
- onChange();
236
+ return (React.createElement("li", { ref: refButtonSubMenu, role: "none" },
237
+ React.createElement("div", { className: "dropdown", onMouseEnter: openSubmenu, onMouseLeave: handleMouseLeave },
238
+ React.createElement("button", { className: "dropdown__toggle dropdown-toggle", role: "menuitem", "aria-haspopup": "menu", "aria-expanded": open, tabIndex: 0, onClick: function () {
239
+ if (onSelect != null) {
240
+ setTimeout(function () { return onSelect(); });
252
241
  }
253
- }, onMouseOver: function () { return setOpen(true); } },
254
- item['icon'] ? React.createElement("i", { className: 'icon-' + item['icon'] }) : null,
255
- item['label']),
256
- React.createElement("ul", { role: "menu", ref: refSubMenu, style: { display: 'none' }, className: "dropdown__menu" }, subMenuItems))));
242
+ onChange === null || onChange === void 0 ? void 0 : onChange();
243
+ }, onMouseOver: openSubmenu },
244
+ item.icon ? React.createElement("i", { className: 'icon-' + item.icon }) : null,
245
+ item.label)),
246
+ open &&
247
+ ReactDOM.createPortal(React.createElement(SubmenuHoverContext.Provider, { value: {
248
+ keepParentOpen: openSubmenu,
249
+ scheduleParentClose: function () { return scheduleClose(true); },
250
+ } },
251
+ React.createElement("ul", { role: "menu", ref: refSubMenu, className: "dropdown__menu", style: { display: 'block', zIndex: submenuZIndex }, onMouseEnter: openSubmenu, onMouseLeave: handleMouseLeave }, subMenuItems)), document.body)));
257
252
  };
@@ -550,4 +550,4 @@
550
550
 
551
551
  import './780.b0b7860a.iframe.bundle.js';
552
552
 
553
- import './main.f0d64657.iframe.bundle.js';</script></body></html>
553
+ import './main.18fb8930.iframe.bundle.js';</script></body></html>