Skip to content

Commit

Permalink
WIP - alternate carousel treatment.
Browse files Browse the repository at this point in the history
  • Loading branch information
zoltan-dulac committed Sep 7, 2022
1 parent ba92f3a commit c7c8ce4
Show file tree
Hide file tree
Showing 11 changed files with 387 additions and 24 deletions.
3 changes: 2 additions & 1 deletion bin/promote-node-modules-to-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ const nodeFiles = [
'node_modules/dialog-polyfill/index.js',
'node_modules/jquery/dist/jquery.min.js',
'node_modules/jquery-validation/dist/jquery.validate.min.js',
'node_modules/accessibility-js-routines/dist/accessibility.module.js'
'node_modules/accessibility-js-routines/dist/accessibility.module.js',
'node_modules/inert-polyfill/inert-polyfill.js'
]


Expand Down
6 changes: 3 additions & 3 deletions content/body/carousel.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
</div>

<div class="glider-contain">
<div class="glider enable-carousel--focus-all-panels">
<div class="glider enable-carousel enable-carousel--focus-arrow-buttons">
<div class="enable-carousel__slide">
<img class="enable-carousel__background" src="images/carousel-example/00-turkish-spider-man.jpg"
alt="Bootleg versions of Spider-Man, Captain America and El Santo fighting.">
Expand Down Expand Up @@ -86,8 +86,8 @@
</div>
</div>

<button class="glider-prev" tabindex="-1" aria-hidden="true">«</button>
<button class="glider-next" tabindex="-1" aria-hidden="true">»</button>
<button class="glider-prev">«</button>
<button class="glider-next">»</button>
<div role="tablist" class="dots"></div>
</div>

Expand Down
6 changes: 4 additions & 2 deletions content/bottom/carousel.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
<script type="module">
import EnableCarousel from "./js/modules/enable-carousel.js";
const focusAllPanelsCarousel = new EnableCarousel(document.querySelector(".enable-carousel--focus-all-panels"));
//const focusAllPanelsCarousel = new EnableCarousel(document.querySelector(".enable-carousel--focus-all-panels"));
const focusArrowButtonsCarousel = new EnableCarousel(document.querySelector(".enable-carousel--focus-arrow-buttons"), {
useArrowButtons: true
});
focusAllPanelsCarousel.init();
//focusAllPanelsCarousel && focusAllPanelsCarousel.init();
focusArrowButtonsCarousel && focusArrowButtonsCarousel.init();
</script>
14 changes: 14 additions & 0 deletions css/enable-carousel.css
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,20 @@
top: 200px;
}
}
.glider-prev:focus,
.glider-next:focus {
outline: solid 2px #3b99fc !important;
outline-offset: -2px;
}
.glider-prev:focus:not(:focus-visible),
.glider-next:focus:not(:focus-visible) {
outline: none;
}
.glider-prev:focus-visible,
.glider-next:focus-visible {
outline: solid 2px #3b99fc !important;
outline-offset: -2px;
}
.glider-prev {
left: 0;
transform-origin: 0 50%;
Expand Down
269 changes: 269 additions & 0 deletions enable-node-libs/inert-polyfill/inert-polyfill.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
/*
* Copyright 2015 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/

if (!('inert' in HTMLElement.prototype)) {
Object.defineProperty(HTMLElement.prototype, 'inert', {
enumerable: true,

/**
* @return {boolean}
* @this {Element}
*/
get: function() { return this.hasAttribute('inert'); },

/**
* @param {boolean} inert
* @this {Element}
*/
set: function(inert) {
if (inert) {
this.setAttribute('inert', '');
} else {
this.removeAttribute('inert');
}
}
});

