@graupl/navigation-shelf 1.0.0-beta.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/css/component/navigation-shelf.css +36 -0
- package/dist/css/component/navigation-shelf.css.map +1 -0
- package/dist/css/component.css +36 -0
- package/dist/css/component.css.map +1 -0
- package/dist/css/navigation-shelf.css +36 -0
- package/dist/css/navigation-shelf.css.map +1 -0
- package/dist/css/utilities/shelf-aware.css +2 -0
- package/dist/css/utilities/shelf-aware.css.map +1 -0
- package/dist/css/utilities/width.css +2 -0
- package/dist/css/utilities/width.css.map +1 -0
- package/dist/css/utilities.css +2 -0
- package/dist/css/utilities.css.map +1 -0
- package/dist/js/component/navigation-shelf.cjs.js +5 -0
- package/dist/js/component/navigation-shelf.cjs.js.map +1 -0
- package/dist/js/component/navigation-shelf.es.js +5 -0
- package/dist/js/component/navigation-shelf.es.js.map +1 -0
- package/dist/js/component/navigation-shelf.iife.js +5 -0
- package/dist/js/component/navigation-shelf.iife.js.map +1 -0
- package/dist/js/generator/navigation-shelf.cjs.js +5 -0
- package/dist/js/generator/navigation-shelf.cjs.js.map +1 -0
- package/dist/js/generator/navigation-shelf.es.js +5 -0
- package/dist/js/generator/navigation-shelf.es.js.map +1 -0
- package/dist/js/generator/navigation-shelf.iife.js +5 -0
- package/dist/js/generator/navigation-shelf.iife.js.map +1 -0
- package/dist/js/navigation-shelf.js +5 -0
- package/dist/js/navigation-shelf.js.map +1 -0
- package/package.json +71 -0
- package/scss/component/navigation-shelf.scss +3 -0
- package/scss/component.scss +3 -0
- package/scss/navigation-shelf.scss +3 -0
- package/scss/utilities/shelf-aware.scss +3 -0
- package/scss/utilities/width.scss +3 -0
- package/scss/utilities.scss +3 -0
- package/src/js/navigation-shelf/NavigationShelf.js +1912 -0
- package/src/js/navigation-shelf/generator.js +38 -0
- package/src/js/navigation-shelf/index.js +5 -0
- package/src/js/validate.js +46 -0
- package/src/scss/_index.scss +2 -0
- package/src/scss/component/_index.scss +1 -0
- package/src/scss/component/navigation-shelf/_defaults.scss +85 -0
- package/src/scss/component/navigation-shelf/_index.scss +228 -0
- package/src/scss/component/navigation-shelf/_variables.scss +196 -0
- package/src/scss/utilities/_index.scss +2 -0
- package/src/scss/utilities/shelf-aware/_defaults.scss +31 -0
- package/src/scss/utilities/shelf-aware/_index.scss +383 -0
- package/src/scss/utilities/shelf-aware/_variables.scss +6 -0
- package/src/scss/utilities/width/_defaults.scss +58 -0
- package/src/scss/utilities/width/_index.scss +290 -0
- package/src/scss/utilities/width/_variables.scss +6 -0
|
@@ -0,0 +1,1912 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isValidClassList,
|
|
3
|
+
isValidType,
|
|
4
|
+
isValidInstance,
|
|
5
|
+
isValidState,
|
|
6
|
+
isValidEvent,
|
|
7
|
+
} from "@graupl/core/src/validate.js";
|
|
8
|
+
import { isValidSideType } from "../validate.js";
|
|
9
|
+
import { keyPress, preventEvent } from "@graupl/core/src/eventHandlers.js";
|
|
10
|
+
import {
|
|
11
|
+
addClass,
|
|
12
|
+
removeClass,
|
|
13
|
+
selectFirstFocusableElement,
|
|
14
|
+
} from "@graupl/core/src/domHelpers.js";
|
|
15
|
+
import storage from "@graupl/core/src/storage.js";
|
|
16
|
+
|
|
17
|
+
class NavigationShelf {
|
|
18
|
+
/**
|
|
19
|
+
* The DOM elements within the shelf.
|
|
20
|
+
*
|
|
21
|
+
* @protected
|
|
22
|
+
*
|
|
23
|
+
* @type {Object<HTMLElement,HTMLElement[]>}
|
|
24
|
+
*
|
|
25
|
+
* @property {HTMLElement} shelf - The shelf element.
|
|
26
|
+
* @property {HTMLElement} controller - The toggle for this shelf.
|
|
27
|
+
* @property {HTMLElement} lockController - The toggle for locking this shelf.
|
|
28
|
+
* @property {HTMLElement} hoverController - The toggle for hoverability of this shelf.
|
|
29
|
+
* @property {HTMLElement} sideController - The toggle for the side controller of this shelf.
|
|
30
|
+
* @property {HTMLElement[]} dependents - The list of dependent elements that should be updated when the shelf is opened or closed.
|
|
31
|
+
*/
|
|
32
|
+
_dom = {
|
|
33
|
+
shelf: null,
|
|
34
|
+
controller: null,
|
|
35
|
+
lockController: null,
|
|
36
|
+
hoverController: null,
|
|
37
|
+
sideController: null,
|
|
38
|
+
dependents: [],
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The query selectors used by the shelf to populate the dom.
|
|
43
|
+
*
|
|
44
|
+
* @protected
|
|
45
|
+
*
|
|
46
|
+
* @type {Object<string>}
|
|
47
|
+
*
|
|
48
|
+
* @property {string} dependents - The query selector for dependent elements.
|
|
49
|
+
*/
|
|
50
|
+
_selectors = {
|
|
51
|
+
dependents: "",
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The class(es) to apply to the shelf and dependent elements in various scenarios.
|
|
56
|
+
*
|
|
57
|
+
* @protected
|
|
58
|
+
*
|
|
59
|
+
* @type {Object<string,string[]>}
|
|
60
|
+
*
|
|
61
|
+
* @property {string|string[]} locked - The class(es) to apply to the shelf and dependent elements when the shelf is locked.
|
|
62
|
+
* @property {string|string[]} unlocked - The class(es) to apply to the shelf and dependent elements when the shelf is unlocked.
|
|
63
|
+
* @property {string|string[]} hover - The class(es) to apply to the shelf element when the shelf is hoverable.
|
|
64
|
+
* @property {string|string[]} noHover - The class(es) to apply to the shelf element when the shelf is not hoverable.
|
|
65
|
+
* @property {string|string[]} left - The class(es) to apply to the shelf and dependent elements when the shelf is on the left side.
|
|
66
|
+
* @property {string|string[]} right - The class(es) to apply to the shelf and dependent elements when the shelf is on the right side.
|
|
67
|
+
* @property {string|string[]} open - The class(es) to apply to the shelf when the shelf is open.
|
|
68
|
+
* @property {string|string[]} close - The class(es) to apply to the shelf when the shelf is closed.
|
|
69
|
+
* @property {string|string[]} transition - The class(es) to apply to the shelf and dependent elements when the shelf is transitioning between states.
|
|
70
|
+
*/
|
|
71
|
+
_classes = {
|
|
72
|
+
locked: "locked",
|
|
73
|
+
unlocked: "unlocked",
|
|
74
|
+
hover: "hoverable",
|
|
75
|
+
noHover: "not-hoverable",
|
|
76
|
+
left: "left-side",
|
|
77
|
+
right: "right-side",
|
|
78
|
+
open: "show",
|
|
79
|
+
close: "hide",
|
|
80
|
+
transistion: "transitioning",
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The duration time (in milliseconds) for the transition between open and closed states.
|
|
85
|
+
*
|
|
86
|
+
* @protected
|
|
87
|
+
*
|
|
88
|
+
* @type {number}
|
|
89
|
+
*/
|
|
90
|
+
_transitionDuration = 250;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The duration time (in milliseconds) for the transition from closed to open states.
|
|
94
|
+
*
|
|
95
|
+
* @protected
|
|
96
|
+
*
|
|
97
|
+
* @type {number}
|
|
98
|
+
*/
|
|
99
|
+
_openDuration = -1;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The duration time (in milliseconds) for the transition from open to closed states.
|
|
103
|
+
*
|
|
104
|
+
* @protected
|
|
105
|
+
*
|
|
106
|
+
* @type {number}
|
|
107
|
+
*/
|
|
108
|
+
_closeDuration = -1;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The current state of the shelf's focus.
|
|
112
|
+
*
|
|
113
|
+
* @protected
|
|
114
|
+
*
|
|
115
|
+
* @type {string}
|
|
116
|
+
*/
|
|
117
|
+
_focusState = "none";
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* This last event triggered on the shelf.
|
|
121
|
+
*
|
|
122
|
+
* @protected
|
|
123
|
+
*
|
|
124
|
+
* @type {string}
|
|
125
|
+
*/
|
|
126
|
+
_currentEvent = "none";
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* A flag to indicate if the shelf is hoverable.
|
|
130
|
+
*
|
|
131
|
+
* @protected
|
|
132
|
+
*
|
|
133
|
+
* @type {boolean}
|
|
134
|
+
*/
|
|
135
|
+
_hover = false;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* The delay time (in milliseconds) used for pointerenter/pointerleave events to take place.
|
|
139
|
+
*
|
|
140
|
+
* @protected
|
|
141
|
+
*
|
|
142
|
+
* @type {number}
|
|
143
|
+
*/
|
|
144
|
+
_hoverDelay = 250;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* The delay time (in milliseconds) used for pointerenter events to take place.
|
|
148
|
+
*
|
|
149
|
+
* @protected
|
|
150
|
+
*
|
|
151
|
+
* @type {number}
|
|
152
|
+
*/
|
|
153
|
+
_enterDelay = -1;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* The delay time (in milliseconds) used for pointerleave events to take place.
|
|
157
|
+
*
|
|
158
|
+
* @protected
|
|
159
|
+
*
|
|
160
|
+
* @type {number}
|
|
161
|
+
*/
|
|
162
|
+
_leaveDelay = -1;
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* A variable to hold the hover timeout function.
|
|
166
|
+
*
|
|
167
|
+
* @protected
|
|
168
|
+
*
|
|
169
|
+
* @type {?Function}
|
|
170
|
+
*/
|
|
171
|
+
_hoverTimeout = null;
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* A flag to indicate if the navigation shelf is locked.
|
|
175
|
+
*
|
|
176
|
+
* @protected
|
|
177
|
+
*
|
|
178
|
+
* @type {boolean}
|
|
179
|
+
*/
|
|
180
|
+
_locked = false;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* A flag to check in the navigation shelf can dynamically close based on if the shelf has been manually interacted with already.
|
|
184
|
+
*
|
|
185
|
+
* @protected
|
|
186
|
+
*
|
|
187
|
+
* @type {boolean}
|
|
188
|
+
*/
|
|
189
|
+
_softLocked = false;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The side of the screen the navigation shelf is on.
|
|
193
|
+
*
|
|
194
|
+
* @protected
|
|
195
|
+
*
|
|
196
|
+
* @type {string}
|
|
197
|
+
*/
|
|
198
|
+
_side = "left";
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* The opposite side of the screen the naigation shelf is on.
|
|
202
|
+
*
|
|
203
|
+
* @protected
|
|
204
|
+
*
|
|
205
|
+
* @type {string}
|
|
206
|
+
*/
|
|
207
|
+
_otherSide = "right";
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* The open state of the shelf.
|
|
211
|
+
*
|
|
212
|
+
* @protected
|
|
213
|
+
*
|
|
214
|
+
* @type {boolean}
|
|
215
|
+
*/
|
|
216
|
+
_open = false;
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* The event that is triggered when the shelf expands.
|
|
220
|
+
*
|
|
221
|
+
* @protected
|
|
222
|
+
*
|
|
223
|
+
* @event grauplNavigationShelfExpand
|
|
224
|
+
*
|
|
225
|
+
* @type {CustomEvent}
|
|
226
|
+
*
|
|
227
|
+
* @property {boolean} bubbles - A flag to bubble the event.
|
|
228
|
+
* @property {Object<NavigationShelf>} detail - The details object containing the NavigationShelf itself.
|
|
229
|
+
*/
|
|
230
|
+
_expandEvent = new CustomEvent("grauplNavigationShelfExpand", {
|
|
231
|
+
bubbles: true,
|
|
232
|
+
detail: { shelf: this },
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* The event that is triggered when the shelf collapses.
|
|
237
|
+
*
|
|
238
|
+
* @protected
|
|
239
|
+
*
|
|
240
|
+
* @event grauplNavigationShelfCollapse
|
|
241
|
+
*
|
|
242
|
+
* @type {CustomEvent}
|
|
243
|
+
*
|
|
244
|
+
* @property {boolean} bubbles - A flag to bubble the event.
|
|
245
|
+
* @property {Object<NavigationShelf>} detail - The details object containing the NavigationShelf itself.
|
|
246
|
+
*/
|
|
247
|
+
_collapseEvent = new CustomEvent("grauplNavigationShelfCollapse", {
|
|
248
|
+
bubbles: true,
|
|
249
|
+
detail: { shelf: this },
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* The event that is triggered when the shelf is locked.
|
|
254
|
+
*
|
|
255
|
+
* @protected
|
|
256
|
+
*
|
|
257
|
+
* @event grauplNavigationShelfLock
|
|
258
|
+
*
|
|
259
|
+
* @type {CustomEvent}
|
|
260
|
+
*
|
|
261
|
+
* @property {boolean} bubbles - A flag to bubble the event.
|
|
262
|
+
* @property {Object<NavigationShelf>} detail - The details object containing the NavigationShelf itself.
|
|
263
|
+
*/
|
|
264
|
+
_lockEvent = new CustomEvent("grauplNavigationShelfLock", {
|
|
265
|
+
bubbles: true,
|
|
266
|
+
detail: { shelf: this },
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* The event that is triggered when the shelf is unlocked.
|
|
271
|
+
*
|
|
272
|
+
* @protected
|
|
273
|
+
*
|
|
274
|
+
* @event grauplNavigationShelfUnlock
|
|
275
|
+
*
|
|
276
|
+
* @type {CustomEvent}
|
|
277
|
+
*
|
|
278
|
+
* @property {boolean} bubbles - A flag to bubble the event.
|
|
279
|
+
* @property {Object<NavigationShelf>} detail - The details object containing the NavigationShelf itself.
|
|
280
|
+
*/
|
|
281
|
+
_unlockEvent = new CustomEvent("grauplNavigationShelfUnlock", {
|
|
282
|
+
bubbles: true,
|
|
283
|
+
detail: { shelf: this },
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* The event that is triggered when the shelf has shifted sides.
|
|
288
|
+
*
|
|
289
|
+
* @protected
|
|
290
|
+
*
|
|
291
|
+
* @event grauplNavigationShelfShift
|
|
292
|
+
*
|
|
293
|
+
* @type {CustomEvent}
|
|
294
|
+
*
|
|
295
|
+
* @property {boolean} bubbles - A flag to bubble the event.
|
|
296
|
+
* @property {Object<NavigationShelf>} detail - The details object containing the NavigationShelf itself.
|
|
297
|
+
*/
|
|
298
|
+
_shiftEvent = new CustomEvent("grauplNavigationShelfShift", {
|
|
299
|
+
bubbles: true,
|
|
300
|
+
detail: {
|
|
301
|
+
shelf: this,
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* The event that is triggered when the shelf's hoverability is enabled.
|
|
307
|
+
*
|
|
308
|
+
* @protected
|
|
309
|
+
*
|
|
310
|
+
* @event grauplNavigationShelfEnableHoverable
|
|
311
|
+
*
|
|
312
|
+
* @type {CustomEvent}
|
|
313
|
+
*
|
|
314
|
+
* @property {boolean} bubbles - A flag to bubble the event.
|
|
315
|
+
* @property {Object<NavigationShelf>} detail - The details object containing the NavigationShelf itself.
|
|
316
|
+
*/
|
|
317
|
+
_enableHoverEvent = new CustomEvent("grauplNavigationShelfEnableHoverable", {
|
|
318
|
+
bubbles: true,
|
|
319
|
+
detail: {
|
|
320
|
+
shelf: this,
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* The event that is triggered when the shelf's hoverability is disabled.
|
|
326
|
+
*
|
|
327
|
+
* @protected
|
|
328
|
+
*
|
|
329
|
+
* @event grauplNavigationShelfDisableHover
|
|
330
|
+
*
|
|
331
|
+
* @type {CustomEvent}
|
|
332
|
+
*
|
|
333
|
+
* @property {boolean} bubbles - A flag to bubble the event.
|
|
334
|
+
* @property {Object<NavigationShelf>} detail - The details object containing the NavigationShelf itself.
|
|
335
|
+
*/
|
|
336
|
+
_disableHoverEvent = new CustomEvent("grauplNavigationShelfDisableHover", {
|
|
337
|
+
bubbles: true,
|
|
338
|
+
detail: {
|
|
339
|
+
shelf: this,
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* The prefix to use for CSS custom properties.
|
|
345
|
+
*
|
|
346
|
+
* @protected
|
|
347
|
+
*
|
|
348
|
+
* @type {string}
|
|
349
|
+
*/
|
|
350
|
+
_prefix = "graupl-";
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* The key used to generate IDs throughout the navigation shelf.
|
|
354
|
+
*
|
|
355
|
+
* @protected
|
|
356
|
+
*
|
|
357
|
+
* @type {string}
|
|
358
|
+
*/
|
|
359
|
+
_key = "";
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* errors - The list of errors found during validation.
|
|
363
|
+
*
|
|
364
|
+
* @protected
|
|
365
|
+
*
|
|
366
|
+
* @type {string[]}
|
|
367
|
+
*/
|
|
368
|
+
_errors = [];
|
|
369
|
+
|
|
370
|
+
constructor({
|
|
371
|
+
shelfElement,
|
|
372
|
+
controllerElement,
|
|
373
|
+
lockControllerElement,
|
|
374
|
+
hoverControllerElement,
|
|
375
|
+
sideControllerElement,
|
|
376
|
+
dependentSelector = ".shelf-aware",
|
|
377
|
+
lockedClass = "locked",
|
|
378
|
+
unlockedClass = "unlocked",
|
|
379
|
+
hoverClass = "hoverable",
|
|
380
|
+
noHoverClass = "not-hoverable",
|
|
381
|
+
leftClass = "left-side",
|
|
382
|
+
rightClass = "right-side",
|
|
383
|
+
openClass = "show",
|
|
384
|
+
closeClass = "hide",
|
|
385
|
+
transitionClass = "transitioning",
|
|
386
|
+
transitionDuration = 250,
|
|
387
|
+
openDuration = -1,
|
|
388
|
+
closeDuration = -1,
|
|
389
|
+
hover = false,
|
|
390
|
+
hoverDelay = 250,
|
|
391
|
+
enterDelay = -1,
|
|
392
|
+
leaveDelay = -1,
|
|
393
|
+
locked = false,
|
|
394
|
+
side = "left",
|
|
395
|
+
prefix = "graupl-",
|
|
396
|
+
initialize = false,
|
|
397
|
+
}) {
|
|
398
|
+
// Set DOM elements.
|
|
399
|
+
this._dom.shelf = shelfElement;
|
|
400
|
+
this._dom.controller = controllerElement || null;
|
|
401
|
+
this._dom.lockController = lockControllerElement || null;
|
|
402
|
+
this._dom.hoverController = hoverControllerElement || null;
|
|
403
|
+
this._dom.sideController = sideControllerElement || null;
|
|
404
|
+
|
|
405
|
+
// Set DOM selectors.
|
|
406
|
+
this._selectors.dependents = dependentSelector;
|
|
407
|
+
|
|
408
|
+
// Set classes.
|
|
409
|
+
this._classes.locked = lockedClass || "";
|
|
410
|
+
this._classes.unlocked = unlockedClass || "";
|
|
411
|
+
this._classes.hover = hoverClass || "";
|
|
412
|
+
this._classes.noHover = noHoverClass || "";
|
|
413
|
+
this._classes.left = leftClass || "";
|
|
414
|
+
this._classes.right = rightClass || "";
|
|
415
|
+
this._classes.open = openClass || "";
|
|
416
|
+
this._classes.close = closeClass || "";
|
|
417
|
+
this._classes.transition = transitionClass || "";
|
|
418
|
+
|
|
419
|
+
// Set transition duration.
|
|
420
|
+
this._transitionDuration = transitionDuration;
|
|
421
|
+
this._openDuration = openDuration;
|
|
422
|
+
this._closeDuration = closeDuration;
|
|
423
|
+
|
|
424
|
+
// Set locked state.
|
|
425
|
+
this._locked = locked;
|
|
426
|
+
|
|
427
|
+
// Set side.
|
|
428
|
+
this._side = side;
|
|
429
|
+
|
|
430
|
+
// Set prefix.
|
|
431
|
+
this._prefix = prefix || "";
|
|
432
|
+
|
|
433
|
+
// Set hover settings.
|
|
434
|
+
this._hover = hover;
|
|
435
|
+
this._hoverDelay = hoverDelay;
|
|
436
|
+
this._enterDelay = enterDelay;
|
|
437
|
+
this._leaveDelay = leaveDelay;
|
|
438
|
+
|
|
439
|
+
if (initialize) {
|
|
440
|
+
this.initialize();
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Initialize the navigation shelf.
|
|
446
|
+
*/
|
|
447
|
+
initialize() {
|
|
448
|
+
try {
|
|
449
|
+
if (!this._validate()) {
|
|
450
|
+
throw new Error(
|
|
451
|
+
`Graupl Navigation Shelf: cannot initialize navigation shelf. The following errors have been found:\n - ${this.errors.join(
|
|
452
|
+
"\n - "
|
|
453
|
+
)}`
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
this._setTransitionDurations();
|
|
458
|
+
|
|
459
|
+
// Set up the DOM.
|
|
460
|
+
this._generateKey();
|
|
461
|
+
this._setDOMElements();
|
|
462
|
+
this._setIds();
|
|
463
|
+
this._setAriaAttributes();
|
|
464
|
+
|
|
465
|
+
// Set up the event listeners.
|
|
466
|
+
this._handleFocus();
|
|
467
|
+
this._handleClick();
|
|
468
|
+
this._handleHover();
|
|
469
|
+
this._handleKeydown();
|
|
470
|
+
this._handleKeyup();
|
|
471
|
+
|
|
472
|
+
// Ensure the initial open state of the shelf.
|
|
473
|
+
if (this.dom.controller.getAttribute("aria-expanded") === "true") {
|
|
474
|
+
this._expand(false);
|
|
475
|
+
} else {
|
|
476
|
+
this._collapse(false);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Ensure the initial hoverability of the shelf.
|
|
480
|
+
if (this.hover) {
|
|
481
|
+
this._enableHover(false);
|
|
482
|
+
} else {
|
|
483
|
+
this._disableHover(false);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Ensure the initial side of the shelf.
|
|
487
|
+
this._shiftSide(false);
|
|
488
|
+
|
|
489
|
+
// Set up the storage.
|
|
490
|
+
storage.initializeStorage("navigation-shelves");
|
|
491
|
+
storage.pushToStorage("navigation-shelves", this.dom.shelf.id, this);
|
|
492
|
+
} catch (error) {
|
|
493
|
+
console.error(error);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* The DOM elements within the shelf.
|
|
499
|
+
*
|
|
500
|
+
* @readonly
|
|
501
|
+
*
|
|
502
|
+
* @type {Object<HTMLElement, HTMLElement[]>}
|
|
503
|
+
*
|
|
504
|
+
* @see _dom
|
|
505
|
+
*/
|
|
506
|
+
get dom() {
|
|
507
|
+
return this._dom;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* The query selectors used by the shelf to populate the DOM.
|
|
512
|
+
*
|
|
513
|
+
* @readonly
|
|
514
|
+
*
|
|
515
|
+
* @type {Object<string>}
|
|
516
|
+
*
|
|
517
|
+
* @see _selectors
|
|
518
|
+
*/
|
|
519
|
+
get selectors() {
|
|
520
|
+
return this._selectors;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* The class(es) to apply to the shelf and dependent elements in various scenarios.
|
|
525
|
+
*
|
|
526
|
+
* @readonly
|
|
527
|
+
*
|
|
528
|
+
* @type {Object<string, string[]>}
|
|
529
|
+
*
|
|
530
|
+
* @see _classes
|
|
531
|
+
*/
|
|
532
|
+
get classes() {
|
|
533
|
+
return this._classes;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* The class(es) to apply to the shelf and dependent elements when the shelf is locked.
|
|
538
|
+
*
|
|
539
|
+
* @type {string|string[]}
|
|
540
|
+
*
|
|
541
|
+
* @see _classes
|
|
542
|
+
*/
|
|
543
|
+
get lockedClass() {
|
|
544
|
+
return this._classes.locked;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* The class(es) to apply to the shelf and dependent elements when the shelf is unlocked.
|
|
549
|
+
*
|
|
550
|
+
* @type {string|string[]}
|
|
551
|
+
*
|
|
552
|
+
* @see _classes
|
|
553
|
+
*/
|
|
554
|
+
get unlockedClass() {
|
|
555
|
+
return this._classes.unlocked;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* The class(es) to apply to the shelf element when the shelf is hoverable.
|
|
560
|
+
*
|
|
561
|
+
* @type {string|string[]}
|
|
562
|
+
*
|
|
563
|
+
* @see _classes
|
|
564
|
+
*/
|
|
565
|
+
get hoverClass() {
|
|
566
|
+
return this._classes.hover;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* The class(es) to apply to the shelf element when the shelf is not hoverable.
|
|
571
|
+
*
|
|
572
|
+
* @type {string|string[]}
|
|
573
|
+
*
|
|
574
|
+
* @see _classes
|
|
575
|
+
*/
|
|
576
|
+
get noHoverClass() {
|
|
577
|
+
return this._classes.noHover;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* The class(es) to apply to the shelf and dependent elements when the shelf is on the left side.
|
|
582
|
+
*
|
|
583
|
+
* @type {string|string[]}
|
|
584
|
+
*
|
|
585
|
+
* @see _classes
|
|
586
|
+
*/
|
|
587
|
+
get leftClass() {
|
|
588
|
+
return this._classes.left;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* The class(es) to apply to the shelf and dependent elements when the shelf is on the right side.
|
|
593
|
+
*
|
|
594
|
+
* @type {string|string[]}
|
|
595
|
+
*
|
|
596
|
+
* @see _classes
|
|
597
|
+
*/
|
|
598
|
+
get rightClass() {
|
|
599
|
+
return this._classes.right;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* The class(es) to apply to the shelf when the shelf is open.
|
|
604
|
+
*
|
|
605
|
+
* @type {string|string[]}
|
|
606
|
+
*
|
|
607
|
+
* @see _classes
|
|
608
|
+
*/
|
|
609
|
+
get openClass() {
|
|
610
|
+
return this._classes.open;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* The class(es) to apply to the shelf when the shelf is closed.
|
|
615
|
+
*
|
|
616
|
+
* @type {string|string[]}
|
|
617
|
+
*
|
|
618
|
+
* @see _classes
|
|
619
|
+
*/
|
|
620
|
+
get closeClass() {
|
|
621
|
+
return this._classes.close;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* The class(es) to apply to the shelf and dependent elements when the shelf is transitioning between states.
|
|
626
|
+
*
|
|
627
|
+
* @type {string|string[]}
|
|
628
|
+
*
|
|
629
|
+
* @see _classes
|
|
630
|
+
*/
|
|
631
|
+
get transitionClass() {
|
|
632
|
+
return this._classes.transition;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* The duration time (in milliseconds) for the transition between open and closed states.
|
|
637
|
+
*
|
|
638
|
+
* Setting this value will also set the --am-transition-duration CSS custom property on the shelf.
|
|
639
|
+
*
|
|
640
|
+
* @type {number}
|
|
641
|
+
*
|
|
642
|
+
* @see _transitionDuration
|
|
643
|
+
*/
|
|
644
|
+
get transitionDuration() {
|
|
645
|
+
return this._transitionDuration;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* The duration time (in milliseconds) for the transition from closed to open states.
|
|
650
|
+
*
|
|
651
|
+
* If openDuration is set to -1, the transitionDuration value will be used instead.
|
|
652
|
+
*
|
|
653
|
+
* Setting this value will also set the --am-open-transition-duration CSS custom property on the shelf.
|
|
654
|
+
*
|
|
655
|
+
* @type {number}
|
|
656
|
+
*
|
|
657
|
+
* @see _openDuration
|
|
658
|
+
*/
|
|
659
|
+
get openDuration() {
|
|
660
|
+
if (this._openDuration === -1) return this.transitionDuration;
|
|
661
|
+
|
|
662
|
+
return this._openDuration;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* The duration time (in milliseconds) for the transition from open to closed states.
|
|
667
|
+
*
|
|
668
|
+
* If closeDuration is set to -1, the transitionDuration value will be used instead.
|
|
669
|
+
*
|
|
670
|
+
* Setting this value will also set the --am-close-transition-duration CSS custom property on the shelf.
|
|
671
|
+
*
|
|
672
|
+
* @type {number}
|
|
673
|
+
*
|
|
674
|
+
* @see _closeDuration
|
|
675
|
+
*/
|
|
676
|
+
get closeDuration() {
|
|
677
|
+
if (this._closeDuration === -1) return this.transitionDuration;
|
|
678
|
+
|
|
679
|
+
return this._closeDuration;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* The current state of the shelf's focus.
|
|
684
|
+
*
|
|
685
|
+
* @type {string}
|
|
686
|
+
*
|
|
687
|
+
* @see _focusState
|
|
688
|
+
*/
|
|
689
|
+
get focusState() {
|
|
690
|
+
return this._focusState;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* The last event triggered on the shelf.
|
|
695
|
+
*
|
|
696
|
+
* @type {string}
|
|
697
|
+
*
|
|
698
|
+
* @see _currentEvent
|
|
699
|
+
*/
|
|
700
|
+
get currentEvent() {
|
|
701
|
+
return this._currentEvent;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* A flag to indicate if the shelf is hoverable.
|
|
706
|
+
*
|
|
707
|
+
* @readonly
|
|
708
|
+
*
|
|
709
|
+
* @type {boolean}
|
|
710
|
+
*
|
|
711
|
+
* @see _hover
|
|
712
|
+
*/
|
|
713
|
+
get hover() {
|
|
714
|
+
return this._hover;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* The delay time (in milliseconds) used for pointerenter/pointerleave events to take place.
|
|
719
|
+
*
|
|
720
|
+
* @type {number}
|
|
721
|
+
*
|
|
722
|
+
* @see _hoverDelay
|
|
723
|
+
*/
|
|
724
|
+
get hoverDelay() {
|
|
725
|
+
return this._hoverDelay;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* The delay time (in milliseconds) used for pointerenter events to take place.
|
|
730
|
+
*
|
|
731
|
+
* If enterDelay is set to -1, the hoverDelay value will be used instead.
|
|
732
|
+
*
|
|
733
|
+
* @type {number}
|
|
734
|
+
*
|
|
735
|
+
* @see _enterDelay
|
|
736
|
+
*/
|
|
737
|
+
get enterDelay() {
|
|
738
|
+
if (this._enterDelay === -1) return this.hoverDelay;
|
|
739
|
+
|
|
740
|
+
return this._enterDelay;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* The delay time (in milliseconds) used for pointerleave events to take place.
|
|
745
|
+
*
|
|
746
|
+
* If leaveDelay is set to -1, the hoverDelay value will be used instead.
|
|
747
|
+
*
|
|
748
|
+
* @type {number}
|
|
749
|
+
*
|
|
750
|
+
* @see _leaveDelay
|
|
751
|
+
*/
|
|
752
|
+
get leaveDelay() {
|
|
753
|
+
if (this._leaveDelay === -1) return this.hoverDelay;
|
|
754
|
+
|
|
755
|
+
return this._leaveDelay;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* The prefix to use for CSS custom properties.
|
|
760
|
+
*
|
|
761
|
+
* @type {string}
|
|
762
|
+
*
|
|
763
|
+
* @see _prefix
|
|
764
|
+
*/
|
|
765
|
+
get prefix() {
|
|
766
|
+
return this._prefix;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* A flag to indicate if the navigation shelf is locked.
|
|
771
|
+
*
|
|
772
|
+
* @readonly
|
|
773
|
+
*
|
|
774
|
+
* @type {boolean}
|
|
775
|
+
*
|
|
776
|
+
* @see _locked
|
|
777
|
+
*/
|
|
778
|
+
get isLocked() {
|
|
779
|
+
return this._locked;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* The side of the screen the navigation shelf is on.
|
|
784
|
+
*
|
|
785
|
+
* @readonly
|
|
786
|
+
*
|
|
787
|
+
* @type {string}
|
|
788
|
+
*
|
|
789
|
+
* @see _side
|
|
790
|
+
*/
|
|
791
|
+
get side() {
|
|
792
|
+
return this._side;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* The opposite side of the screen the navigation shelf is on.
|
|
797
|
+
*
|
|
798
|
+
* @readonly
|
|
799
|
+
*
|
|
800
|
+
* @type {string}
|
|
801
|
+
*
|
|
802
|
+
* @see _otherSide
|
|
803
|
+
*/
|
|
804
|
+
get otherSide() {
|
|
805
|
+
return this._otherSide;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* The key used to generate IDs throughout the accordion.
|
|
810
|
+
*
|
|
811
|
+
* @type {string}
|
|
812
|
+
*
|
|
813
|
+
* @see _key
|
|
814
|
+
*/
|
|
815
|
+
get key() {
|
|
816
|
+
return this._key;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* A flag to check if the shelf can dynamically hover.
|
|
821
|
+
*
|
|
822
|
+
* @type {boolean}
|
|
823
|
+
*
|
|
824
|
+
* @see _softLocked
|
|
825
|
+
*/
|
|
826
|
+
get isSoftLocked() {
|
|
827
|
+
return this._softLocked;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* The open state on the shelf.
|
|
832
|
+
*
|
|
833
|
+
* @type {boolean}
|
|
834
|
+
*
|
|
835
|
+
* @see _open
|
|
836
|
+
*/
|
|
837
|
+
get isOpen() {
|
|
838
|
+
return this._open;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* An array of error messages generated by the shelf.
|
|
843
|
+
*
|
|
844
|
+
* @readonly
|
|
845
|
+
*
|
|
846
|
+
* @type {string[]}
|
|
847
|
+
*
|
|
848
|
+
* @see _errors
|
|
849
|
+
*/
|
|
850
|
+
get errors() {
|
|
851
|
+
return this._errors;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
set dependentLockedClass(value) {
|
|
855
|
+
isValidClassList({ dependentLockedClass: value });
|
|
856
|
+
if (this._classes.dependentLocked !== value) {
|
|
857
|
+
this._classes.dependentLocked = value;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
set dependentUnlockedClass(value) {
|
|
862
|
+
isValidClassList({ dependentUnlockedClass: value });
|
|
863
|
+
if (this._classes.dependentUnlocked !== value) {
|
|
864
|
+
this._classes.dependentUnlocked = value;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
set openClass(value) {
|
|
869
|
+
isValidClassList({ openClass: value });
|
|
870
|
+
|
|
871
|
+
if (this._classes.open !== value) {
|
|
872
|
+
this._classes.open = value;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
set closeClass(value) {
|
|
877
|
+
isValidClassList({ closeClass: value });
|
|
878
|
+
|
|
879
|
+
if (this._classes.close !== value) {
|
|
880
|
+
this._classes.close = value;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
set transitionClass(value) {
|
|
885
|
+
isValidClassList({ transitionClass: value });
|
|
886
|
+
|
|
887
|
+
if (this._classes.transition !== value) {
|
|
888
|
+
this._classes.transition = value;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
set transitionDuration(value) {
|
|
893
|
+
isValidType("number", { value });
|
|
894
|
+
|
|
895
|
+
if (this._transitionDuration !== value) {
|
|
896
|
+
this._transitionDuration = value;
|
|
897
|
+
this._setTransitionDurations();
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
set openDuration(value) {
|
|
902
|
+
isValidType("number", { value });
|
|
903
|
+
|
|
904
|
+
if (this._openDuration !== value) {
|
|
905
|
+
this._openDuration = value;
|
|
906
|
+
this._setTransitionDurations();
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
set closeDuration(value) {
|
|
911
|
+
isValidType("number", { value });
|
|
912
|
+
|
|
913
|
+
if (this._closeDuration !== value) {
|
|
914
|
+
this._closeDuration = value;
|
|
915
|
+
this._setTransitionDurations();
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
set focusState(value) {
|
|
920
|
+
isValidState({ value });
|
|
921
|
+
|
|
922
|
+
if (this._focusState !== value) {
|
|
923
|
+
this._focusState = value;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
set currentEvent(value) {
|
|
928
|
+
isValidEvent({ value });
|
|
929
|
+
|
|
930
|
+
if (this._currentEvent !== value) {
|
|
931
|
+
this._currentEvent = value;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
set hoverDelay(value) {
|
|
936
|
+
isValidType("number", { value });
|
|
937
|
+
|
|
938
|
+
if (this._hoverDelay !== value) {
|
|
939
|
+
this._hoverDelay = value;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
set enterDelay(value) {
|
|
944
|
+
isValidType("number", { value });
|
|
945
|
+
|
|
946
|
+
if (this._enterDelay !== value) {
|
|
947
|
+
this._enterDelay = value;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
set leaveDelay(value) {
|
|
952
|
+
isValidType("number", { value });
|
|
953
|
+
|
|
954
|
+
if (this._leaveDelay !== value) {
|
|
955
|
+
this._leaveDelay = value;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
set prefix(value) {
|
|
960
|
+
isValidType("string", { value });
|
|
961
|
+
|
|
962
|
+
if (this._prefix !== value) {
|
|
963
|
+
this._prefix = value;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
set key(value) {
|
|
968
|
+
isValidType("string", { value });
|
|
969
|
+
|
|
970
|
+
if (this._key !== value) {
|
|
971
|
+
this._key = value;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
set isSoftLocked(value) {
|
|
976
|
+
isValidType("boolean", { value });
|
|
977
|
+
|
|
978
|
+
if (this._softLocked !== value) {
|
|
979
|
+
this._softLocked = value;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
/**
|
|
984
|
+
* Validates all aspects of the shelf to ensure proper functionality.
|
|
985
|
+
*
|
|
986
|
+
* @protected
|
|
987
|
+
*
|
|
988
|
+
* @return {boolean} - The result of the validation.
|
|
989
|
+
*/
|
|
990
|
+
_validate() {
|
|
991
|
+
let check = true;
|
|
992
|
+
|
|
993
|
+
// HTML element checks.
|
|
994
|
+
const htmlElements = {
|
|
995
|
+
shelfElement: this._dom.shelf,
|
|
996
|
+
controllerElement: this._dom.controller,
|
|
997
|
+
};
|
|
998
|
+
|
|
999
|
+
if (this._dom.lockController) {
|
|
1000
|
+
htmlElements.lockControllerElement = this._dom.lockController;
|
|
1001
|
+
}
|
|
1002
|
+
if (this._dom.hoverController) {
|
|
1003
|
+
htmlElements.hoverControllerElement = this._dom.hoverController;
|
|
1004
|
+
}
|
|
1005
|
+
if (this._dom.sideController) {
|
|
1006
|
+
htmlElements.sideControllerElement = this._dom.sideController;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
const htmlElementChecks = isValidInstance(HTMLElement, htmlElements);
|
|
1010
|
+
|
|
1011
|
+
if (!htmlElementChecks.status) {
|
|
1012
|
+
this._errors.push(htmlElementChecks.error.message);
|
|
1013
|
+
check = false;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
// Class list checks.
|
|
1017
|
+
const classes = {};
|
|
1018
|
+
for (const key of Object.keys(this.classes)) {
|
|
1019
|
+
if (this._classes[key] === "") continue;
|
|
1020
|
+
|
|
1021
|
+
classes[`${key}Class`] = this._classes[key];
|
|
1022
|
+
}
|
|
1023
|
+
const classChecks = isValidClassList(classes);
|
|
1024
|
+
|
|
1025
|
+
if (!classChecks.status) {
|
|
1026
|
+
this._errors.push(classChecks.error.message);
|
|
1027
|
+
check = false;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
// Transition duration check.
|
|
1031
|
+
const transitionDurationCheck = isValidType("number", {
|
|
1032
|
+
transitionDuration: this._transitionDuration,
|
|
1033
|
+
});
|
|
1034
|
+
|
|
1035
|
+
if (!transitionDurationCheck.status) {
|
|
1036
|
+
this._errors.push(transitionDurationCheck.error.message);
|
|
1037
|
+
check = false;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// Open duration check.
|
|
1041
|
+
const openDurationCheck = isValidType("number", {
|
|
1042
|
+
openDuration: this._openDuration,
|
|
1043
|
+
});
|
|
1044
|
+
|
|
1045
|
+
if (!openDurationCheck.status) {
|
|
1046
|
+
this._errors.push(openDurationCheck.error.message);
|
|
1047
|
+
check = false;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// Close duration check.
|
|
1051
|
+
const closeDurationCheck = isValidType("number", {
|
|
1052
|
+
closeDuration: this._closeDuration,
|
|
1053
|
+
});
|
|
1054
|
+
|
|
1055
|
+
if (!closeDurationCheck.status) {
|
|
1056
|
+
this._errors.push(closeDurationCheck.error.message);
|
|
1057
|
+
check = false;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// Hover check.
|
|
1061
|
+
const hoverCheck = isValidType("boolean", { hover: this._hover });
|
|
1062
|
+
|
|
1063
|
+
if (!hoverCheck.status) {
|
|
1064
|
+
this._errors.push(hoverCheck.error.message);
|
|
1065
|
+
check = false;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
// Hover delay check.
|
|
1069
|
+
const hoverDelayCheck = isValidType("number", {
|
|
1070
|
+
hoverDelay: this._hoverDelay,
|
|
1071
|
+
});
|
|
1072
|
+
|
|
1073
|
+
if (!hoverDelayCheck.status) {
|
|
1074
|
+
this._errors.push(hoverDelayCheck.error.message);
|
|
1075
|
+
check = false;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// Enter delay check.
|
|
1079
|
+
const enterDelayCheck = isValidType("number", {
|
|
1080
|
+
enterDelay: this._enterDelay,
|
|
1081
|
+
});
|
|
1082
|
+
|
|
1083
|
+
if (!enterDelayCheck.status) {
|
|
1084
|
+
this._errors.push(enterDelayCheck.error.message);
|
|
1085
|
+
check = false;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
// Leave delay check.
|
|
1089
|
+
const leaveDelayCheck = isValidType("number", {
|
|
1090
|
+
leaveDelay: this._leaveDelay,
|
|
1091
|
+
});
|
|
1092
|
+
|
|
1093
|
+
if (!leaveDelayCheck.status) {
|
|
1094
|
+
this._errors.push(leaveDelayCheck.error.message);
|
|
1095
|
+
check = false;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
// Prefix check.
|
|
1099
|
+
const prefixCheck = isValidType("string", { prefix: this._prefix });
|
|
1100
|
+
|
|
1101
|
+
if (!prefixCheck.status) {
|
|
1102
|
+
this._errors.push(prefixCheck.error.message);
|
|
1103
|
+
check = false;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
// Locked check.
|
|
1107
|
+
const lockedCheck = isValidType("boolean", { locked: this._locked });
|
|
1108
|
+
if (!lockedCheck.status) {
|
|
1109
|
+
this._errors.push(lockedCheck.error.message);
|
|
1110
|
+
check = false;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
// Side check.
|
|
1114
|
+
const sideCheck = isValidSideType({ side: this._side });
|
|
1115
|
+
if (!sideCheck.status) {
|
|
1116
|
+
this._errors.push(sideCheck.error.message);
|
|
1117
|
+
check = false;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
return check;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
/**
|
|
1124
|
+
* Sets DOM elements within the shelf.
|
|
1125
|
+
*
|
|
1126
|
+
* The shelf, controller, lockController, and hoverController elements _cannot_ be set through this method.
|
|
1127
|
+
*
|
|
1128
|
+
* @protected
|
|
1129
|
+
*
|
|
1130
|
+
* @param {string} elementType - The type of element to populate.
|
|
1131
|
+
* @param {HTMLElement} [base = this.dom.shelf] - The element used as the base for the querySelector.
|
|
1132
|
+
* @param {boolean} [overwrite = true] - A flag to set if the existing elements will be overwritten.
|
|
1133
|
+
* @param {boolean} [strict = true] - A flag to set if the elements must be direct children of the base.
|
|
1134
|
+
*/
|
|
1135
|
+
_setDOMElementType(
|
|
1136
|
+
elementType,
|
|
1137
|
+
base = this.dom.shelf,
|
|
1138
|
+
overwrite = true,
|
|
1139
|
+
strict = true
|
|
1140
|
+
) {
|
|
1141
|
+
if (typeof this.selectors[elementType] === "string") {
|
|
1142
|
+
if (
|
|
1143
|
+
elementType === "shelf" ||
|
|
1144
|
+
elementType === "controller" ||
|
|
1145
|
+
elementType === "lockController" ||
|
|
1146
|
+
elementType === "hoverController"
|
|
1147
|
+
) {
|
|
1148
|
+
throw new Error(
|
|
1149
|
+
`Graupl Navigation Shelf: "${elementType}" element cannot be set through _setDOMElementType.`
|
|
1150
|
+
);
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
if (base !== this.dom.shelf) isValidInstance(HTMLElement, { base });
|
|
1154
|
+
|
|
1155
|
+
if (Array.isArray(this._dom[elementType])) {
|
|
1156
|
+
// Get all the elements matching the selector in the base.
|
|
1157
|
+
const domElements = Array.from(
|
|
1158
|
+
base.querySelectorAll(this.selectors[elementType])
|
|
1159
|
+
);
|
|
1160
|
+
|
|
1161
|
+
// Filter the elements so only direct children of the base are kept.
|
|
1162
|
+
const filteredElements = domElements.filter((item) =>
|
|
1163
|
+
strict ? item.parentElement === base : true
|
|
1164
|
+
);
|
|
1165
|
+
|
|
1166
|
+
if (overwrite) {
|
|
1167
|
+
this._dom[elementType] = filteredElements;
|
|
1168
|
+
} else {
|
|
1169
|
+
this._dom[elementType] = [
|
|
1170
|
+
...this._dom[elementType],
|
|
1171
|
+
...filteredElements,
|
|
1172
|
+
];
|
|
1173
|
+
}
|
|
1174
|
+
} else {
|
|
1175
|
+
// Get the single element matching the selector in the base.
|
|
1176
|
+
const domElement = base.querySelector(this.selectors[elementType]);
|
|
1177
|
+
|
|
1178
|
+
// Ensure the element is a direct child of the base.
|
|
1179
|
+
if (domElement && domElement.parentElement !== base) {
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
if (overwrite) {
|
|
1184
|
+
this._dom[elementType] = domElement;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
} else {
|
|
1188
|
+
throw new Error(
|
|
1189
|
+
`Graupl Navigation Shelf: "${elementType}" is not a valid element type within the navigation shelf.`
|
|
1190
|
+
);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
/**
|
|
1195
|
+
* Resets DOM elements within the menu.
|
|
1196
|
+
*
|
|
1197
|
+
* The shelf, controller, lockController, and hoverController elements _cannot_ be set through this method.
|
|
1198
|
+
*
|
|
1199
|
+
* @protected
|
|
1200
|
+
*
|
|
1201
|
+
* @param {string} elementType - The type of element to clear.
|
|
1202
|
+
*/
|
|
1203
|
+
_resetDOMElementType(elementType) {
|
|
1204
|
+
if (typeof this.selectors[elementType] === "string") {
|
|
1205
|
+
if (
|
|
1206
|
+
elementType === "shelf" ||
|
|
1207
|
+
elementType === "controller" ||
|
|
1208
|
+
elementType === "lockController" ||
|
|
1209
|
+
elementType === "hoverController"
|
|
1210
|
+
) {
|
|
1211
|
+
throw new Error(
|
|
1212
|
+
`Graupl Navigation Shelf: "${elementType}" element cannot be reset through _resetDOMElementType.`
|
|
1213
|
+
);
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
if (Array.isArray(this._dom[elementType])) {
|
|
1217
|
+
this._dom[elementType] = [];
|
|
1218
|
+
} else {
|
|
1219
|
+
this._dom[elementType] = null;
|
|
1220
|
+
}
|
|
1221
|
+
} else {
|
|
1222
|
+
throw new Error(
|
|
1223
|
+
`Graupl Navigation Shelf: "${elementType}" is not a valid element type within the navigation shelf.`
|
|
1224
|
+
);
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
/**
|
|
1229
|
+
* Sets all DOM elements within the shelf.
|
|
1230
|
+
*
|
|
1231
|
+
* Utilizes _setDOMElementType and _resetDOMElementType.
|
|
1232
|
+
*
|
|
1233
|
+
* @protected
|
|
1234
|
+
*/
|
|
1235
|
+
_setDOMElements() {
|
|
1236
|
+
this._setDOMElementType("dependents", document, true, false);
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
/**
|
|
1240
|
+
* Generates a key for the navigation shelf.
|
|
1241
|
+
*
|
|
1242
|
+
* @param {boolean} [regenerate = false] - A flag to determine if the key should be regenerated.
|
|
1243
|
+
*/
|
|
1244
|
+
_generateKey(regenerate = false) {
|
|
1245
|
+
if (this.key === "" || regenerate) {
|
|
1246
|
+
this.key = Math.random()
|
|
1247
|
+
.toString(36)
|
|
1248
|
+
.replace(/[^a-z]+/g, "")
|
|
1249
|
+
.substring(0, 10);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
/**
|
|
1254
|
+
* Sets the IDs of the navigation shelf and it's elements if they do not already exist.
|
|
1255
|
+
*
|
|
1256
|
+
* The generated IDs use the key and follow the format:
|
|
1257
|
+
* - navigation shelf: `navigation-shelf-${key}`
|
|
1258
|
+
* - navigation shelf toggle: `navigation-shelf-toggle-${key}`
|
|
1259
|
+
* - navigation shelf lock toggle: `navigation-shelf-lock-toggle-${key}`
|
|
1260
|
+
* - navigation shelf hover toggle: `navigation-shelf-hover-toggle-${key}`
|
|
1261
|
+
*/
|
|
1262
|
+
_setIds() {
|
|
1263
|
+
this.dom.shelf.id = this.dom.shelf.id || `navigation-shelf-${this.key}`;
|
|
1264
|
+
if (this.dom.controller) {
|
|
1265
|
+
this.dom.controller.id =
|
|
1266
|
+
this.dom.controller.id || `navigation-shelf-toggle-${this.key}`;
|
|
1267
|
+
}
|
|
1268
|
+
if (this.dom.lockController) {
|
|
1269
|
+
this.dom.lockController.id =
|
|
1270
|
+
this.dom.lockController.id ||
|
|
1271
|
+
`navigation-shelf-lock-toggle-${this.key}`;
|
|
1272
|
+
}
|
|
1273
|
+
if (this.dom.hoverController) {
|
|
1274
|
+
this.dom.hoverController.id =
|
|
1275
|
+
this.dom.hoverController.id ||
|
|
1276
|
+
`navigation-shelf-hover-toggle-${this.key}`;
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
/**
|
|
1281
|
+
* Sets the aria attributes for the navigation shelf.
|
|
1282
|
+
*/
|
|
1283
|
+
_setAriaAttributes() {
|
|
1284
|
+
if (this.dom.controller) {
|
|
1285
|
+
this.dom.controller.setAttribute("aria-controls", this.dom.shelf.id);
|
|
1286
|
+
|
|
1287
|
+
if (this.dom.controller.getAttribute("aria-expanded") !== "true") {
|
|
1288
|
+
this.dom.controller.setAttribute("aria-expanded", "false");
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
if (this.dom.lockController) {
|
|
1293
|
+
this.dom.lockController.setAttribute("aria-controls", this.dom.shelf.id);
|
|
1294
|
+
this.dom.lockController.setAttribute(
|
|
1295
|
+
"aria-pressed",
|
|
1296
|
+
this._locked ? "true" : "false"
|
|
1297
|
+
);
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
if (this.dom.hoverController) {
|
|
1301
|
+
this.dom.hoverController.setAttribute("aria-controls", this.dom.shelf.id);
|
|
1302
|
+
this.dom.hoverController.setAttribute(
|
|
1303
|
+
"aria-pressed",
|
|
1304
|
+
this._hoverType === "on" ? "true" : "false"
|
|
1305
|
+
);
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
if (this.dom.sideController) {
|
|
1309
|
+
this.dom.sideController.setAttribute("aria-controls", this.dom.shelf.id);
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
/**
|
|
1314
|
+
* Clears the hover timeout.
|
|
1315
|
+
*
|
|
1316
|
+
* @protected
|
|
1317
|
+
*/
|
|
1318
|
+
_clearTimeout() {
|
|
1319
|
+
clearTimeout(this._hoverTimeout);
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
/**
|
|
1323
|
+
* Sets the hover timeout.
|
|
1324
|
+
*
|
|
1325
|
+
* @protected
|
|
1326
|
+
*
|
|
1327
|
+
* @param {Function} callback - The callback function to execute.
|
|
1328
|
+
* @param {number} delay - The delay time in milliseconds.
|
|
1329
|
+
*/
|
|
1330
|
+
_setTimeout(callback, delay) {
|
|
1331
|
+
isValidType("function", { callback });
|
|
1332
|
+
isValidType("number", { delay });
|
|
1333
|
+
|
|
1334
|
+
this._hoverTimeout = setTimeout(callback, delay);
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
_handleFocus() {
|
|
1338
|
+
this.dom.shelf.addEventListener("focusin", () => {
|
|
1339
|
+
this.focusState = "self";
|
|
1340
|
+
this.open();
|
|
1341
|
+
});
|
|
1342
|
+
|
|
1343
|
+
this.dom.shelf.addEventListener("focusout", (event) => {
|
|
1344
|
+
if (
|
|
1345
|
+
event.relatedTarget === null ||
|
|
1346
|
+
this.dom.shelf.contains(event.relatedTarget)
|
|
1347
|
+
)
|
|
1348
|
+
return;
|
|
1349
|
+
|
|
1350
|
+
this.focusState = "none";
|
|
1351
|
+
this.close();
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
_handleClick() {
|
|
1356
|
+
// Prevent pointer down events on all controlled elements.
|
|
1357
|
+
for (const element of Object.values(this.dom)) {
|
|
1358
|
+
if (!element) continue;
|
|
1359
|
+
if (Array.isArray(element)) continue;
|
|
1360
|
+
|
|
1361
|
+
element.addEventListener(
|
|
1362
|
+
"pointerdown",
|
|
1363
|
+
() => {
|
|
1364
|
+
this.currentEvent = "mouse";
|
|
1365
|
+
this._clearTimeout();
|
|
1366
|
+
},
|
|
1367
|
+
{ passive: true }
|
|
1368
|
+
);
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
// Toggle the shelf when the controlled is clicked.
|
|
1372
|
+
if (this.dom.controller) {
|
|
1373
|
+
this.dom.controller.addEventListener("pointerup", (event) => {
|
|
1374
|
+
if (event.button !== 0) return;
|
|
1375
|
+
|
|
1376
|
+
this.currentEvent = "mouse";
|
|
1377
|
+
preventEvent(event);
|
|
1378
|
+
this.toggle();
|
|
1379
|
+
|
|
1380
|
+
if (this.isOpen) {
|
|
1381
|
+
this.focusState = "self";
|
|
1382
|
+
this.isSoftLocked = true;
|
|
1383
|
+
}
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
// Toggle hoverability when the hover controller is clicked.
|
|
1388
|
+
if (this.dom.hoverController) {
|
|
1389
|
+
this.dom.hoverController.addEventListener("pointerup", (event) => {
|
|
1390
|
+
if (event.button !== 0) return;
|
|
1391
|
+
|
|
1392
|
+
this.currentEvent = "mouse";
|
|
1393
|
+
preventEvent(event);
|
|
1394
|
+
this.focusState = "self";
|
|
1395
|
+
this.toggleHover();
|
|
1396
|
+
|
|
1397
|
+
if (this.hover) {
|
|
1398
|
+
this.open();
|
|
1399
|
+
}
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
// Toggle shelf lock when the lock controller is clicked.
|
|
1404
|
+
if (this.dom.lockController) {
|
|
1405
|
+
this.dom.lockController.addEventListener("pointerup", (event) => {
|
|
1406
|
+
if (event.button !== 0) return;
|
|
1407
|
+
|
|
1408
|
+
this.currentEvent = "mouse";
|
|
1409
|
+
preventEvent(event);
|
|
1410
|
+
this.focusState = "self";
|
|
1411
|
+
this.toggleLock();
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
// Toggle shifting sides when the side controller is clicked.
|
|
1416
|
+
if (this.dom.sideController) {
|
|
1417
|
+
this.dom.sideController.addEventListener("pointerup", (event) => {
|
|
1418
|
+
if (event.button !== 0) return;
|
|
1419
|
+
|
|
1420
|
+
this.currentEvent = "mouse";
|
|
1421
|
+
preventEvent(event);
|
|
1422
|
+
this.focusState = "self";
|
|
1423
|
+
this.toggleSide();
|
|
1424
|
+
});
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
// Catch all to open if shelf if there is a click inside of it.
|
|
1428
|
+
this.dom.shelf.addEventListener("pointerup", (event) => {
|
|
1429
|
+
if (event.button !== 0) return;
|
|
1430
|
+
|
|
1431
|
+
this.currentEvent = "mouse";
|
|
1432
|
+
this.focusState = "self";
|
|
1433
|
+
this.isSoftLocked = true;
|
|
1434
|
+
this.open();
|
|
1435
|
+
});
|
|
1436
|
+
|
|
1437
|
+
// Close the shelf if a click happens outside of it.
|
|
1438
|
+
document.addEventListener("pointerup", (event) => {
|
|
1439
|
+
if (this.focusState === "none") return;
|
|
1440
|
+
if (this.isLocked) return;
|
|
1441
|
+
if (
|
|
1442
|
+
this.dom.shelf === event.target ||
|
|
1443
|
+
this.dom.shelf.contains(event.target)
|
|
1444
|
+
)
|
|
1445
|
+
return;
|
|
1446
|
+
|
|
1447
|
+
this.currentEvent = "mouse";
|
|
1448
|
+
this.close();
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
_handleHover() {
|
|
1453
|
+
this.dom.shelf.addEventListener("pointerenter", (event) => {
|
|
1454
|
+
if (event.pointerType === "pen" || event.pointerType === "touch") return;
|
|
1455
|
+
if (this.isLocked || this.isSoftLocked) return;
|
|
1456
|
+
if (!this.hover) return;
|
|
1457
|
+
|
|
1458
|
+
this.currentEvent = "mouse";
|
|
1459
|
+
|
|
1460
|
+
if (this.enterDelay > 0) {
|
|
1461
|
+
this._clearTimeout();
|
|
1462
|
+
this._setTimeout(() => {
|
|
1463
|
+
if (!this.isOpen) {
|
|
1464
|
+
this.open();
|
|
1465
|
+
}
|
|
1466
|
+
}, this.enterDelay);
|
|
1467
|
+
} else {
|
|
1468
|
+
this.open();
|
|
1469
|
+
}
|
|
1470
|
+
});
|
|
1471
|
+
|
|
1472
|
+
this.dom.shelf.addEventListener("pointerleave", (event) => {
|
|
1473
|
+
if (event.pointerType === "pen" || event.pointerType === "touch") return;
|
|
1474
|
+
if (this.isLocked || this.isSoftLocked) return;
|
|
1475
|
+
if (!this.hover) return;
|
|
1476
|
+
|
|
1477
|
+
this.currentEvent = "mouse";
|
|
1478
|
+
|
|
1479
|
+
if (this.leaveDelay > 0) {
|
|
1480
|
+
this._clearTimeout();
|
|
1481
|
+
this._setTimeout(() => {
|
|
1482
|
+
if (this.isOpen) {
|
|
1483
|
+
this.close();
|
|
1484
|
+
}
|
|
1485
|
+
}, this.leaveDelay);
|
|
1486
|
+
} else {
|
|
1487
|
+
this.close();
|
|
1488
|
+
}
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
_handleKeydown() {
|
|
1493
|
+
// Prevent keydown events on the shelf if they are `Escape`.
|
|
1494
|
+
this.dom.shelf.addEventListener("keydown", (event) => {
|
|
1495
|
+
const key = keyPress(event);
|
|
1496
|
+
|
|
1497
|
+
if (key === "Escape") {
|
|
1498
|
+
preventEvent(event);
|
|
1499
|
+
}
|
|
1500
|
+
});
|
|
1501
|
+
|
|
1502
|
+
// Prevent keydown events on all controller elements if they are `Space` or `Enter`.
|
|
1503
|
+
for (const element of Object.values(this.dom)) {
|
|
1504
|
+
if (!element) continue;
|
|
1505
|
+
if (Array.isArray(element)) continue;
|
|
1506
|
+
if (element === this.dom.shelf) continue;
|
|
1507
|
+
|
|
1508
|
+
element.addEventListener("keydown", (event) => {
|
|
1509
|
+
this.currentEvent = "keyboard";
|
|
1510
|
+
|
|
1511
|
+
const key = keyPress(event);
|
|
1512
|
+
|
|
1513
|
+
if (key === "Space" || key === "Enter") {
|
|
1514
|
+
preventEvent(event);
|
|
1515
|
+
}
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
_handleKeyup() {
|
|
1521
|
+
// Close the shelf on `Escape`.
|
|
1522
|
+
this.dom.shelf.addEventListener("keyup", (event) => {
|
|
1523
|
+
this.currentEvent = "keyboard";
|
|
1524
|
+
|
|
1525
|
+
const key = keyPress(event);
|
|
1526
|
+
|
|
1527
|
+
if (key === "Escape") {
|
|
1528
|
+
this.close();
|
|
1529
|
+
}
|
|
1530
|
+
});
|
|
1531
|
+
|
|
1532
|
+
// Toggle the shelf on `Space` or `Enter` on the controller.
|
|
1533
|
+
if (this.dom.controller) {
|
|
1534
|
+
this.dom.controller.addEventListener("keyup", (event) => {
|
|
1535
|
+
this.currentEvent = "keyboard";
|
|
1536
|
+
|
|
1537
|
+
const key = keyPress(event);
|
|
1538
|
+
|
|
1539
|
+
if (key === "Space" || key === "Enter") {
|
|
1540
|
+
preventEvent(event);
|
|
1541
|
+
this.toggle();
|
|
1542
|
+
|
|
1543
|
+
if (this.isOpen) {
|
|
1544
|
+
const element = selectFirstFocusableElement(this.dom.shelf);
|
|
1545
|
+
element.focus();
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
// Toggle hover on `Space` or `Enter` on the hover controller.
|
|
1552
|
+
if (this.dom.hoverController) {
|
|
1553
|
+
this.dom.hoverController.addEventListener("keyup", (event) => {
|
|
1554
|
+
this.currentEvent = "keyboard";
|
|
1555
|
+
|
|
1556
|
+
const key = keyPress(event);
|
|
1557
|
+
|
|
1558
|
+
if (key === "Space" || key === "Enter") {
|
|
1559
|
+
preventEvent(event);
|
|
1560
|
+
this.toggleHover();
|
|
1561
|
+
}
|
|
1562
|
+
});
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
// Toggle lock on `Space` or `Enter` on the lock controller.
|
|
1566
|
+
if (this.dom.lockController) {
|
|
1567
|
+
this.dom.lockController.addEventListener("keyup", (event) => {
|
|
1568
|
+
this.currentEvent = "keyboard";
|
|
1569
|
+
|
|
1570
|
+
const key = keyPress(event);
|
|
1571
|
+
|
|
1572
|
+
if (key === "Space" || key === "Enter") {
|
|
1573
|
+
preventEvent(event);
|
|
1574
|
+
this.toggleLock();
|
|
1575
|
+
}
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
// Shift sides on `Space` or `Enter` on the side controller.
|
|
1580
|
+
if (this.dom.sideController) {
|
|
1581
|
+
this.dom.sideController.addEventListener("keyup", (event) => {
|
|
1582
|
+
this.currentEvent = "keyboard";
|
|
1583
|
+
|
|
1584
|
+
const key = keyPress(event);
|
|
1585
|
+
|
|
1586
|
+
if (key === "Space" || key === "Enter") {
|
|
1587
|
+
preventEvent(event);
|
|
1588
|
+
this.toggleSide();
|
|
1589
|
+
}
|
|
1590
|
+
});
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
/**
|
|
1595
|
+
* Sets the transition durations of the shelf as a CSS custom properties.
|
|
1596
|
+
*
|
|
1597
|
+
* The custom properties are:
|
|
1598
|
+
* - `--graupl-transition-duration`,
|
|
1599
|
+
* - `--graupl-open-transition-duration`, and
|
|
1600
|
+
* - `--graupl-close-transition-duration`.
|
|
1601
|
+
*
|
|
1602
|
+
* The prefix of `graupl-` can be changed by setting the shelf's prefix value.
|
|
1603
|
+
*
|
|
1604
|
+
* @protected
|
|
1605
|
+
*/
|
|
1606
|
+
_setTransitionDurations() {
|
|
1607
|
+
this.dom.shelf.style.setProperty(
|
|
1608
|
+
`--${this.prefix}navigation-shelf-transition-duration`,
|
|
1609
|
+
`${this.transitionDuration}ms`
|
|
1610
|
+
);
|
|
1611
|
+
|
|
1612
|
+
this.dom.shelf.style.setProperty(
|
|
1613
|
+
`--${this.prefix}navigation-shelf-open-transition-duration`,
|
|
1614
|
+
`${this.openDuration}ms`
|
|
1615
|
+
);
|
|
1616
|
+
|
|
1617
|
+
this.dom.shelf.style.setProperty(
|
|
1618
|
+
`--${this.prefix}navigation-shelf-close-transition-duration`,
|
|
1619
|
+
`${this.closeDuration}ms`
|
|
1620
|
+
);
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
_expand(emit = true) {
|
|
1624
|
+
if (this.dom.controller) {
|
|
1625
|
+
this.dom.controller.setAttribute("aria-expanded", "true");
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
// If we're dealing with transition classes, then we need to utilize
|
|
1629
|
+
// requestAnimationFrame to add the transition class, remove the close class,
|
|
1630
|
+
// add the open class, and finally remove the transition class.
|
|
1631
|
+
if (this.transitionClass !== "") {
|
|
1632
|
+
addClass(this.transitionClass, this.dom.shelf);
|
|
1633
|
+
|
|
1634
|
+
requestAnimationFrame(() => {
|
|
1635
|
+
removeClass(this.closeClass, this.dom.shelf);
|
|
1636
|
+
|
|
1637
|
+
requestAnimationFrame(() => {
|
|
1638
|
+
addClass(this.openClass, this.dom.shelf);
|
|
1639
|
+
|
|
1640
|
+
requestAnimationFrame(() => {
|
|
1641
|
+
setTimeout(() => {
|
|
1642
|
+
removeClass(this.transitionClass, this.dom.shelf);
|
|
1643
|
+
}, this.openDuration);
|
|
1644
|
+
});
|
|
1645
|
+
});
|
|
1646
|
+
});
|
|
1647
|
+
} else {
|
|
1648
|
+
// Add the open class
|
|
1649
|
+
addClass(this.openClass, this.dom.shelf);
|
|
1650
|
+
|
|
1651
|
+
// Remove the close class.
|
|
1652
|
+
removeClass(this.closeClass, this.dom.shelf);
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
if (emit) {
|
|
1656
|
+
this.dom.shelf.dispatchEvent(this._expandEvent);
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
_collapse(emit = true) {
|
|
1661
|
+
if (this.dom.controller) {
|
|
1662
|
+
this.dom.controller.setAttribute("aria-expanded", "false");
|
|
1663
|
+
}
|
|
1664
|
+
this.isSoftLocked = false;
|
|
1665
|
+
|
|
1666
|
+
// If we're dealing with transition classes, then we need to utilize
|
|
1667
|
+
// requestAnimationFrame to add the transition class, remove the open class,
|
|
1668
|
+
// add the close class, and finally remove the transition class.
|
|
1669
|
+
if (this.transitionClass !== "") {
|
|
1670
|
+
addClass(this.transitionClass, this.dom.shelf);
|
|
1671
|
+
|
|
1672
|
+
requestAnimationFrame(() => {
|
|
1673
|
+
removeClass(this.openClass, this.dom.shelf);
|
|
1674
|
+
|
|
1675
|
+
requestAnimationFrame(() => {
|
|
1676
|
+
addClass(this.closeClass, this.dom.shelf);
|
|
1677
|
+
|
|
1678
|
+
requestAnimationFrame(() => {
|
|
1679
|
+
setTimeout(() => {
|
|
1680
|
+
removeClass(this.transitionClass, this.dom.shelf);
|
|
1681
|
+
}, this.closeDuration);
|
|
1682
|
+
});
|
|
1683
|
+
});
|
|
1684
|
+
});
|
|
1685
|
+
} else {
|
|
1686
|
+
// Add the close class
|
|
1687
|
+
addClass(this.closeClass, this.dom.shelf);
|
|
1688
|
+
|
|
1689
|
+
// Remove the open class.
|
|
1690
|
+
removeClass(this.openClass, this.dom.shelf);
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
if (emit) {
|
|
1694
|
+
this.dom.shelf.dispatchEvent(this._collapseEvent);
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
_lock(emit = true) {
|
|
1699
|
+
if (this.dom.lockController) {
|
|
1700
|
+
this.dom.lockController.setAttribute("aria-pressed", "true");
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
// Add the locked class
|
|
1704
|
+
addClass(this.lockedClass, this.dom.shelf);
|
|
1705
|
+
|
|
1706
|
+
// Add the locked class to dependent elements.
|
|
1707
|
+
this.dom.dependents.forEach((dependent) => {
|
|
1708
|
+
addClass(this.lockedClass, dependent);
|
|
1709
|
+
});
|
|
1710
|
+
|
|
1711
|
+
// Remove the unlocked class.
|
|
1712
|
+
removeClass(this.unlockedClass, this.dom.shelf);
|
|
1713
|
+
|
|
1714
|
+
// Remove the unlocked class from dependent elements.
|
|
1715
|
+
this.dom.dependents.forEach((dependent) => {
|
|
1716
|
+
removeClass(this.unlockedClass, dependent);
|
|
1717
|
+
});
|
|
1718
|
+
|
|
1719
|
+
if (emit) {
|
|
1720
|
+
this.dom.shelf.dispatchEvent(this._lockEvent);
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
_unlock(emit = true) {
|
|
1725
|
+
if (this.dom.lockController) {
|
|
1726
|
+
this.dom.lockController.setAttribute("aria-pressed", "false");
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
// Add the unlocked class
|
|
1730
|
+
addClass(this.unlockedClass, this.dom.shelf);
|
|
1731
|
+
|
|
1732
|
+
// Add the unlocked class to dependent elements.
|
|
1733
|
+
this.dom.dependents.forEach((dependent) => {
|
|
1734
|
+
addClass(this.unlockedClass, dependent);
|
|
1735
|
+
});
|
|
1736
|
+
|
|
1737
|
+
// Remove the locked class.
|
|
1738
|
+
removeClass(this.lockedClass, this.dom.shelf);
|
|
1739
|
+
|
|
1740
|
+
// Remove the locked class from dependent elements.
|
|
1741
|
+
this.dom.dependents.forEach((dependent) => {
|
|
1742
|
+
removeClass(this.lockedClass, dependent);
|
|
1743
|
+
});
|
|
1744
|
+
|
|
1745
|
+
if (emit) {
|
|
1746
|
+
this.dom.shelf.dispatchEvent(this._unlockEvent);
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
_shiftSide(emit = true) {
|
|
1751
|
+
const toClass = this._classes[this.side];
|
|
1752
|
+
const fromClass = this._classes[this.otherSide];
|
|
1753
|
+
|
|
1754
|
+
// Add the to class
|
|
1755
|
+
addClass(toClass, this.dom.shelf);
|
|
1756
|
+
|
|
1757
|
+
// Add the to class to dependent elements.
|
|
1758
|
+
this.dom.dependents.forEach((dependent) => {
|
|
1759
|
+
addClass(toClass, dependent);
|
|
1760
|
+
});
|
|
1761
|
+
|
|
1762
|
+
// Remove the from class.
|
|
1763
|
+
removeClass(fromClass, this.dom.shelf);
|
|
1764
|
+
|
|
1765
|
+
// Remove the from class from dependent elements.
|
|
1766
|
+
this.dom.dependents.forEach((dependent) => {
|
|
1767
|
+
removeClass(fromClass, dependent);
|
|
1768
|
+
});
|
|
1769
|
+
|
|
1770
|
+
if (emit) {
|
|
1771
|
+
this.dom.shelf.dispatchEvent(this._shiftEvent);
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
_enableHover(emit = true) {
|
|
1776
|
+
if (this.dom.hoverController) {
|
|
1777
|
+
this.dom.hoverController.setAttribute("aria-pressed", "true");
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
addClass(this.hoverClass, this.dom.shelf);
|
|
1781
|
+
|
|
1782
|
+
removeClass(this.noHoverClass, this.dom.shelf);
|
|
1783
|
+
|
|
1784
|
+
if (emit) {
|
|
1785
|
+
this.dom.shelf.dispatchEvent(this._enableHoverEvent);
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
_disableHover(emit = true) {
|
|
1790
|
+
if (this.dom.hoverController) {
|
|
1791
|
+
this.dom.hoverController.setAttribute("aria-pressed", "false");
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
addClass(this.noHoverClass, this.dom.shelf);
|
|
1795
|
+
|
|
1796
|
+
removeClass(this.hoverClass, this.dom.shelf);
|
|
1797
|
+
|
|
1798
|
+
if (emit) {
|
|
1799
|
+
this.dom.shelf.dispatchEvent(this._disableHoverEvent);
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
open(force = false) {
|
|
1804
|
+
// Only open if the shelf is closed.
|
|
1805
|
+
if (this.isOpen && !force) return;
|
|
1806
|
+
|
|
1807
|
+
this._expand();
|
|
1808
|
+
|
|
1809
|
+
// Set the open flag.
|
|
1810
|
+
this._open = true;
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
close(force = false) {
|
|
1814
|
+
// Only close if the shelf is open.
|
|
1815
|
+
if (!this.isOpen && !force) return;
|
|
1816
|
+
|
|
1817
|
+
this.unlock();
|
|
1818
|
+
this._collapse();
|
|
1819
|
+
|
|
1820
|
+
// Set the open flag.
|
|
1821
|
+
this._open = false;
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
toggle() {
|
|
1825
|
+
if (this.isOpen) {
|
|
1826
|
+
this.close();
|
|
1827
|
+
} else {
|
|
1828
|
+
this.open();
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
lock() {
|
|
1833
|
+
// Only lock if the shelf is unlocked.
|
|
1834
|
+
if (this.isLocked) return;
|
|
1835
|
+
|
|
1836
|
+
this._lock();
|
|
1837
|
+
|
|
1838
|
+
// Set the locked flag.
|
|
1839
|
+
this._locked = true;
|
|
1840
|
+
|
|
1841
|
+
// Open the shelf.
|
|
1842
|
+
this.open(true);
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
unlock() {
|
|
1846
|
+
// Only unlock if the shelf is locked.
|
|
1847
|
+
if (!this.isLocked) return;
|
|
1848
|
+
|
|
1849
|
+
this._unlock();
|
|
1850
|
+
|
|
1851
|
+
// Set the locked flag.
|
|
1852
|
+
this._locked = false;
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
toggleLock() {
|
|
1856
|
+
if (this.isLocked) {
|
|
1857
|
+
this.unlock();
|
|
1858
|
+
} else {
|
|
1859
|
+
this.lock();
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
toLeft() {
|
|
1864
|
+
if (this.side === "left") return;
|
|
1865
|
+
|
|
1866
|
+
this._side = "left";
|
|
1867
|
+
this._otherSide = "right";
|
|
1868
|
+
this._shiftSide();
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
toRight() {
|
|
1872
|
+
if (this.side === "right") return;
|
|
1873
|
+
|
|
1874
|
+
this._side = "right";
|
|
1875
|
+
this._otherSide = "left";
|
|
1876
|
+
this._shiftSide();
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
toggleSide() {
|
|
1880
|
+
if (this.side === "left") {
|
|
1881
|
+
this.toRight();
|
|
1882
|
+
} else {
|
|
1883
|
+
this.toLeft();
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
enableHover() {
|
|
1888
|
+
if (this.hover) return;
|
|
1889
|
+
|
|
1890
|
+
this._enableHover();
|
|
1891
|
+
|
|
1892
|
+
this._hover = true;
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
disableHover() {
|
|
1896
|
+
if (!this.hover) return;
|
|
1897
|
+
|
|
1898
|
+
this._disableHover();
|
|
1899
|
+
|
|
1900
|
+
this._hover = false;
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
toggleHover() {
|
|
1904
|
+
if (this.hover) {
|
|
1905
|
+
this.disableHover();
|
|
1906
|
+
} else {
|
|
1907
|
+
this.enableHover();
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
export default NavigationShelf;
|