@ulu/frontend 0.0.23 → 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 (211) hide show
  1. package/deprecated/js/drupal-programmatic-modal.js +91 -0
  2. package/{js/ui/modals.js → deprecated/js/micromodal-modals.js} +41 -67
  3. package/dist/ulu-frontend.min.css +1 -1
  4. package/dist/ulu-frontend.min.js +70 -1
  5. package/index.js +6 -1
  6. package/js/events/index.js +58 -7
  7. package/js/index.js +3 -7
  8. package/js/{helpers/css-breakpoint.js → ui/breakpoints.js} +9 -11
  9. package/js/ui/collapsible.js +195 -0
  10. package/js/ui/dialog.js +157 -0
  11. package/js/ui/dialog.todo +37 -0
  12. package/js/ui/flipcard.js +55 -11
  13. package/js/ui/grid.js +2 -47
  14. package/js/ui/index.js +21 -0
  15. package/js/ui/modal-builder.js +197 -0
  16. package/js/ui/overflow-scroller-pager.js +1 -1
  17. package/js/ui/overflow-scroller.js +8 -5
  18. package/js/ui/page.js +14 -0
  19. package/js/ui/popover.js +135 -0
  20. package/js/ui/print-details.js +44 -0
  21. package/js/ui/print.js +67 -0
  22. package/js/ui/programmatic-modal.js +79 -81
  23. package/js/ui/proxy-click.js +80 -0
  24. package/js/ui/resizer.js +3 -3
  25. package/js/ui/scroll-slider.js +56 -0
  26. package/js/ui/scrollpoint.js +300 -0
  27. package/js/ui/slider.js +72 -10
  28. package/js/ui/tabs.js +85 -58
  29. package/js/ui/theme-toggle.js +129 -0
  30. package/js/ui/tooltip.js +268 -67
  31. package/js/utils/{logger.js → class-logger.js} +6 -5
  32. package/js/utils/dom.js +122 -0
  33. package/js/utils/file-save.js +67 -0
  34. package/js/utils/floating-ui.js +83 -0
  35. package/js/utils/id.js +22 -0
  36. package/js/utils/index.js +7 -0
  37. package/js/{helpers → utils}/pause-youtube-video.js +1 -1
  38. package/package.json +32 -11
  39. package/resources/drupal/twig-macros/accordion.twig +99 -0
  40. package/resources/drupal/twig-macros/dropdown.twig +44 -0
  41. package/resources/drupal/twig-macros/flipcard.twig +69 -0
  42. package/resources/drupal/twig-macros/image.twig +30 -0
  43. package/resources/drupal/twig-macros/layout.twig +338 -0
  44. package/resources/drupal/twig-macros/slider.twig +214 -0
  45. package/resources/drupal/twig-macros/tabs.twig +84 -0
  46. package/scss/README.md +13 -1
  47. package/scss/_breakpoint.scss +69 -26
  48. package/scss/_button.scss +148 -57
  49. package/scss/_color.scss +46 -28
  50. package/scss/_cssvar.scss +103 -12
  51. package/scss/_element.scss +84 -67
  52. package/scss/_index.scss +0 -3
  53. package/scss/_layout.scss +57 -26
  54. package/scss/_path.scss +2 -2
  55. package/scss/_selector.scss +20 -11
  56. package/scss/_typography.scss +115 -82
  57. package/scss/_units.scss +14 -13
  58. package/scss/_utils.scss +280 -18
  59. package/scss/base/_color.scss +2 -1
  60. package/scss/base/_elements.scss +61 -35
  61. package/scss/base/_index.scss +60 -23
  62. package/scss/base/_keyframes.scss +115 -16
  63. package/scss/base/_layout.scss +10 -6
  64. package/scss/base/_normalize.scss +6 -122
  65. package/scss/base/_print.scss +49 -0
  66. package/scss/base/_root.scss +28 -0
  67. package/scss/base/_typography.scss +4 -1
  68. package/scss/components/_accordion.scss +217 -0
  69. package/scss/components/_adaptive-spacing.scss +148 -0
  70. package/scss/components/_badge.scss +17 -14
  71. package/scss/components/_button-verbose.scss +138 -0
  72. package/scss/components/_button.scss +9 -4
  73. package/scss/components/_callout.scss +175 -0
  74. package/scss/components/_captioned-figure.scss +173 -0
  75. package/scss/components/_card-grid.scss +75 -0
  76. package/scss/components/_card.scss +420 -0
  77. package/scss/components/_css-icon.scss +433 -0
  78. package/scss/{_grid.scss → components/_data-grid.scss} +100 -68
  79. package/scss/components/_data-table.scss +180 -0
  80. package/scss/components/_fill-context.scss +20 -22
  81. package/scss/components/_flipcard-grid.scss +66 -0
  82. package/scss/components/_flipcard.scss +304 -0
  83. package/scss/components/_form-theme.scss +633 -0
  84. package/scss/components/_hero.scss +183 -0
  85. package/scss/components/_horizontal-rule.scss +51 -0
  86. package/scss/components/_image-grid.scss +71 -0
  87. package/scss/components/_index.scss +276 -38
  88. package/scss/components/_links.scss +1 -1
  89. package/scss/components/_list-lines.scss +14 -3
  90. package/scss/components/_list-ordered.scss +3 -1
  91. package/scss/components/_list-unordered.scss +3 -1
  92. package/scss/components/_menu-stack.scss +245 -0
  93. package/scss/components/_modal.scss +495 -0
  94. package/scss/components/_nav-strip.scss +148 -0
  95. package/scss/components/_overlay-section.scss +122 -0
  96. package/scss/components/_pager.scss +168 -0
  97. package/scss/components/_placeholder-block.scss +121 -0
  98. package/scss/components/_popover.scss +263 -0
  99. package/scss/components/_pull-quote.scss +111 -0
  100. package/scss/components/_ratio-box.scss +64 -0
  101. package/scss/components/_rule.scss +12 -9
  102. package/scss/components/_scroll-slider.scss +204 -0
  103. package/scss/components/_skip-link.scss +92 -0
  104. package/scss/components/_slider.scss +241 -0
  105. package/scss/components/_spoke-spinner.scss +193 -0
  106. package/scss/components/_tabs.scss +179 -0
  107. package/scss/components/_tag.scss +142 -0
  108. package/scss/components/_tile-button.scss +131 -0
  109. package/scss/components/_tile-grid-overlay.scss +132 -0
  110. package/scss/components/_tile-grid.scss +172 -0
  111. package/scss/components/_vignette.scss +65 -0
  112. package/scss/components/_wysiwyg.scss +94 -0
  113. package/scss/helpers/_color.scss +1 -0
  114. package/scss/helpers/_display.scss +2 -1
  115. package/scss/helpers/_index.scss +45 -22
  116. package/scss/helpers/_print.scss +20 -43
  117. package/scss/helpers/_typography.scss +3 -0
  118. package/scss/helpers/_units.scss +10 -13
  119. package/scss/helpers/_utilities.scss +5 -1
  120. package/scss/stylesheets/base-styles.scss +7 -0
  121. package/scss/stylesheets/component-styles.scss +7 -0
  122. package/scss/stylesheets/helper-styles.scss +7 -0
  123. package/types/events/index.d.ts +1 -1
  124. package/types/events/index.d.ts.map +1 -1
  125. package/types/index.d.ts +2 -2
  126. package/types/{helpers/css-breakpoint.d.ts → ui/breakpoints.d.ts} +3 -3
  127. package/types/ui/breakpoints.d.ts.map +1 -0
  128. package/types/ui/collapsible.d.ts +67 -0
  129. package/types/ui/collapsible.d.ts.map +1 -0
  130. package/types/ui/dialog.d.ts +42 -0
  131. package/types/ui/dialog.d.ts.map +1 -0
  132. package/types/ui/flipcard.d.ts +8 -1
  133. package/types/ui/flipcard.d.ts.map +1 -1
  134. package/types/ui/grid.d.ts +0 -11
  135. package/types/ui/grid.d.ts.map +1 -1
  136. package/types/ui/index.d.ts +23 -0
  137. package/types/ui/index.d.ts.map +1 -0
  138. package/types/ui/modal-builder.d.ts +54 -0
  139. package/types/ui/modal-builder.d.ts.map +1 -0
  140. package/types/ui/overflow-scroller-pager.d.ts +1 -1
  141. package/types/ui/overflow-scroller-pager.d.ts.map +1 -1
  142. package/types/ui/overflow-scroller.d.ts +3 -1
  143. package/types/ui/overflow-scroller.d.ts.map +1 -1
  144. package/types/ui/page.d.ts +5 -0
  145. package/types/ui/page.d.ts.map +1 -0
  146. package/types/ui/popover.d.ts +40 -0
  147. package/types/ui/popover.d.ts.map +1 -0
  148. package/types/ui/print-details.d.ts +10 -0
  149. package/types/ui/print-details.d.ts.map +1 -0
  150. package/types/ui/print.d.ts +10 -0
  151. package/types/ui/print.d.ts.map +1 -0
  152. package/types/ui/programmatic-modal.d.ts +19 -1
  153. package/types/ui/programmatic-modal.d.ts.map +1 -1
  154. package/types/ui/proxy-click.d.ts +18 -0
  155. package/types/ui/proxy-click.d.ts.map +1 -0
  156. package/types/ui/resizer.d.ts +1 -1
  157. package/types/ui/resizer.d.ts.map +1 -1
  158. package/types/ui/scroll-slider.d.ts +13 -0
  159. package/types/ui/scroll-slider.d.ts.map +1 -0
  160. package/types/ui/scrollpoint.d.ts +133 -0
  161. package/types/ui/scrollpoint.d.ts.map +1 -0
  162. package/types/ui/slider.d.ts +14 -2
  163. package/types/ui/slider.d.ts.map +1 -1
  164. package/types/ui/tabs.d.ts +22 -0
  165. package/types/ui/tabs.d.ts.map +1 -1
  166. package/types/ui/theme-toggle.d.ts +14 -0
  167. package/types/ui/theme-toggle.d.ts.map +1 -0
  168. package/types/ui/tooltip.d.ts +92 -10
  169. package/types/ui/tooltip.d.ts.map +1 -1
  170. package/types/utils/{logger.d.ts → class-logger.d.ts} +1 -1
  171. package/types/utils/class-logger.d.ts.map +1 -0
  172. package/types/utils/dom.d.ts +48 -0
  173. package/types/utils/dom.d.ts.map +1 -0
  174. package/types/utils/file-save.d.ts +64 -0
  175. package/types/utils/file-save.d.ts.map +1 -0
  176. package/types/utils/floating-ui.d.ts +19 -0
  177. package/types/utils/floating-ui.d.ts.map +1 -0
  178. package/types/utils/id.d.ts +10 -0
  179. package/types/utils/id.d.ts.map +1 -0
  180. package/types/utils/index.d.ts +9 -0
  181. package/types/utils/index.d.ts.map +1 -0
  182. package/types/utils/pause-youtube-video.d.ts.map +1 -0
  183. package/js/helpers/file-save.js +0 -52
  184. package/js/helpers/scrollbar-width-property.js +0 -14
  185. package/project.todo +0 -22
  186. package/scss/_calculate.scss +0 -64
  187. package/scss/_utility.scss +0 -12
  188. package/types/helpers/css-breakpoint.d.ts.map +0 -1
  189. package/types/helpers/file-save.d.ts +0 -17
  190. package/types/helpers/file-save.d.ts.map +0 -1
  191. package/types/helpers/node-data-manager.d.ts +0 -45
  192. package/types/helpers/node-data-manager.d.ts.map +0 -1
  193. package/types/helpers/pause-youtube-video.d.ts.map +0 -1
  194. package/types/helpers/scrollbar-width-property.d.ts +0 -11
  195. package/types/helpers/scrollbar-width-property.d.ts.map +0 -1
  196. package/types/ui/modals.d.ts +0 -27
  197. package/types/ui/modals.d.ts.map +0 -1
  198. package/types/utils/logger.d.ts.map +0 -1
  199. package/vite.config.js +0 -36
  200. /package/{js/deprecated → deprecated/js}/doc-ready.js +0 -0
  201. /package/{js/deprecated → deprecated/js}/jquery-prototypes.js +0 -0
  202. /package/{js/deprecated → deprecated/js}/mini-collapsible-popper-positioning.js +0 -0
  203. /package/{js/deprecated → deprecated/js}/mini-collapsible.js +0 -0
  204. /package/{js/helpers → deprecated/js}/node-data-manager.js +0 -0
  205. /package/{js/deprecated → deprecated/js}/script-loader.js +0 -0
  206. /package/{js/deprecated → deprecated/js}/waypoints/README.md +0 -0
  207. /package/{js/deprecated → deprecated/js}/waypoints/anchor-menu.js +0 -0
  208. /package/{js/deprecated → deprecated/js}/waypoints/element-waypoint.js +0 -0
  209. /package/{js/deprecated → deprecated/js}/waypoints/examples/page-link-menu.md +0 -0
  210. /package/{js/deprecated → deprecated/js}/waypoints/state-in-attribute.js +0 -0
  211. /package/types/{helpers → utils}/pause-youtube-video.d.ts +0 -0
