@ulu/frontend 0.1.0-beta.7 → 0.1.0-beta.71

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 (286) hide show
  1. package/CHANGELOG.md +483 -2
  2. package/README.dev.md +3 -3
  3. package/README.md +14 -4
  4. package/dist/ulu-frontend.min.css +1 -1
  5. package/dist/ulu-frontend.min.js +28 -27
  6. package/docs-dev/assets/main.js +8290 -635
  7. package/docs-dev/assets/placeholder/icon-calendar.svg +1 -0
  8. package/docs-dev/assets/placeholder/icon-check.svg +1 -0
  9. package/docs-dev/assets/style.css +789 -338
  10. package/docs-dev/changelog/index.html +6438 -0
  11. package/docs-dev/demos/accordion/index.html +850 -328
  12. package/docs-dev/demos/badge/index.html +5265 -0
  13. package/docs-dev/demos/basic-hero/index.html +111 -0
  14. package/docs-dev/demos/breakpoints-manager/index.html +5276 -0
  15. package/docs-dev/demos/button/index.html +860 -327
  16. package/docs-dev/demos/button-verbose/index.html +5268 -0
  17. package/docs-dev/demos/callout/index.html +895 -332
  18. package/docs-dev/demos/captioned-figure/index.html +850 -327
  19. package/docs-dev/demos/card/index.html +930 -768
  20. package/docs-dev/demos/card-grid/index.html +5387 -0
  21. package/docs-dev/demos/counter-list/index.html +5270 -0
  22. package/docs-dev/demos/css-icons/index.html +850 -327
  23. package/docs-dev/demos/data-grid/index.html +974 -531
  24. package/docs-dev/demos/data-table/index.html +1024 -368
  25. package/docs-dev/demos/details-group/index.html +5297 -0
  26. package/docs-dev/demos/file-save/index.html +850 -327
  27. package/docs-dev/demos/flipcard/index.html +850 -327
  28. package/docs-dev/demos/form-theme/index.html +868 -358
  29. package/docs-dev/demos/hero/index.html +12 -4
  30. package/docs-dev/demos/image-grid/index.html +12 -4
  31. package/docs-dev/demos/index.html +851 -328
  32. package/docs-dev/demos/list-inline/index.html +850 -327
  33. package/docs-dev/demos/list-lines/index.html +850 -327
  34. package/docs-dev/demos/menu-stack/index.html +884 -346
  35. package/docs-dev/demos/modals/index.html +922 -329
  36. package/docs-dev/demos/nav-strip/index.html +850 -327
  37. package/docs-dev/demos/overlay-section/index.html +939 -346
  38. package/docs-dev/demos/popovers/index.html +1112 -347
  39. package/docs-dev/demos/print/index.html +850 -327
  40. package/docs-dev/demos/pull-quote/index.html +850 -327
  41. package/docs-dev/demos/rule/index.html +863 -328
  42. package/docs-dev/demos/scroll-slider/index.html +72 -106
  43. package/docs-dev/demos/scrollpoints/index.html +851 -328
  44. package/docs-dev/demos/slider/index.html +12 -4
  45. package/docs-dev/demos/spoke-spinner/index.html +850 -327
  46. package/docs-dev/demos/{list-inline.1 → sticky-list}/index.html +883 -357
  47. package/docs-dev/demos/tabs/index.html +886 -327
  48. package/docs-dev/demos/tag/index.html +850 -327
  49. package/docs-dev/demos/theme-toggle/index.html +5309 -0
  50. package/docs-dev/demos/tile-grid-overlay/index.html +12 -4
  51. package/docs-dev/demos/tiles/index.html +850 -327
  52. package/docs-dev/demos/tooltip/index.html +850 -327
  53. package/docs-dev/guide/building-stylesheet/index.html +850 -327
  54. package/docs-dev/guide/developing-ulu-scss-module/index.html +850 -327
  55. package/docs-dev/guide/index.html +850 -327
  56. package/docs-dev/index.html +850 -327
  57. package/docs-dev/javascript/events/index.html +847 -326
  58. package/docs-dev/javascript/index.html +850 -327
  59. package/docs-dev/javascript/settings/index.html +5430 -0
  60. package/docs-dev/javascript/ui-breakpoints/index.html +862 -341
  61. package/docs-dev/javascript/ui-collapsible/index.html +847 -326
  62. package/docs-dev/javascript/ui-details-group/index.html +5352 -0
  63. package/docs-dev/javascript/ui-dialog/index.html +879 -343
  64. package/docs-dev/javascript/ui-flipcard/index.html +908 -331
  65. package/docs-dev/javascript/ui-grid/index.html +857 -362
  66. package/docs-dev/javascript/ui-modal-builder/index.html +1027 -386
  67. package/docs-dev/javascript/ui-overflow-scroller/index.html +847 -326
  68. package/docs-dev/javascript/ui-overflow-scroller-pager/index.html +847 -326
  69. package/docs-dev/javascript/ui-page/index.html +847 -326
  70. package/docs-dev/javascript/ui-popover/index.html +855 -338
  71. package/docs-dev/javascript/ui-print/index.html +847 -334
  72. package/docs-dev/javascript/ui-print-details/index.html +847 -326
  73. package/docs-dev/javascript/ui-programmatic-modal/index.html +847 -326
  74. package/docs-dev/javascript/ui-proxy-click/index.html +934 -328
  75. package/docs-dev/javascript/ui-resizer/index.html +847 -326
  76. package/docs-dev/javascript/ui-scroll-slider/index.html +885 -332
  77. package/docs-dev/javascript/ui-scrollpoint/index.html +853 -339
  78. package/docs-dev/javascript/ui-slider/index.html +1043 -331
  79. package/docs-dev/javascript/ui-tabs/index.html +858 -374
  80. package/docs-dev/javascript/ui-theme-toggle/index.html +5440 -0
  81. package/docs-dev/javascript/ui-tooltip/index.html +854 -337
  82. package/docs-dev/javascript/utils-class-logger/index.html +847 -326
  83. package/docs-dev/javascript/utils-css/index.html +5254 -0
  84. package/docs-dev/javascript/utils-dom/index.html +1014 -359
  85. package/docs-dev/javascript/utils-file-save/index.html +847 -326
  86. package/docs-dev/javascript/utils-floating-ui/index.html +847 -326
  87. package/docs-dev/javascript/utils-id/index.html +847 -326
  88. package/docs-dev/javascript/utils-pause-youtube-video/index.html +847 -326
  89. package/docs-dev/javascript/utils-system/index.html +5557 -0
  90. package/docs-dev/sass/base/color/index.html +847 -326
  91. package/docs-dev/sass/base/elements/index.html +847 -326
  92. package/docs-dev/sass/base/index/index.html +847 -326
  93. package/docs-dev/sass/base/index.html +850 -327
  94. package/docs-dev/sass/base/keyframes/index.html +847 -326
  95. package/docs-dev/sass/base/layout/index.html +847 -326
  96. package/docs-dev/sass/base/normalize/index.html +847 -326
  97. package/docs-dev/sass/base/print/index.html +847 -326
  98. package/docs-dev/sass/base/root/index.html +847 -326
  99. package/docs-dev/sass/base/typography/index.html +847 -326
  100. package/docs-dev/sass/components/accordion/index.html +866 -338
  101. package/docs-dev/sass/components/adaptive-spacing/index.html +847 -326
  102. package/docs-dev/sass/components/badge/index.html +869 -337
  103. package/docs-dev/sass/components/basic-hero/index.html +5415 -0
  104. package/docs-dev/sass/components/button/index.html +847 -326
  105. package/docs-dev/sass/components/button-verbose/index.html +933 -337
  106. package/docs-dev/sass/components/callout/index.html +958 -418
  107. package/docs-dev/sass/components/captioned-figure/index.html +970 -334
  108. package/docs-dev/sass/components/card/index.html +939 -346
  109. package/docs-dev/sass/components/card-grid/index.html +847 -326
  110. package/docs-dev/sass/components/counter-list/index.html +5497 -0
  111. package/docs-dev/sass/components/css-icon/index.html +864 -336
  112. package/docs-dev/sass/components/data-grid/index.html +868 -340
  113. package/docs-dev/sass/components/data-table/index.html +1066 -353
  114. package/docs-dev/sass/components/fill-context/index.html +847 -326
  115. package/docs-dev/sass/components/flipcard/index.html +888 -336
  116. package/docs-dev/sass/components/flipcard-grid/index.html +847 -326
  117. package/docs-dev/sass/components/form-theme/index.html +1063 -446
  118. package/docs-dev/sass/components/hero/index.html +903 -334
  119. package/docs-dev/sass/components/horizontal-rule/index.html +847 -326
  120. package/docs-dev/sass/components/image-grid/index.html +847 -326
  121. package/docs-dev/sass/components/index/index.html +860 -336
  122. package/docs-dev/sass/components/index.html +850 -327
  123. package/docs-dev/sass/components/links/index.html +847 -326
  124. package/docs-dev/sass/components/list-inline/index.html +847 -326
  125. package/docs-dev/sass/components/list-lines/index.html +847 -326
  126. package/docs-dev/sass/components/list-ordered/index.html +847 -326
  127. package/docs-dev/sass/components/list-unordered/index.html +847 -326
  128. package/docs-dev/sass/components/menu-stack/index.html +881 -347
  129. package/docs-dev/sass/components/modal/index.html +854 -333
  130. package/docs-dev/sass/components/nav-strip/index.html +855 -334
  131. package/docs-dev/sass/components/overlay-section/index.html +855 -334
  132. package/docs-dev/sass/components/pager/index.html +847 -326
  133. package/docs-dev/sass/components/placeholder-block/index.html +847 -326
  134. package/docs-dev/sass/components/popover/index.html +904 -347
  135. package/docs-dev/sass/components/pull-quote/index.html +859 -338
  136. package/docs-dev/sass/components/ratio-box/index.html +855 -334
  137. package/docs-dev/sass/components/rule/index.html +848 -327
  138. package/docs-dev/sass/components/scroll-slider/index.html +855 -346
  139. package/docs-dev/sass/components/skip-link/index.html +847 -326
  140. package/docs-dev/sass/components/slider/index.html +897 -388
  141. package/docs-dev/sass/components/spoke-spinner/index.html +849 -328
  142. package/docs-dev/sass/components/sticky-list/index.html +5633 -0
  143. package/docs-dev/sass/components/tabs/index.html +872 -336
  144. package/docs-dev/sass/components/tag/index.html +849 -328
  145. package/docs-dev/sass/components/tile-button/index.html +847 -326
  146. package/docs-dev/sass/components/tile-grid/index.html +847 -326
  147. package/docs-dev/sass/components/tile-grid-overlay/index.html +847 -326
  148. package/docs-dev/sass/components/vignette/index.html +861 -334
  149. package/docs-dev/sass/components/wysiwyg/index.html +847 -326
  150. package/docs-dev/sass/core/breakpoint/index.html +931 -358
  151. package/docs-dev/sass/core/button/index.html +847 -326
  152. package/docs-dev/sass/core/color/index.html +1019 -366
  153. package/docs-dev/sass/core/cssvar/index.html +847 -326
  154. package/docs-dev/sass/core/element/index.html +1108 -381
  155. package/docs-dev/sass/core/index.html +847 -326
  156. package/docs-dev/sass/core/layout/index.html +903 -363
  157. package/docs-dev/sass/core/path/index.html +847 -326
  158. package/docs-dev/sass/core/selector/index.html +847 -326
  159. package/docs-dev/sass/core/typography/index.html +847 -326
  160. package/docs-dev/sass/core/units/index.html +857 -330
  161. package/docs-dev/sass/core/utils/index.html +2104 -476
  162. package/docs-dev/sass/helpers/color/index.html +847 -326
  163. package/docs-dev/sass/helpers/display/index.html +848 -327
  164. package/docs-dev/sass/helpers/index/index.html +847 -326
  165. package/docs-dev/sass/helpers/index.html +850 -327
  166. package/docs-dev/sass/helpers/print/index.html +759 -298
  167. package/docs-dev/sass/helpers/typography/index.html +847 -326
  168. package/docs-dev/sass/helpers/units/index.html +847 -326
  169. package/docs-dev/sass/helpers/utilities/index.html +849 -328
  170. package/docs-dev/sass/index.html +850 -327
  171. package/js/index.js +1 -0
  172. package/js/settings.js +95 -0
  173. package/js/ui/breakpoints.js +19 -16
  174. package/js/ui/collapsible.js +8 -1
  175. package/js/ui/details-group.js +112 -0
  176. package/js/ui/dialog.js +90 -42
  177. package/js/ui/dialog.todo +2 -36
  178. package/js/ui/flipcard.js +37 -57
  179. package/js/ui/grid.js +15 -13
  180. package/js/ui/index.js +1 -0
  181. package/js/ui/modal-builder.js +76 -56
  182. package/js/ui/overflow-scroller.js +6 -4
  183. package/js/ui/popover.js +38 -38
  184. package/js/ui/print.js +16 -25
  185. package/js/ui/programmatic-modal.js +9 -3
  186. package/js/ui/proxy-click.js +50 -36
  187. package/js/ui/scroll-slider.js +24 -30
  188. package/js/ui/scrollpoint.js +29 -64
  189. package/js/ui/slider.js +108 -63
  190. package/js/ui/tabs.js +23 -36
  191. package/js/ui/theme-toggle.js +331 -94
  192. package/js/ui/tooltip.js +27 -32
  193. package/js/utils/css.js +13 -0
  194. package/js/utils/dom.js +85 -8
  195. package/js/utils/font-awesome.js +18 -0
  196. package/js/utils/index.js +2 -1
  197. package/js/utils/system.js +154 -0
  198. package/package.json +23 -8
  199. package/scss/README.md +4 -0
  200. package/scss/_breakpoint.scss +38 -4
  201. package/scss/_color.scss +40 -9
  202. package/scss/_element.scss +108 -2
  203. package/scss/_layout.scss +7 -8
  204. package/scss/_units.scss +3 -2
  205. package/scss/_utils.scss +380 -16
  206. package/scss/components/README.todos +14 -0
  207. package/scss/components/_accordion.scss +33 -19
  208. package/scss/components/_badge.scss +23 -4
  209. package/scss/components/_basic-hero.scss +112 -0
  210. package/scss/components/_button-verbose.scss +100 -18
  211. package/scss/components/_callout.scss +125 -78
  212. package/scss/components/_captioned-figure.scss +17 -0
  213. package/scss/components/_card-grid.scss +1 -1
  214. package/scss/components/_card.scss +246 -74
  215. package/scss/components/_counter-list.scss +151 -0
  216. package/scss/components/_css-icon.scss +25 -21
  217. package/scss/components/_data-grid.scss +55 -9
  218. package/scss/components/_data-table.scss +44 -4
  219. package/scss/components/_flipcard.scss +8 -3
  220. package/scss/components/_form-theme.scss +119 -108
  221. package/scss/components/_hero.scss +12 -10
  222. package/scss/components/_index.scss +18 -0
  223. package/scss/components/_menu-stack.scss +42 -26
  224. package/scss/components/_modal.scss +13 -6
  225. package/scss/components/_nav-strip.scss +2 -0
  226. package/scss/components/_overlay-section.scss +2 -5
  227. package/scss/components/_popover.scss +165 -64
  228. package/scss/components/_pull-quote.scss +13 -13
  229. package/scss/components/_ratio-box.scss +2 -5
  230. package/scss/components/_rule.scss +1 -0
  231. package/scss/components/_scroll-slider.scss +1 -5
  232. package/scss/components/_slider.scss +49 -72
  233. package/scss/components/_spoke-spinner.scss +2 -2
  234. package/scss/components/_sticky-list.scss +206 -0
  235. package/scss/components/_tabs.scss +24 -4
  236. package/scss/components/_vignette.scss +3 -5
  237. package/scss/helpers/_display.scss +15 -18
  238. package/scss/helpers/_print.scss +12 -7
  239. package/scss/helpers/_utilities.scss +42 -32
  240. package/types/index.d.ts +1 -0
  241. package/types/settings.d.ts +66 -0
  242. package/types/settings.d.ts.map +1 -0
  243. package/types/ui/breakpoints.d.ts +14 -14
  244. package/types/ui/breakpoints.d.ts.map +1 -1
  245. package/types/ui/collapsible.d.ts.map +1 -1
  246. package/types/ui/details-group.d.ts +38 -0
  247. package/types/ui/details-group.d.ts.map +1 -0
  248. package/types/ui/dialog.d.ts +20 -14
  249. package/types/ui/dialog.d.ts.map +1 -1
  250. package/types/ui/flipcard.d.ts +16 -10
  251. package/types/ui/flipcard.d.ts.map +1 -1
  252. package/types/ui/grid.d.ts +4 -6
  253. package/types/ui/grid.d.ts.map +1 -1
  254. package/types/ui/index.d.ts +2 -1
  255. package/types/ui/modal-builder.d.ts +93 -11
  256. package/types/ui/modal-builder.d.ts.map +1 -1
  257. package/types/ui/overflow-scroller.d.ts +2 -2
  258. package/types/ui/overflow-scroller.d.ts.map +1 -1
  259. package/types/ui/popover.d.ts +6 -7
  260. package/types/ui/popover.d.ts.map +1 -1
  261. package/types/ui/print.d.ts +0 -4
  262. package/types/ui/print.d.ts.map +1 -1
  263. package/types/ui/programmatic-modal.d.ts.map +1 -1
  264. package/types/ui/proxy-click.d.ts +19 -3
  265. package/types/ui/proxy-click.d.ts.map +1 -1
  266. package/types/ui/scroll-slider.d.ts +5 -7
  267. package/types/ui/scroll-slider.d.ts.map +1 -1
  268. package/types/ui/scrollpoint.d.ts +3 -8
  269. package/types/ui/scrollpoint.d.ts.map +1 -1
  270. package/types/ui/slider.d.ts +33 -14
  271. package/types/ui/slider.d.ts.map +1 -1
  272. package/types/ui/tabs.d.ts +6 -8
  273. package/types/ui/tabs.d.ts.map +1 -1
  274. package/types/ui/theme-toggle.d.ts +51 -7
  275. package/types/ui/theme-toggle.d.ts.map +1 -1
  276. package/types/ui/tooltip.d.ts +3 -5
  277. package/types/ui/tooltip.d.ts.map +1 -1
  278. package/types/utils/css.d.ts +11 -0
  279. package/types/utils/css.d.ts.map +1 -0
  280. package/types/utils/dom.d.ts +45 -6
  281. package/types/utils/dom.d.ts.map +1 -1
  282. package/types/utils/font-awesome.d.ts +5 -0
  283. package/types/utils/font-awesome.d.ts.map +1 -0
  284. package/types/utils/index.d.ts +2 -1
  285. package/types/utils/system.d.ts +113 -0
  286. package/types/utils/system.d.ts.map +1 -0
