@ulu/frontend 0.0.22 → 0.1.0-beta.0

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.
Files changed (212) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/deprecated/js/drupal-programmatic-modal.js +91 -0
  3. package/{js/ui/modals.js → deprecated/js/micromodal-modals.js} +41 -67
  4. package/dist/ulu-frontend.min.css +1 -1
  5. package/dist/ulu-frontend.min.js +70 -1
  6. package/index.js +6 -1
  7. package/js/events/index.js +58 -7
  8. package/js/index.js +3 -7
  9. package/js/{helpers/css-breakpoint.js → ui/breakpoints.js} +9 -11
  10. package/js/ui/collapsible.js +195 -0
  11. package/js/ui/dialog.js +157 -0
  12. package/js/ui/dialog.todo +37 -0
  13. package/js/ui/flipcard.js +55 -11
  14. package/js/ui/grid.js +2 -47
  15. package/js/ui/index.js +21 -0
  16. package/js/ui/modal-builder.js +197 -0
  17. package/js/ui/overflow-scroller-pager.js +1 -1
  18. package/js/ui/overflow-scroller.js +8 -5
  19. package/js/ui/page.js +14 -0
  20. package/js/ui/popover.js +135 -0
  21. package/js/ui/print-details.js +44 -0
  22. package/js/ui/print.js +67 -0
  23. package/js/ui/programmatic-modal.js +79 -81
  24. package/js/ui/proxy-click.js +80 -0
  25. package/js/ui/resizer.js +3 -3
  26. package/js/ui/scroll-slider.js +56 -0
  27. package/js/ui/scrollpoint.js +300 -0
  28. package/js/ui/slider.js +72 -10
  29. package/js/ui/tabs.js +85 -58
  30. package/js/ui/theme-toggle.js +129 -0
  31. package/js/ui/tooltip.js +268 -67
  32. package/js/utils/{logger.js → class-logger.js} +6 -5
  33. package/js/utils/dom.js +122 -0
  34. package/js/utils/file-save.js +67 -0
  35. package/js/utils/floating-ui.js +83 -0
  36. package/js/utils/id.js +22 -0
  37. package/js/utils/index.js +7 -0
  38. package/js/{helpers → utils}/pause-youtube-video.js +1 -1
  39. package/package.json +32 -11
  40. package/resources/drupal/twig-macros/accordion.twig +99 -0
  41. package/resources/drupal/twig-macros/dropdown.twig +44 -0
  42. package/resources/drupal/twig-macros/flipcard.twig +69 -0
  43. package/resources/drupal/twig-macros/image.twig +30 -0
  44. package/resources/drupal/twig-macros/layout.twig +338 -0
  45. package/resources/drupal/twig-macros/slider.twig +214 -0
  46. package/resources/drupal/twig-macros/tabs.twig +84 -0
  47. package/scss/README.md +13 -1
  48. package/scss/_breakpoint.scss +69 -26
  49. package/scss/_button.scss +148 -57
  50. package/scss/_color.scss +46 -28
  51. package/scss/_cssvar.scss +103 -12
  52. package/scss/_element.scss +84 -67
  53. package/scss/_index.scss +0 -3
  54. package/scss/_layout.scss +71 -37
  55. package/scss/_path.scss +2 -2
  56. package/scss/_selector.scss +20 -11
  57. package/scss/_typography.scss +115 -82
  58. package/scss/_units.scss +14 -13
  59. package/scss/_utils.scss +280 -18
  60. package/scss/base/_color.scss +2 -1
  61. package/scss/base/_elements.scss +61 -35
  62. package/scss/base/_index.scss +60 -23
  63. package/scss/base/_keyframes.scss +115 -16
  64. package/scss/base/_layout.scss +10 -6
  65. package/scss/base/_normalize.scss +6 -122
  66. package/scss/base/_print.scss +49 -0
  67. package/scss/base/_root.scss +28 -0
  68. package/scss/base/_typography.scss +4 -1
  69. package/scss/components/_accordion.scss +217 -0
  70. package/scss/components/_adaptive-spacing.scss +148 -0
  71. package/scss/components/_badge.scss +17 -14
  72. package/scss/components/_button-verbose.scss +138 -0
  73. package/scss/components/_button.scss +9 -4
  74. package/scss/components/_callout.scss +175 -0
  75. package/scss/components/_captioned-figure.scss +173 -0
  76. package/scss/components/_card-grid.scss +75 -0
  77. package/scss/components/_card.scss +420 -0
  78. package/scss/components/_css-icon.scss +433 -0
  79. package/scss/{_grid.scss → components/_data-grid.scss} +100 -68
  80. package/scss/components/_data-table.scss +180 -0
  81. package/scss/components/_fill-context.scss +20 -22
  82. package/scss/components/_flipcard-grid.scss +66 -0
  83. package/scss/components/_flipcard.scss +304 -0
  84. package/scss/components/_form-theme.scss +633 -0
  85. package/scss/components/_hero.scss +183 -0
  86. package/scss/components/_horizontal-rule.scss +51 -0
  87. package/scss/components/_image-grid.scss +71 -0
  88. package/scss/components/_index.scss +276 -38
  89. package/scss/components/_links.scss +1 -1
  90. package/scss/components/_list-lines.scss +14 -3
  91. package/scss/components/_list-ordered.scss +3 -1
  92. package/scss/components/_list-unordered.scss +3 -1
  93. package/scss/components/_menu-stack.scss +245 -0
  94. package/scss/components/_modal.scss +495 -0
  95. package/scss/components/_nav-strip.scss +148 -0
  96. package/scss/components/_overlay-section.scss +122 -0
  97. package/scss/components/_pager.scss +168 -0
  98. package/scss/components/_placeholder-block.scss +121 -0
  99. package/scss/components/_popover.scss +263 -0
  100. package/scss/components/_pull-quote.scss +111 -0
  101. package/scss/components/_ratio-box.scss +64 -0
  102. package/scss/components/_rule.scss +12 -9
  103. package/scss/components/_scroll-slider.scss +204 -0
  104. package/scss/components/_skip-link.scss +92 -0
  105. package/scss/components/_slider.scss +241 -0
  106. package/scss/components/_spoke-spinner.scss +193 -0
  107. package/scss/components/_tabs.scss +179 -0
  108. package/scss/components/_tag.scss +142 -0
  109. package/scss/components/_tile-button.scss +131 -0
  110. package/scss/components/_tile-grid-overlay.scss +132 -0
  111. package/scss/components/_tile-grid.scss +172 -0
  112. package/scss/components/_vignette.scss +65 -0
  113. package/scss/components/_wysiwyg.scss +94 -0
  114. package/scss/helpers/_color.scss +1 -0
  115. package/scss/helpers/_display.scss +2 -1
  116. package/scss/helpers/_index.scss +45 -22
  117. package/scss/helpers/_print.scss +20 -43
  118. package/scss/helpers/_typography.scss +3 -0
  119. package/scss/helpers/_units.scss +10 -13
  120. package/scss/helpers/_utilities.scss +5 -1
  121. package/scss/stylesheets/base-styles.scss +7 -0
  122. package/scss/stylesheets/component-styles.scss +7 -0
  123. package/scss/stylesheets/helper-styles.scss +7 -0
  124. package/types/events/index.d.ts +1 -1
  125. package/types/events/index.d.ts.map +1 -1
  126. package/types/index.d.ts +2 -2
  127. package/types/{helpers/css-breakpoint.d.ts → ui/breakpoints.d.ts} +3 -3
  128. package/types/ui/breakpoints.d.ts.map +1 -0
  129. package/types/ui/collapsible.d.ts +67 -0
  130. package/types/ui/collapsible.d.ts.map +1 -0
  131. package/types/ui/dialog.d.ts +42 -0
  132. package/types/ui/dialog.d.ts.map +1 -0
  133. package/types/ui/flipcard.d.ts +8 -1
  134. package/types/ui/flipcard.d.ts.map +1 -1
  135. package/types/ui/grid.d.ts +0 -11
  136. package/types/ui/grid.d.ts.map +1 -1
  137. package/types/ui/index.d.ts +23 -0
  138. package/types/ui/index.d.ts.map +1 -0
  139. package/types/ui/modal-builder.d.ts +54 -0
  140. package/types/ui/modal-builder.d.ts.map +1 -0
  141. package/types/ui/overflow-scroller-pager.d.ts +1 -1
  142. package/types/ui/overflow-scroller-pager.d.ts.map +1 -1
  143. package/types/ui/overflow-scroller.d.ts +3 -1
  144. package/types/ui/overflow-scroller.d.ts.map +1 -1
  145. package/types/ui/page.d.ts +5 -0
  146. package/types/ui/page.d.ts.map +1 -0
  147. package/types/ui/popover.d.ts +40 -0
  148. package/types/ui/popover.d.ts.map +1 -0
  149. package/types/ui/print-details.d.ts +10 -0
  150. package/types/ui/print-details.d.ts.map +1 -0
  151. package/types/ui/print.d.ts +10 -0
  152. package/types/ui/print.d.ts.map +1 -0
  153. package/types/ui/programmatic-modal.d.ts +19 -1
  154. package/types/ui/programmatic-modal.d.ts.map +1 -1
  155. package/types/ui/proxy-click.d.ts +18 -0
  156. package/types/ui/proxy-click.d.ts.map +1 -0
  157. package/types/ui/resizer.d.ts +1 -1
  158. package/types/ui/resizer.d.ts.map +1 -1
  159. package/types/ui/scroll-slider.d.ts +13 -0
  160. package/types/ui/scroll-slider.d.ts.map +1 -0
  161. package/types/ui/scrollpoint.d.ts +133 -0
  162. package/types/ui/scrollpoint.d.ts.map +1 -0
  163. package/types/ui/slider.d.ts +14 -2
  164. package/types/ui/slider.d.ts.map +1 -1
  165. package/types/ui/tabs.d.ts +22 -0
  166. package/types/ui/tabs.d.ts.map +1 -1
  167. package/types/ui/theme-toggle.d.ts +14 -0
  168. package/types/ui/theme-toggle.d.ts.map +1 -0
  169. package/types/ui/tooltip.d.ts +92 -10
  170. package/types/ui/tooltip.d.ts.map +1 -1
  171. package/types/utils/{logger.d.ts → class-logger.d.ts} +1 -1
  172. package/types/utils/class-logger.d.ts.map +1 -0
  173. package/types/utils/dom.d.ts +48 -0
  174. package/types/utils/dom.d.ts.map +1 -0
  175. package/types/utils/file-save.d.ts +64 -0
  176. package/types/utils/file-save.d.ts.map +1 -0
  177. package/types/utils/floating-ui.d.ts +19 -0
  178. package/types/utils/floating-ui.d.ts.map +1 -0
  179. package/types/utils/id.d.ts +10 -0
  180. package/types/utils/id.d.ts.map +1 -0
  181. package/types/utils/index.d.ts +9 -0
  182. package/types/utils/index.d.ts.map +1 -0
  183. package/types/utils/pause-youtube-video.d.ts.map +1 -0
  184. package/js/helpers/file-save.js +0 -52
  185. package/js/helpers/scrollbar-width-property.js +0 -14
  186. package/project.todo +0 -22
  187. package/scss/_calculate.scss +0 -64
  188. package/scss/_utility.scss +0 -12
  189. package/types/helpers/css-breakpoint.d.ts.map +0 -1
  190. package/types/helpers/file-save.d.ts +0 -17
  191. package/types/helpers/file-save.d.ts.map +0 -1
  192. package/types/helpers/node-data-manager.d.ts +0 -45
  193. package/types/helpers/node-data-manager.d.ts.map +0 -1
  194. package/types/helpers/pause-youtube-video.d.ts.map +0 -1
  195. package/types/helpers/scrollbar-width-property.d.ts +0 -11
  196. package/types/helpers/scrollbar-width-property.d.ts.map +0 -1
  197. package/types/ui/modals.d.ts +0 -27
  198. package/types/ui/modals.d.ts.map +0 -1
  199. package/types/utils/logger.d.ts.map +0 -1
  200. package/vite.config.js +0 -36
  201. /package/{js/deprecated → deprecated/js}/doc-ready.js +0 -0
  202. /package/{js/deprecated → deprecated/js}/jquery-prototypes.js +0 -0
  203. /package/{js/deprecated → deprecated/js}/mini-collapsible-popper-positioning.js +0 -0
  204. /package/{js/deprecated → deprecated/js}/mini-collapsible.js +0 -0
  205. /package/{js/helpers → deprecated/js}/node-data-manager.js +0 -0
  206. /package/{js/deprecated → deprecated/js}/script-loader.js +0 -0
  207. /package/{js/deprecated → deprecated/js}/waypoints/README.md +0 -0
  208. /package/{js/deprecated → deprecated/js}/waypoints/anchor-menu.js +0 -0
  209. /package/{js/deprecated → deprecated/js}/waypoints/element-waypoint.js +0 -0
  210. /package/{js/deprecated → deprecated/js}/waypoints/examples/page-link-menu.md +0 -0
  211. /package/{js/deprecated → deprecated/js}/waypoints/state-in-attribute.js +0 -0
  212. /package/types/{helpers → utils}/pause-youtube-video.d.ts +0 -0