package/js/ui/tooltip.js CHANGED
@@ -1,85 +1,286 @@
1
1
  /**
2
2
  * @module ui/tooltip
3
3
  */
4
- // =============================================================================
5
- // Tooltip
6
- // =============================================================================
7
4
 
8
- // Version: 1.0.1
5
+ import { getName as getEventName } from "../events/index.js";
6
+ import { createFloatingUi } from "../utils/floating-ui.js";
7
+ import { createElementFromHtml } from "@ulu/utils/browser/dom.js";
8
+ import { logError } from "../utils/class-logger.js";
9
+ import { getDatasetOptionalJson } from "../utils/dom.js";
10
+ import { newId, ensureId } from "../utils/id.js";
9
11
 
10
- // Description: Adds a single tooltip div to bottom of document to be used to
11
- // show text/simple markup of mouse hover or focus
12
+ const attrs = {
13
+ trigger: "data-ulu-tooltip",
14
+ init: "data-ulu-init",
15
+ body: "data-ulu-tooltip-display-body",
16
+ arrow: "data-ulu-tooltip-arrow"
17
+ };
18
+ const attrSelector = key => `[${ attrs[key] }]`;
19
+ const attrSelectorInitial = key => `${ attrSelector(key) }:not([${ attrs.init }])`;
20
+ /**
21
+ * Initialize default popover
22
+ */
23
+ export function init() {
24
+ document.addEventListener(getEventName("pageModified"), setup);
25
+ setup();
26
+ }
27
+
28
+ /**
29
+ * Query all popovers on current page and set them up
30
+ * - Use this manually if needed
31
+ * - Won't setup a popover more than once
32
+ */
33
+ export function setup() {
34
+ const triggers = document.querySelectorAll(attrSelectorInitial("trigger"));
35
+ triggers.forEach(setupTrigger);
36
+ }
12
37
 
