blob: 03ceed82a12af9b6fb8d10306a95b994b45fa203 [file] [log] [blame]
Copybara botbe50d492023-11-30 00:16:42 +01001'use strict';
2
3var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray');
4
5var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2);
6
7var _constants = require('../utils/constants');
8
9function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
10
11var 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})();