Skip to content

Commit

Permalink
Remove runtime checks with a new type and some casts.
Browse files Browse the repository at this point in the history
  • Loading branch information
aomarks committed Jul 26, 2019
1 parent 5e1a3f9 commit c419da5
Showing 1 changed file with 41 additions and 42 deletions.
83 changes: 41 additions & 42 deletions src/blocking-elements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,17 @@ const _getDistributedChildren = Symbol();
const _isInertable = Symbol();
const _handleMutations = Symbol();

interface InertableHTMLElement extends HTMLElement {
interface Inertable extends HTMLElement {
inert?: boolean;
[_siblingsToRestore]?: Set<InertableHTMLElement>;
[_parentMO]?: MutationObserver;
}

interface InternalState {
[_siblingsToRestore]: Set<MaybeHasInternalState>;
[_parentMO]: MutationObserver;
}
interface HasInternalState extends Inertable, InternalState {}
interface MaybeHasInternalState extends Inertable, Partial<InternalState> {}

/**
* `BlockingElements` manages a stack of elements that inert the interaction
* outside them. The top element is the interactive part of the document.
Expand All @@ -47,21 +52,21 @@ class BlockingElements {
/**
* The blocking elements.
*/
private[_blockingElements]: InertableHTMLElement[] = [];
private[_blockingElements]: MaybeHasInternalState[] = [];

/**
* Used to keep track of the parents of the top element, from the element
* itself up to body. When top changes, the old top might have been removed
* from the document, so we need to memoize the inerted parents' siblings
* in order to restore their inerteness when top changes.
*/
private[_topElParents]: InertableHTMLElement[] = [];
private[_topElParents]: HasInternalState[] = [];

/**
* Elements that are already inert before the first blocking element is
* pushed.
*/
private[_alreadyInertElements] = new Set<InertableHTMLElement>();
private[_alreadyInertElements] = new Set<MaybeHasInternalState>();

/**
* Call this whenever this object is about to become obsolete. This empties
Expand Down Expand Up @@ -145,7 +150,7 @@ class BlockingElements {
* Sets `inert` to all document elements except the new top element, its
* parents, and its distributed content.
*/
private[_topChanged](newTop: HTMLElement|null): void {
private[_topChanged](newTop: MaybeHasInternalState|null): void {
const toKeepInert = this[_alreadyInertElements];
const oldParents = this[_topElParents];
// No new top, reset old top if any.
Expand All @@ -161,7 +166,8 @@ class BlockingElements {
if (newParents[newParents.length - 1].parentNode !== document.body) {
throw Error('Non-connected element cannot be a blocking element');
}
this[_topElParents] = newParents;
// Cast here because we know we'll call _inertSiblings on newParents below.
this[_topElParents] = newParents as Array<HasInternalState>;

const toSkip = this[_getDistributedChildren](newTop);

Expand Down Expand Up @@ -196,26 +202,24 @@ class BlockingElements {
* https://html.spec.whatwg.org/multipage/interaction.html#inert
*/
private[_swapInertedSibling](
oldInert: InertableHTMLElement, newInert: InertableHTMLElement): void {
oldInert: HasInternalState, newInert: MaybeHasInternalState): void {
const siblingsToRestore = oldInert[_siblingsToRestore];
// oldInert is not contained in siblings to restore, so we have to check
// if it's inertable and if already inert.
if (this[_isInertable](oldInert) && !oldInert.inert) {
oldInert.inert = true;
if (siblingsToRestore) {
siblingsToRestore.add(oldInert);
}
siblingsToRestore.add(oldInert);
}
// If newInert was already between the siblings to restore, it means it is
// inertable and must be restored.
if (siblingsToRestore && siblingsToRestore.has(newInert)) {
if (siblingsToRestore.has(newInert)) {
newInert.inert = false;
siblingsToRestore.delete(newInert);
}
newInert[_parentMO] = oldInert[_parentMO];
oldInert[_parentMO] = undefined;
newInert[_siblingsToRestore] = siblingsToRestore;
oldInert[_siblingsToRestore] = undefined;
(oldInert as MaybeHasInternalState)[_parentMO] = undefined;
(oldInert as MaybeHasInternalState)[_siblingsToRestore] = undefined;
}

/**
Expand All @@ -224,21 +228,19 @@ class BlockingElements {
* doesn't specify if it should be reflected.
* https://html.spec.whatwg.org/multipage/interaction.html#inert
*/
private[_restoreInertedSiblings](elements: InertableHTMLElement[]) {
elements.forEach((el) => {
const mo = el[_parentMO];
if (mo !== undefined) {
mo.disconnect();
}
el[_parentMO] = undefined;
const siblings = el[_siblingsToRestore];
private[_restoreInertedSiblings](elements: HasInternalState[]) {
for (const element of elements) {
const mo = element[_parentMO];
mo.disconnect();
(element as MaybeHasInternalState)[_parentMO] = undefined;
const siblings = element[_siblingsToRestore];
if (siblings !== undefined) {
for (const sibling of siblings) {
sibling.inert = false;
}
}
el[_siblingsToRestore] = undefined;
});
(element as MaybeHasInternalState)[_siblingsToRestore] = undefined;
}
}

/**
Expand All @@ -250,14 +252,14 @@ class BlockingElements {
* https://html.spec.whatwg.org/multipage/interaction.html#inert
*/
private[_inertSiblings](
elements: InertableHTMLElement[], toSkip: Set<HTMLElement>|null,
elements: MaybeHasInternalState[], toSkip: Set<HTMLElement>|null,
toKeepInert: Set<HTMLElement>|null) {
for (const element of elements) {
const children =
element.parentNode !== null ? element.parentNode.children : [];
// Assume element is not a Document, so it must have a parentNode.
const children = element.parentNode!.children;
const inertedSiblings = new Set<HTMLElement>();
for (let j = 0; j < children.length; j++) {
const sibling = children[j] as InertableHTMLElement;
const sibling = children[j] as MaybeHasInternalState;
// Skip the input element, if not inertable or to be skipped.
if (sibling === element || !this[_isInertable](sibling) ||
(toSkip && toSkip.has(sibling))) {
Expand All @@ -276,11 +278,10 @@ class BlockingElements {
// Observe only immediate children mutations on the parent.
const mo = new MutationObserver(this[_handleMutations].bind(this));
element[_parentMO] = mo;
if (element.parentNode !== null) {
mo.observe(element.parentNode, {
childList: true,
});
}
// Assume element is not a Document, so it must have a parentNode.
mo.observe(element.parentNode!, {
childList: true,
});
}
}

Expand All @@ -295,37 +296,35 @@ class BlockingElements {
for (const mutation of mutations) {
const idx = mutation.target === document.body ?
parents.length :
parents.indexOf(mutation.target as InertableHTMLElement);
parents.indexOf(mutation.target as HasInternalState);
const inertedChild = parents[idx - 1];
const inertedSiblings = inertedChild[_siblingsToRestore];

// To restore.
for (let i = 0; i < mutation.removedNodes.length; i++) {
const sibling = mutation.removedNodes[i] as InertableHTMLElement;
const sibling = mutation.removedNodes[i] as MaybeHasInternalState;
if (sibling === inertedChild) {
console.info('Detected removal of the top Blocking Element.');
this.pop();
return;
}
if (inertedSiblings && inertedSiblings.has(sibling)) {
if (inertedSiblings.has(sibling)) {
sibling.inert = false;
inertedSiblings.delete(sibling);
}
}

// To inert.
for (let i = 0; i < mutation.addedNodes.length; i++) {
const sibling = mutation.removedNodes[i] as InertableHTMLElement;
const sibling = mutation.removedNodes[i] as MaybeHasInternalState;
if (!this[_isInertable](sibling)) {
continue;
}
if (toKeepInert && sibling.inert) {
toKeepInert.add(sibling);
} else {
sibling.inert = true;
if (inertedSiblings) {
inertedSiblings.add(sibling);
}
inertedSiblings.add(sibling);
}
}
}
Expand Down Expand Up @@ -354,7 +353,7 @@ class BlockingElements {
// ShadowDom v1
if (current.assignedSlot) {
// Collect slots from deepest slot to top.
while ((current = current.assignedSlot)) {
while (current = current.assignedSlot) {
parents.push(current);
}
// Continue the search on the top slot.
Expand Down

0 comments on commit c419da5

Please sign in to comment.