@@ -1,94 +1,92 @@
1
1
  /**
2
2
  * @module ui/programmatic-modal
3
3
  */
4
- // =============================================================================
5
- // Grabs Breakpoint from CSS
6
- // =============================================================================
7
4
 
8
- // Version: 1.0.3
9
- // Changes:
10
- // 1.0.2 | Updates to work with the updated modal script which has
11
- // to attach it's own trigger handlers
12
- // Description: Drupal programmatic modal insertion script (interface = jquery prototype)
13
- // Changes: 1.0.2 - Added ability to pass class to container
14
5
 
15
- import { setupModal, show, attachTriggers, triggerAttr } from "./modals.js";
16
- import { dispatch } from "./events.js.js";
6
+ import { getName, dispatch } from "../events/index.js";
7
+ import { newId } from "../utils/id.js";
8
+ import { buildModal } from "./modal-builder.js";
17
9
 
18
- const $ = window.jQuery;
19
- const containerId = 'programmatic-modal';
20
- const selectorTrigger = '[data-programmatic-modal-trigger]';
21
- const defaults = {
22
- removeOnClose: true,
23
- settings: {},
24
- classes: []
25
- };
26
-
27
- let count = 0;
28
- let cachedTrigger;
29
-
30
- // Drupal calls the jquery 'programaticModal' from InvokeCommand())
31
- $.fn.programaticModal = newModal;
10
+ export class ProgrammaticModalManager {
11
+ static defaults = {
12
+ triggerSelector: "[data-ulu-programmatic-modal-trigger]",
13
+ triggerInitAttr: "data-ulu-programmatic-modal-init"
14
+ };
15
+ constructor(passedOptions) {
16
+ const options = Object.assign({}, ProgrammaticModalManager.defaults, passedOptions);
17
+ this.options = options;
18
+ this.triggers = null;
19
+ this.cachedTrigger = null;
20
+ this.triggerListener;
21
+ this.onTriggerClick = (event) => {
22
+ const trigger = event.target.closest(options.triggerSelector);
23
+ if (trigger) this.cachedTrigger = trigger;
24
+ };
25
+ this.onPageModified = () => {
26
+ this.setupTriggers();
27
+ };
28
+ document.addEventListener(getName("pageModified"), this.onPageModified);
29
+ this.setupTriggers();
30
+ }
31
+ setupTriggers() {
32
+ const { triggerSelector, triggerInitAttr } = this.options;
33
+ const triggers = document.querySelectorAll(`${ triggerSelector }:not([${ triggerInitAttr }])`);
34
+ triggers.forEach(trigger => {
35
+ trigger.addEventListener("click", this.onTriggerClick);
36
+ });
37
+ }
38
+ destroy() {
39
+ const { triggerSelector } = this.options;
40
+ const triggers = document.querySelectorAll(triggerSelector);
41
+ triggers.forEach(trigger => {
42
+ trigger.removeEventListener("click", this.onTriggerClick);
43
+ });
44
+ }
45
+ createAndOpen(config, afterCreate) {
46
+ const { selector, noClickTrigger, removeOnClose } = config;
47
+ const content = document.querySelector(selector);
48
+ if (!content.id) {
49
+ content.id = newId();
50
+ }
32
51
 
33
- // keep track of trigger clicks to return user on close (Drupal doesn't send trigger clicked, that I know of)
34
- document.addEventListener('click', cacheTrigger, true);
52
+ let trigger;
53
+ if (!noClickTrigger) {
54
+ trigger = this.cachedTrigger;
55
+ // Remove cached trigger (since it no longer applies)
56
+ this.cachedTrigger = null;
57
+ }
35
58
 
36
- /**
37
- * Sets up a new ajax triggered modal and opens it
38
- * @param {String} args Arguments provided from Drupal (JSON format)
39
- */
40
- function newModal(args) {
41
- args = args ? JSON.parse(args) : {};
42
- const config = Object.assign({}, defaults, args);
43
- const modal = document.querySelector(`#${ containerId }`);
44
- const id = setModalId(modal, config.id);
45
- const classes = ["programmatic-modal-content", ...config.classes ];
46
- modal.classList.add(...classes);
47
- // Add a new placeholder container
48
- newContainer();
49
- // Intialize and open the new modal
50
- setupModal(modal, config.settings);
51
- show(id, {
52
- onShow(modal) {
53
- dispatch('pageModified', modal);
54
- },
55
- onClose(element) {
56
- if (config.removeOnClose) {
57
- element.parentNode.removeChild(element);
59
+ if (!content) {
60
+ console.error("No element found from config.selector. ", config);
61
+ return;
62
+ }
63
+ const { modal } = buildModal(content, config.modal);
64
+ const ctx = { trigger, modal, config };
65
+ if (afterCreate) {
66
+ afterCreate(ctx);
67
+ }
68
+ const onModalClose = () => {
69
+ if (removeOnClose) {
70
+ modal.remove();
58
71
  }
59
- // For accessiblity/usablity (return to last clicked trigger)
60
- if (cachedTrigger) {
61
- cachedTrigger.focus();
72
+ if (trigger) {
73
+ trigger.focus();
62
74
  }
75
+ };
76
+ // Add close event (to refocus the element that triggered)
77
+ modal.addEventListener("close", onModalClose, { once: true });
78
+
79
+ // Setup trigger to show this modal again and not do it's old behavior (if ajax link)
80
+ if (!removeOnClose && trigger) {
81
+ trigger.addEventListener("click", (event) => {
82
+ event.preventDefault();
83
+ modal.showModal();
84
+ });
63
85
  }
64
- });
65
- // Attach handler so that it can reopen that modal
66
- if (!config.removeOnClose && cachedTrigger) {
67
- cachedTrigger.setAttribute(triggerAttr, id);
68
- attachTriggers();
86
+ dispatch("pageModified", modal);
87
+ // Open the new modal for the first time
88
+ modal.showModal();
89
+ return ctx;
69
90
  }
70
91
  }
71
- /**
72
- * Sets and returns the modal's id
73
- */
74
- function setModalId(element, id) {
75
- element.id = id || `programmatic-modal--id-${ ++count }`;
76
- return element.id;
77
- }
78
- /**
79
- * Once we remove the placeholder containers id (above)
80
- * we create another programmatic placeholder container
81
- * for the next programmitic container
82
- */
83
- function newContainer() {
84
- const container = document.createElement('div');
85
- container.id = 'programmatic-modal';
86
- document.body.append(container);
87
- }
88
- /**
89
- * Document click handler, will cache the trigger that caused the modal to open
90
- */
91
- function cacheTrigger(event) {
92
- const trigger = event.target.closest(selectorTrigger);
93
- if (trigger) cachedTrigger = trigger;
94
- }
92
+
@@ -0,0 +1,80 @@
1
+ /**
2
+ * @module ui/proxy-click
3
+ */
4
+
5
+
6
+ // Used for cards and things that look like they should be clickable even
7
+ // though the link in their content is the only clickable element. This way the
8
+ // entire cards content doesn't need to be in a link (which isn't accessible)
9
+ // - The script allows only for clicks with a duration of 250ms to avoid
10
+ // conflict with a user selecting text.
11
+ // - Works with either links or buttons because it just uses the elements .click()
12
+ // - Uses data-attributes for selection
13
+ import { getName } from "../events/index.js";
14
+ import { getDatasetOptionalJson } from "../utils/dom.js";
15
+
16
+ const attrs = {
17
+ trigger: "data-ulu-proxy-click",
18
+ init: "data-ulu-proxy-click-init",
19
+ };
20
+
21
+ const attrSelector = key => `[${ attrs[key] }]`;
22
+ const attrSelectorInitial = key => `${ attrSelector(key) }:not([${ attrs.init }])`;
23
+
24
+ export const defaults = {
25
+ selector: "[data-ulu-proxy-click-source]",
26
+ selectorPreventBase: "input, select, textarea, button, a, [tabindex='-1']",
27
+ selectorPrevent: "",
28
+ mousedownDurationPrevent: 250,
29
+ };
30
+
31
+ // Current default objects (user can override these)
32
+ let currentDefaults = { ...defaults };
33
+
34
+ /**
35
+ * @param {Object} options Change options used as default for dialogs, can then be overriden by data attribute settings on element
36
+ */
37
+ export function setDefaults(options) {
38
+ currentDefaults = Object.assign({}, currentDefaults, options);
39
+ }
40
+ /**
41
+ * Initialize everything in document
42
+ * - This will only initialize elements once, it is safe to call on page changes
43
+ */
44
+ export function init() {
45
+ document.addEventListener(getName("pageModified"), () => setup());
46
+ setup();
47
+ }
48
+
49
+ export function setup(context = document) {
50
+ const proxies = context.querySelectorAll(attrSelectorInitial("trigger"));
51
+ proxies.forEach(proxy => {
52
+ const elOptions = getDatasetOptionalJson(proxy, "siteProxyClick");
53
+ const options = Object.assign({}, currentDefaults, elOptions);
54
+ const child = proxy.querySelector(options.selector);
55
+ if (child) {
56
+ attachHandlers(proxy, child, options);
57
+ proxy.setAttribute(attrs.init, "");
58
+ } else {
59
+ console.error("Unable to locate proxy click source", options.selector);
60
+ }
61
+ });
62
+ }
63
+ export function attachHandlers(proxy, child, options) {
64
+ const { selectorPreventBase: spb, selectorPrevent: sp } = options;
65
+ const selectorPrevent = `${ spb }${ sp ? `, ${ sp }` : "" }`;
66
+ let start, shouldProxy;
67
+ proxy.addEventListener("mousedown", ({ target, timeStamp }) => {
68
+ shouldProxy = false;
69
+ if (!target.matches(selectorPrevent)) {
70
+ shouldProxy = true;
71
+ start = timeStamp;
72
+ }
73
+ });
74
+ proxy.addEventListener("mouseup", ({ timeStamp }) => {
75
+ if (shouldProxy && timeStamp - start < options.mousedownDurationPrevent) {
76
+ child.click();
77
+ }
78
+ });
79
+ proxy.style.cursor = "pointer";
80
+ }
package/js/ui/resizer.js CHANGED
@@ -11,9 +11,9 @@
11
11
 
12
12
  // Reference: - http://jsfiddle.net/3jMQD/614/
13
13
 
14
- import { logError } from "../utils/logger.js";
14
+ import { logError } from "../utils/class-logger.js";
15
15
 
16
- export default class ElementResizer {
16
+ export class Resizer {
17
17
  static defaults = {
18
18
  debug: false,
19
19
  overrideMaxWidth: false,
@@ -32,7 +32,7 @@ export default class ElementResizer {
32
32
  if (!control || !container) {
33
33
  logError(this, "Missing required elements 'control' or 'container'");
34
34
  }
35
- this.options = Object.assign({}, ElementResizer.defaults, options);
35
+ this.options = Object.assign({}, Resizer.defaults, options);
36
36
  this.container = container;
37
37
  this.control = control;
38
38
  this.handlerMousedown = this.onMousedown.bind(this);
@@ -0,0 +1,56 @@
1
+ /**
2
+ * @module ui/scroll-slider
3
+ */
4
+
5
+ import { OverflowScroller } from "./overflow-scroller.js";
6
+ import { createPager } from "./overflow-scroller-pager.js";
7
+ import { getName } from "../events/index.js";
8
+ import { getDatasetOptionalJson } from "../utils/dom.js";
9
+
10
+
11
+ /**
12
+ * Default data attributes
13
+ */
14
+ export const attrs = {
15
+ init: "data-ulu-scroll-slider-init",
16
+ slider: "data-ulu-scroll-slider",
17
+ track: "data-ulu-scroll-slider-track",
18
+ controls: "data-ulu-scroll-slider-control-context"
19
+ };
20
+
21
+ // Utils for selecting things based on attributes
22
+ const attrSelector = key => `[${ attrs[key] }]`;
23
+ const attrSelectorInitial = key => `${ attrSelector(key) }:not([${ attrs.init }])`;
24
+
25
+
26
+ const instances = [];
27
+
28
+ const defaults = {
29
+ amount: createPager()
30
+ };
31
+
32
+ /**
33
+ * Initialize everything in document
34
+ * - This will only initialize elements once, it is safe to call on page changes
35
+ */
36
+ export function init() {
37
+ document.addEventListener(getName("pageModified"), setup);
38
+ setup();
39
+ }
40
+
41
+ export function setup() {
42
+ const builders = document.querySelectorAll(attrSelectorInitial("slider"));
43
+ builders.forEach(setupSlider);
44
+ }
45
+
46
+ // getDatasetOptionalJson
47
+ function setupSlider(container) {
48
+ container.setAttribute(attrs.init, "");
49
+ const options = getDatasetOptionalJson(container, "uluScrollSlider");
50
+ const config = Object.assign({}, defaults, options);
51
+ const elements = {
52
+ track: container.querySelector(attrSelector("track")),
53
+ controls: container.querySelector(attrSelector("controls"))
54
+ };
55
+ instances.push(new OverflowScroller(elements, config));
56
+ }
@@ -0,0 +1,300 @@
1
+ /**
2
+ * @module ui/scrollpoint
3
+ */
4
+
5
+ // Module that uses intersection observer to add scrollpoint like behavior.
6
+ /**
7
+ * TODO:
8
+ * - Included a group option or attribute (on container)
9
+ // for things like anchor menus (one active in group at a time).
10
+ *
11
+ * How to link elements of group
12
+ * <div group={ groupName: test }>
13
+ * <div point={ groupName: test, mirror: ["#menu-link-1"] }>
14
+ * or
15
+ * <div group={ groupName: test, children: [".selector"] }>
16
+ * <div class=".selector">
17
+ */
18
+ import { getName } from "../events/index.js";
19
+ import { getDatasetOptionalJson, getElement } from "../utils/dom.js";
20
+ import { logError } from "../utils/class-logger.js";
21
+
22
+ /**
23
+ * Default data attributes
24
+ */
25
+ export const attrs = {
26
+ init: "data-ulu-scrollpoint-init",
27
+ /**
28
+ * Individual scrollpoint
29
+ */
30
+ point: "data-ulu-scrollpoint",
31
+ group: "data-ulu-scrollpoint-group",
32
+ groupAnchors: "data-ulu-scrollpoint-anchors"
33
+ // Goes on container for all items
34
+ // group: "data-ulu-scrollpoint-group"
35
+ };
36
+
37
+ // Utils for selecting things based on attributes
38
+ const attrSelector = key => `[${ attrs[key] }]`;
39
+ const attrSelectorInitial = key => `${ attrSelector(key) }:not([${ attrs.init }])`;
40
+ const queryAllInitial = key => document.querySelectorAll(attrSelectorInitial(key));
41
+
42
+ /**
43
+ * Initialize everything in document
44
+ * - This will only initialize elements once, it is safe to call on page changes
45
+ */
46
+ export function init() {
47
+ document.addEventListener(getName("pageModified"), setup);
48
+ setup();
49
+ }
50
+
51
+ /**
52
+ * Setup all points and groups
53
+ */
54
+ export function setup() {
55
+ const elements = queryAllInitial("point");
56
+ // const points = Array.from(elements).map(resolve);
57
+ // const groups = points
58
+ // .filter(({ config }) => config.groupName)
59
+ // .reduce((acc, point) => {
60
+ // const { groupName } = point.config;
61
+ // if (acc.has(groupName)) {
62
+ // acc.get(groupName).push(point);
63
+ // } else {
64
+ // acc.set(groupName, [point]);
65
+ // }
66
+ // }, new Map());
67
+ // const singles = points.filter(({ config }) => !config.groupName);
68
+ // groups.forEach(setupGroup);
69
+ elements.forEach(element => {
70
+ const elOptions = getDatasetOptionalJson(element, "uluScrollpoint");
71
+ const config = Object.assign({}, elOptions);
72
+ element.setAttribute(attrs.init, "");
73
+ new Scrollpoint(element, config);
74
+ });
75
+ }
76
+
77
+
78
+
79
+ /**
80
+ * Single scrollpoint
81
+ * - Note "forward" and "reverse" refer to scroll directions
82
+ * - forward = vertical below / horizontal right
83
+ * - reverse = vertical above / horizontal left
84
+ * @todo Convert margin to offset
85
+ * @todo This only goes one direction
86
+ */
87
+ export class Scrollpoint {
88
+ static defaults = {
89
+ /**
90
+ * Default observer root element
91
+ */
92
+ root: null,
93
+ /**
94
+ * Use a selector to select the observer root element
95
+ */
96
+ rootSelector: null,
97
+ /**
98
+ * Log debug info to console
99
+ */
100
+ debug: false,
101
+ /**
102
+ * Change scroll orientation to horizontal
103
+ */
104
+ horizontal: false,
105
+ /**
106
+ * Margin for observer top or left (depending on orientation)
107
+ */
108
+ marginStart: "-25%",
109
+ /**
110
+ * Margin for observer bottom or right (depending on orientation)
111
+ */
112
+ marginEnd: "-55%",
113
+ /**
114
+ * Threshold for observer
115
+ */
116
+ threshold: [0,1],
117
+ /**
118
+ * The point can exited (else persists)
119
+ */
120
+ exit: true,
121
+ /**
122
+ * The point can exit from the end
123
+ */
124
+ exitForward: true,
125
+ /**
126
+ * The point can exit from the start
127
+ */
128
+ exitReverse: true,
129
+ /**
130
+ * Set state classes
131
+ */
132
+ setClasses: false,
133
+ /**
134
+ * Prefix for classes
135
+ */
136
+ classPrefix: "scrollpoint",
137
+ /**
138
+ * Set attribute for state (less verbose same info as classes)
139
+ */
140
+ setAttribute: true,
141
+ /**
142
+ * Attribute name to set state info in
143
+ */
144
+ attributeName: "data-scrollpoint-state",
145
+ /**
146
+ * Group multiple points, one active at a time (scroll menus)
147
+ */
148
+ // groupName: null,
149
+ /**
150
+ * Elements that should also get active state info (classes or attributes)
151
+ */
152
+ syncElements: [],
153
+ /**
154
+ * Callback called when state changes
155
+ */
156
+ onChange(_ctx) {
157
+ // do something
158
+ }
159
+ };
160
+ /**
161
+ * Setup a new scrollpoint
162
+ * @param {Node} element The element to create the scrollpoint for
163
+ * @param {Object} config Options to configure the scrollpoint see Scrollpoint.defaults for more information on settings
164
+ */
165
+ constructor(element, config) {
166
+ const options = Object.assign({}, Scrollpoint.defaults, config);
167
+ if (!element) {
168
+ logError(this, "Missing required element");
169
+ return;
170
+ }
171
+ if (options.rootSelector) {
172
+ options.root = document.querySelector(options.rootSelector);
173
+ delete options.rootSelector;
174
+ }
175
+ this.options = options;
176
+ this.observer = null;
177
+ this.lastPosition = null;
178
+ this.isActive = false;
179
+ this.element = element;
180
+ this.syncedElements = [
181
+ element,
182
+ ...options.syncElements.map(target => getElement(target))
183
+ ];
184
+ this.classes = {
185
+ enter: this.getClassname("enter"),
186
+ enterForward: this.getClassname("enter--from-forward"),
187
+ enterReverse: this.getClassname("enter--from-reverse"),
188
+ exit: this.getClassname("exit"),
189
+ exitForward: this.getClassname("exit--from-forward"),
190
+ exitReverse: this.getClassname("exit--from-reverse"),
191
+ };
192
+ this.setupObserver();
193
+ if (options.debug) {
194
+ console.log("Scrollpoint", this);
195
+ }
196
+ }
197
+ getClassname(suffix) {
198
+ return this.options.classPrefix + "-" + suffix;
199
+ }
200
+ getObserverOptions() {
201
+ const { root, marginStart, marginEnd, threshold, horizontal } = this.options;
202
+ const rootMargin = horizontal
203
+ ? `0px ${ marginStart } 0px ${ marginEnd }`
204
+ : `${ marginStart } 0px ${ marginEnd } 0px`;
205
+ return { root, rootMargin, threshold };
206
+ }
207
+ /**
208
+ * IntersectionObserver Callback
209
+ * - Should set the state
210
+ */
211
+ onObserve(entries) {
212
+ const y = this.getScrollY();
213
+ const { lastPosition, isActive, options } = this;
214
+ const isForward = lastPosition === null ? null : lastPosition < y;
215
+ entries.forEach(entry => {
216
+ const { isIntersecting } = entry;
217
+ // Entering for first time
218
+ if (isIntersecting && !isActive) {
219
+ this.setState(true, isForward);
220
+ // Exiting
221
+ } else if (!isIntersecting && isActive && options.exit) {
222
+ // Call if allowed in either direction
223
+ if (isForward && options.exitForward || !isForward && options.exitReverse) {
224
+ this.setState(false, isForward);
225
+ }
226
+ }
227
+ });
228
+ this.lastPosition = y;
229
+ }
230
+ setupObserver() {
231
+ const handler = entries => {
232
+ this.onObserve(entries);
233
+ };
234
+ const config = this.getObserverOptions();
235
+ if (this.options.debug) {
236
+ console.log("Scrollpoint (IntersectionObserver)", config);
237
+ }
238
+ this.observer = new IntersectionObserver(handler, config);
239
+ this.observer.observe(this.element);
240
+ }
241
+ getScrollY() {
242
+ const { root } = this.options;
243
+ return root === null || root === document ? window.scrollY : root.scrollTop;
244
+ }
245
+ setState(isActive, isForward) {
246
+ const { element } = this;
247
+ const ctx = { isActive, isForward, element, instance: this };
248
+ const { setClasses, setAttribute, onChange } = this.options;
249
+ if (setClasses) {
250
+ this.updateClasses(isActive, isForward);
251
+ }
252
+ if (setAttribute) {
253
+ this.updateStateAttribute(isActive, isForward);
254
+ }
255
+ if (onChange) {
256
+ onChange(ctx);
257
+ }
258
+ this.isActive = isActive;
259
+ }
260
+ getAllClasses() {
261
+ return Object.values(this.classes);
262
+ }
263
+ updateClasses(isActive, isForward) {
264
+ const { classes } = this;
265
+ const all = this.getAllClasses();
266
+ const classesEnter = [
267
+ classes.enter,
268
+ isForward ? classes.enterForward : classes.enterReverse
269
+ ];
270
+ const classesExit = [
271
+ classes.exit,
272
+ isForward ? classes.exitForward : classes.exitReverse
273
+ ];
274
+ this.syncedElements.forEach(element => {
275
+ element.classList.remove(...all);
276
+ if (isActive) {
277
+ element.classList.add(...classesEnter);
278
+ } else {
279
+ element.classList.add(...classesExit);
280
+ }
281
+ });
282
+ }
283
+ updateStateAttribute(isActive, isForward) {
284
+ const activeTerm = isActive ? "enter" : "exit";
285
+ const side = isForward ? "forward" : "reverse";
286
+ this.syncedElements.forEach(element => {
287
+ element.setAttribute(this.options.attributeName, `${ activeTerm }-${ side }`);
288
+ });
289
+ }
290
+ destroy() {
291
+ this.observer.disconnect();
292
+ this.observer = null;
293
+ if (this.options.setClasses) {
294
+ this.element.classList.remove(...this.getAllClasses());
295
+ }
296
+ if (this.options.setAttribute) {
297
+ this.element.removeAttribute(this.options.attributeName)
298
+ }
299
+ }
300
+ }