package/js/ui/tabs.js CHANGED
@@ -9,9 +9,7 @@
9
9
  // setting this up to destroy tab interface when ui layout changes?
10
10
 
11
11
  import AriaTablist from "aria-tablist";
12
-
13
- const initAttr = "data-ulu-tablist-init";
14
- const errorHeader = "[data-ulu-tablist] error:";
12
+ import { ComponentInitializer } from "../utils/system.js";
15
13
 
16
14
  /**
17
15
  * Array of current tab instances (exported if you need to interact with them)
@@ -19,13 +17,28 @@ const errorHeader = "[data-ulu-tablist] error:";
19
17
  */
20
18
  export const instances = [];
21
19
 
20
+ /**
21
+ * Tabs Component Initializer
22
+ */
23
+ export const initializer = new ComponentInitializer({
24
+ type: "tabs",
25
+ baseAttribute: "data-ulu-tablist"
26
+ });
27
+
22
28
  /**
23
29
  * Init all instances currently in document
24
- * @param {Object} options Options to serve as defaults
25
30
  */
26
- export function init(options = {}) {
31
+ export function init() {
27
32
  const initial = () => {
28
- initWithin(document, options);
33
+ initializer.init({
34
+ events: ["pageModified"],
35
+ withData: true,
36
+ setup({ element, data, initialize }) {
37
+ setup(element, data);
38
+ initialize();
39
+ }
40
+ });
41
+
29
42
  // Run this on page load, optionally exported for use when page is running
30
43
  instances.forEach(openByCurrentHash);
31
44
  };
@@ -35,22 +48,6 @@ export function init(options = {}) {
35
48
  } else {
36
49
  window.addEventListener("load", initial);
37
50
  }
38
- // Initialize when page updates/changes
39
- document.addEventListener("pageModified", e => initWithin(e.target, options));
40
- }
41
-
42
- /**
43
- * Init all tabs within a certain context
44
- * @param {Node} context Element to init within
45
- * @param {Object} options Options to serve as defaults
46
- */
47
- export function initWithin(context, options = {}) {
48
- if (!context) {
49
- console.warn("Missing context to initWithin, skipping init of tabs");
50
- return;
51
- }
52
- const tablists = context.querySelectorAll(`[data-ulu-tablist]:not([${ initAttr }])`);
53
- tablists.forEach(element => setup(element, options));
54
51
  }
55
52
 
56
53
  /**
@@ -60,17 +57,7 @@ export function initWithin(context, options = {}) {
60
57
  * @return {Object} Instance object
61
58
  */
62
59
  export function setup(element, options = {}) {
63
- let elementOptions = {};
64
-
65
- if (element.dataset.uluTablist) {
66
- try {
67
- elementOptions = JSON.parse(element.dataset.uluTablist);
68
- } catch(e) {
69
- console.error(errorHeader, "(JSON Parse for options)", element);
70
- }
71
- }
72
-
73
- const config = Object.assign({}, options, elementOptions);
60
+ const config = Object.assign({}, options);
74
61
 
75
62
  if (config.vertical) {
76
63
  config.allArrows = true;
@@ -91,8 +78,6 @@ export function setup(element, options = {}) {
91
78
  if (config.equalHeights) {
92
79
  setHeights(element);
93
80
  }
94
-
95
- element.setAttribute(initAttr, "");
96
81
 
97
82
  return instance;
98
83
  }
@@ -150,7 +135,9 @@ function setHeights(element) {
150
135
  if (panel.hidden) {
151
136
  panel.hidden = false;
152
137
  panelHeight = panel.offsetHeight;
153
- panel.hidden = true;
138
+ // This explicity needs "hidden" for aria-tablist (it checks this string value)
139
+ // Will break the initial window push state when using openWithUrlHash
140
+ panel.setAttribute("hidden", "hidden");
154
141
  }
155
142
  return panelHeight;
156
143
  });
@@ -1,129 +1,366 @@
1
- // Progressive Enhancement turns select elements into accessible autocomplete fields
1
+ /**
2
+ * @module ui/theme-toggle
3
+ */
2
4
 
3
- // import { getName } from "@ulu/frontend/js/events/index.js";
5
+ import { ComponentInitializer } from "../utils/system.js";
4
6
  import { getName } from "../events/index.js";
7
+ import { getElements, resolveClasses } from "../utils/dom.js";
8
+ import { hasRequiredProps } from "@ulu/utils/object.js";
5
9
 
6
- const attrs = {
7
- trigger: "data-site-theme-toggle",
8
- icon: "data-site-theme-toggle-icon",
9
- init: "data-site-theme-toggle-init",
10
- };
10
+ /**
11
+ * Theme Toggle Component Initializer
12
+ */
13
+ export const initializer = new ComponentInitializer({
14
+ type: "theme-toggle",
15
+ baseAttribute: "data-ulu-theme-toggle"
16
+ });
11
17
 
12
- const attrSelector = key => `[${ attrs[key] }]`;
13
- const attrSelectorInitial = key => `${ attrSelector(key) }:not([${ attrs.init }])`;
14
-
15
- // @dan change to options and remove options
16
- // add a preferred print theme option
17
- export const options = {
18
- darkTheme: "theme-dark",
19
- lightTheme: "theme-light",
20
- defaultTheme: "dark",
21
- darkIcon: "fa-solid fa-moon",
22
- lightIcon: "fa-solid fa-sun",
18
+ const attrSelectorLabel = initializer.attributeSelector("label");
19
+ const attrSelectorIcon = initializer.attributeSelector("icon");
20
+ const attrRemote = initializer.getAttribute("remote");
21
+ const attrInit = initializer.getAttribute("init");
22
+ const attrState = initializer.getAttribute("state");
23
+
24
+ // Utils for selecting things based on attributes
25
+ const queryRemotes = group => document.querySelectorAll(
26
+ `[${ attrRemote }="${ group }"]`
27
+ );
28
+ const queryRemotesInitial = group => document.querySelectorAll(
29
+ `[${ attrRemote }="${ group }"]:not([${ attrInit }])`
30
+ );
31
+ const requiredToggleProps = ["target"];
32
+ const checkToggleProps = hasRequiredProps(requiredToggleProps);
33
+ const when = (cond, fn) => cond ? fn() : null; // Consider adding as util
34
+
35
+ /**
36
+ * Default Options
37
+ * - Can be overridden using data-attributes
38
+ */
39
+ export const defaults = {
40
+ /**
41
+ * Object of each theme that should be toggle/cycled through
42
+ */
43
+ themes: {
44
+ light: {
45
+ label: "Light",
46
+ value: "light",
47
+ iconClass: "fas fa-moon",
48
+ targetClass: "theme-light",
49
+ mediaQuery: "(prefers-color-scheme: light)"
50
+ },
51
+ dark: {
52
+ label: "Dark",
53
+ iconClass: "fas fa-sun",
54
+ targetClass: "theme-dark",
55
+ mediaQuery: "(prefers-color-scheme: dark)"
56
+ }
57
+ },
58
+ /**
59
+ * Required this is the element(s) that should be changed by a specific toggle
60
+ * - The element should have data-ulu-theme-toggle-target="SOME_IDENTIFIER"
61
+ */
62
+ target: "body",
63
+ /**
64
+ * Optional group to link remote toggles (toggles that follow the main one and can toggle too)
65
+ */
66
+ group: null,
67
+ /**
68
+ * Optional callback to do something when the state changes
69
+ */
70
+ onChange(_ctx) {},
71
+ /**
72
+ * The initial state for this component
73
+ * - May be overridden by saved preference or media query if options are enabled
74
+ */
75
+ initialState: "light",
76
+ /**
77
+ * Check the OS systems user preference via 'preferenceQuery' option
78
+ */
79
+ checkMediaQuery: false,
80
+ /**
81
+ * Will store the preference in local storage so it persists between page loads
82
+ */
83
+ savePreference: false,
84
+ /**
85
+ * The key that will be used to store the preference in local storage
86
+ * - This will be used as prefix in combination with group if defined
87
+ */
88
+ storagePrefix: "ulu-theme-",
89
+ /**
90
+ * Output information to console for debugging
91
+ */
92
+ debug: false
23
93
  };
24
94
 
25
- const body = document.querySelector("[data-site-theme]");
26
- let currentTheme = body.classList.contains(options.darkTheme) ? options.darkTheme : options.lightTheme;
27
- // used to see if machine preference differs from default theme
28
- const defaultThemeInverse = options.defaultTheme === "dark" ? "light" : "dark";
95
+
96
+ // Current default objects (user can override these)
97
+ let currentDefaults = { ...defaults };
98
+
99
+ /**
100
+ * @param {Object} options Change options used as default for dialogs, can then be overridden by data attribute settings on element
101
+ */
102
+ export function setDefaults(options) {
103
+ currentDefaults = Object.assign({}, currentDefaults, options);
104
+ }
29
105
 
30
106
  /**
31
107
  * Initialize everything in document
32
108
  * - This will only initialize elements once, it is safe to call on page changes
33
109
  */
34
110
  export function init() {
35
- // switch to light theme for printing
36
- document.addEventListener(getName("beforePrint"), () => printSetup());
37
- // switch back to original theme after printing
38
- document.addEventListener(getName("afterPrint"), () => printTearDown());
39
- // document.addEventListener(getName("pageModified"), () => setup());
40
- setup();
111
+ initializer.init({
112
+ events: ["pageModified"],
113
+ withData: true,
114
+ setup({ element, data, initialize }) {
115
+ setupToggle(element, data);
116
+ initialize();
117
+ }
118
+ });
41
119
  }
42
120
 
43
- export function setup(context = document) {
44
- const body = context.querySelector("[data-site-theme]");
45
- // Initial theme on load
46
- setupTheme(body);
47
- // Add toggle event listener to buttons
48
- // @daniel add the init attribute
49
- const elements = context.querySelectorAll(attrSelectorInitial("trigger"));
50
- elements.forEach(element => {
51
- element.setAttribute(attrs.init, "");
52
- element.addEventListener("click", () => {
53
- changeTheme(body);
121
+ /**
122
+ * Sets up a single toggle
123
+ * @param {HTMLElement} toggle A toggle to be setup
124
+ */
125
+ export function setupToggle(toggle, userOptions) {
126
+ const options = Object.assign({}, defaults, userOptions);
127
+
128
+ if (!checkToggleProps(options)) {
129
+ console.error(`Missing a required option: ${ requiredToggleProps.join(", ") }`);
130
+ return;
131
+ }
132
+
133
+ const group = options.group;
134
+ const ctx = { toggle, options };
135
+ const initialKey = resolveInitial(options);
136
+
137
+ if (!initialKey) {
138
+ console.error("Unable to resolve initial key");
139
+ return;
140
+ }
141
+
142
+ setState(initialKey, ctx);
143
+
144
+ toggle.addEventListener("click", onToggleClick);
145
+
146
+ // Remotes listeners are attached initially and then we also
147
+ // update them vs toggles which would be updated by the main pageModified
148
+ // event in init
149
+ attachRemotes();
150
+ document.addEventListener(getName("pageModified"), attachRemotes);
151
+
152
+ /**
153
+ * Instance function to get the next theme in cycle
154
+ */
155
+ function toggleState(event) {
156
+ const targets = getElements(options.target);
157
+ const lastKey = targets[0].dataset.uluThemeToggleState;
158
+ const key = getNextThemeKey(lastKey, options);
159
+ if (!key) {
160
+ console.error("Issue getting next theme key");
161
+ return;
162
+ }
163
+ setState(key, { ...ctx, event });
164
+ }
165
+
166
+ /**
167
+ * Handler for click for both toggle and remote toggles
168
+ */
169
+ function onToggleClick(event) {
170
+ toggleState(event);
171
+ }
172
+
173
+ /**
174
+ * Utility to attach remote handlers
175
+ * - Used initially and when page is modified
176
+ */
177
+ function attachRemotes() {
178
+ if (!group) return;
179
+ const remotes = queryRemotesInitial(group);
180
+ remotes.forEach(remote => {
181
+ remote.addEventListener("click", onToggleClick);
182
+ initializer.initializeElement(remote);
54
183
  });
55
- });
56
- // Initial icon setup
57
- changeIcons();
184
+ }
185
+
186
+ /**
187
+ * This only cleans up remotes that are still in DOM
188
+ * - For ones that have been removed we don't store any references to them
189
+ */
190
+ function cleanupRemotes() {
191
+ if (!group) return;
192
+ const remotes = queryRemotesInitial(group);
193
+ remotes.forEach(remote => {
194
+ remote.removeEventListener("click", onToggleClick);
195
+ remote.removeAttribute(attrInit, "");
196
+ });
197
+ }
198
+
199
+ /**
200
+ * Function to cleanup listeners and remove init attributes
201
+ */
202
+ function destroy() {
203
+ toggle.removeEventListener("click", onToggleClick);
204
+ toggle.removeAttribute(attrInit, "");
205
+ cleanupRemotes();
206
+ document.removeEventListener(getName("pageModified"), attachRemotes);
207
+ }
208
+
209
+ return {
210
+ destroy,
211
+ toggle,
212
+ options,
213
+ toggleState,
214
+ setState(themeKey) {
215
+ setState(themeKey, ctx);
216
+ }
217
+ };
58
218
  }
59
219
 
220
+
221
+
60
222
  /**
61
- *
62
- * @param {Element} body Sets up initial theme on load based on user preference.
223
+ * Change the state of target/toggle
63
224
  */
64
- function setupTheme(body) {
65
- const sitePreference = localStorage.getItem("data-theme");
66
- const machinePreference = window.matchMedia && window.matchMedia(`(prefers-color-scheme: ${defaultThemeInverse})`).matches;
67
- if(sitePreference && sitePreference != currentTheme){
68
- // Check if local storage has site specific preference. And that preference is not the default.
69
- changeTheme(body);
70
- } else if (machinePreference) {
71
- // Check if user system preference differs from default theme.
72
- changeTheme(body);
73
- }
225
+ function setState(key, ctx) {
226
+ if (!key) {
227
+ console.error("Missing key");
228
+ return;
229
+ }
230
+
231
+ const { toggle, options } = ctx;
232
+ const { themes, group } = options;
233
+ const elements = {
234
+ targets: getElements(options.target),
235
+ toggles: [toggle, ...(group ? queryRemotes(group) : [])]
236
+ };
237
+
238
+ if (!elements.targets.length || !elements.toggles.length) {
239
+ console.error("Issue setting state, couldn't find needed elements", elements);
240
+ return;
241
+ }
242
+
243
+ const theme = themes[key];
244
+ const otherThemes = getOtherThemes(key, themes);
245
+ const stateCtx = {
246
+ ...ctx,
247
+ key,
248
+ elements,
249
+ theme,
250
+ otherThemes
251
+ };
252
+
253
+ if (options.debug) {
254
+ initializer.log("Set state context", stateCtx);
255
+ }
256
+
257
+ // Prepare classes to remove
258
+ const otherTargetClasses = concatThemeClasses(otherThemes, "targetClass");
259
+ const otherIconClasses = concatThemeClasses(otherThemes, "iconClass");
260
+
261
+ // Update all targets
262
+ elements.targets.forEach(element => {
263
+ element.setAttribute(attrState, key);
264
+ element.classList.remove(...otherTargetClasses);
265
+ element.classList.add(...resolveClasses(theme.targetClass));
266
+ });
267
+
268
+ // Update all toggles and inner children
269
+ elements.toggles.forEach(element => {
270
+ const label = element.querySelector(attrSelectorLabel);
271
+ const icon = element.querySelector(attrSelectorIcon);
272
+ if (label) {
273
+ label.textContent = theme.label;
274
+ }
275
+ if (icon) {
276
+ icon.classList.remove(...otherIconClasses);
277
+ icon.classList.add(...resolveClasses(theme.iconClass));
278
+ }
279
+ element.setAttribute(attrState, key);
280
+ });
281
+
282
+ // Optional callback if user want to set other things (ie. data-theme or something)
283
+ if (options.onChange) {
284
+ options.onChange(stateCtx);
285
+ }
286
+
287
+ if (options.savePreference) {
288
+ localStorage.setItem(getStorageKey(options), key);
289
+ }
74
290
  }
75
291
 
76
292
  /**
77
- *
78
- * @param {Element} body Changes the theme of the body.
293
+ * Function determines what the initial state is
294
+ * - Check OS preference, saved preference, or initialState depending on options
295
+ * @return {String} The resolved initial theme's key
79
296
  */
80
- function changeTheme(body) {
81
- let newTheme;
82
- let oldTheme;
83
- if (body.classList.contains(options.darkTheme)) {
84
- oldTheme = options.darkTheme;
85
- newTheme = options.lightTheme;
86
- } else if (body.classList.contains(options.lightTheme)) {
87
- oldTheme = options.lightTheme;
88
- newTheme = options.darkTheme;
297
+ function resolveInitial(options) {
298
+ const { savePreference, checkMediaQuery, themes, initialState } = options;
299
+ const storageKey = getStorageKey(options);
300
+ const saved = when(savePreference, () => localStorage.getItem(storageKey));
301
+ const mediaQueryPreference = when(checkMediaQuery, () => getMatchingThemeQuery(themes));
302
+ const resolved = saved || mediaQueryPreference || initialState;
303
+
304
+ if (options.debug) {
305
+ initializer.log("Preference Saved", saved);
306
+ initializer.log("Media Query Preference", mediaQueryPreference);
307
+ initializer.log("Initial State:", initialState);
308
+ }
309
+
310
+ if (!resolved) {
311
+ initializer.logError("Failed to resolve initial theme (pass 'initialState' to options)");
89
312
  }
90
- body.classList.remove(oldTheme);
91
- body.classList.add(newTheme);
92
- localStorage.setItem("data-theme", newTheme);
93
- currentTheme = newTheme;
94
- changeIcons();
313
+
314
+ return resolved;
95
315
  }
96
316
 
97
317
  /**
98
- *
99
- * @param {Element} body Used to check for theme.
100
- * @param {Element} context Used to find the icons.
318
+ * Check each theme for a matching media query
319
+ * @return {String} Matching theme key
101
320
  */
102
- function changeIcons(context = document) {
103
- const icons = context.querySelectorAll(attrSelectorInitial("icon"));
104
- icons.forEach(icon => {
105
- if (currentTheme == options.lightTheme) {
106
- icon.classList = options.darkIcon;
107
- } else {
108
- icon.classList = options.lightIcon;
321
+ function getMatchingThemeQuery(themes) {
322
+ const found = Object.entries(themes).find(([_key, theme]) => {
323
+ if (theme.mediaQuery) {
324
+ return window.matchMedia(theme.mediaQuery).matches;
109
325
  }
110
326
  });
327
+ // Return just the key
328
+ return found ? found[0] : null;
111
329
  }
112
330
 
113
- // run on beforeprint event
114
- function printSetup() {
115
- const body = document.querySelector("body");
116
- if (body.classList.contains(options.darkTheme)) {
117
- body.classList.remove(options.darkTheme);
118
- body.classList.add(options.lightTheme);
119
- }
331
+ /**
332
+ * Get the next key in the themes based on the currentKey
333
+ */
334
+ function getNextThemeKey(activeKey, options) {
335
+ const { themes } = options;
336
+ const keys = Object.keys(themes);
337
+ const index = keys.findIndex(theme => theme === activeKey);
338
+ // If not found return first, else calculate next index (wrapping)
339
+ const nextIndex = index === -1 ? 0 : (index + 1) % keys.length;
340
+ return keys[nextIndex];
120
341
  }
121
342
 
122
- // run on afterprint event
123
- function printTearDown() {
124
- const body = document.querySelector("body");
125
- if (!body.classList.contains(currentTheme)) {
126
- body.classList.remove(options.lightTheme);
127
- body.classList.add(options.darkTheme);
128
- }
343
+ /**
344
+ * Get all other theme object except the current
345
+ */
346
+ function getOtherThemes(currentKey, themes) {
347
+ const all = Object.entries(themes);
348
+ return all.filter(([key]) => key !== currentKey).map(([_key, value]) => value);
349
+ }
350
+
351
+ /**
352
+ * Concatenates multiple class properties into one array
353
+ */
354
+ function concatThemeClasses(themes, property) {
355
+ return themes.reduce((acc, theme) => {
356
+ return acc.concat(resolveClasses(theme[property]));
357
+ }, []);
358
+ }
359
+
360
+ /**
361
+ * Creates the storage key (either prefix or prefix with group name)
362
+ */
363
+ function getStorageKey(options) {
364
+ const { storagePrefix, group } = options;
365
+ return group ? `${ storagePrefix }${ group }` : storagePrefix;
129
366
  }
package/js/ui/tooltip.js CHANGED
@@ -2,46 +2,41 @@
2
2
  * @module ui/tooltip
3
3
  */
4
4
 
5
+ import { ComponentInitializer } from "../utils/system.js";
5
6
  import { getName as getEventName } from "../events/index.js";
6
7
  import { createFloatingUi } from "../utils/floating-ui.js";
7
8
  import { createElementFromHtml } from "@ulu/utils/browser/dom.js";
8
9
  import { logError } from "../utils/class-logger.js";
9
- import { getDatasetOptionalJson } from "../utils/dom.js";
10
10
  import { newId, ensureId } from "../utils/id.js";
11
11
 
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
12
  /**
21
- * Initialize default popover
13
+ * Tooltip Component Initializer
22
14
  */
23
- export function init() {
24
- document.addEventListener(getEventName("pageModified"), setup);
25
- setup();
26
- }
15
+ export const initializer = new ComponentInitializer({
16
+ type: "tooltip",
17
+ baseAttribute: "data-ulu-tooltip"
18
+ });
19
+
20
+ const attrBody = initializer.getAttribute("body");
21
+ const attrSelectorBody = initializer.attributeSelector("body");
22
+ const attrSelectorArrow = initializer.attributeSelector("arrow");
27
23
 
28
24
  /**
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
25
+ * Initialize default popover
32
26
  */
33
- export function setup() {
34
- const triggers = document.querySelectorAll(attrSelectorInitial("trigger"));
35
- triggers.forEach(setupTrigger);
36
- }
37
-
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);
27
+ export function init() {
28
+ initializer.init({
29
+ events: ["pageModified"],
30
+ withData: true,
31
+ setup({ element: trigger, data, initialize }) {
32
+ const options = typeof data === "object" ? data : {};
33
+ if (typeof data === "string") {
34
+ options.content = data;
35
+ }
36
+ initialize();
37
+ (new Tooltip({ trigger }, options));
38
+ }
39
+ });
45
40
  }
46
41
 
47
42
  /**
@@ -103,7 +98,7 @@ export class Tooltip {
103
98
  template(_config) {
104
99
  return `
105
100
  <div class="popover popover--tooltip">
106
- <div class="popover__inner" ${ attrs.body }>
101
+ <div class="popover__inner" ${ attrBody }>
107
102
  </div>
108
103
  <span class="popover__arrow" data-ulu-tooltip-arrow></span>
109
104
  </div>
@@ -177,7 +172,7 @@ export class Tooltip {
177
172
  createContentElement() {
178
173
  const { options } = this;
179
174
  const content = createElementFromHtml(options.template(options));
180
- const body = content.querySelector(attrSelector("body"));
175
+ const body = content.querySelector(attrSelectorBody);
181
176
  const innerContent = this.getInnerContent();
182
177
  if (options.isHtml) {
183
178
  body.innerHTML = innerContent;
@@ -190,7 +185,7 @@ export class Tooltip {
190
185
  }
191
186
 
192
187
  this.elements.content = content;
193
- this.elements.contentArrow = content.querySelector(attrSelector("arrow"));
188
+ this.elements.contentArrow = content.querySelector(attrSelectorArrow);
194
189
  document.body.appendChild(content);
195
190
  }
196
191
  attachHandlers() {
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @module utils/css
3
+ */
4
+
5
+ /**
6
+ * Generates a CSS custom property name with a given prefix.
7
+ * @param {string} prefix The prefix to apply to the custom property name.
8
+ * @param {string} propertyName The base name of the custom property.
9
+ * @returns {string} The fully formed CSS custom property name (e.g., "--prefix-propertyName").
10
+ */
11
+ export function getCustomProperty(prefix, propertyName) {
12
+ return `--${prefix}-${propertyName}`;
13
+ }