Skip to content

Commit

Permalink
Revert "refactor Dialog to use <dialog> internally" (#2468)
Browse files Browse the repository at this point in the history
  • Loading branch information
camertron authored Dec 19, 2023
1 parent 52df12f commit 0a25f58
Show file tree
Hide file tree
Showing 18 changed files with 237 additions and 144 deletions.
5 changes: 0 additions & 5 deletions .changeset/wise-coats-buy.md

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -279,15 +279,7 @@ export class ActionMenuElement extends HTMLElement {
if (this.#isOpen()) {
this.#hide()
}
const activeElement = this.ownerDocument.activeElement
const lostFocus = this.ownerDocument.activeElement === this.ownerDocument.body
const focusInClosedMenu = this.contains(activeElement)
if (lostFocus || focusInClosedMenu) {
setTimeout(() => this.invokerElement?.focus(), 0)
}
}
// a modal <dialog> element will close all popovers
setTimeout(() => this.#show(), 0)
dialog.addEventListener('close', handleDialogClose, {signal})
dialog.addEventListener('cancel', handleDialogClose, {signal})
}
Expand Down
4 changes: 2 additions & 2 deletions app/components/primer/alpha/dialog.html.erb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<%= show_button %>
<dialog-helper>
<div class="Overlay--hidden <%= @backdrop_classes %>" data-modal-dialog-overlay>
<%= render Primer::BaseComponent.new(**@system_arguments) do %>
<%= header %>
<% if content.present? %>
Expand All @@ -11,4 +11,4 @@
<%= footer %>
<% end %>
<% end %>
</dialog-helper>
</div>
10 changes: 0 additions & 10 deletions app/components/primer/alpha/dialog.pcss
Original file line number Diff line number Diff line change
@@ -1,15 +1,5 @@
/* Overlay */

dialog.Overlay:not([open]) {
display: none;
}

/* dialog defualts to CanvasText not inherit */
dialog.Overlay {
color: var(--fgColor-default);
}


.Overlay--hidden {
display: none !important;
}
Expand Down
3 changes: 2 additions & 1 deletion app/components/primer/alpha/dialog.rb
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ def initialize(
@position_narrow = position_narrow
@visually_hide_title = visually_hide_title

@system_arguments[:tag] = "dialog"
@system_arguments[:tag] = "modal-dialog"
@system_arguments[:role] = "dialog"
@system_arguments[:id] = @id
@system_arguments[:aria] = { modal: true }
@system_arguments[:aria] = merge_aria(
Expand Down
199 changes: 199 additions & 0 deletions app/components/primer/alpha/modal_dialog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import {focusTrap} from '@primer/behaviors'
import {getFocusableChild} from '@primer/behaviors/utils'

function focusIfNeeded(elem: HTMLElement | undefined | null) {
if (document.activeElement !== elem) {
elem?.focus()
}
}

const overlayStack: ModalDialogElement[] = []

function clickHandler(event: Event) {
const target = event.target as HTMLElement
const button = target?.closest('button')

if (!button || button.hasAttribute('disabled') || button.getAttribute('aria-disabled') === 'true') return

// If the user is clicking a valid dialog trigger
let dialogId = button?.getAttribute('data-show-dialog-id')
if (dialogId) {
event.stopPropagation()
const dialog = document.getElementById(dialogId)
if (dialog instanceof ModalDialogElement) {
dialog.openButton = button
dialog.show()
// A buttons default behaviour in some browsers it to send a pointer event
// If the behaviour is allowed through the dialog will be shown but then
// quickly hidden- as if it were never shown. This prevents that.
event.preventDefault()
return
}
}

if (!overlayStack.length) return

dialogId = button.getAttribute('data-close-dialog-id') || button.getAttribute('data-submit-dialog-id')
if (dialogId) {
const dialog = document.getElementById(dialogId)
if (dialog instanceof ModalDialogElement) {
const dialogIndex = overlayStack.findIndex(ele => ele.id === dialogId)
overlayStack.splice(dialogIndex, 1)
dialog.close(button.hasAttribute('data-submit-dialog-id'))
}
}
}

function keydownHandler(event: Event) {
if (
!(event instanceof KeyboardEvent) ||
event.type !== 'keydown' ||
event.key !== 'Enter' ||
event.ctrlKey ||
event.altKey ||
event.metaKey ||
event.shiftKey
)
return

clickHandler(event)
}

function mousedownHandler(event: Event) {
const target = event.target as HTMLElement
if (target?.closest('button')) return

// Find the top level dialog that is open.
const topLevelDialog = overlayStack[overlayStack.length - 1]
if (!topLevelDialog) return

// Check if the mousedown happened outside the boundary of the top level dialog
const mouseDownOutsideDialog = !target.closest(`#${topLevelDialog.getAttribute('id')}`)

// Only close dialog if it's a click outside the dialog and the dialog has a button?
if (mouseDownOutsideDialog) {
target.ownerDocument.addEventListener(
'mouseup',
(upEvent: Event) => {
if (upEvent.target === target) {
overlayStack.pop()
topLevelDialog.close()
}
},
{once: true},
)
}
}

export class ModalDialogElement extends HTMLElement {
//TODO: Do we remove the abortController from focusTrap?
#focusAbortController = new AbortController()
openButton: HTMLButtonElement | null

get open() {
return this.hasAttribute('open')
}
set open(value: boolean) {
if (value) {
if (this.open) return
this.setAttribute('open', '')
this.setAttribute('aria-disabled', 'false')
document.body.style.paddingRight = `${window.innerWidth - document.body.clientWidth}px`
document.body.style.overflow = 'hidden'
this.#overlayBackdrop?.classList.remove('Overlay--hidden')
if (this.#focusAbortController.signal.aborted) {
this.#focusAbortController = new AbortController()
}
focusTrap(this, this.querySelector('[autofocus]') as HTMLElement, this.#focusAbortController.signal)
overlayStack.push(this)
} else {
if (!this.open) return
this.removeAttribute('open')
this.setAttribute('aria-disabled', 'true')
this.#overlayBackdrop?.classList.add('Overlay--hidden')
document.body.style.paddingRight = '0'
document.body.style.overflow = 'initial'
this.#focusAbortController.abort()
// if #openButton is a child of a menu, we need to focus a suitable child of the menu
// element since it is expected for the menu to close on click
const menu = this.openButton?.closest('details') || this.openButton?.closest('action-menu')
if (menu) {
focusIfNeeded(getFocusableChild(menu))
} else {
focusIfNeeded(this.openButton)
}
this.openButton = null
}
}

get #overlayBackdrop(): HTMLElement | null {
if (this.parentElement?.hasAttribute('data-modal-dialog-overlay')) {
return this.parentElement
}

return null
}

get showButtons(): NodeList {
// Dialogs may also be opened from any arbitrary button with a matching show-dialog-id data attribute
return document.querySelectorAll(`button[data-show-dialog-id='${this.id}']`)
}

connectedCallback(): void {
if (!this.hasAttribute('role')) this.setAttribute('role', 'dialog')

document.addEventListener('click', clickHandler)
document.addEventListener('keydown', keydownHandler)
document.addEventListener('mousedown', mousedownHandler)

this.addEventListener('keydown', e => this.#keydown(e))
}

show() {
this.open = true
}

close(closedNotCancelled = false) {
if (this.open === false) return
const eventType = closedNotCancelled ? 'close' : 'cancel'
const dialogEvent = new Event(eventType)
this.dispatchEvent(dialogEvent)
this.open = false
}

#keydown(event: Event) {
if (!(event instanceof KeyboardEvent)) return
if (event.isComposing) return
if (!this.open) return

switch (event.key) {
case 'Escape':
this.close()
event.preventDefault()
event.stopPropagation()
break
case 'Enter': {
const target = event.target as HTMLElement

if (target.getAttribute('data-close-dialog-id') === this.id) {
event.stopPropagation()
}
break
}
}
}
}

declare global {
interface Window {
ModalDialogElement: typeof ModalDialogElement
}
interface HTMLElementTagNameMap {
'modal-dialog': ModalDialogElement
}
}

if (!window.customElements.get('modal-dialog')) {
window.ModalDialogElement = ModalDialogElement
window.customElements.define('modal-dialog', ModalDialogElement)
}
9 changes: 1 addition & 8 deletions app/components/primer/alpha/overlay.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,6 @@ anchored-position[popover] {
overflow: visible;
}

.Overlay {
display: flex;
border-width: 0;
padding: 0;
}

anchored-position:not(.Overlay) {
background: none;
}
Expand All @@ -21,7 +15,7 @@ anchored-position:not(.Overlay) {
display: none
}

anchored-position.not-anchored::backdrop, dialog::backdrop {
anchored-position.not-anchored::backdrop {
background-color: var(--overlay-backdrop-bgColor, var(--color-neutral-muted));
}

Expand All @@ -30,4 +24,3 @@ anchored-position.not-anchored::backdrop, dialog::backdrop {
outline: solid 1px transparent;
}
}

77 changes: 0 additions & 77 deletions app/components/primer/dialog_helper.ts

This file was deleted.

2 changes: 1 addition & 1 deletion app/components/primer/primer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import '@github/include-fragment-element'
import './alpha/action_bar_element'
import './alpha/dropdown'
import './anchored_position'
import './dialog_helper'
import './focus_group'
import './scrollable_region'
import './alpha/image_crop'
import './alpha/modal_dialog'
import './beta/nav_list'
import './beta/nav_list_group_element'
import './alpha/segmented_control'
Expand Down
1 change: 0 additions & 1 deletion demo/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@
"turbolinks": "^5.2.0",
"webpack-dev-server": "^4.15.1"
}
}
}
Loading

0 comments on commit 0a25f58

Please sign in to comment.