window.addEventListener('load', function() {
function applyStyle(css) {
var style = document.createElement('style');
style.type = 'text/css';
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
document.body.appendChild(style);
}
var css = "/*[inert]*/*[inert]{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;pointer-events:none}";
applyStyle(css);

/**
* Sends a fake tab event. This is only supported by some browsers.
*
* @param {boolean=} opt_shiftKey whether to send this tab with shiftKey
*/
function dispatchTabEvent(opt_shiftKey) {
var ev = null;
try {
ev = new KeyboardEvent('keydown', {
keyCode: 9,
which: 9,
key: 'Tab',
code: 'Tab',
keyIdentifier: 'U+0009',
shiftKey: !!opt_shiftKey,
bubbles: true
});
} catch (e) {
try {
// Internet Explorer
ev = document.createEvent('KeyboardEvent');
ev.initKeyboardEvent(
'keydown',
true,
true,
window,
'Tab',
0,
opt_shiftKey ? 'Shift' : '',
false,
'en'
)
} catch (e) {}
}
if (ev) {
try {
Object.defineProperty(ev, 'keyCode', { value: 9 });
} catch (e) {}
document.dispatchEvent(ev);
}
}

/**
* Determines whether the specified element is inert, and returns the element
* which caused this state. This is limited to, but may include, the body
* element.
*
* @param {Element} e to check
* @return {Element} element is made inert by, if any
*/
function madeInertBy(e) {
while (e && e !== document.documentElement) {
if (e.hasAttribute('inert')) {
return e;
}
e = e.parentElement;
}
return null;
}

/**
* Finds the nearest shadow root from an element that's within said shadow root.
*
* TODO(samthor): We probably want to find the highest shadow root.
*
* @param {Element} e to check
* @return {Node} shadow root, if any
*/
var findShadowRoot = function(e) { return null; };
if (window.ShadowRoot) {
findShadowRoot = function(e) {
while (e && e !== document.documentElement) {
if (e instanceof window.ShadowRoot) { return e; }
e = e.parentNode;
}
return null;
}
}

/**
* Returns the target of the passed event. If there's a path (shadow DOM only), then prefer it.
*
* @param {!Event} event
* @return {Element} target of event
*/
function targetForEvent(event) {
var p = event.path;
return /** @type {Element} */ (p && p[0] || event.target);
}

// Hold onto the last tab direction: next (tab) or previous (shift-tab). This
// can be used to step over inert elements in the correct direction. Mouse
// or non-tab events should reset this and inert events should focus nothing.
var lastTabDirection = 0;
document.addEventListener('keydown', function(ev) {
if (ev.keyCode === 9) {
lastTabDirection = ev.shiftKey ? -1 : +1;
} else {
lastTabDirection = 0;
}
});
document.addEventListener('mousedown', function(ev) {
lastTabDirection = 0;
});

// Retain the currently focused shadowRoot.
var focusedShadowRoot = null;
function updateFocusedShadowRoot(root) {
if (root == focusedShadowRoot) { return; }
if (focusedShadowRoot) {
if (!(focusedShadowRoot instanceof window.ShadowRoot)) {
throw new Error('not shadow root: ' + focusedShadowRoot);
}
focusedShadowRoot.removeEventListener('focusin', shadowFocusHandler, true); // remove
}
if (root) {
root.addEventListener('focusin', shadowFocusHandler, true); // add
}
focusedShadowRoot = root;
}

/**
* Focus handler on a Shadow DOM host. This traps focus events within that root.
*
* @param {!Event} ev
*/
function shadowFocusHandler(ev) {
// ignore "direct" focus, we only want shadow root focus
var last = ev.path[ev.path.length - 1];
if (last === /** @type {*} */ (window)) { return; }
sharedFocusHandler(targetForEvent(ev));
ev.preventDefault();
ev.stopPropagation();
}

/**
* Called indirectly by both the regular focus handler and Shadow DOM host focus handler. This
* is the bulk of the polyfill which prevents focus.
*
* @param {Element} target focused on
*/
function sharedFocusHandler(target) {
var inertElement = madeInertBy(target);
if (!inertElement) { return; }

// If the page has been tabbed recently, then focus the next element
// in the known direction (if available).
if (document.hasFocus() && lastTabDirection !== 0) {
function getFocused() {
return (focusedShadowRoot || document).activeElement;
}

// Send a fake tab event to enumerate through the browser's view of
// focusable elements. This is supported in some browsers (not Firefox).
var previous = getFocused();
dispatchTabEvent(lastTabDirection < 0 ? true : false);
if (previous != getFocused()) { return; }

// Otherwise, enumerate through adjacent elements to find the next
// focusable element. This won't respect any custom tabIndex.
var filter = /** @type {NodeFilter} */ ({
/**
* @param {Node} node
* @return {number}
*/
acceptNode: function(node) {
if (!node || !node.focus || node.tabIndex < 0) {
return NodeFilter.FILTER_SKIP; // look at descendants
}
var contained = inertElement.contains(node);
return contained ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT;
},
});
var walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, filter);
walker.currentNode = inertElement;

var nextFunc = Math.sign(lastTabDirection) === -1 ? walker.previousNode : walker.nextNode
var next = nextFunc.bind(walker);
for (var candidate; candidate = next(); ) {
candidate.focus();
if (getFocused() !== previous) { return; }
}

// FIXME: If a focusable element can't be found here, it's likely to mean
// that this is the start or end of the page. Blurring is then not quite
// right, as it prevents access to the browser chrome.
}

// Otherwise, immediately blur the targeted element. Technically, this
// still generates focus and blur events on the element. This is (probably)
// the price to pay for this polyfill.
target.blur();
}

// The 'focusin' event bubbles, but instead, use 'focus' with useCapture set
// to true as this is supported in Firefox. Additionally, target the body so
// this doesn't generate superfluous events on document itself.
document.body.addEventListener('focus', function(ev) {
var target = targetForEvent(ev);
updateFocusedShadowRoot((target == ev.target ? null : findShadowRoot(target)));
sharedFocusHandler(target); // either real DOM node or shadow node
}, true);

// Use a capturing click listener as both a safety fallback where pointer-events is not
// available (IE10 and below), and to prevent accessKey access to inert elements.
// TODO(samthor): Note that pointer-events polyfills trap more mouse events, e.g.-
// https://github.com/kmewhort/pointer_events_polyfill
document.addEventListener('click', function(ev) {
var target = targetForEvent(ev);
if (madeInertBy(target)) {
ev.preventDefault();
ev.stopPropagation();
}
}, true);
});
}
Loading

0 comments on commit c7c8ce4

Please sign in to comment.