@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,22 +1,47 @@
1
1
  /**
2
2
  * @module events
3
3
  */
4
+
4
5
  import { debounce } from "@ulu/utils/performance.js";
6
+ import { isBrowser } from "@ulu/utils/browser/dom.js";
7
+
8
+ // Setup global document events
9
+ if (isBrowser()) {
10
+ initResize();
11
+ initPrint();
12
+ }
5
13
 
6
14
  /**
7
15
  * Event object - called on dispatch
8
16
  */
9
17
  const events = {
18
+ /**
19
+ * Event is dispatched when DOM in the page has changed, triggers updates from
20
+ * all modules listening for the change (init instances, etc)
21
+ * - Is triggered by modules that were responsible for modifying the page
22
+ */
10
23
  pageModified(context) {
11
- context.dispatchEvent(new Event(getName("pageModified"), { bubbles: true }));
24
+ context.dispatchEvent(new CustomEvent(getName("pageModified"), { bubbles: true }));
12
25
  },
26
+ /**
27
+ * Event called when page is resized
28
+ */
13
29
  pageResized(context) {
14
- context.dispatchEvent(new Event(getName("pageResized"), { bubbles: true }));
30
+ context.dispatchEvent(new CustomEvent(getName("pageResized"), { bubbles: true }));
31
+ },
32
+ /**
33
+ * Event dispatched before page print begins (teardown/restructure/hide things)
34
+ */
35
+ beforePrint(context) {
36
+ context.dispatchEvent(new CustomEvent(getName("beforePrint"), { bubbles: true }));
37
+ },
38
+ /**
39
+ * Event dispatched after page print (cleanup)
40
+ */
41
+ afterPrint(context) {
42
+ context.dispatchEvent(new CustomEvent(getName("afterPrint"), { bubbles: true }));
15
43
  }
16
- }
17
-
18
- // Add global document events
19
- window.addEventListener('resize', debounce(() => dispatch("pageResized", document), 250));
44
+ };
20
45
 
21
46
  /**
22
47
  * Triggers one of our custom events
@@ -36,10 +61,36 @@ export function dispatch(type, context) {
36
61
  }
37
62
 
38
63
  /**
39
- * Handles the actual event names being used (future could namespace)
64
+ * Namespaced event
40
65
  * @param {String} type Type of event to get the actual event name for
41
66
  * @returns {String}
42
67
  */
43
68
  export function getName(type) {
44
69
  return "ulu:" + type;
70
+ }
71
+
72
+ /**
73
+ * Setup resize handler/dispatch
74
+ */
75
+ function initResize() {
76
+ window.addEventListener("resize", debounce(() => dispatch("pageResized", document), 250));
77
+ }
78
+
79
+ /**
80
+ * Setup print listeners
81
+ * - Note: Tested with matchMedia but these events are more consistent
82
+ * Experimented with normalizing both events but they fired
83
+ * strangely, using any delay won't work (ie setTimeout / RAF)
84
+ * chrome pauses immediately javascript after the initial event.
85
+ * Reverting to a straightforward method for now. If this ends up
86
+ * needing something more robust we can work that out on this side
87
+ * and it won't change how the custom events file.
88
+ */
89
+ function initPrint() {
90
+ window.addEventListener("beforeprint", () => {
91
+ dispatch("beforePrint", document);
92
+ });
93
+ window.addEventListener("afterprint", () => {
94
+ dispatch("afterPrint", document);
95
+ });
45
96
  }
package/js/index.js CHANGED
@@ -1,14 +1,10 @@
1
- // =============================================================================
2
1
  // Main Library Import
3
- // =============================================================================
4
-
5
- // Used for:
6
2
  // - Allow users to access commonly needed items with at one point/file
3
+ // - This is the entry for the pre-built version
7
4
  // - Could allow changing of the ulu/js file structure if needed
8
5
  // - Will not include things that aren't used in every site (those would need to
9
6
  // imported manually
10
7
 
11
- export { CssBreakpoints } from "./helpers/css-breakpoint.js";
12
8
  export * as events from "./events/index.js";
13
- export * as grid from "./ui/grid.js";
14
- export * as tabs from "./ui/tabs.js";
9
+ export * as ui from "./ui/index.js";
10
+ export * as utils from "./utils/index.js";
@@ -1,16 +1,16 @@
1
1
  /**
2
- * @module helpers/css-breakpoint
2
+ * @module ui/breakpoints
3
3
  */