13
- import { logError } from "../utils/logger.js";
14
- import { createPopper } from '@popperjs/core';
38
+ export function setupTrigger(trigger) {
39
+ const passed = getDatasetOptionalJson(trigger, "uluTooltip");
40
+ const options = typeof passed === "object" ? passed : {};
41
+ if (typeof passed === "string") {
42
+ options.content = passed;
43
+ }
44
+ return new Tooltip({ trigger }, options);
45
+ }
15
46
 
16
- const ATTR_DESC = "aria-describedby";
17
- const popperOptions = {
18
- placement: "auto",
19
- strategy: 'fixed',
20
- modifiers: [
21
- {
22
- name: 'eventListeners',
23
- enabled: false
47
+ /**
48
+ * Tooltip
49
+ * - Provides basic tooltip functionality
50
+ * - Uses floating UI for positioning
51
+ */
52
+ export class Tooltip {
53
+ /**
54
+ * Defaults options
55
+ */
56
+ static defaults = {
57
+ /**
58
+ * Should the tooltip and content be linked accessibly
59
+ * - Note tooltips can only apply to interactive elements! (ie button, input, role="...", etc)
60
+ * @type {Boolean}
61
+ */
62
+ accessible: true,
63
+ /**
64
+ * String/markup to insert into tooltip display
65
+ * @type {String}
66
+ */
67
+ content: null,
68
+ openClass: "is-active",
69
+ contentClass: "",
70
+ isHtml: false,
71
+ /**
72
+ * Pull content from pre-existing content on page
73
+ * @type {String|Node}
74
+ */
75
+ fromElement: null,
76
+ /**
77
+ * If used on a link that is an anchor link it will display the content of the anchor like fromElement
78
+ */
79
+ fromAnchor: false,
80
+ /**
81
+ * Move the content to the bottom of the document
82
+ * @type {Boolean}
83
+ */
84
+ endOfDocument: true,
85
+ /**
86
+ * Events to show tooltip on
87
+ * @type {Array.<String>}
88
+ */
89
+ showEvents: ["pointerenter", "focus"],
90
+ /**
91
+ * Events to hide tooltip on
92
+ * @type {Array.<String>}
93
+ */
94
+ hideEvents: ["pointerleave", "blur"],
95
+ /**
96
+ * Delay when using the directive
97
+ * @type {Number}
98
+ */
99
+ delay: 500,
100
+ /**
101
+ * Template for the content display
102
+ */
103
+ template(_config) {
104
+ return `
105
+ <div class="popover popover--tooltip">
106
+ <div class="popover__inner" ${ attrs.body }>
107
+ </div>
108
+ <span class="popover__arrow" data-ulu-tooltip-arrow></span>
109
+ </div>
110
+ `;
24
111
  },
25
- {
26
- name: 'preventOverflow',
27
- enabled: true,
28
- options: {
29
- mainAxis: true
112
+ /**
113
+ * Callback when tooltip is shown or hidden
114
+ * @type {Function}
115
+ */
116
+ onChange(_ctx) {
117
+ // do something
118
+ }
119
+ };
120
+ static defaultFloatingOptions = {
121
+ // strategy: "fixed"
122
+ };
123
+ constructor(elements, userOptions, floatingOptions) {
124
+ const { trigger } = elements;
125
+ if (!trigger) {
126
+ logError(this, "missing required trigger");
127
+ return;
128
+ }
129
+ this.options = Object.assign({}, Tooltip.defaults, userOptions);
130
+ this.floatingOptions = Object.assign({}, Tooltip.defaultFloatingOptions, floatingOptions);
131
+ this.elements = { ...elements };
132
+ this.handlers = {};
133
+ this.isOpen = false;
134
+ ensureId(trigger);
135
+ this.setup();
136
+ }
137
+ setup() {
138
+ this.createContentElement();
139
+ this.attachHandlers();
140
+ this.setupAccessibility();
141
+ }
142
+ setupAccessibility() {
143
+ const { trigger, content } = this.elements;
144
+ const { accessible } = this.options;
145
+ if (!accessible) return;
146
+ trigger.setAttribute("aria-describedby", content.id);
147
+ }
148
+ destroy() {
149
+ this.destroyHandlers();
150
+ this.destroyDisplay();
151
+ }
152
+ getInnerContent() {
153
+ const { fromElement, content, isHtml, fromAnchor } = this.options;
154
+ if (content) {
155
+ return content;
156
+ } else if (fromElement || fromAnchor) {
157
+ const element = fromAnchor ? this.getAnchorElement() : document.querySelector(fromElement);
158
+ if (element) {
159
+ return isHtml ? element.innerHTML : element.innerText;
160
+ } else {
161
+ return "";
30
162
  }
31
- },
32
- // Arrow
33
- {
34
- name: 'offset',
35
- options: {
36
- offset: [ 0, 10 ],
37
- },
163
+ } else {
164
+ logError(this, "Could not resolve inner content");
38
165
  }
39
- ]
40
- };
41
-
42
- export default class Tooltip {
43
- static defaults = {
44
- namespace: "Tooltip",
45
- describedBy: false,
46
- arrowSize: 10,
47
- classes: []
48
- }
49
- constructor(context, markup, config) {
50
- if (!context) {
51
- logError(this, 'Missing context element');
166
+ }
167
+ getAnchorElement() {
168
+ const { trigger } = this.elements;
169
+ const { href } = trigger;
170
+ const id = href ? href.split("#")[1] : null;
171
+ const element = id ? document.getElementById(id) : null;
172
+ if (!element) {
173
+ console.error("Unable to get 'fromAnchor' element", trigger);
52
174
  }
53
- this.options = Object.assign({}, Tooltip.defaults, config);
54
- this.context = context;
55
- this.element = this.create(markup);
56
- createPopper(context, this.element, popperOptions);
57
- }
58
- create(markup) {
59
- const { namespace } = this.options;
60
- const element = document.createElement("div");
61
-
62
- element.id = namespace + "--" + Date.now();
63
- element.innerHTML = markup;
64
- element.classList.add(namespace);
65
- element.classList.add(...this.options.classes);
66
-
67
- const arrow = document.createElement("div");
68
- arrow.setAttribute("data-popper-arrow", "");
175
+ return element;
176
+ }
177
+ createContentElement() {
178
+ const { options } = this;
179
+ const content = createElementFromHtml(options.template(options));
180
+ const body = content.querySelector(attrSelector("body"));
181
+ const innerContent = this.getInnerContent();
182
+ if (options.isHtml) {
183
+ body.innerHTML = innerContent;
184
+ } else {
185
+ body.textContent = innerContent;
186
+ }
187
+ content.id = newId();
188
+ if (options.contentClass) {
189
+ content.classList.add(options.contentClass);
190
+ }
191
+
192
+ this.elements.content = content;
193
+ this.elements.contentArrow = content.querySelector(attrSelector("arrow"));
194
+ document.body.appendChild(content);
195
+ }
196
+ attachHandlers() {
197
+ const { trigger } = this.elements;
198
+ const { showEvents, hideEvents, delay } = this.options;
199
+ let tid = null;
200
+
201
+ const onShow = (event) => {
202
+ if (tid) return;
203
+ tid = setTimeout(() => {
204
+ this.show(event);
205
+ clearTimeout(tid);
206
+ }, delay);
207
+ };
69
208
 
70
- if (this.options.describedBy) {
71
- this.context.setAttribute(ATTR_DESC, element.id);
209
+ const onHide = (event) => {
210
+ if (tid) {
211
+ clearTimeout(tid);
212
+ tid = null;
213
+ }
214
+ this.hide(event);
215
+ };
216
+ const onDocumentKeydown = (event) => {
217
+ if (event.key === "Escape") {
218
+ this.hide(event);
219
+ }
220
+ };
221
+ showEvents.forEach(name => {
222
+ trigger.addEventListener(name, onShow);
223
+ });
224
+ hideEvents.forEach(name => {
225
+ trigger.addEventListener(name, onHide);
226
+ });
227
+ document.addEventListener("keydown", onDocumentKeydown);
228
+ this.handlers = { onShow, onHide, onDocumentKeydown };
229
+ }
230
+ destroyHandlers() {
231
+ const { trigger } = this;
232
+ const { onShow, onHide, onDocumentKeydown } = this.handlers;
233
+ const { showEvents, hideEvents } = this.options;
234
+ if (onShow) {
235
+ showEvents.forEach(name => {
236
+ trigger.removeEventListener(name, onShow);
237
+ });
238
+ }
239
+ if (onHide) {
240
+ hideEvents.forEach(name => {
241
+ trigger.removeEventListener(name, onHide);
242
+ });
243
+ }
244
+ if (onDocumentKeydown) {
245
+ document.removeEventListener("keydown", onDocumentKeydown);
72
246
  }
73
- this.inPage = true;
74
- element.appendChild(arrow);
75
- return document.body.appendChild(element);
76
247
  }
77
- destroy() {
78
- if (this.inPage) {
79
- document.body.removeChild(this.element);
248
+ setState(isOpen, event) {
249
+ const ctx = {
250
+ instance: this,
251
+ isOpen,
252
+ event
253
+ };
254
+ const { trigger, content } = this.elements;
255
+ const { openClass } = this.options;
256
+ const setClass = el => el.classList[isOpen ? "add" : "remove"](openClass);
257
+ // trigger.setAttribute("aria-expanded", isOpen ? "true" : "false");
258
+ setClass(trigger);
259
+ setClass(content);
260
+ this.isOpen = isOpen;
261
+ this.options.onChange(ctx);
262
+ trigger.dispatchEvent(this.createEvent("change", ctx));
263
+ this.destroyFloatingInstance();
264
+ if (isOpen) {
265
+ this.createFloatingInstance();
80
266
  }
81
- if (this.options.describedBy) {
82
- this.context.removeAttribute(ATTR_DESC);
267
+ }
268
+ createEvent(name, detail) {
269
+ return new CustomEvent(getEventName("tooltip:" + name), { detail });
270
+ }
271
+ createFloatingInstance() {
272
+ this.floatingCleanup = createFloatingUi(this.elements, this.floatingOptions);
273
+ }
274
+ destroyFloatingInstance() {
275
+ if (this.floatingCleanup) {
276
+ this.floatingCleanup();
277
+ this.floatingCleanup = null;
83
278
  }
84
279
  }
280
+ show(event) {
281
+ this.setState(true, event);
282
+ }
283
+ hide(event) {
284
+ this.setState(false, event);
285
+ }
85
286
  }
@@ -1,6 +1,7 @@
1
1
  /**
2
- * @module utils/logger
2
+ * @module utils/class-logger
3
3
  */
4
+
4
5
  // Goal: minimzing console conditions for nessasary production log statements
5
6
 
6
7
  /**
@@ -18,17 +19,17 @@ const hasConsole = "console" in window;
18
19
  // If no context output only if config (global) debug is enabled
19
20
  function allow(context) {
20
21
  return hasConsole && config.debug && (context?.debug || context == null);
21
- };
22
+ }
22
23
  function getName(context) {
23
24
  return typeof context === "object" && context?.constructor?.name;
24
- };
25
+ }
25
26
  function output(method, context, messages) {
26
27
  const label = getName(context) || "Logger";
27
28
  console[method](label, ...messages);
28
29
  if (config.outputContext) {
29
- console.log('Context:\n', context);
30
+ console.log("Context:\n", context);
30
31
  }
31
- };
32
+ }
32
33
 
33
34
  /**
34
35
  * Changes to make to configuration
@@ -0,0 +1,122 @@
1
+ /**
2
+ * @module utils/dom
3
+ */
4
+
5
+
6
+ export const regexJsonString = /^[{\[][\s\S]*[}\]]$/;
7
+
8
+ /**
9
+ * Get an elements JSON dataset value
10
+ * @param {Node} element
11
+ * @param {String} key key in dataset object for element
12
+ * @returns {Object} Empty object or JSON object from dataset
13
+ */
14
+ export function getDatasetJson(element, key) {
15
+ const passed = element.dataset[key];
16
+ try {
17
+ return JSON.parse(passed);
18
+ } catch (error) {
19
+ console.error(`Error getting JSON from dataset (${ key }) -- "${ passed }"\n`, element, error);
20
+ return {};
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Get an elements JSON dataset value that could potentially just be a single string
26
+ * - If JSON it will return the object else it will return the value directly
27
+ * @param {Node} element
28
+ * @param {String} key key in dataset object for element
29
+ * @returns {Object|String} JSON object or current dataset value (string or empty string if no value)
30
+ */
31
+ export function getDatasetOptionalJson(element, key) {
32
+ const passed = element.dataset[key];
33
+ if (passed && regexJsonString.test(passed.trim())) {
34
+ return getDatasetJson(element, key);
35
+ } else {
36
+ return passed;
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Check if a pointer event x/y was outside an elements bounding box
42
+ */
43
+ export function wasClickOutside(element, event) {
44
+ const rect = element.getBoundingClientRect();
45
+ return (event.clientY < rect.top || // above
46
+ event.clientY > rect.top + rect.height || // below
47
+ event.clientX < rect.left || // left side
48
+ event.clientX > rect.left + rect.width); // right side
49
+ }
50
+
51
+
52
+ /**
53
+ * Sets up the positional classes that would come from the equal
54
+ * height module. Needs to be rerun by user when layout changes
55
+ * or new instances are added to the screen
56
+ * - Used for gutter crops
57
+ * - Used for rule placement
58
+ * - **Devs** Remember that default classes should match sass defaults
59
+ * @param {Node} parent The grid parent <data-grid="">
60
+ * @param {Object} classes Override the default equal heights classes
61
+ */
62
+ export function setPositionClasses(parent, classes = {
63
+ columnFirst: 'position-column-first',
64
+ columnLast: 'position-column-last',
65
+ rowFirst: 'position-row-first',
66
+ rowLast: 'position-row-last'
67
+ }) {
68
+ const children = [...parent.children];
69
+ const rows = [];
70
+ let lastY;
71
+ // Check element against last
72
+ // If they don't match it's a new row create a new array
73
+ // Then push into the last array in the rows array
74
+ children.forEach((child) => {
75
+ const y = child.getBoundingClientRect().y;
76
+ if (lastY !== y) rows.push([]);
77
+ rows[rows.length - 1].push(child);
78
+ lastY = y;
79
+ child.classList.remove(...Object.values(classes)); // Remove previously set classes
80
+ });
81
+ // Apply Classes
82
+ rows.forEach((row, index) => {
83
+ if (index === 0)
84
+ row.forEach(child => child.classList.add(classes.rowFirst));
85
+ if (index == rows.length - 1)
86
+ row.forEach(child => child.classList.add(classes.rowLast));
87
+
88
+ row.forEach((child, childIndex) => {
89
+ if (childIndex === 0)
90
+ child.classList.add(classes.columnFirst);
91
+ if (childIndex == row.length - 1)
92
+ child.classList.add(classes.columnLast);
93
+ });
94
+ });
95
+ }
96
+
97
+ /**
98
+ * Resolve a target to Element
99
+ * @param {String|Node} target The selector or node/element
100
+ * @param {Object} context [document] The context to query possible selectors from
101
+ */
102
+ export function getElement(target, context = document) {
103
+ if (typeof target === "string") {
104
+ return context.querySelector(target);
105
+ } else if (target instanceof Element) {
106
+ return target;
107
+ } else {
108
+ console.warn("Unable to getElement()", target);
109
+ return null;
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Sets a CSS custom property equal to the scrollbar width
115
+ * @param {Node} element The element that is the child of a scrollabel container
116
+ * @param {Node} container The container that can be scrolled
117
+ * @param {Stirng} propName Custom property to set
118
+ */
119
+ export function addScrollbarProperty(element = document.body, container = window, propName = "--ulu-scrollbar-width") {
120
+ const scrollbarWidth = container.innerWidth - element.clientWidth;
121
+ element.style.setProperty(propName, `${ scrollbarWidth }px`);
122
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * @module utils/file-save
3
+ */
4
+
5
+ /**
6
+ * Options
7
+ * @typedef {Object} FileSaveOptions
8
+ * @property {String} filename Filename for blob when creating a link (ie createLink) [default "filesave-file.txt"]
9
+ * @property {String} type Filename for blob when creating a link (ie createLink) [default "text/plain;charset=utf-8"]
10
+ */
11
+
12
+
13
+ /**
14
+ * Simple script that is useful for testing
15
+ * - Make a file
16
+ * - Create a URL to it
17
+ * - Gives utility function to create a link to the file (for front end)
18
+ * - Gives method to destroy the file when no longer needed
19
+ * - User can redefine the program by passing options object matching props.
20
+ * Limited to new browsers that support Blob(), also user preferences or type of browser may limit access to Blob functionality
21
+ */
22
+ export class FileSave {
23
+ static defaults = {
24
+ filename: "filesave-file.txt",
25
+ type: "text/plain;charset=utf-8"
26
+ }
27
+ /**
28
+ * @param {*} data Data to put in blob file
29
+ * @param {FileSaveOptions} options Options for file, see defaults (ie. type, filename)
30
+ */
31
+ constructor(data, options) {
32
+ this.options = Object.assign({}, FileSave.defaults, options);
33
+ this.data = data;
34
+ this.blob = new Blob([data], { type: this.options.type });
35
+ this.url = URL.createObjectURL(this.blob);
36
+ }
37
+ /**
38
+ * Remove the blob url
39
+ */
40
+ destroy() {
41
+ return URL.revokeObjectURL(this.url);
42
+ }
43
+ /**
44
+ * Get the blob url
45
+ */
46
+ getUrl() {
47
+ return this.url;
48
+ }
49
+ /**
50
+ * Create link element with blob as href
51
+ * @param {String} text The text to put in the link
52
+ */
53
+ createLink(text) {
54
+ const link = document.createElement('a');
55
+ const textNode = document.createTextNode(text);
56
+ link.setAttribute('download', this.options.filename);
57
+ link.setAttribute('href', this.url);
58
+ link.appendChild(textNode);
59
+ return link;
60
+ }
61
+ /**
62
+ * Check for Compatibility (optional, implement on user side)
63
+ */
64
+ static isBrowserSupported() {
65
+ return "FileReader" in window;
66
+ }
67
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * @module utils/floating-ui
3
+ */
4
+
5
+ import {
6
+ computePosition,
7
+ autoUpdate,
8
+ offset,
9
+ inline,
10
+ flip,
11
+ shift,
12
+ arrow,
13
+ } from "@floating-ui/dom";
14
+
15
+ export const defaults = {
16
+ strategy: "absolute",
17
+ placement: "bottom",
18
+ inline: false,
19
+ offset: {
20
+ mainAxis: 16
21
+ },
22
+ shift: true,
23
+ flip: true,
24
+ arrow: true, // Options for arrow (not element)
25
+ };
26
+
27
+ /**
28
+ *
29
+ * @param {Object} elements Elements (trigger, content, and optionally contentArrow)
30
+ * @param {*} options Configuration options for floatingUi
31
+ * @returns {Function} floating cleanup function call when no longer needed
32
+ */
33
+ export function createFloatingUi(elements, config) {
34
+ const options = Object.assign({}, defaults, config);
35
+ const { placement, strategy } = options;
36
+ const { trigger, content, contentArrow } = elements;
37
+
38
+ return autoUpdate(trigger, content, () => {
39
+ computePosition(trigger, content, {
40
+ placement,
41
+ strategy,
42
+ middleware: [
43
+ ...addPlugin(inline, options.inline),
44
+ ...addPlugin(offset, options.offset),
45
+ ...addPlugin(flip, options.flip),
46
+ ...addPlugin(shift, options.shift),
47
+ ...addPlugin(arrow, contentArrow && options.arrow, { element: contentArrow }),
48
+ ]
49
+ }).then(data => {
50
+ const { x, y, middlewareData, placement } = data;
51
+ const arrowPos = middlewareData.arrow;
52
+
53
+ // Update computed styles for the content (popover container)
54
+ Object.assign(content.style, {
55
+ left: `${ x }px`,
56
+ top: `${ y }px`
57
+ });
58
+
59
+ // Update placement attribute (used by arrow for theming)
60
+ content.setAttribute("data-placement", placement);
61
+
62
+ // If arrow was enabled, add it's computed styles
63
+ if (arrowPos) {
64
+ Object.assign(contentArrow.style, {
65
+ // position: "absolute",
66
+ left: arrowPos?.x != null ? `${ arrowPos.x }px` : "",
67
+ top: arrowPos?.y != null ? `${ arrowPos.y }px` : "",
68
+ });
69
+ }
70
+ });
71
+ });
72
+ }
73
+
74
+ function addPlugin(plugin, option, overrides = {}) {
75
+ if (!option) {
76
+ return [];
77
+ // If object add it as options, else just enable without options
78
+ } else if (typeof option === "object") {
79
+ return [plugin({ ...option, ...overrides })];
80
+ } else {
81
+ return [plugin(overrides)];
82
+ }
83
+ }
package/js/utils/id.js ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @module utils/id
3
+ */
4
+
5
+ let idCount = 0;
6
+
7
+ /**
8
+ * Create new uid
9
+ */
10
+ export function newId() {
11
+ return `ulu-uid-${ ++idCount }`;
12
+ }
13
+
14
+ /**
15
+ * Sets an ID if element doesn't have one vie newUid
16
+ * @param {Node} element Element to make sure has an id
17
+ */
18
+ export function ensureId(element) {
19
+ if (!element.id) {
20
+ element.id = newId();
21
+ }
22
+ }
@@ -0,0 +1,7 @@
1
+ export * as classLogger from "./class-logger.js";
2
+ export * as dom from "./dom.js";
3
+ export * as floatingUi from "./floating-ui.js";
4
+ export * as id from "./id.js";
5
+ export * as index from "./index.js";
6
+ export * as pauseYoutubeVideo from "./pause-youtube-video.js";
7
+ export * as fileSave from "./file-save.js";
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @module helpers/pause-youtube-video
2
+ * @module utils/pause-youtube-video
3
3
  */
4
4
 
5
5
  // Version: 1.0.4