@ulu/frontend 0.1.0-beta.4 → 0.1.0-beta.40

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 (295) hide show
  1. package/CHANGELOG.md +332 -1
  2. package/dist/ulu-frontend.min.css +1 -1
  3. package/dist/ulu-frontend.min.js +28 -27
  4. package/docs-dev/assets/main.js +832 -421
  5. package/docs-dev/assets/placeholder/icon-calendar.svg +1 -0
  6. package/docs-dev/assets/placeholder/icon-check.svg +1 -0
  7. package/docs-dev/assets/style.css +629 -233
  8. package/docs-dev/changelog/index.html +6079 -0
  9. package/docs-dev/changelog/updates-and-changes/index.html +5109 -0
  10. package/docs-dev/demos/accordion/index.html +904 -321
  11. package/docs-dev/demos/basic-hero/index.html +111 -0
  12. package/docs-dev/demos/breakpoints-manager/index.html +5246 -0
  13. package/docs-dev/demos/button/index.html +916 -323
  14. package/docs-dev/demos/button-verbose/index.html +5238 -0
  15. package/docs-dev/demos/callout/index.html +951 -328
  16. package/docs-dev/demos/captioned-figure/index.html +904 -321
  17. package/docs-dev/demos/card/index.html +970 -748
  18. package/docs-dev/demos/card-grid/index.html +5357 -0
  19. package/docs-dev/demos/card-new/index.html +5088 -0
  20. package/docs-dev/demos/card-old/index.html +5223 -0
  21. package/docs-dev/demos/card.1/index.html +5223 -0
  22. package/docs-dev/demos/card.TRASH/index.html +5541 -0
  23. package/docs-dev/demos/counter-list/index.html +5224 -0
  24. package/docs-dev/demos/css-icons/index.html +904 -321
  25. package/docs-dev/demos/data-grid/index.html +1014 -511
  26. package/docs-dev/demos/data-table/index.html +1064 -348
  27. package/docs-dev/demos/details-group/index.html +5267 -0
  28. package/docs-dev/demos/file-save/index.html +904 -321
  29. package/docs-dev/demos/flipcard/index.html +904 -321
  30. package/docs-dev/demos/form-theme/index.html +922 -352
  31. package/docs-dev/demos/hero/index.html +12 -4
  32. package/docs-dev/demos/image-grid/index.html +12 -4
  33. package/docs-dev/demos/index.html +904 -321
  34. package/docs-dev/demos/list-inline/index.html +5220 -0
  35. package/docs-dev/demos/list-inline.1/index.html +4727 -0
  36. package/docs-dev/demos/list-lines/index.html +5210 -0
  37. package/docs-dev/demos/menu-stack/index.html +975 -377
  38. package/docs-dev/demos/modals/index.html +1010 -357
  39. package/docs-dev/demos/nav-strip/index.html +924 -377
  40. package/docs-dev/demos/overlay-section/index.html +979 -326
  41. package/docs-dev/demos/popovers/index.html +1152 -327
  42. package/docs-dev/demos/print/index.html +904 -321
  43. package/docs-dev/demos/pull-quote/index.html +904 -321
  44. package/docs-dev/demos/rule/index.html +952 -357
  45. package/docs-dev/demos/scroll-slider/index.html +72 -106
  46. package/docs-dev/demos/scrollpoints/index.html +905 -322
  47. package/docs-dev/demos/slider/index.html +12 -4
  48. package/docs-dev/demos/spoke-spinner/index.html +904 -321
  49. package/docs-dev/demos/sticky-list/index.html +5223 -0
  50. package/docs-dev/demos/tabs/index.html +944 -325
  51. package/docs-dev/demos/tag/index.html +904 -321
  52. package/docs-dev/demos/theme-toggle/index.html +5279 -0
  53. package/docs-dev/demos/tile-grid-overlay/index.html +12 -4
  54. package/docs-dev/demos/tiles/index.html +904 -321
  55. package/docs-dev/demos/tooltip/index.html +904 -321
  56. package/docs-dev/guide/building-stylesheet/index.html +904 -321
  57. package/docs-dev/guide/developing-ulu-scss-module/index.html +904 -321
  58. package/docs-dev/guide/index.html +904 -321
  59. package/docs-dev/guide/updates-and-changes/index.html +5033 -0
  60. package/docs-dev/index.html +904 -321
  61. package/docs-dev/javascript/events/index.html +901 -320
  62. package/docs-dev/javascript/index.html +904 -321
  63. package/docs-dev/javascript/settings/index.html +5400 -0
  64. package/docs-dev/javascript/ui-breakpoints/index.html +916 -335
  65. package/docs-dev/javascript/ui-collapsible/index.html +901 -320
  66. package/docs-dev/javascript/ui-details-group/index.html +5322 -0
  67. package/docs-dev/javascript/ui-dialog/index.html +967 -371
  68. package/docs-dev/javascript/ui-flipcard/index.html +964 -327
  69. package/docs-dev/javascript/ui-grid/index.html +913 -358
  70. package/docs-dev/javascript/ui-modal-builder/index.html +914 -354
  71. package/docs-dev/javascript/ui-overflow-scroller/index.html +901 -320
  72. package/docs-dev/javascript/ui-overflow-scroller-pager/index.html +901 -320
  73. package/docs-dev/javascript/ui-page/index.html +901 -320
  74. package/docs-dev/javascript/ui-popover/index.html +911 -334
  75. package/docs-dev/javascript/ui-print/index.html +901 -328
  76. package/docs-dev/javascript/ui-print-details/index.html +901 -320
  77. package/docs-dev/javascript/ui-programmatic-modal/index.html +901 -320
  78. package/docs-dev/javascript/ui-proxy-click/index.html +990 -324
  79. package/docs-dev/javascript/ui-resizer/index.html +901 -320
  80. package/docs-dev/javascript/ui-scroll-slider/index.html +941 -328
  81. package/docs-dev/javascript/ui-scrollpoint/index.html +907 -333
  82. package/docs-dev/javascript/ui-slider/index.html +1099 -327
  83. package/docs-dev/javascript/ui-tabs/index.html +914 -370
  84. package/docs-dev/javascript/ui-theme-toggle/index.html +5410 -0
  85. package/docs-dev/javascript/ui-tooltip/index.html +940 -363
  86. package/docs-dev/javascript/utils-class-logger/index.html +901 -320
  87. package/docs-dev/javascript/utils-css/index.html +5224 -0
  88. package/docs-dev/javascript/utils-dom/index.html +1037 -334
  89. package/docs-dev/javascript/utils-file-save/index.html +901 -320
  90. package/docs-dev/javascript/utils-floating-ui/index.html +901 -320
  91. package/docs-dev/javascript/utils-id/index.html +901 -320
  92. package/docs-dev/javascript/utils-pause-youtube-video/index.html +901 -320
  93. package/docs-dev/javascript/utils-system/index.html +5527 -0
  94. package/docs-dev/sass/base/color/index.html +901 -320
  95. package/docs-dev/sass/base/elements/index.html +973 -392
  96. package/docs-dev/sass/base/index/index.html +959 -378
  97. package/docs-dev/sass/base/index.html +904 -321
  98. package/docs-dev/sass/base/keyframes/index.html +901 -320
  99. package/docs-dev/sass/base/layout/index.html +966 -385
  100. package/docs-dev/sass/base/normalize/index.html +901 -320
  101. package/docs-dev/sass/base/print/index.html +901 -320
  102. package/docs-dev/sass/base/root/index.html +901 -320
  103. package/docs-dev/sass/base/typography/index.html +901 -320
  104. package/docs-dev/sass/components/accordion/index.html +1026 -445
  105. package/docs-dev/sass/components/adaptive-spacing/index.html +1027 -446
  106. package/docs-dev/sass/components/badge/index.html +1005 -424
  107. package/docs-dev/sass/components/basic-hero/index.html +5385 -0
  108. package/docs-dev/sass/components/button/index.html +952 -371
  109. package/docs-dev/sass/components/button-verbose/index.html +1089 -433
  110. package/docs-dev/sass/components/callout/index.html +1086 -482
  111. package/docs-dev/sass/components/captioned-figure/index.html +1070 -374
  112. package/docs-dev/sass/components/card/index.html +1121 -491
  113. package/docs-dev/sass/components/card-grid/index.html +973 -392
  114. package/docs-dev/sass/components/counter-list/index.html +5458 -0
  115. package/docs-dev/sass/components/css-icon/index.html +1052 -464
  116. package/docs-dev/sass/components/data-grid/index.html +1087 -499
  117. package/docs-dev/sass/components/data-table/index.html +1154 -381
  118. package/docs-dev/sass/components/fill-context/index.html +901 -320
  119. package/docs-dev/sass/components/flipcard/index.html +1071 -459
  120. package/docs-dev/sass/components/flipcard-grid/index.html +960 -379
  121. package/docs-dev/sass/components/form-theme/index.html +1349 -672
  122. package/docs-dev/sass/components/hero/index.html +1016 -387
  123. package/docs-dev/sass/components/horizontal-rule/index.html +959 -378
  124. package/docs-dev/sass/components/image-grid/index.html +966 -385
  125. package/docs-dev/sass/components/index/index.html +1004 -419
  126. package/docs-dev/sass/components/index.html +904 -321
  127. package/docs-dev/sass/components/links/index.html +901 -320
  128. package/docs-dev/sass/components/list-inline/index.html +5399 -0
  129. package/docs-dev/sass/components/list-lines/index.html +1009 -432
  130. package/docs-dev/sass/components/list-ordered/index.html +903 -322
  131. package/docs-dev/sass/components/list-unordered/index.html +901 -320
  132. package/docs-dev/sass/components/menu-stack/index.html +1050 -456
  133. package/docs-dev/sass/components/modal/index.html +1032 -444
  134. package/docs-dev/sass/components/nav-strip/index.html +1023 -442
  135. package/docs-dev/sass/components/overlay-section/index.html +1010 -429
  136. package/docs-dev/sass/components/pager/index.html +1017 -436
  137. package/docs-dev/sass/components/placeholder-block/index.html +1017 -436
  138. package/docs-dev/sass/components/popover/index.html +1068 -451
  139. package/docs-dev/sass/components/pull-quote/index.html +1017 -436
  140. package/docs-dev/sass/components/ratio-box/index.html +969 -388
  141. package/docs-dev/sass/components/rule/index.html +972 -391
  142. package/docs-dev/sass/components/scroll-slider/index.html +1019 -450
  143. package/docs-dev/sass/components/skip-link/index.html +961 -380
  144. package/docs-dev/sass/components/slider/index.html +1023 -442
  145. package/docs-dev/sass/components/spoke-spinner/index.html +961 -380
  146. package/docs-dev/sass/components/sticky-list/index.html +5603 -0
  147. package/docs-dev/sass/components/tabs/index.html +1020 -439
  148. package/docs-dev/sass/components/tag/index.html +1064 -483
  149. package/docs-dev/sass/components/tile-button/index.html +1004 -423
  150. package/docs-dev/sass/components/tile-grid/index.html +1045 -464
  151. package/docs-dev/sass/components/tile-grid-overlay/index.html +940 -359
  152. package/docs-dev/sass/components/vignette/index.html +965 -378
  153. package/docs-dev/sass/components/wysiwyg/index.html +968 -387
  154. package/docs-dev/sass/core/breakpoint/index.html +1045 -450
  155. package/docs-dev/sass/core/button/index.html +1438 -857
  156. package/docs-dev/sass/core/color/index.html +1084 -496
  157. package/docs-dev/sass/core/cssvar/index.html +950 -369
  158. package/docs-dev/sass/core/element/index.html +1504 -782
  159. package/docs-dev/sass/core/index.html +901 -320
  160. package/docs-dev/sass/core/layout/index.html +1062 -462
  161. package/docs-dev/sass/core/path/index.html +953 -372
  162. package/docs-dev/sass/core/selector/index.html +952 -371
  163. package/docs-dev/sass/core/typography/index.html +1171 -590
  164. package/docs-dev/sass/core/units/index.html +984 -403
  165. package/docs-dev/sass/core/utils/index.html +1941 -500
  166. package/docs-dev/sass/helpers/color/index.html +901 -320
  167. package/docs-dev/sass/helpers/display/index.html +902 -321
  168. package/docs-dev/sass/helpers/index/index.html +956 -375
  169. package/docs-dev/sass/helpers/index.html +904 -321
  170. package/docs-dev/sass/helpers/print/index.html +843 -292
  171. package/docs-dev/sass/helpers/typography/index.html +901 -320
  172. package/docs-dev/sass/helpers/units/index.html +950 -369
  173. package/docs-dev/sass/helpers/utilities/index.html +903 -322
  174. package/docs-dev/sass/index.html +904 -321
  175. package/js/index.js +1 -0
  176. package/js/settings.js +95 -0
  177. package/js/ui/breakpoints.js +19 -16
  178. package/js/ui/collapsible.js +8 -1
  179. package/js/ui/details-group.js +112 -0
  180. package/js/ui/dialog.js +90 -42
  181. package/js/ui/dialog.todo +2 -36
  182. package/js/ui/flipcard.js +37 -57
  183. package/js/ui/grid.js +15 -13
  184. package/js/ui/index.js +1 -0
  185. package/js/ui/modal-builder.js +45 -54
  186. package/js/ui/overflow-scroller.js +6 -4
  187. package/js/ui/popover.js +38 -38
  188. package/js/ui/print.js +16 -25
  189. package/js/ui/programmatic-modal.js +9 -3
  190. package/js/ui/proxy-click.js +50 -36
  191. package/js/ui/scroll-slider.js +24 -30
  192. package/js/ui/scrollpoint.js +28 -64
  193. package/js/ui/slider.js +61 -62
  194. package/js/ui/tabs.js +23 -36
  195. package/js/ui/theme-toggle.js +331 -94
  196. package/js/ui/tooltip.js +27 -32
  197. package/js/utils/css.js +13 -0
  198. package/js/utils/dom.js +69 -4
  199. package/js/utils/font-awesome.js +18 -0
  200. package/js/utils/index.js +2 -1
  201. package/js/utils/system.js +154 -0
  202. package/package.json +10 -7
  203. package/scss/_breakpoint.scss +16 -3
  204. package/scss/_color.scss +9 -2
  205. package/scss/_element.scss +91 -0
  206. package/scss/_layout.scss +7 -8
  207. package/scss/_utils.scss +248 -13
  208. package/scss/components/README.todos +14 -0
  209. package/scss/components/_accordion.scss +18 -20
  210. package/scss/components/_badge.scss +3 -2
  211. package/scss/components/_basic-hero.scss +112 -0
  212. package/scss/components/_button-verbose.scss +102 -20
  213. package/scss/components/_callout.scss +127 -79
  214. package/scss/components/_captioned-figure.scss +23 -5
  215. package/scss/components/_card-grid.scss +1 -1
  216. package/scss/components/_card.scss +242 -88
  217. package/scss/components/_counter-list.scss +133 -0
  218. package/scss/components/_css-icon.scss +33 -28
  219. package/scss/components/_data-grid.scss +38 -9
  220. package/scss/components/_data-table.scss +44 -4
  221. package/scss/components/_flipcard.scss +21 -15
  222. package/scss/components/_form-theme.scss +146 -135
  223. package/scss/components/_hero.scss +12 -10
  224. package/scss/components/_index.scss +24 -0
  225. package/scss/components/_list-inline.scss +80 -0
  226. package/scss/components/_list-lines.scss +44 -33
  227. package/scss/components/_list-ordered.scss +0 -1
  228. package/scss/components/_menu-stack.scss +42 -26
  229. package/scss/components/_modal.scss +29 -19
  230. package/scss/components/_nav-strip.scss +25 -16
  231. package/scss/components/_overlay-section.scss +4 -6
  232. package/scss/components/_pager.scss +6 -6
  233. package/scss/components/_placeholder-block.scss +4 -4
  234. package/scss/components/_popover.scss +174 -73
  235. package/scss/components/_pull-quote.scss +13 -13
  236. package/scss/components/_ratio-box.scss +2 -5
  237. package/scss/components/_rule.scss +1 -1
  238. package/scss/components/_scroll-slider.scss +2 -6
  239. package/scss/components/_skip-link.scss +2 -1
  240. package/scss/components/_slider.scss +17 -38
  241. package/scss/components/_spoke-spinner.scss +2 -2
  242. package/scss/components/_sticky-list.scss +206 -0
  243. package/scss/components/_tabs.scss +4 -2
  244. package/scss/components/_tag.scss +1 -1
  245. package/scss/components/_vignette.scss +3 -5
  246. package/scss/helpers/_display.scss +15 -18
  247. package/scss/helpers/_print.scss +12 -7
  248. package/scss/helpers/_utilities.scss +42 -32
  249. package/types/index.d.ts +1 -0
  250. package/types/settings.d.ts +66 -0
  251. package/types/settings.d.ts.map +1 -0
  252. package/types/ui/breakpoints.d.ts +14 -14
  253. package/types/ui/breakpoints.d.ts.map +1 -1
  254. package/types/ui/collapsible.d.ts.map +1 -1
  255. package/types/ui/details-group.d.ts +38 -0
  256. package/types/ui/details-group.d.ts.map +1 -0
  257. package/types/ui/dialog.d.ts +20 -14
  258. package/types/ui/dialog.d.ts.map +1 -1
  259. package/types/ui/flipcard.d.ts +16 -10
  260. package/types/ui/flipcard.d.ts.map +1 -1
  261. package/types/ui/grid.d.ts +4 -6
  262. package/types/ui/grid.d.ts.map +1 -1
  263. package/types/ui/index.d.ts +1 -0
  264. package/types/ui/modal-builder.d.ts +8 -11
  265. package/types/ui/modal-builder.d.ts.map +1 -1
  266. package/types/ui/overflow-scroller.d.ts +2 -2
  267. package/types/ui/overflow-scroller.d.ts.map +1 -1
  268. package/types/ui/popover.d.ts +6 -7
  269. package/types/ui/popover.d.ts.map +1 -1
  270. package/types/ui/print.d.ts +0 -4
  271. package/types/ui/print.d.ts.map +1 -1
  272. package/types/ui/programmatic-modal.d.ts.map +1 -1
  273. package/types/ui/proxy-click.d.ts +19 -3
  274. package/types/ui/proxy-click.d.ts.map +1 -1
  275. package/types/ui/scroll-slider.d.ts +5 -7
  276. package/types/ui/scroll-slider.d.ts.map +1 -1
  277. package/types/ui/scrollpoint.d.ts +3 -8
  278. package/types/ui/scrollpoint.d.ts.map +1 -1
  279. package/types/ui/slider.d.ts +24 -14
  280. package/types/ui/slider.d.ts.map +1 -1
  281. package/types/ui/tabs.d.ts +6 -8
  282. package/types/ui/tabs.d.ts.map +1 -1
  283. package/types/ui/theme-toggle.d.ts +51 -7
  284. package/types/ui/theme-toggle.d.ts.map +1 -1
  285. package/types/ui/tooltip.d.ts +3 -5
  286. package/types/ui/tooltip.d.ts.map +1 -1
  287. package/types/utils/css.d.ts +11 -0
  288. package/types/utils/css.d.ts.map +1 -0
  289. package/types/utils/dom.d.ts +36 -4
  290. package/types/utils/dom.d.ts.map +1 -1
  291. package/types/utils/font-awesome.d.ts +5 -0
  292. package/types/utils/font-awesome.d.ts.map +1 -0
  293. package/types/utils/index.d.ts +1 -0
  294. package/types/utils/system.d.ts +113 -0
  295. package/types/utils/system.d.ts.map +1 -0
