aboutsummaryrefslogtreecommitdiffstats
path: root/old-apps/projects/src/app/pages/nav/js
diff options
context:
space:
mode:
authorivarlovlie <git@ivarlovlie.no>2022-09-20 09:24:27 +0200
committerivarlovlie <git@ivarlovlie.no>2022-09-20 09:24:27 +0200
commita9072370ca1eb9a5cce928b1d487db0f307edea6 (patch)
tree59c3c23df930a8b5f888dc7813923abf4ceefed4 /old-apps/projects/src/app/pages/nav/js
parent56fa963a1d63cbe0bf28e29e717cceaa417c45c1 (diff)
downloadgreatoffice-a9072370ca1eb9a5cce928b1d487db0f307edea6.tar.xz
greatoffice-a9072370ca1eb9a5cce928b1d487db0f307edea6.zip
feat: Move old apps into it's own directory
Diffstat (limited to 'old-apps/projects/src/app/pages/nav/js')
-rw-r--r--old-apps/projects/src/app/pages/nav/js/_1_diagonal-movement.js296
-rw-r--r--old-apps/projects/src/app/pages/nav/js/_1_responsive-sidebar.js215
-rw-r--r--old-apps/projects/src/app/pages/nav/js/_2_side-navigation-v4.js73
3 files changed, 584 insertions, 0 deletions
diff --git a/old-apps/projects/src/app/pages/nav/js/_1_diagonal-movement.js b/old-apps/projects/src/app/pages/nav/js/_1_diagonal-movement.js
new file mode 100644
index 0000000..ed4a47d
--- /dev/null
+++ b/old-apps/projects/src/app/pages/nav/js/_1_diagonal-movement.js
@@ -0,0 +1,296 @@
+// File#: _1_diagonal-movement
+// Usage: codyhouse.co/license
+/*
+ Modified version of the jQuery-menu-aim plugin
+ https://github.com/kamens/jQuery-menu-aim
+ - Replaced jQuery with Vanilla JS
+ - Minor changes
+*/
+(function() {
+ var menuAim = function(opts) {
+ init(opts);
+ };
+
+ window.menuAim = menuAim;
+
+ function init(opts) {
+ var activeRow = null,
+ mouseLocs = [],
+ lastDelayLoc = null,
+ timeoutId = null,
+ options = Util.extend({
+ menu: '',
+ rows: false, //if false, get direct children - otherwise pass nodes list
+ submenuSelector: "*",
+ submenuDirection: "right",
+ tolerance: 75, // bigger = more forgivey when entering submenu
+ enter: function(){},
+ exit: function(){},
+ activate: function(){},
+ deactivate: function(){},
+ exitMenu: function(){}
+ }, opts),
+ menu = options.menu;
+
+ var MOUSE_LOCS_TRACKED = 3, // number of past mouse locations to track
+ DELAY = 300; // ms delay when user appears to be entering submenu
+
+ /**
+ * Keep track of the last few locations of the mouse.
+ */
+ var mouseMoveFallback = function(event) {
+ (!window.requestAnimationFrame) ? mousemoveDocument(event) : window.requestAnimationFrame(function(){mousemoveDocument(event);});
+ };
+
+ var mousemoveDocument = function(e) {
+ mouseLocs.push({x: e.pageX, y: e.pageY});
+
+ if (mouseLocs.length > MOUSE_LOCS_TRACKED) {
+ mouseLocs.shift();
+ }
+ };
+
+ /**
+ * Cancel possible row activations when leaving the menu entirely
+ */
+ var mouseleaveMenu = function() {
+ if (timeoutId) {
+ clearTimeout(timeoutId);
+ }
+
+ // If exitMenu is supplied and returns true, deactivate the
+ // currently active row on menu exit.
+ if (options.exitMenu(this)) {
+ if (activeRow) {
+ options.deactivate(activeRow);
+ }
+
+ activeRow = null;
+ }
+ };
+
+ /**
+ * Trigger a possible row activation whenever entering a new row.
+ */
+ var mouseenterRow = function() {
+ if (timeoutId) {
+ // Cancel any previous activation delays
+ clearTimeout(timeoutId);
+ }
+
+ options.enter(this);
+ possiblyActivate(this);
+ },
+ mouseleaveRow = function() {
+ options.exit(this);
+ };
+
+ /*
+ * Immediately activate a row if the user clicks on it.
+ */
+ var clickRow = function() {
+ activate(this);
+ };
+
+ /**
+ * Activate a menu row.
+ */
+ var activate = function(row) {
+ if (row == activeRow) {
+ return;
+ }
+
+ if (activeRow) {
+ options.deactivate(activeRow);
+ }
+
+ options.activate(row);
+ activeRow = row;
+ };
+
+ /**
+ * Possibly activate a menu row. If mouse movement indicates that we
+ * shouldn't activate yet because user may be trying to enter
+ * a submenu's content, then delay and check again later.
+ */
+ var possiblyActivate = function(row) {
+ var delay = activationDelay();
+
+ if (delay) {
+ timeoutId = setTimeout(function() {
+ possiblyActivate(row);
+ }, delay);
+ } else {
+ activate(row);
+ }
+ };
+
+ /**
+ * Return the amount of time that should be used as a delay before the
+ * currently hovered row is activated.
+ *
+ * Returns 0 if the activation should happen immediately. Otherwise,
+ * returns the number of milliseconds that should be delayed before
+ * checking again to see if the row should be activated.
+ */
+ var activationDelay = function() {
+ if (!activeRow || !Util.is(activeRow, options.submenuSelector)) {
+ // If there is no other submenu row already active, then
+ // go ahead and activate immediately.
+ return 0;
+ }
+
+ function getOffset(element) {
+ var rect = element.getBoundingClientRect();
+ return { top: rect.top + window.pageYOffset, left: rect.left + window.pageXOffset };
+ };
+
+ var offset = getOffset(menu),
+ upperLeft = {
+ x: offset.left,
+ y: offset.top - options.tolerance
+ },
+ upperRight = {
+ x: offset.left + menu.offsetWidth,
+ y: upperLeft.y
+ },
+ lowerLeft = {
+ x: offset.left,
+ y: offset.top + menu.offsetHeight + options.tolerance
+ },
+ lowerRight = {
+ x: offset.left + menu.offsetWidth,
+ y: lowerLeft.y
+ },
+ loc = mouseLocs[mouseLocs.length - 1],
+ prevLoc = mouseLocs[0];
+
+ if (!loc) {
+ return 0;
+ }
+
+ if (!prevLoc) {
+ prevLoc = loc;
+ }
+
+ if (prevLoc.x < offset.left || prevLoc.x > lowerRight.x || prevLoc.y < offset.top || prevLoc.y > lowerRight.y) {
+ // If the previous mouse location was outside of the entire
+ // menu's bounds, immediately activate.
+ return 0;
+ }
+
+ if (lastDelayLoc && loc.x == lastDelayLoc.x && loc.y == lastDelayLoc.y) {
+ // If the mouse hasn't moved since the last time we checked
+ // for activation status, immediately activate.
+ return 0;
+ }
+
+ // Detect if the user is moving towards the currently activated
+ // submenu.
+ //
+ // If the mouse is heading relatively clearly towards
+ // the submenu's content, we should wait and give the user more
+ // time before activating a new row. If the mouse is heading
+ // elsewhere, we can immediately activate a new row.
+ //
+ // We detect this by calculating the slope formed between the
+ // current mouse location and the upper/lower right points of
+ // the menu. We do the same for the previous mouse location.
+ // If the current mouse location's slopes are
+ // increasing/decreasing appropriately compared to the
+ // previous's, we know the user is moving toward the submenu.
+ //
+ // Note that since the y-axis increases as the cursor moves
+ // down the screen, we are looking for the slope between the
+ // cursor and the upper right corner to decrease over time, not
+ // increase (somewhat counterintuitively).
+ function slope(a, b) {
+ return (b.y - a.y) / (b.x - a.x);
+ };
+
+ var decreasingCorner = upperRight,
+ increasingCorner = lowerRight;
+
+ // Our expectations for decreasing or increasing slope values
+ // depends on which direction the submenu opens relative to the
+ // main menu. By default, if the menu opens on the right, we
+ // expect the slope between the cursor and the upper right
+ // corner to decrease over time, as explained above. If the
+ // submenu opens in a different direction, we change our slope
+ // expectations.
+ if (options.submenuDirection == "left") {
+ decreasingCorner = lowerLeft;
+ increasingCorner = upperLeft;
+ } else if (options.submenuDirection == "below") {
+ decreasingCorner = lowerRight;
+ increasingCorner = lowerLeft;
+ } else if (options.submenuDirection == "above") {
+ decreasingCorner = upperLeft;
+ increasingCorner = upperRight;
+ }
+
+ var decreasingSlope = slope(loc, decreasingCorner),
+ increasingSlope = slope(loc, increasingCorner),
+ prevDecreasingSlope = slope(prevLoc, decreasingCorner),
+ prevIncreasingSlope = slope(prevLoc, increasingCorner);
+
+ if (decreasingSlope < prevDecreasingSlope && increasingSlope > prevIncreasingSlope) {
+ // Mouse is moving from previous location towards the
+ // currently activated submenu. Delay before activating a
+ // new menu row, because user may be moving into submenu.
+ lastDelayLoc = loc;
+ return DELAY;
+ }
+
+ lastDelayLoc = null;
+ return 0;
+ };
+
+ var reset = function(triggerDeactivate) {
+ if (timeoutId) {
+ clearTimeout(timeoutId);
+ }
+
+ if (activeRow && triggerDeactivate) {
+ options.deactivate(activeRow);
+ }
+
+ activeRow = null;
+ };
+
+ var destroyInstance = function() {
+ menu.removeEventListener('mouseleave', mouseleaveMenu);
+ document.removeEventListener('mousemove', mouseMoveFallback);
+ if(rows.length > 0) {
+ for(var i = 0; i < rows.length; i++) {
+ rows[i].removeEventListener('mouseenter', mouseenterRow);
+ rows[i].removeEventListener('mouseleave', mouseleaveRow);
+ rows[i].removeEventListener('click', clickRow);
+ }
+ }
+
+ };
+
+ /**
+ * Hook up initial menu events
+ */
+ menu.addEventListener('mouseleave', mouseleaveMenu);
+ var rows = (options.rows) ? options.rows : menu.children;
+ if(rows.length > 0) {
+ for(var i = 0; i < rows.length; i++) {(function(i){
+ rows[i].addEventListener('mouseenter', mouseenterRow);
+ rows[i].addEventListener('mouseleave', mouseleaveRow);
+ rows[i].addEventListener('click', clickRow);
+ })(i);}
+ }
+
+ document.addEventListener('mousemove', mouseMoveFallback);
+
+ /* Reset/destroy menu */
+ menu.addEventListener('reset', function(event){
+ reset(event.detail);
+ });
+ menu.addEventListener('destroy', destroyInstance);
+ };
+}());
+
diff --git a/old-apps/projects/src/app/pages/nav/js/_1_responsive-sidebar.js b/old-apps/projects/src/app/pages/nav/js/_1_responsive-sidebar.js
new file mode 100644
index 0000000..f9599d8
--- /dev/null
+++ b/old-apps/projects/src/app/pages/nav/js/_1_responsive-sidebar.js
@@ -0,0 +1,215 @@
+// File#: _1_responsive-sidebar
+// Usage: codyhouse.co/license
+(function() {
+ var Sidebar = function(element) {
+ this.element = element;
+ this.triggers = document.querySelectorAll('[aria-controls="'+this.element.getAttribute('id')+'"]');
+ this.firstFocusable = null;
+ this.lastFocusable = null;
+ this.selectedTrigger = null;
+ this.showClass = "sidebar--is-visible";
+ this.staticClass = "sidebar--static";
+ this.customStaticClass = "";
+ this.readyClass = "sidebar--loaded";
+ this.contentReadyClass = "sidebar-loaded:show";
+ this.layout = false; // this will be static or mobile
+ this.preventScrollEl = getPreventScrollEl(this);
+ getCustomStaticClass(this); // custom classes for static version
+ initSidebar(this);
+ };
+
+ function getPreventScrollEl(element) {
+ var scrollEl = false;
+ var querySelector = element.element.getAttribute('data-sidebar-prevent-scroll');
+ if(querySelector) scrollEl = document.querySelector(querySelector);
+ return scrollEl;
+ };
+
+ function getCustomStaticClass(element) {
+ var customClasses = element.element.getAttribute('data-static-class');
+ if(customClasses) element.customStaticClass = ' '+customClasses;
+ };
+
+ function initSidebar(sidebar) {
+ initSidebarResize(sidebar); // handle changes in layout -> mobile to static and viceversa
+
+ if ( sidebar.triggers ) { // open sidebar when clicking on trigger buttons - mobile layout only
+ for(var i = 0; i < sidebar.triggers.length; i++) {
+ sidebar.triggers[i].addEventListener('click', function(event) {
+ event.preventDefault();
+ toggleSidebar(sidebar, event.target);
+ });
+ }
+ }
+
+ // use the 'openSidebar' event to trigger the sidebar
+ sidebar.element.addEventListener('openSidebar', function(event) {
+ toggleSidebar(sidebar, event.detail);
+ });
+ };
+
+ function toggleSidebar(sidebar, target) {
+ if(Util.hasClass(sidebar.element, sidebar.showClass)) {
+ sidebar.selectedTrigger = target;
+ closeSidebar(sidebar);
+ return;
+ }
+ sidebar.selectedTrigger = target;
+ showSidebar(sidebar);
+ initSidebarEvents(sidebar);
+ };
+
+ function showSidebar(sidebar) { // mobile layout only
+ Util.addClass(sidebar.element, sidebar.showClass);
+ getFocusableElements(sidebar);
+ Util.moveFocus(sidebar.element);
+ // change the overflow of the preventScrollEl
+ if(sidebar.preventScrollEl) sidebar.preventScrollEl.style.overflow = 'hidden';
+ };
+
+ function closeSidebar(sidebar) { // mobile layout only
+ Util.removeClass(sidebar.element, sidebar.showClass);
+ sidebar.firstFocusable = null;
+ sidebar.lastFocusable = null;
+ if(sidebar.selectedTrigger) sidebar.selectedTrigger.focus();
+ sidebar.element.removeAttribute('tabindex');
+ //remove listeners
+ cancelSidebarEvents(sidebar);
+ // change the overflow of the preventScrollEl
+ if(sidebar.preventScrollEl) sidebar.preventScrollEl.style.overflow = '';
+ };
+
+ function initSidebarEvents(sidebar) { // mobile layout only
+ //add event listeners
+ sidebar.element.addEventListener('keydown', handleEvent.bind(sidebar));
+ sidebar.element.addEventListener('click', handleEvent.bind(sidebar));
+ };
+
+ function cancelSidebarEvents(sidebar) { // mobile layout only
+ //remove event listeners
+ sidebar.element.removeEventListener('keydown', handleEvent.bind(sidebar));
+ sidebar.element.removeEventListener('click', handleEvent.bind(sidebar));
+ };
+
+ function handleEvent(event) { // mobile layout only
+ switch(event.type) {
+ case 'click': {
+ initClick(this, event);
+ }
+ case 'keydown': {
+ initKeyDown(this, event);
+ }
+ }
+ };
+
+ function initKeyDown(sidebar, event) { // mobile layout only
+ if( event.keyCode && event.keyCode == 27 || event.key && event.key == 'Escape' ) {
+ //close sidebar window on esc
+ closeSidebar(sidebar);
+ } else if( event.keyCode && event.keyCode == 9 || event.key && event.key == 'Tab' ) {
+ //trap focus inside sidebar
+ trapFocus(sidebar, event);
+ }
+ };
+
+ function initClick(sidebar, event) { // mobile layout only
+ //close sidebar when clicking on close button or sidebar bg layer
+ if( !event.target.closest('.js-sidebar__close-btn') && !Util.hasClass(event.target, 'js-sidebar') ) return;
+ event.preventDefault();
+ closeSidebar(sidebar);
+ };
+
+ function trapFocus(sidebar, event) { // mobile layout only
+ if( sidebar.firstFocusable == document.activeElement && event.shiftKey) {
+ //on Shift+Tab -> focus last focusable element when focus moves out of sidebar
+ event.preventDefault();
+ sidebar.lastFocusable.focus();
+ }
+ if( sidebar.lastFocusable == document.activeElement && !event.shiftKey) {
+ //on Tab -> focus first focusable element when focus moves out of sidebar
+ event.preventDefault();
+ sidebar.firstFocusable.focus();
+ }
+ };
+
+ function initSidebarResize(sidebar) {
+ // custom event emitted when window is resized - detect only if the sidebar--static@{breakpoint} class was added
+ var beforeContent = getComputedStyle(sidebar.element, ':before').getPropertyValue('content');
+ if(beforeContent && beforeContent !='' && beforeContent !='none') {
+ checkSidebarLayout(sidebar);
+
+ sidebar.element.addEventListener('update-sidebar', function(event){
+ checkSidebarLayout(sidebar);
+ });
+ }
+ // check if there a main element to show
+ var mainContent = document.getElementsByClassName(sidebar.contentReadyClass);
+ if(mainContent.length > 0) Util.removeClass(mainContent[0], sidebar.contentReadyClass);
+ Util.addClass(sidebar.element, sidebar.readyClass);
+ };
+
+ function checkSidebarLayout(sidebar) {
+ var layout = getComputedStyle(sidebar.element, ':before').getPropertyValue('content').replace(/\'|"/g, '');
+ if(layout == sidebar.layout) return;
+ sidebar.layout = layout;
+ if(layout != 'static') Util.addClass(sidebar.element, 'is-hidden');
+ Util.toggleClass(sidebar.element, sidebar.staticClass + sidebar.customStaticClass, layout == 'static');
+ if(layout != 'static') setTimeout(function(){Util.removeClass(sidebar.element, 'is-hidden')});
+ // reset element role
+ (layout == 'static') ? sidebar.element.removeAttribute('role', 'alertdialog') : sidebar.element.setAttribute('role', 'alertdialog');
+ // reset mobile behaviour
+ if(layout == 'static' && Util.hasClass(sidebar.element, sidebar.showClass)) closeSidebar(sidebar);
+ };
+
+ function getFocusableElements(sidebar) {
+ //get all focusable elements inside the drawer
+ var allFocusable = sidebar.element.querySelectorAll('[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [tabindex]:not([tabindex="-1"]), [contenteditable], audio[controls], video[controls], summary');
+ getFirstVisible(sidebar, allFocusable);
+ getLastVisible(sidebar, allFocusable);
+ };
+
+ function getFirstVisible(sidebar, elements) {
+ //get first visible focusable element inside the sidebar
+ for(var i = 0; i < elements.length; i++) {
+ if( elements[i].offsetWidth || elements[i].offsetHeight || elements[i].getClientRects().length ) {
+ sidebar.firstFocusable = elements[i];
+ return true;
+ }
+ }
+ };
+
+ function getLastVisible(sidebar, elements) {
+ //get last visible focusable element inside the sidebar
+ for(var i = elements.length - 1; i >= 0; i--) {
+ if( elements[i].offsetWidth || elements[i].offsetHeight || elements[i].getClientRects().length ) {
+ sidebar.lastFocusable = elements[i];
+ return true;
+ }
+ }
+ };
+
+ window.Sidebar = Sidebar;
+
+ //initialize the Sidebar objects
+ var sidebar = document.getElementsByClassName('js-sidebar');
+ if( sidebar.length > 0 ) {
+ for( var i = 0; i < sidebar.length; i++) {
+ (function(i){new Sidebar(sidebar[i]);})(i);
+ }
+ // switch from mobile to static layout
+ var customEvent = new CustomEvent('update-sidebar');
+ window.addEventListener('resize', function(event){
+ (!window.requestAnimationFrame) ? setTimeout(function(){resetLayout();}, 250) : window.requestAnimationFrame(resetLayout);
+ });
+
+ (window.requestAnimationFrame) // init sidebar layout
+ ? window.requestAnimationFrame(resetLayout)
+ : resetLayout();
+
+ function resetLayout() {
+ for( var i = 0; i < sidebar.length; i++) {
+ (function(i){sidebar[i].dispatchEvent(customEvent)})(i);
+ };
+ };
+ }
+}()); \ No newline at end of file
diff --git a/old-apps/projects/src/app/pages/nav/js/_2_side-navigation-v4.js b/old-apps/projects/src/app/pages/nav/js/_2_side-navigation-v4.js
new file mode 100644
index 0000000..63ef9c4
--- /dev/null
+++ b/old-apps/projects/src/app/pages/nav/js/_2_side-navigation-v4.js
@@ -0,0 +1,73 @@
+// File#: _2_side-navigation-v4
+// Usage: codyhouse.co/license
+(function() {
+ function initSideNav(nav) {
+ // create btns - visible on mobile only
+ createBtns(nav);
+ // toggle sublists on mobile when clicking on buttons
+ toggleSubLists(nav);
+ // init diagonal movement
+ initDiagonalMove(nav);
+ };
+
+ function createBtns(nav) {
+ // on mobile -> create a <button> element for each link with a submenu
+ var expandableLinks = nav.getElementsByClassName('js-sidenav-v4__link');
+ for(var i = 0; i < expandableLinks.length; i++) {
+ createSingleBtn(expandableLinks[i]);
+ }
+ };
+
+ function createSingleBtn(link) {
+ if(!hasSubList(link)) return;
+ // create btn and insert it into the DOM
+ var btnClasses = link.getAttribute('class').replace('js-sidenav-v4__link', 'js-sidenav-v4__btn');
+ btnClasses = btnClasses +' sidenav-v4__link--btn';
+ var btnHtml = '<button class="reset '+btnClasses+'">'+link.innerHTML+'</button>';
+ link.insertAdjacentHTML('afterend', btnHtml);
+ // add class to link element
+ Util.addClass(link, 'sidenav-v4__link--href');
+ // check if we need to add the collpsed class to the <li> element
+ var listItem = link.parentElement;
+ if(!Util.hasClass(listItem, 'sidenav-v4__item--current')) Util.addClass(listItem, 'sidenav-v4__item--collapsed');
+ };
+
+ function hasSubList(link) {
+ // check if link has submenu
+ var sublist = link.nextElementSibling;
+ if(!sublist) return false;
+ return Util.hasClass(sublist, 'sidenav-v4__sub-list');
+ };
+
+ function toggleSubLists(nav) {
+ // open/close sublist on mobile
+ nav.addEventListener('click', function(event){
+ var btn = event.target.closest('.js-sidenav-v4__btn');
+ if(!btn) return;
+ Util.toggleClass(btn.parentElement, 'sidenav-v4__item--collapsed', !Util.hasClass(btn.parentElement, 'sidenav-v4__item--collapsed'));
+ });
+ };
+
+ function initDiagonalMove(nav) {
+ // improve dropdown navigation
+ new menuAim({
+ menu: nav.querySelector('ul'),
+ activate: function(row) {
+ Util.addClass(row, 'sidenav-v4__item--hover');
+ },
+ deactivate: function(row) {
+ Util.removeClass(row, 'sidenav-v4__item--hover');
+ },
+ exitMenu: function() {
+ return true;
+ },
+ });
+ };
+
+ var sideNavs = document.getElementsByClassName('js-sidenav-v4');
+ if( sideNavs.length > 0 ) {
+ for( var i = 0; i < sideNavs.length; i++) {
+ (function(i){initSideNav(sideNavs[i]);})(i);
+ }
+ }
+}()); \ No newline at end of file