openproject-primer_view_components 0.22.0 → 0.22.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/app/assets/javascripts/primer_view_components.js +1 -1
- data/app/assets/javascripts/primer_view_components.js.map +1 -1
- data/app/components/primer/alpha/action_bar_element.js +12 -4
- data/app/components/primer/alpha/action_bar_element.ts +10 -4
- data/app/components/primer/alpha/action_menu/action_menu_element.js +4 -1
- data/app/components/primer/alpha/action_menu/action_menu_element.ts +4 -1
- data/app/components/primer/alpha/banner.html.erb +1 -2
- data/app/components/primer/alpha/banner.rb +7 -0
- data/app/components/primer/alpha/dialog.rb +0 -1
- data/app/components/primer/alpha/modal_dialog.js +3 -0
- data/app/components/primer/alpha/modal_dialog.ts +3 -0
- data/app/components/primer/alpha/toggle_switch.js +2 -2
- data/app/components/primer/alpha/toggle_switch.ts +2 -2
- data/app/components/primer/alpha/tool_tip.js +1 -0
- data/app/components/primer/alpha/tool_tip.ts +1 -0
- data/app/components/primer/beta/clipboard_copy.ts +1 -1
- data/app/components/primer/beta/nav_list.js +2 -0
- data/app/components/primer/beta/nav_list.ts +2 -0
- data/app/components/primer/dialog_helper.js +38 -7
- data/app/components/primer/dialog_helper.ts +42 -7
- data/app/components/primer/focus_group.ts +1 -1
- data/lib/primer/view_components/version.rb +1 -1
- data/previews/primer/alpha/dialog_preview/dialog_inside_overlay.html.erb +20 -0
- data/previews/primer/alpha/dialog_preview/nested_dialog.html.erb +7 -0
- data/previews/primer/alpha/dialog_preview.rb +24 -0
- data/static/info_arch.json +14 -1
- data/static/previews.json +13 -0
- metadata +4 -3
@@ -140,12 +140,20 @@ _ActionBarElement_firstItem_get = function _ActionBarElement_firstItem_get() {
|
|
140
140
|
return foundItem;
|
141
141
|
};
|
142
142
|
_ActionBarElement_showItem = function _ActionBarElement_showItem(index) {
|
143
|
-
this.items[index]
|
144
|
-
__classPrivateFieldGet(this, _ActionBarElement_instances, "a", _ActionBarElement_menuItems_get)[index]
|
143
|
+
const item = this.items[index];
|
144
|
+
const menuItem = __classPrivateFieldGet(this, _ActionBarElement_instances, "a", _ActionBarElement_menuItems_get)[index];
|
145
|
+
if (!item || !menuItem)
|
146
|
+
return;
|
147
|
+
item.style.setProperty('visibility', 'visible');
|
148
|
+
menuItem.hidden = true;
|
145
149
|
};
|
146
150
|
_ActionBarElement_hideItem = function _ActionBarElement_hideItem(index) {
|
147
|
-
this.items[index]
|
148
|
-
__classPrivateFieldGet(this, _ActionBarElement_instances, "a", _ActionBarElement_menuItems_get)[index]
|
151
|
+
const item = this.items[index];
|
152
|
+
const menuItem = __classPrivateFieldGet(this, _ActionBarElement_instances, "a", _ActionBarElement_menuItems_get)[index];
|
153
|
+
if (!item || !menuItem)
|
154
|
+
return;
|
155
|
+
item.style.setProperty('visibility', 'hidden');
|
156
|
+
menuItem.hidden = false;
|
149
157
|
};
|
150
158
|
_ActionBarElement_menuItems_get = function _ActionBarElement_menuItems_get() {
|
151
159
|
return this.moreMenu.querySelectorAll('[role="menu"] > li');
|
@@ -145,13 +145,19 @@ class ActionBarElement extends HTMLElement {
|
|
145
145
|
}
|
146
146
|
|
147
147
|
#showItem(index: number) {
|
148
|
-
this.items[index]
|
149
|
-
this.#menuItems[index]
|
148
|
+
const item = this.items[index]
|
149
|
+
const menuItem = this.#menuItems[index]
|
150
|
+
if (!item || !menuItem) return
|
151
|
+
item.style.setProperty('visibility', 'visible')
|
152
|
+
menuItem.hidden = true
|
150
153
|
}
|
151
154
|
|
152
155
|
#hideItem(index: number) {
|
153
|
-
this.items[index]
|
154
|
-
this.#menuItems[index]
|
156
|
+
const item = this.items[index]
|
157
|
+
const menuItem = this.#menuItems[index]
|
158
|
+
if (!item || !menuItem) return
|
159
|
+
item.style.setProperty('visibility', 'hidden')
|
160
|
+
menuItem.hidden = false
|
155
161
|
}
|
156
162
|
|
157
163
|
get #menuItems(): NodeListOf<HTMLElement> {
|
@@ -265,7 +265,9 @@ _ActionMenuElement_potentiallyDisallowActivation = function _ActionMenuElement_p
|
|
265
265
|
return false;
|
266
266
|
if (item.getAttribute('aria-disabled')) {
|
267
267
|
event.preventDefault();
|
268
|
+
/* eslint-disable-next-line no-restricted-syntax */
|
268
269
|
event.stopPropagation();
|
270
|
+
/* eslint-disable-next-line no-restricted-syntax */
|
269
271
|
event.stopImmediatePropagation();
|
270
272
|
return true;
|
271
273
|
}
|
@@ -294,6 +296,7 @@ _ActionMenuElement_isActivation = function _ActionMenuElement_isActivation(event
|
|
294
296
|
};
|
295
297
|
_ActionMenuElement_handleInvokerActivated = function _ActionMenuElement_handleInvokerActivated(event) {
|
296
298
|
event.preventDefault();
|
299
|
+
/* eslint-disable-next-line no-restricted-syntax */
|
297
300
|
event.stopPropagation();
|
298
301
|
if (__classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_isOpen).call(this)) {
|
299
302
|
__classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_hide).call(this);
|
@@ -320,7 +323,6 @@ _ActionMenuElement_handleDialogItemActivated = function _ActionMenuElement_handl
|
|
320
323
|
}
|
321
324
|
};
|
322
325
|
// a modal <dialog> element will close all popovers
|
323
|
-
setTimeout(() => __classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_show).call(this), 0);
|
324
326
|
dialog.addEventListener('close', handleDialogClose, { signal });
|
325
327
|
dialog.addEventListener('cancel', handleDialogClose, { signal });
|
326
328
|
};
|
@@ -377,6 +379,7 @@ _ActionMenuElement_activateItem = function _ActionMenuElement_activateItem(event
|
|
377
379
|
return;
|
378
380
|
// otherwise, event will not result in activation by default, so we stop it and
|
379
381
|
// simulate a click
|
382
|
+
/* eslint-disable-next-line no-restricted-syntax */
|
380
383
|
event.stopPropagation();
|
381
384
|
const elem = item;
|
382
385
|
elem.click();
|
@@ -141,7 +141,9 @@ export class ActionMenuElement extends HTMLElement {
|
|
141
141
|
|
142
142
|
if (item.getAttribute('aria-disabled')) {
|
143
143
|
event.preventDefault()
|
144
|
+
/* eslint-disable-next-line no-restricted-syntax */
|
144
145
|
event.stopPropagation()
|
146
|
+
/* eslint-disable-next-line no-restricted-syntax */
|
145
147
|
event.stopImmediatePropagation()
|
146
148
|
return true
|
147
149
|
}
|
@@ -260,6 +262,7 @@ export class ActionMenuElement extends HTMLElement {
|
|
260
262
|
|
261
263
|
#handleInvokerActivated(event: Event) {
|
262
264
|
event.preventDefault()
|
265
|
+
/* eslint-disable-next-line no-restricted-syntax */
|
263
266
|
event.stopPropagation()
|
264
267
|
|
265
268
|
if (this.#isOpen()) {
|
@@ -287,7 +290,6 @@ export class ActionMenuElement extends HTMLElement {
|
|
287
290
|
}
|
288
291
|
}
|
289
292
|
// a modal <dialog> element will close all popovers
|
290
|
-
setTimeout(() => this.#show(), 0)
|
291
293
|
dialog.addEventListener('close', handleDialogClose, {signal})
|
292
294
|
dialog.addEventListener('cancel', handleDialogClose, {signal})
|
293
295
|
}
|
@@ -354,6 +356,7 @@ export class ActionMenuElement extends HTMLElement {
|
|
354
356
|
|
355
357
|
// otherwise, event will not result in activation by default, so we stop it and
|
356
358
|
// simulate a click
|
359
|
+
/* eslint-disable-next-line no-restricted-syntax */
|
357
360
|
event.stopPropagation()
|
358
361
|
const elem = item as HTMLElement
|
359
362
|
elem.click()
|
@@ -23,8 +23,7 @@
|
|
23
23
|
scheme: :invisible,
|
24
24
|
icon: :x,
|
25
25
|
aria: { label: @dismiss_label },
|
26
|
-
data: { action: catalyst_action(event: "click", function: "dismiss") }
|
27
|
-
autofocus: true
|
26
|
+
data: { action: catalyst_action(event: "click", function: "dismiss") }
|
28
27
|
)
|
29
28
|
) %>
|
30
29
|
</div>
|
@@ -3,6 +3,13 @@
|
|
3
3
|
module Primer
|
4
4
|
module Alpha
|
5
5
|
# Use `Banner` to highlight important information.
|
6
|
+
#
|
7
|
+
# @accessibility
|
8
|
+
# Depending on the scenario, some Banners may need to receive focus when they appear. This helps to maximize discoverability of the message, especially in critical scenarios. Visit the [Banner's Accessibility section](https://primer.style/components/banner#accessibility) or defer to the accessibility team to determine if your scenario requires focusing the banner.
|
9
|
+
#
|
10
|
+
# To properly focus a banner, add a `tabindex="-1"` to the Banner container, and focus that container (one way is using the [`focus()` API](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus)).
|
11
|
+
#
|
12
|
+
# For more information about the focus management technique, visit the [Accessible Banner Prototype docs](https://github.com/github/accessibility/blob/main/docs/coaching-recommendations/toast-flash-banner/accessible-banner-prototype.md#consideration). This guidance is subject to change.
|
6
13
|
class Banner < Primer::Component
|
7
14
|
status :alpha
|
8
15
|
|
@@ -26,6 +26,7 @@ function clickHandler(event) {
|
|
26
26
|
// If the user is clicking a valid dialog trigger
|
27
27
|
let dialogId = button === null || button === void 0 ? void 0 : button.getAttribute('data-show-dialog-id');
|
28
28
|
if (dialogId) {
|
29
|
+
/* eslint-disable-next-line no-restricted-syntax */
|
29
30
|
event.stopPropagation();
|
30
31
|
const dialog = document.getElementById(dialogId);
|
31
32
|
if (dialog instanceof ModalDialogElement) {
|
@@ -169,11 +170,13 @@ _ModalDialogElement_focusAbortController = new WeakMap(), _ModalDialogElement_in
|
|
169
170
|
case 'Escape':
|
170
171
|
this.close();
|
171
172
|
event.preventDefault();
|
173
|
+
/* eslint-disable-next-line no-restricted-syntax */
|
172
174
|
event.stopPropagation();
|
173
175
|
break;
|
174
176
|
case 'Enter': {
|
175
177
|
const target = event.target;
|
176
178
|
if (target.getAttribute('data-close-dialog-id') === this.id) {
|
179
|
+
/* eslint-disable-next-line no-restricted-syntax */
|
177
180
|
event.stopPropagation();
|
178
181
|
}
|
179
182
|
break;
|
@@ -18,6 +18,7 @@ function clickHandler(event: Event) {
|
|
18
18
|
// If the user is clicking a valid dialog trigger
|
19
19
|
let dialogId = button?.getAttribute('data-show-dialog-id')
|
20
20
|
if (dialogId) {
|
21
|
+
/* eslint-disable-next-line no-restricted-syntax */
|
21
22
|
event.stopPropagation()
|
22
23
|
const dialog = document.getElementById(dialogId)
|
23
24
|
if (dialog instanceof ModalDialogElement) {
|
@@ -170,12 +171,14 @@ export class ModalDialogElement extends HTMLElement {
|
|
170
171
|
case 'Escape':
|
171
172
|
this.close()
|
172
173
|
event.preventDefault()
|
174
|
+
/* eslint-disable-next-line no-restricted-syntax */
|
173
175
|
event.stopPropagation()
|
174
176
|
break
|
175
177
|
case 'Enter': {
|
176
178
|
const target = event.target as HTMLElement
|
177
179
|
|
178
180
|
if (target.getAttribute('data-close-dialog-id') === this.id) {
|
181
|
+
/* eslint-disable-next-line no-restricted-syntax */
|
179
182
|
event.stopPropagation()
|
180
183
|
}
|
181
184
|
break
|
@@ -134,9 +134,9 @@ let ToggleSwitchElement = class ToggleSwitchElement extends HTMLElement {
|
|
134
134
|
credentials: 'same-origin',
|
135
135
|
method: 'POST',
|
136
136
|
headers: {
|
137
|
-
'Requested-With': 'XMLHttpRequest'
|
137
|
+
'Requested-With': 'XMLHttpRequest',
|
138
138
|
},
|
139
|
-
body
|
139
|
+
body,
|
140
140
|
});
|
141
141
|
}
|
142
142
|
catch (error) {
|
@@ -163,9 +163,9 @@ class ToggleSwitchElement extends HTMLElement {
|
|
163
163
|
credentials: 'same-origin',
|
164
164
|
method: 'POST',
|
165
165
|
headers: {
|
166
|
-
'Requested-With': 'XMLHttpRequest'
|
166
|
+
'Requested-With': 'XMLHttpRequest',
|
167
167
|
},
|
168
|
-
body
|
168
|
+
body,
|
169
169
|
})
|
170
170
|
} catch (error) {
|
171
171
|
throw new Error('A network error occurred, please try again.')
|
@@ -310,6 +310,7 @@ class ToolTipElement extends HTMLElement {
|
|
310
310
|
const isOpeningOtherPopover = event.type === 'beforetoggle' && event.currentTarget !== this;
|
311
311
|
const shouldHide = isMouseLeaveFromButton || isEscapeKeydown || isMouseDownOnButton || isOpeningOtherPopover;
|
312
312
|
if (showing && isEscapeKeydown) {
|
313
|
+
/* eslint-disable-next-line no-restricted-syntax */
|
313
314
|
event.stopImmediatePropagation();
|
314
315
|
event.preventDefault();
|
315
316
|
}
|
@@ -316,6 +316,7 @@ class ToolTipElement extends HTMLElement {
|
|
316
316
|
const shouldHide = isMouseLeaveFromButton || isEscapeKeydown || isMouseDownOnButton || isOpeningOtherPopover
|
317
317
|
|
318
318
|
if (showing && isEscapeKeydown) {
|
319
|
+
/* eslint-disable-next-line no-restricted-syntax */
|
319
320
|
event.stopImmediatePropagation()
|
320
321
|
event.preventDefault()
|
321
322
|
}
|
@@ -77,6 +77,7 @@ let NavListElement = class NavListElement extends HTMLElement {
|
|
77
77
|
else {
|
78
78
|
this.expandItem(button);
|
79
79
|
}
|
80
|
+
/* eslint-disable-next-line no-restricted-syntax */
|
80
81
|
e.stopPropagation();
|
81
82
|
}
|
82
83
|
// collapse item
|
@@ -97,6 +98,7 @@ let NavListElement = class NavListElement extends HTMLElement {
|
|
97
98
|
if (this.itemIsExpanded(button) && e.key === 'Escape') {
|
98
99
|
this.collapseItem(button);
|
99
100
|
}
|
101
|
+
/* eslint-disable-next-line no-restricted-syntax */
|
100
102
|
e.stopPropagation();
|
101
103
|
}
|
102
104
|
};
|
@@ -74,6 +74,7 @@ export class NavListElement extends HTMLElement {
|
|
74
74
|
this.expandItem(button)
|
75
75
|
}
|
76
76
|
|
77
|
+
/* eslint-disable-next-line no-restricted-syntax */
|
77
78
|
e.stopPropagation()
|
78
79
|
}
|
79
80
|
|
@@ -96,6 +97,7 @@ export class NavListElement extends HTMLElement {
|
|
96
97
|
this.collapseItem(button)
|
97
98
|
}
|
98
99
|
|
100
|
+
/* eslint-disable-next-line no-restricted-syntax */
|
99
101
|
e.stopPropagation()
|
100
102
|
}
|
101
103
|
|
@@ -11,6 +11,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
11
11
|
};
|
12
12
|
var _DialogHelperElement_abortController;
|
13
13
|
function dialogInvokerButtonHandler(event) {
|
14
|
+
var _a;
|
14
15
|
const target = event.target;
|
15
16
|
const button = target === null || target === void 0 ? void 0 : target.closest('button');
|
16
17
|
if (!button || button.hasAttribute('disabled') || button.getAttribute('aria-disabled') === 'true')
|
@@ -18,7 +19,6 @@ function dialogInvokerButtonHandler(event) {
|
|
18
19
|
// If the user is clicking a valid dialog trigger
|
19
20
|
let dialogId = button === null || button === void 0 ? void 0 : button.getAttribute('data-show-dialog-id');
|
20
21
|
if (dialogId) {
|
21
|
-
event.stopPropagation();
|
22
22
|
const dialog = document.getElementById(dialogId);
|
23
23
|
if (dialog instanceof HTMLDialogElement) {
|
24
24
|
dialog.showModal();
|
@@ -26,7 +26,41 @@ function dialogInvokerButtonHandler(event) {
|
|
26
26
|
// If the behaviour is allowed through the dialog will be shown but then
|
27
27
|
// quickly hidden- as if it were never shown. This prevents that.
|
28
28
|
event.preventDefault();
|
29
|
-
|
29
|
+
// In some older browsers, such as Chrome 122, when a top layer element (such as a dialog)
|
30
|
+
// opens from within a popover, the "hide all popovers" internal algorithm runs, hiding
|
31
|
+
// any popover that is currently open, regardless of whether or not another top layer element,
|
32
|
+
// such as a <dialog> is nested inside.
|
33
|
+
// See https://github.com/whatwg/html/issues/9998.
|
34
|
+
// This is fixed by https://github.com/whatwg/html/pull/10116, but while we still support browsers
|
35
|
+
// that present this bug, we must undo the work they did to hide ancestral popovers of the dialog:
|
36
|
+
let node = button;
|
37
|
+
let fixed = false;
|
38
|
+
while (node) {
|
39
|
+
node = (_a = node.parentElement) === null || _a === void 0 ? void 0 : _a.closest('[popover]:not(:popover-open)');
|
40
|
+
if (node && node.popover === 'auto') {
|
41
|
+
node.classList.add('dialog-inside-popover-fix');
|
42
|
+
node.popover = 'manual';
|
43
|
+
node.showPopover();
|
44
|
+
fixed = true;
|
45
|
+
}
|
46
|
+
}
|
47
|
+
if (fixed) {
|
48
|
+
// We need to re-open the dialog as modal, and also ensure no close event listeners
|
49
|
+
// are trying to act on the close
|
50
|
+
/* eslint-disable-next-line no-restricted-syntax */
|
51
|
+
dialog.addEventListener('close', e => e.stopImmediatePropagation(), { once: true });
|
52
|
+
dialog.close();
|
53
|
+
dialog.showModal();
|
54
|
+
dialog.addEventListener('close', () => {
|
55
|
+
for (const el of dialog.ownerDocument.querySelectorAll('.dialog-inside-popover-fix')) {
|
56
|
+
if (el.contains(dialog)) {
|
57
|
+
el.classList.remove('dialog-inside-popover-fix');
|
58
|
+
el.popover = 'auto';
|
59
|
+
el.showPopover();
|
60
|
+
}
|
61
|
+
}
|
62
|
+
}, { once: true });
|
63
|
+
}
|
30
64
|
}
|
31
65
|
}
|
32
66
|
dialogId = button.getAttribute('data-close-dialog-id') || button.getAttribute('data-submit-dialog-id');
|
@@ -65,11 +99,8 @@ export class DialogHelperElement extends HTMLElement {
|
|
65
99
|
handleEvent(event) {
|
66
100
|
const target = event.target;
|
67
101
|
const dialog = this.dialog;
|
68
|
-
|
69
|
-
|
70
|
-
// if the target is inside the dialog, but is not the dialog itself, leave
|
71
|
-
// the dialog open
|
72
|
-
if ((target === null || target === void 0 ? void 0 : target.closest('dialog')) === dialog && target !== dialog)
|
102
|
+
// The click target _must_ be the dialog element itself, and not elements underneath or inside.
|
103
|
+
if (target !== dialog || !(dialog === null || dialog === void 0 ? void 0 : dialog.open))
|
73
104
|
return;
|
74
105
|
const rect = dialog.getBoundingClientRect();
|
75
106
|
const clickWasInsideDialog = rect.top <= event.clientY &&
|
@@ -7,7 +7,6 @@ function dialogInvokerButtonHandler(event: Event) {
|
|
7
7
|
// If the user is clicking a valid dialog trigger
|
8
8
|
let dialogId = button?.getAttribute('data-show-dialog-id')
|
9
9
|
if (dialogId) {
|
10
|
-
event.stopPropagation()
|
11
10
|
const dialog = document.getElementById(dialogId)
|
12
11
|
if (dialog instanceof HTMLDialogElement) {
|
13
12
|
dialog.showModal()
|
@@ -15,7 +14,46 @@ function dialogInvokerButtonHandler(event: Event) {
|
|
15
14
|
// If the behaviour is allowed through the dialog will be shown but then
|
16
15
|
// quickly hidden- as if it were never shown. This prevents that.
|
17
16
|
event.preventDefault()
|
18
|
-
|
17
|
+
|
18
|
+
// In some older browsers, such as Chrome 122, when a top layer element (such as a dialog)
|
19
|
+
// opens from within a popover, the "hide all popovers" internal algorithm runs, hiding
|
20
|
+
// any popover that is currently open, regardless of whether or not another top layer element,
|
21
|
+
// such as a <dialog> is nested inside.
|
22
|
+
// See https://github.com/whatwg/html/issues/9998.
|
23
|
+
// This is fixed by https://github.com/whatwg/html/pull/10116, but while we still support browsers
|
24
|
+
// that present this bug, we must undo the work they did to hide ancestral popovers of the dialog:
|
25
|
+
let node: HTMLElement | null | undefined = button
|
26
|
+
let fixed = false
|
27
|
+
while (node) {
|
28
|
+
node = node.parentElement?.closest('[popover]:not(:popover-open)')
|
29
|
+
if (node && node.popover === 'auto') {
|
30
|
+
node.classList.add('dialog-inside-popover-fix')
|
31
|
+
node.popover = 'manual'
|
32
|
+
node.showPopover()
|
33
|
+
fixed = true
|
34
|
+
}
|
35
|
+
}
|
36
|
+
if (fixed) {
|
37
|
+
// We need to re-open the dialog as modal, and also ensure no close event listeners
|
38
|
+
// are trying to act on the close
|
39
|
+
/* eslint-disable-next-line no-restricted-syntax */
|
40
|
+
dialog.addEventListener('close', e => e.stopImmediatePropagation(), {once: true})
|
41
|
+
dialog.close()
|
42
|
+
dialog.showModal()
|
43
|
+
dialog.addEventListener(
|
44
|
+
'close',
|
45
|
+
() => {
|
46
|
+
for (const el of dialog.ownerDocument.querySelectorAll<HTMLElement>('.dialog-inside-popover-fix')) {
|
47
|
+
if (el.contains(dialog)) {
|
48
|
+
el.classList.remove('dialog-inside-popover-fix')
|
49
|
+
el.popover = 'auto'
|
50
|
+
el.showPopover()
|
51
|
+
}
|
52
|
+
}
|
53
|
+
},
|
54
|
+
{once: true},
|
55
|
+
)
|
56
|
+
}
|
19
57
|
}
|
20
58
|
}
|
21
59
|
|
@@ -58,11 +96,8 @@ export class DialogHelperElement extends HTMLElement {
|
|
58
96
|
handleEvent(event: MouseEvent) {
|
59
97
|
const target = event.target as HTMLElement
|
60
98
|
const dialog = this.dialog
|
61
|
-
|
62
|
-
|
63
|
-
// if the target is inside the dialog, but is not the dialog itself, leave
|
64
|
-
// the dialog open
|
65
|
-
if (target?.closest('dialog') === dialog && target !== dialog) return
|
99
|
+
// The click target _must_ be the dialog element itself, and not elements underneath or inside.
|
100
|
+
if (target !== dialog || !dialog?.open) return
|
66
101
|
|
67
102
|
const rect = dialog.getBoundingClientRect()
|
68
103
|
const clickWasInsideDialog =
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<%= render(Primer::Alpha::Overlay.new(title: "An overlay", id: "first-overlay")) do |o| %>
|
2
|
+
<% o.with_show_button() { "Show overlay" } %>
|
3
|
+
<% o.with_body() do %>
|
4
|
+
<%= render(Primer::Alpha::Dialog.new(id: "dialog-one", title: title, position: position, subtitle: subtitle, visually_hide_title: false)) do |d| %>
|
5
|
+
<% d.with_show_button { button_text } %>
|
6
|
+
<% d.with_body { body_text } %>
|
7
|
+
<% d.with_footer(show_divider: true) do %>
|
8
|
+
<%= render(Primer::ButtonComponent.new(data: { "close-dialog-id": "dialog-one" })) { "Cancel" } %>
|
9
|
+
<% end %>
|
10
|
+
<% end %>
|
11
|
+
<% end %>
|
12
|
+
<% end %>
|
13
|
+
|
14
|
+
<script type="module">
|
15
|
+
document.getElementById('overlay-show-first-overlay')?.addEventListener('click', e => {
|
16
|
+
setTimeout(() => {
|
17
|
+
document.getElementById('first-overlay').querySelector('button')?.click()
|
18
|
+
});
|
19
|
+
});
|
20
|
+
</script>
|
@@ -12,3 +12,10 @@
|
|
12
12
|
<% end %>
|
13
13
|
<% end %>
|
14
14
|
<% end %>
|
15
|
+
|
16
|
+
<div style="margin-top:2rem">
|
17
|
+
<%= render(Primer::Beta::Flash.new(scheme: :warning)) do %>
|
18
|
+
<p>Please be careful nesting dialogs! Note that in this example, opening the second dialog does not close the first.</p>
|
19
|
+
<p>Closing a dialog while opening a dialog inside, will cause both to be invisible which will lead to undesired effects!</p>
|
20
|
+
<% end %>
|
21
|
+
</div>
|
@@ -248,6 +248,30 @@ module Primer
|
|
248
248
|
visually_hide_title: visually_hide_title
|
249
249
|
})
|
250
250
|
end
|
251
|
+
|
252
|
+
# @label Dialog inside Overlay
|
253
|
+
#
|
254
|
+
# @param title [String] text
|
255
|
+
# @param subtitle [String] text
|
256
|
+
# @param size [Symbol] select [small, medium, medium_portrait, large, xlarge]
|
257
|
+
# @param position [Symbol] select [center, right, left]
|
258
|
+
# @param position_narrow [Symbol] select [inherit, bottom, fullscreen, left, right]
|
259
|
+
# @param visually_hide_title [Boolean] toggle
|
260
|
+
# @param button_text [String] text
|
261
|
+
# @param body_text [String] text
|
262
|
+
# @snapshot interactive
|
263
|
+
def dialog_inside_overlay(title: "Test Dialog", subtitle: nil, position: :center, size: :medium, button_text: "Show Dialog", body_text: "Content", position_narrow: :fullscreen, visually_hide_title: false)
|
264
|
+
render_with_template(locals: {
|
265
|
+
title: title,
|
266
|
+
subtitle: subtitle,
|
267
|
+
position: position,
|
268
|
+
size: size,
|
269
|
+
button_text: button_text,
|
270
|
+
body_text: body_text,
|
271
|
+
position_narrow: position_narrow,
|
272
|
+
visually_hide_title: visually_hide_title
|
273
|
+
})
|
274
|
+
end
|
251
275
|
end
|
252
276
|
end
|
253
277
|
end
|
data/static/info_arch.json
CHANGED
@@ -2232,7 +2232,7 @@
|
|
2232
2232
|
{
|
2233
2233
|
"fully_qualified_name": "Primer::Alpha::Banner",
|
2234
2234
|
"description": "Use `Banner` to highlight important information.",
|
2235
|
-
"accessibility_docs":
|
2235
|
+
"accessibility_docs": "Depending on the scenario, some Banners may need to receive focus when they appear. This helps to maximize discoverability of the message, especially in critical scenarios. Visit the [Banner's Accessibility section](https://primer.style/components/banner#accessibility) or defer to the accessibility team to determine if your scenario requires focusing the banner.\n\nTo properly focus a banner, add a `tabindex=\"-1\"` to the Banner container, and focus that container (one way is using the [`focus()` API](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus)).\n\nFor more information about the focus management technique, visit the [Accessible Banner Prototype docs](https://github.com/github/accessibility/blob/main/docs/coaching-recommendations/toast-flash-banner/accessible-banner-prototype.md#consideration). This guidance is subject to change.",
|
2236
2236
|
"is_form_component": false,
|
2237
2237
|
"is_published": true,
|
2238
2238
|
"requires_js": true,
|
@@ -3375,6 +3375,19 @@
|
|
3375
3375
|
"color-contrast"
|
3376
3376
|
]
|
3377
3377
|
}
|
3378
|
+
},
|
3379
|
+
{
|
3380
|
+
"preview_path": "primer/alpha/dialog/dialog_inside_overlay",
|
3381
|
+
"name": "dialog_inside_overlay",
|
3382
|
+
"snapshot": "interactive",
|
3383
|
+
"skip_rules": {
|
3384
|
+
"wont_fix": [
|
3385
|
+
"region"
|
3386
|
+
],
|
3387
|
+
"will_fix": [
|
3388
|
+
"color-contrast"
|
3389
|
+
]
|
3390
|
+
}
|
3378
3391
|
}
|
3379
3392
|
],
|
3380
3393
|
"subcomponents": [
|
data/static/previews.json
CHANGED
@@ -3186,6 +3186,19 @@
|
|
3186
3186
|
"color-contrast"
|
3187
3187
|
]
|
3188
3188
|
}
|
3189
|
+
},
|
3190
|
+
{
|
3191
|
+
"preview_path": "primer/alpha/dialog/dialog_inside_overlay",
|
3192
|
+
"name": "dialog_inside_overlay",
|
3193
|
+
"snapshot": "interactive",
|
3194
|
+
"skip_rules": {
|
3195
|
+
"wont_fix": [
|
3196
|
+
"region"
|
3197
|
+
],
|
3198
|
+
"will_fix": [
|
3199
|
+
"color-contrast"
|
3200
|
+
]
|
3201
|
+
}
|
3189
3202
|
}
|
3190
3203
|
]
|
3191
3204
|
},
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: openproject-primer_view_components
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.22.
|
4
|
+
version: 0.22.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitHub Open Source
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2024-02-
|
12
|
+
date: 2024-02-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: actionview
|
@@ -760,6 +760,7 @@ files:
|
|
760
760
|
- previews/primer/alpha/dialog_preview/autofocus_element.html.erb
|
761
761
|
- previews/primer/alpha/dialog_preview/body_has_scrollbar_overflow.html.erb
|
762
762
|
- previews/primer/alpha/dialog_preview/custom_header.html.erb
|
763
|
+
- previews/primer/alpha/dialog_preview/dialog_inside_overlay.html.erb
|
763
764
|
- previews/primer/alpha/dialog_preview/nested_dialog.html.erb
|
764
765
|
- previews/primer/alpha/dialog_preview/scroll_container.html.erb
|
765
766
|
- previews/primer/alpha/dialog_preview/test.html.erb
|
@@ -915,7 +916,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
915
916
|
requirements:
|
916
917
|
- - ">="
|
917
918
|
- !ruby/object:Gem::Version
|
918
|
-
version: 2.
|
919
|
+
version: 2.7.0
|
919
920
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
920
921
|
requirements:
|
921
922
|
- - ">="
|