@@ -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
+ }
package/js/utils/dom.js CHANGED
@@ -5,6 +5,18 @@
5
5
 
6
6
  export const regexJsonString = /^[{\[][\s\S]*[}\]]$/;
7
7
 
8
+ /**
9
+ * Converts a data attribute name to its corresponding dataset property name.
10
+ * @param {string} dataAttribute - The data attribute name (e.g., "data-ulu-dialog").
11
+ * @returns {string} - The dataset property name (e.g., "uluDialog").
12
+ */
13
+ export function dataAttributeToDatasetKey(attribute) {
14
+ // Remove "data-" prefix then convert kebab-case to camelCase
15
+ return attribute
16
+ .replace(/^data-/, "")
17
+ .replace(/-([a-z])/g, (_match, letter) => letter.toUpperCase());
18
+ }
19
+
8
20
  /**
9
21
  * Get an elements JSON dataset value
10
22
  * @param {Node} element
@@ -98,6 +110,7 @@ export function setPositionClasses(parent, classes = {
98
110
  * Resolve a target to Element
99
111
  * @param {String|Node} target The selector or node/element
100
112
  * @param {Object} context [document] The context to query possible selectors from
113
+ * @return {HTMLElement} The element or null if not found
101
114
  */
102
115
  export function getElement(target, context = document) {
103
116
  if (typeof target === "string") {
@@ -105,18 +118,70 @@ export function getElement(target, context = document) {
105
118
  } else if (target instanceof Element) {
106
119
  return target;
107
120
  } else {
108
- console.warn("Unable to getElement()", target);
121
+ console.warn("getElement: Invalid target type (expected String/Node)", target);
109
122
  return null;
110
123
  }
111
124
  }
112
125
 
126
+ /**
127
+ * Resolve a target to Elements
128
+ * @param {String|Node} target The selector or node/element
129
+ * @param {Object} context [document] The context to query possible selectors from
130
+ * @return {Array} The elements or null if not found
131
+ */
132
+ export function getElements(target, context = document) {
133
+ if (typeof target === "string") {
134
+ return [...context.querySelectorAll(target)];
135
+ } else if (target instanceof Element) {
136
+ return [target];
137
+ } else if (Array.isArray(target) || target instanceof NodeList) {
138
+ return [...target];
139
+ } else {
140
+ console.warn("getElement: Invalid target type (expected String/Node/Array/Node List)", target);
141
+ return null;
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Resolves a class input (string or array) into a consistent array of class names.
147
+ * @param {string|string[]} input - The class input, which can be a string, an array of strings, or any other value.
148
+ * @returns {string[]} An array of class names. Returns an empty array for invalid or falsy input.
149
+ * @example
150
+ * resolveClassArray("fas fa-check my-class"); // Returns ["fas", "fa-check", "my-class"]
151
+ * resolveClassArray(["another-class", "yet-another-class"]); // Returns ["another-class", "yet-another-class"]
152
+ * resolveClassArray("single-class"); // Returns ["single-class"]
153
+ */
154
+ export function resolveClasses(classes) {
155
+ if (typeof classes === "string") {
156
+ return classes.split(" ").filter(c => c !== ""); // Split and remove empty strings
157
+ } else if (Array.isArray(classes)) {
158
+ return classes;
159
+ } else if (!classes) {
160
+ return [];
161
+ } else {
162
+ console.warn("resolveClassArray: Invalid class input type.", classes);
163
+ return [];
164
+ }
165
+ }
166
+
113
167
  /**
114
168
  * 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
169
+ * @param {HTMLElement} [element=document.body] -The element that is the child of a scrollable container
170
+ * @param {Window|HTMLElement} [container=window] - The container that can be scrolled
117
171
  * @param {Stirng} propName Custom property to set
118
172
  */
119
173
  export function addScrollbarProperty(element = document.body, container = window, propName = "--ulu-scrollbar-width") {
120
- const scrollbarWidth = container.innerWidth - element.clientWidth;
174
+ const scrollbarWidth = getScrollbarWidth(element, container);
121
175
  element.style.setProperty(propName, `${ scrollbarWidth }px`);
176
+ }
177
+
178
+ /**
179
+ * Calculates the width of the scrollbar.
180
+ *
181
+ * @param {HTMLElement} [element=document.body] -The element that is the child of a scrollable container
182
+ * @param {Window|HTMLElement} [container=window] - The container that can be scrolled
183
+ * @returns {number} The width of the scrollbar in pixels.
184
+ */
185
+ export function getScrollbarWidth(element = document.body, container = window) {
186
+ return container.innerWidth - element.clientWidth;
122
187
  }