4
4
 
5
5
  // Pass breakpoints from CSS to stylesheet, use this to attach behaviors on breakpoints
6
6
  import { removeArrayElement } from "@ulu/utils/array.js";
7
7
  import { getName } from "../events/index.js";
8
- import { log, logError } from "../utils/logger.js";
8
+ import { log, logError } from "../utils/class-logger.js";
9
9
 
10
10
 
11
11
  // Resize Handler to update breakpoints for all instances (Called after resize finished)
12
12
  window.addEventListener(getName("pageResized"), () => {
13
- CssBreakpoints.instances.forEach(i => i.update());
13
+ BreakpointManager.instances.forEach(i => i.update());
14
14
  });
15
15
 
16
16
  /**
@@ -18,10 +18,10 @@ window.addEventListener(getName("pageResized"), () => {
18
18
  * Class that provides method for retrieving and acting on breakpoints passed
19
19
  * from CSS (using element psuedo content prop)
20
20
  */
21
- export class CssBreakpoints {
21
+ export class BreakpointManager {
22
22
  static instances = [];
23
23
  static defaults = {
24
- element: document.documentElement,
24
+ element: document?.documentElement,
25
25
  valueFromPsuedo: false,
26
26
  customProperty: "--breakpoint",
27
27
  psuedoSelector: ':before',
@@ -37,7 +37,7 @@ export class CssBreakpoints {
37
37
  * @param {String} config.psuedoSelector Change psuedo selector used to get the breakpoint from the psuedo's content property
38
38
  */
39
39
  constructor(config) {
40
- Object.assign(this, CssBreakpoints.defaults, config);
40
+ Object.assign(this, BreakpointManager.defaults, config);
41
41
  this.active = null;
42
42
  this.previous = null;
43
43
  this.activeIndex = null;
@@ -48,7 +48,7 @@ export class CssBreakpoints {
48
48
  this.order.forEach(n => this.breakpoints[n] = new Breakpoint(n, this));
49
49
  log(this, this);
50
50
  this.update(); // Run for the first time, then whenever browser resizes
51
- CssBreakpoints.instances.push(this);
51
+ BreakpointManager.instances.push(this);
52
52
  }
53
53
  /**
54
54
  * Add a callback for everytime a breakpoint changes
@@ -76,7 +76,7 @@ export class CssBreakpoints {
76
76
  * Get breakpoint from a custom property
77
77
  */
78
78
  getBreakpointInProperty() {
79
- return getComputedStyle(this.element).getPropertyValue(this.customProperty);
79
+ return getComputedStyle(this.element).getPropertyValue(this.customProperty).trim();
80
80
  }
81
81
  /**
82
82
  * Get breakpoint from element (design note: user could override prototype)
@@ -281,6 +281,4 @@ class Breakpoint {
281
281
  msg.unshift(`Breakpoint (${ this.name }):`);
282
282
  this._manager.log.apply(this._manager, msg);
283
283
  }
284
- }
285
-
286
- export default CssBreakpoints;
284
+ }
@@ -0,0 +1,195 @@
1
+ /**
2
+ * @module ui/collapsible
3
+ */
4
+
5
+ import { getName as getEventName } from "../events/index.js";
6
+ import { log, logError } from "../utils/class-logger.js";
7
+ import { ensureId } from "../utils/id.js";
8
+
9
+ /**
10
+ * Class for accessible hide/show components
11
+ */
12
+ export class Collapsible {
13
+ static defaults = {
14
+ clickOutsideCloses: false,
15
+ // oneOpenPerContext: false, // This should be another module that manages instances within a context (accordions)
16
+ // clickWithinCloses: false, // Not sure how this was used but seems like it should be separate
17
+ focusoutCloses: false,
18
+ escapeCloses: false,
19
+ /**
20
+ * The module won't attach the handlers (you need to do it yourself)
21
+ */
22
+ selfManaged: false,
23
+
24
+ /**
25
+ * This collapsible starts in open state
26
+ */
27
+ startOpen: false,
28
+ /**
29
+ * Open/active state class
30
+ */
31
+ openClass: "is-active",
32
+ /**
33
+ * Output debug info
34
+ */
35
+ debug: true,
36
+ onChange(_ctx) {
37
+ // do something
38
+ }
39
+ };
40
+ /**
41
+ * @param {Object} elements Elements object
42
+ * @param {Node} elements.trigger Trigger button/element that opens/closes collapsible
43
+ * @param {Node} elements.content The content element that the trigger reveals
44
+ * @param {Object} config Configuration options (see defaults)
45
+ * @returns {Object} Collapsible instance
46
+ */
47
+ constructor(elements, config) {
48
+ const { trigger, content } = elements;
49
+ if (!trigger || !content) {
50
+ logError(this, "missing required elements (trigger or content)");
51
+ return;
52
+ }
53
+ const options = Object.assign({}, Collapsible.defaults, config);
54
+ this.elements = elements;
55
+ this.options = options;
56
+ this.isOpen = false;
57
+ this.handlers = {}; // Spot to cache event handlers
58
+ ensureId(trigger);
59
+ ensureId(content);
60
+ this.debugLog(this, this);
61
+ if (!options.selfManaged) {
62
+ this.attachHandlers();
63
+ }
64
+ this.setup();
65
+ }
66
+ attachHandlers() {
67
+ const { trigger, content } = this.elements;
68
+ const { focusoutCloses } = this.options;
69
+ this.clickHandler = event => {
70
+ this.onClick(event);
71
+ }
72
+ this.focusoutHandler = (event) => {
73
+ if (focusoutCloses) {
74
+ this.close(event);
75
+ }
76
+ };
77
+ trigger.addEventListener("click", this.clickHandler);
78
+ content.addEventListener("focusout", this.focusoutHandler);
79
+ }
80
+ removeHandlers() {
81
+ const { trigger, content } = this.elements;
82
+ trigger.removeEventListener("click", this.clickHandler);
83
+ content.removeEventListener("focusout", this.focusoutHandler);
84
+ }
85
+ onClick(event) {
86
+ this.toggle(event);
87
+ }
88
+ destroy() {
89
+ this.removeHandlers();
90
+ this.destroyTemporaryHandlers();
91
+ }
92
+ debugLog(...msgs) {
93
+ if (this.options.debug) {
94
+ log(this, ...msgs);
95
+ }
96
+ }
97
+ setup() {
98
+ const { trigger, content } = this.elements;
99
+ const { startOpen } = this.options;
100
+ trigger.setAttribute("role", "button");
101
+ trigger.setAttribute("aria-controls", content.id);
102
+ content.setAttribute("aria-labelledby", trigger.id);
103
+ this.setState(startOpen);
104
+ }
105
+ createEvent(name, detail) {
106
+ return new CustomEvent(getEventName("collapsible:" + name), { detail });
107
+ }
108
+ setState(isOpen, event) {
109
+ const ctx = {
110
+ collapsible: this,
111
+ isOpen,
112
+ event
113
+ };
114
+ this.debugLog(this, "Set state", ctx);
115
+ const { trigger, content } = this.elements;
116
+ const { openClass } = this.options;
117
+ const setClass = el => el.classList[isOpen ? "add" : "remove"](openClass);
118
+ trigger.setAttribute("aria-expanded", isOpen ? "true" : "false");
119
+ setClass(trigger);
120
+ setClass(content);
121
+ this.isOpen = isOpen;
122
+ this.options.onChange(ctx);
123
+ trigger.dispatchEvent(this.createEvent("change", ctx));
124
+ if (isOpen) {
125
+ this.setupTemporaryHandlers();
126
+ } else {
127
+ this.destroyTemporaryHandlers();
128
+ }
129
+ }
130
+ /**
131
+ * Setup handlers needed for closing once open
132
+ */
133
+ setupTemporaryHandlers() {
134
+ const { content, trigger } = this.elements;
135
+ const { clickOutsideCloses, escapeCloses } = this.options;
136
+ const onDocumentClick = (event) => {
137
+ const { target } = event;
138
+ const inTrigger = trigger.contains(target);
139
+ const inContent = content.contains(target);
140
+ if (clickOutsideCloses && !inTrigger && !inContent) {
141
+ this.close(event);
142
+ }
143
+ };
144
+ const onDocumentKeydown = (event) => {
145
+ if (escapeCloses && event.key === "Escape") {
146
+ this.close(event);
147
+ }
148
+ };
149
+ document.addEventListener("click", onDocumentClick);
150
+ document.addEventListener("keydown", onDocumentKeydown);
151
+ this.handlers.onDocumentClick = onDocumentClick;
152
+ this.handlers.onDocumentKeydown = onDocumentKeydown;
153
+ }
154
+ /**
155
+ * Destroy handlers attached for closing once open
156
+ */
157
+ destroyTemporaryHandlers() {
158
+ const { onDocumentClick, onDocumentKeydown } = this.handlers;
159
+ if (onDocumentClick) {
160
+ document.removeEventListener("click", onDocumentClick);
161
+ }
162
+ if (onDocumentClick) {
163
+ document.removeEventListener("keydown", onDocumentKeydown);
164
+ }
165
+ }
166
+ open(event) {
167
+ this.setState(true, event);
168
+ }
169
+ close(event) {
170
+ this.setState(false, event);
171
+ }
172
+ toggle(event) {
173
+ this.setState(!this.isOpen, event);
174
+ }
175
+
176
+ // This is removed because I think it's not useful, users should keep references
177
+ // Static Methods for managing instances of this class
178
+ // static instances = [];
179
+ // /**
180
+ // * Get collapsible instance by trigger element
181
+ // * @param {Node|String} trigger Trigger node or trigger ID
182
+ // */
183
+ // static getInstance(trigger) {
184
+ // return Collapsible.instances.find(c => typeof trigger === "string" ?
185
+ // c.elements.trigger.id === trigger :
186
+ // c.elements.trigger === trigger
187
+ // );
188
+ // }
189
+ // static removeInstance(instance) {
190
+ // const index = Collapsible.instances.findIndex(c => c === instance);
191
+ // if (index > -1) {
192
+ // Collapsible.instances.splice(index, 1);
193
+ // }
194
+ // }
195
+ }
@@ -0,0 +1,157 @@
1
+ /**
2
+ * @module ui/dialog
3
+ */
4
+
5
+ import { getName } from "../events/index.js";
6
+ import { getDatasetJson, wasClickOutside } from "../utils/dom.js";
7
+ import { pauseVideos as pauseYoutubeVideos, prepVideos as prepYoutubeVideos } from "../utils/pause-youtube-video.js";
8
+ /**
9
+ * Default data attributes
10
+ */
11
+ export const attrs = {
12
+ init: "data-ulu-dialog-init",
13
+ dialog: "data-ulu-dialog",
14
+ trigger: "data-ulu-dialog-trigger",
15
+ close: "data-ulu-dialog-close",
16
+ };
17
+
18
+ // Utils for selecting things based on attributes
19
+ const attrSelector = key => `[${ attrs[key] }]`;
20
+ const attrSelectorInitial = key => `${ attrSelector(key) }:not([${ attrs.init }])`;
21
+ const queryAllInitial = key => document.querySelectorAll(attrSelectorInitial(key));
22
+
23
+ /**
24
+ * Dialog Defaults
25
+ * - Can be overridden using data-attributes
26
+ */
27
+ export const defaults = {
28
+ /**
29
+ * Use non-modal interface for dialog
30
+ */
31
+ nonModal: false,
32
+ /**
33
+ * Move the dialog to the document end (hoist out of content)
34
+ * - helpful if dialogs are within editor body, etc
35
+ */
36
+ documentEnd: false,
37
+ /**
38
+ * Requires styling that reduces any padding/border on dialog
39
+ */
40
+ clickOutsideCloses: true,
41
+ /**
42
+ * Whether or not to pause videos when dialog closes (currently just youtube and native)
43
+ */
44
+ pauseVideos: true,
45
+ };
46
+
47
+
48
+ // Current default objects (user can override these)
49
+ let currentDefaults = { ...defaults };
50
+
51
+ /**
52
+ * @param {Object} options Change options used as default for dialogs, can then be overriden by data attribute settings on element
53
+ */
54
+ export function setDefaults(options) {
55
+ currentDefaults = Object.assign({}, currentDefaults, options);
56
+ }
57
+
58
+ /**
59
+ * Initialize everything in document
60
+ * - This will only initialize elements once, it is safe to call on page changes
61
+ */
62
+ export function init() {
63
+ document.addEventListener(getName("pageModified"), setup);
64
+ setup();
65
+ }
66
+
67
+ /**
68
+ * Setup dialogs and triggers
69
+ */
70
+ export function setup() {
71
+ // Then setup all dialogs (including those that were built)
72
+ const dialogs = queryAllInitial("dialog");
73
+ dialogs.forEach(setupDialog);
74
+
75
+ const triggers = queryAllInitial("trigger");
76
+ triggers.forEach(setupTrigger);
77
+ }
78
+
79
+ /**
80
+ * Setup click handlers on a trigger
81
+ * @param {Node} trigger
82
+ */
83
+ export function setupTrigger(trigger) {
84
+ trigger.addEventListener("click", handleTrigger);
85
+ trigger.setAttribute(attrs.init, "");
86
+
87
+ function handleTrigger() {
88
+ const id = trigger.dataset.uluDialogTrigger;
89
+ const dialog = document.getElementById(id);
90
+ if (!dialog) {
91
+ console.error("Could not locate dialog (id)", id);
92
+ return;
93
+ }
94
+ if (dialog?.tagName?.toLowerCase() !== "dialog") {
95
+ console.error("Attempted to trigger non <dialog> element. Did you mean to use modal builder?" );
96
+ return;
97
+ }
98
+ const options = getDialogOptions(dialog);
99
+ dialog[options.nonModal ? "show" : "showModal"]();
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Setup click handlers for a dialog
105
+ * @param {Node} dialog
106
+ */
107
+ export function setupDialog(dialog) {
108
+ const options = getDialogOptions(dialog);
109
+ dialog.addEventListener("click", handleClicks);
110
+ dialog.setAttribute(attrs.init, "");
111
+ if (options.documentEnd) {
112
+ document.body.appendChild(dialog);
113
+ }
114
+ if (options.pauseVideos) {
115
+ prepVideos(dialog);
116
+ }
117
+
118
+ function handleClicks(event) {
119
+ const { target } = event;
120
+ const closeFromButton = target.closest("[data-ulu-dialog-close]");
121
+ let closeFromOutside = options.clickOutsideCloses &&
122
+ target === dialog &&
123
+ wasClickOutside(dialog, event);
124
+ if (closeFromOutside || closeFromButton) {
125
+ if (options.pauseVideos) {
126
+ pauseVideos(dialog);
127
+ }
128
+ dialog.close();
129
+ }
130
+ }
131
+ }
132
+
133
+ /**
134
+ * For a given dialog, get it's options (from data attribute)
135
+ * @param {Node} dialog
136
+ * @returns {Object}
137
+ */
138
+ export function getDialogOptions(dialog) {
139
+ const options = getDatasetJson(dialog, "uluDialog");
140
+ return Object.assign({}, currentDefaults, options);
141
+ }
142
+
143
+ /**
144
+ * Pause native and youtube videos for a given dialog
145
+ */
146
+ function prepVideos(dialog) {
147
+ prepYoutubeVideos(dialog);
148
+ }
149
+ /**
150
+ * Prep videos to be paused for a given dialog
151
+ */
152
+ function pauseVideos(dialog) {
153
+ pauseYoutubeVideos(dialog);
154
+ const nativeVideos = dialog.querySelectorAll("video");
155
+ nativeVideos.forEach(video => video.pause());
156
+ }
157
+
@@ -0,0 +1,37 @@
1
+ Styling:
2
+ ✔ Animations? @done
3
+ ☐ Check styling on left right
4
+ ☐ Setup fullscreen option / modifier
5
+ ☐ Setup programmatic modal with new system
6
+ ☐ Resizing
7
+ ✔ Work out how this can work with native and click outside @done
8
+ ✔ Figure out how to setup icon for the resizer for all sites @done
9
+ * Fontawesome, or should this just be CSS so we don't need any icons for it
10
+ * Same for close button should we just use CSS and omit any FA requirements
11
+ * Or make it super easy to implement the icon only in the template (like default icon classses)
12
+ * Then user can choose to implement their own (via class) or use the styles that come with it
13
+ ☐ Prevent Browser Scroll
14
+ ✔ How should height work? Should a centered modal expand to it's content's height (up to the viewport height) or always be cropped and scroll (static height)? @done
15
+ Todos:
16
+ ✔ How should the relationship between a dialog and it's trigger work @done
17
+ * Think there should be no relationship? Just triggers are triggers just attach a handler to open DOM centric way (add errors when the dialog doesn't exist)
18
+ * For the dialogs they should just have handlers attached for close
19
+ * Init should find all one's that need to be built and build them, then it should initialize them the same way as non-built dialogs
20
+ ✔ Support both modal and non-modal dialogs (rename)? @done
21
+ ✘ Divide into 4 modules @cancelled
22
+ 1. Initializer (data-attributes)
23
+ 2. Templater (for Drupal projects and standard modals)
24
+ 3. Open/Close Behaviors
25
+ 4. Programmatic Modal
26
+ * This is tough because its opionated towards our system in Drupal (jQuery)
27
+ ✔ How should this all be structured @done
28
+ * JS template dialogs?
29
+ * Pros
30
+ * Many users create the modals in content in CMS we don't want to make that difficult or get stuck with structure
31
+ * Cons
32
+ * Templating is in JS so dialogs don't make much sense on their own
33
+ * Could continue the div to dialog conversion (so modal content is inline unless JS running). Don't want to optimize too much for no js anymore anyways
34
+ * writing the dialog by hand makes sense for non defualt modal styles or users that have another structure but need the scripting part
35
+ * Solution
36
+ * Breakup module into two parts the underlying modal scripting (open close trigger) and (conversion modal templating [w. resizer] and as <div>)
37
+ * Then we can have both without any extra code and seperation
package/js/ui/flipcard.js CHANGED
@@ -2,16 +2,10 @@
2
2
  * @module ui/flipcard
3
3
  */
4
4
 
5
- // =============================================================================
6
- // Flipcard
7
- // =============================================================================
8
-
9
- // Version: 1.0.1
10
-
11
- // Changes 1.0.1 | Added allow selection
12
-
13
5
  import { trimWhitespace } from "@ulu/utils/string.js";
14
- import { log, logError } from "../utils/logger.js";
6
+ import { log, logError } from "../utils/class-logger.js";
7
+ import { getName } from "../events/index.js";
8
+ import { getDatasetOptionalJson } from "../utils/dom.js";
15
9
  const debugMode = false; // Global dev debug
16
10
 
17
11
  export class Flipcard {
@@ -23,7 +17,7 @@ export class Flipcard {
23
17
  selectionMin: 10, // Minimum length that qualifies as a selection
24
18
  exclude: "a, input, textarea, button" // Selectors to avoid closing a flipcard onProxyclick
25
19
  },
26
- }
20
+ };
27
21
  constructor(container, front, back, config, debug = false) {
28
22
  if (!container, !front, !back) {
29
23
  logError(this, 'Missing an element (container, front, back)');
@@ -133,7 +127,57 @@ export class Flipcard {
133
127
  `;
134
128
  }
135
129
  }
136
- export default Flipcard;
130
+
131
+ /**
132
+ * Default data attributes
133
+ */
134
+ export const attrs = {
135
+ init: "data-ulu-flipcard-init",
136
+ flipcard: "data-ulu-flipcard",
137
+ front: "data-ulu-flipcard-front",
138
+ back: "data-ulu-flipcard-back",
139
+ };
140
+
141
+ // Utils for selecting things based on attributes
142
+ const attrSelector = key => `[${ attrs[key] }]`;
143
+ const attrSelectorInitial = key => `${ attrSelector(key) }:not([${ attrs.init }])`;
144
+
145
+ // const containers = document.querySelectorAll('[data-ulu-flipcard]');
146
+ const instances = [];
147
+
148
+ export function init() {
149
+ document.addEventListener(getName("pageModified"), setup);
150
+ setup();
151
+ }
152
+
153
+ export function setup() {
154
+ const builders = document.querySelectorAll(attrSelectorInitial("flipcard"));
155
+ builders.forEach(setupFlipcard);
156
+ }
157
+
158
+ // containers.forEach(init);
159
+
160
+ function setupFlipcard(container) {
161
+ container.setAttribute(attrs.init, "");
162
+ const options = getDatasetOptionalJson(container, "uluFlipcard");
163
+ const config = Object.assign({}, options);
164
+ const front = container.querySelector(attrSelectorInitial("front"));
165
+ const back = container.querySelector(attrSelectorInitial("back"));
166
+ instances.push(new Flipcard(container, front, back, config));
167
+ }
168
+
169
+ // getDatasetOptionalJson
170
+ function setupSlider(container) {
171
+ container.setAttribute(attrs.init, "");
172
+ const options = getDatasetOptionalJson(container, "uluFlipcard");
173
+ const config = Object.assign({}, options);
174
+ const elements = {
175
+ track: container.querySelector(attrSelector("track")),
176
+ controls: container.querySelector(attrSelector("controls"))
177
+ };
178
+ // replace with OverflowScroller when finished removing sitescrollslider
179
+ instances.push(new SiteScrollSlider(elements, config));
180
+ }
137
181
 
138
182
  /**
139
183
  * Preliminary Notes: