Copybara bot | be50d49 | 2023-11-30 00:16:42 +0100 | [diff] [blame] | 1 | 'use strict'; |
| 2 | |
| 3 | var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray'); |
| 4 | |
| 5 | var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2); |
| 6 | |
| 7 | var _constants = require('../utils/constants'); |
| 8 | |
| 9 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } |
| 10 | |
| 11 | var MDL_RIPPLE_CONTAINER = 'mdlext-lightboard__slide__ripple-container'; /** |
| 12 | * @license |
| 13 | * Copyright 2016 Leif Olsen. All Rights Reserved. |
| 14 | * |
| 15 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 16 | * you may not use this file except in compliance with the License. |
| 17 | * You may obtain a copy of the License at |
| 18 | * |
| 19 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 20 | * |
| 21 | * Unless required by applicable law or agreed to in writing, software |
| 22 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 23 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 24 | * See the License for the specific language governing permissions and |
| 25 | * limitations under the License. |
| 26 | * |
| 27 | * This code is built with Google Material Design Lite, |
| 28 | * which is Licensed under the Apache License, Version 2.0 |
| 29 | */ |
| 30 | |
| 31 | /** |
| 32 | * A lightboard is a translucent surface illuminated from behind, used for situations |
| 33 | * where a shape laid upon the surface needs to be seen with high contrast. In the "old days" of photography |
| 34 | * photograpers used a lightboard to get a quick view of their slides. The goal is to create a responsive lightbox |
| 35 | * design, based on flex layout, similar to what is used in Adobe LightRoom to browse images. |
| 36 | */ |
| 37 | |
| 38 | (function () { |
| 39 | 'use strict'; |
| 40 | |
| 41 | //const LIGHTBOARD = 'mdlext-lightboard'; |
| 42 | |
| 43 | var LIGHTBOARD_ROLE = 'grid'; |
| 44 | var SLIDE = 'mdlext-lightboard__slide'; |
| 45 | var SLIDE_ROLE = 'gridcell'; |
| 46 | var SLIDE_TABSTOP = 'mdlext-lightboard__slide__frame'; |
| 47 | /** |
| 48 | * @constructor |
| 49 | * @param {Element} element The element that will be upgraded. |
| 50 | */ |
| 51 | var MaterialExtLightboard = function MaterialExtLightboard(element) { |
| 52 | // Stores the element. |
| 53 | this.element_ = element; |
| 54 | |
| 55 | // Initialize instance. |
| 56 | this.init(); |
| 57 | }; |
| 58 | window['MaterialExtLightboard'] = MaterialExtLightboard; |
| 59 | |
| 60 | // Helpers |
| 61 | var getSlide = function getSlide(element) { |
| 62 | return element ? element.closest('.' + SLIDE) : null; |
| 63 | }; |
| 64 | |
| 65 | // Private methods. |
| 66 | |
| 67 | /** |
| 68 | * Select a slide, i.e. set aria-selected="true" |
| 69 | * @param element |
| 70 | * @private |
| 71 | */ |
| 72 | MaterialExtLightboard.prototype.selectSlide_ = function (element) { |
| 73 | var slide = getSlide(element); |
| 74 | if (slide && !slide.hasAttribute('aria-selected')) { |
| 75 | [].concat((0, _toConsumableArray3.default)(this.element_.querySelectorAll('.' + SLIDE + '[aria-selected="true"]'))).forEach(function (selectedSlide) { |
| 76 | return selectedSlide.removeAttribute('aria-selected'); |
| 77 | }); |
| 78 | |
| 79 | slide.setAttribute('aria-selected', 'true'); |
| 80 | } |
| 81 | }; |
| 82 | |
| 83 | /** |
| 84 | * Dispatch select event |
| 85 | * @param {Element} slide The slide that caused the event |
| 86 | * @private |
| 87 | */ |
| 88 | MaterialExtLightboard.prototype.dispatchSelectEvent_ = function (slide) { |
| 89 | this.element_.dispatchEvent(new CustomEvent('select', { |
| 90 | bubbles: true, |
| 91 | cancelable: true, |
| 92 | detail: { source: slide } |
| 93 | })); |
| 94 | }; |
| 95 | |
| 96 | /** |
| 97 | * Handles custom command event, 'first', 'next', 'prev', 'last', 'select' or upgrade |
| 98 | * @param event. A custom event |
| 99 | * @private |
| 100 | */ |
| 101 | MaterialExtLightboard.prototype.commandHandler_ = function (event) { |
| 102 | event.preventDefault(); |
| 103 | event.stopPropagation(); |
| 104 | |
| 105 | if (event && event.detail) { |
| 106 | this.command(event.detail); |
| 107 | } |
| 108 | }; |
| 109 | |
| 110 | // Public methods |
| 111 | |
| 112 | /** |
| 113 | * Initialize lightboard slides |
| 114 | * @public |
| 115 | */ |
| 116 | MaterialExtLightboard.prototype.upgradeSlides = function () { |
| 117 | |
| 118 | var addRipple = function addRipple(slide) { |
| 119 | // Use slide frame as ripple container |
| 120 | if (!slide.querySelector('.' + MDL_RIPPLE_CONTAINER)) { |
| 121 | var a = slide.querySelector('.' + SLIDE_TABSTOP); |
| 122 | if (a) { |
| 123 | var rippleContainer = a; |
| 124 | rippleContainer.classList.add(MDL_RIPPLE_CONTAINER); |
| 125 | rippleContainer.classList.add(_constants.MDL_RIPPLE_EFFECT); |
| 126 | var ripple = document.createElement('span'); |
| 127 | ripple.classList.add(_constants.MDL_RIPPLE); |
| 128 | rippleContainer.appendChild(ripple); |
| 129 | componentHandler.upgradeElement(rippleContainer, _constants.MDL_RIPPLE_COMPONENT); |
| 130 | } |
| 131 | } |
| 132 | }; |
| 133 | |
| 134 | var hasRippleEffect = this.element_.classList.contains(_constants.MDL_RIPPLE_EFFECT); |
| 135 | |
| 136 | [].concat((0, _toConsumableArray3.default)(this.element_.querySelectorAll('.' + SLIDE))).forEach(function (slide) { |
| 137 | |
| 138 | slide.setAttribute('role', SLIDE_ROLE); |
| 139 | |
| 140 | if (!slide.querySelector('a')) { |
| 141 | slide.setAttribute('tabindex', '0'); |
| 142 | } |
| 143 | if (hasRippleEffect) { |
| 144 | addRipple(slide); |
| 145 | } |
| 146 | }); |
| 147 | }; |
| 148 | MaterialExtLightboard.prototype['upgradeSlides'] = MaterialExtLightboard.prototype.upgradeSlides; |
| 149 | |
| 150 | /** |
| 151 | * Execute command |
| 152 | * @param detail |
| 153 | * @public |
| 154 | */ |
| 155 | MaterialExtLightboard.prototype.command = function (detail) { |
| 156 | var _this = this; |
| 157 | |
| 158 | var firstSlide = function firstSlide() { |
| 159 | return _this.element_.querySelector('.' + SLIDE + ':first-child'); |
| 160 | }; |
| 161 | |
| 162 | var lastSlide = function lastSlide() { |
| 163 | return _this.element_.querySelector('.' + SLIDE + ':last-child'); |
| 164 | }; |
| 165 | |
| 166 | var nextSlide = function nextSlide() { |
| 167 | var slide = _this.element_.querySelector('.' + SLIDE + '[aria-selected="true"]').nextElementSibling; |
| 168 | return slide ? slide : firstSlide(); |
| 169 | }; |
| 170 | |
| 171 | var prevSlide = function prevSlide() { |
| 172 | var slide = _this.element_.querySelector('.' + SLIDE + '[aria-selected="true"]').previousElementSibling; |
| 173 | return slide ? slide : lastSlide(); |
| 174 | }; |
| 175 | |
| 176 | if (detail && detail.action) { |
| 177 | var action = detail.action, |
| 178 | target = detail.target; |
| 179 | |
| 180 | |
| 181 | var slide = void 0; |
| 182 | switch (action.toLowerCase()) { |
| 183 | case 'select': |
| 184 | slide = getSlide(target); |
| 185 | this.dispatchSelectEvent_(slide); |
| 186 | break; |
| 187 | case 'first': |
| 188 | slide = firstSlide(); |
| 189 | break; |
| 190 | case 'next': |
| 191 | slide = nextSlide(); |
| 192 | break; |
| 193 | case 'prev': |
| 194 | slide = prevSlide(); |
| 195 | break; |
| 196 | case 'last': |
| 197 | slide = lastSlide(); |
| 198 | break; |
| 199 | case 'upgrade': |
| 200 | this.upgradeSlides(); |
| 201 | break; |
| 202 | default: |
| 203 | throw new Error('Unknown action "' + action + '". Action must be one of "first", "next", "prev", "last", "select" or "upgrade"'); |
| 204 | } |
| 205 | |
| 206 | if (slide) { |
| 207 | var a = slide.querySelector('a'); |
| 208 | if (a) { |
| 209 | a.focus(); |
| 210 | } else { |
| 211 | slide.focus(); |
| 212 | } |
| 213 | |
| 214 | // Workaround for JSDom testing: |
| 215 | // In JsDom 'element.focus()' does not trigger any focus event |
| 216 | if (!slide.hasAttribute('aria-selected')) { |
| 217 | this.selectSlide_(slide); |
| 218 | } |
| 219 | } |
| 220 | } |
| 221 | }; |
| 222 | MaterialExtLightboard.prototype['command'] = MaterialExtLightboard.prototype.command; |
| 223 | |
| 224 | /** |
| 225 | * Initialize component |
| 226 | */ |
| 227 | MaterialExtLightboard.prototype.init = function () { |
| 228 | var _this2 = this; |
| 229 | |
| 230 | var keydownHandler = function keydownHandler(event) { |
| 231 | |
| 232 | if (event.target !== _this2.element_) { |
| 233 | var action = void 0; |
| 234 | var target = void 0; |
| 235 | switch (event.keyCode) { |
| 236 | case _constants.VK_HOME: |
| 237 | action = 'first'; |
| 238 | break; |
| 239 | case _constants.VK_END: |
| 240 | action = 'last'; |
| 241 | break; |
| 242 | case _constants.VK_ARROW_UP: |
| 243 | case _constants.VK_ARROW_LEFT: |
| 244 | action = 'prev'; |
| 245 | break; |
| 246 | case _constants.VK_ARROW_DOWN: |
| 247 | case _constants.VK_ARROW_RIGHT: |
| 248 | action = 'next'; |
| 249 | break; |
| 250 | case _constants.VK_ENTER: |
| 251 | case _constants.VK_SPACE: |
| 252 | action = 'select'; |
| 253 | target = event.target; |
| 254 | break; |
| 255 | } |
| 256 | if (action) { |
| 257 | event.preventDefault(); |
| 258 | event.stopPropagation(); |
| 259 | _this2.command({ action: action, target: target }); |
| 260 | } |
| 261 | } |
| 262 | }; |
| 263 | |
| 264 | var clickHandler = function clickHandler(event) { |
| 265 | event.preventDefault(); |
| 266 | event.stopPropagation(); |
| 267 | |
| 268 | if (event.target !== _this2.element_) { |
| 269 | _this2.command({ action: 'select', target: event.target }); |
| 270 | } |
| 271 | }; |
| 272 | |
| 273 | var focusHandler = function focusHandler(event) { |
| 274 | event.preventDefault(); |
| 275 | event.stopPropagation(); |
| 276 | |
| 277 | if (event.target !== _this2.element_) { |
| 278 | _this2.selectSlide_(event.target); |
| 279 | } |
| 280 | }; |
| 281 | |
| 282 | if (this.element_) { |
| 283 | this.element_.setAttribute('role', LIGHTBOARD_ROLE); |
| 284 | |
| 285 | if (this.element_.classList.contains(_constants.MDL_RIPPLE_EFFECT)) { |
| 286 | this.element_.classList.add(_constants.MDL_RIPPLE_EFFECT_IGNORE_EVENTS); |
| 287 | } |
| 288 | |
| 289 | // Remove listeners, just in case ... |
| 290 | this.element_.removeEventListener('command', this.commandHandler_); |
| 291 | this.element_.removeEventListener('keydown', keydownHandler); |
| 292 | this.element_.removeEventListener('click', clickHandler); |
| 293 | this.element_.removeEventListener('focus', focusHandler); |
| 294 | |
| 295 | this.element_.addEventListener('command', this.commandHandler_.bind(this), false); |
| 296 | this.element_.addEventListener('keydown', keydownHandler, true); |
| 297 | this.element_.addEventListener('click', clickHandler, true); |
| 298 | this.element_.addEventListener('focus', focusHandler, true); |
| 299 | |
| 300 | this.upgradeSlides(); |
| 301 | |
| 302 | this.element_.classList.add(_constants.IS_UPGRADED); |
| 303 | } |
| 304 | }; |
| 305 | |
| 306 | // The component registers itself. It can assume componentHandler is available |
| 307 | // in the global scope. |
| 308 | /* eslint no-undef: 0 */ |
| 309 | /* jshint undef:false */ |
| 310 | componentHandler.register({ |
| 311 | constructor: MaterialExtLightboard, |
| 312 | classAsString: 'MaterialExtLightboard', |
| 313 | cssClass: 'mdlext-js-lightboard', |
| 314 | widget: true |
| 315 | }); |
| 316 | })(); |