@ulu/frontend 0.1.0-beta.8 → 0.1.0-beta.80

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 (287) hide show
  1. package/CHANGELOG.md +529 -0
  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 +35 -28
  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 +6553 -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 +892 -328
  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 +870 -347
  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 +968 -330
  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 +1047 -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 +887 -446
  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 +1063 -352
  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 +891 -342
  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 +85 -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 +105 -59
  182. package/js/ui/overflow-scroller.js +6 -4
  183. package/js/ui/page.js +2 -2
  184. package/js/ui/popover.js +38 -38
  185. package/js/ui/print.js +16 -25
  186. package/js/ui/programmatic-modal.js +9 -3
  187. package/js/ui/proxy-click.js +50 -36
  188. package/js/ui/scroll-slider.js +24 -30
  189. package/js/ui/scrollpoint.js +29 -64
  190. package/js/ui/slider.js +108 -63
  191. package/js/ui/tabs.js +23 -36
  192. package/js/ui/theme-toggle.js +332 -94
  193. package/js/ui/tooltip.js +27 -32
  194. package/js/utils/css.js +13 -0
  195. package/js/utils/dom.js +23 -64
  196. package/js/utils/font-awesome.js +18 -0
  197. package/js/utils/index.js +2 -1
  198. package/js/utils/system.js +155 -0
  199. package/package.json +23 -8
  200. package/scss/README.md +4 -0
  201. package/scss/_breakpoint.scss +38 -4
  202. package/scss/_color.scss +40 -9
  203. package/scss/_element.scss +108 -2
  204. package/scss/_layout.scss +7 -8
  205. package/scss/_units.scss +3 -2
  206. package/scss/_utils.scss +380 -16
  207. package/scss/components/README.todos +14 -0
  208. package/scss/components/_accordion.scss +33 -19
  209. package/scss/components/_badge.scss +23 -4
  210. package/scss/components/_basic-hero.scss +112 -0
  211. package/scss/components/_button-verbose.scss +100 -18
  212. package/scss/components/_callout.scss +125 -78
  213. package/scss/components/_captioned-figure.scss +17 -0
  214. package/scss/components/_card-grid.scss +1 -1
  215. package/scss/components/_card.scss +246 -74
  216. package/scss/components/_counter-list.scss +151 -0
  217. package/scss/components/_css-icon.scss +25 -21
  218. package/scss/components/_data-grid.scss +55 -9
  219. package/scss/components/_data-table.scss +39 -3
  220. package/scss/components/_flipcard.scss +8 -3
  221. package/scss/components/_form-theme.scss +119 -108
  222. package/scss/components/_hero.scss +12 -10
  223. package/scss/components/_index.scss +18 -0
  224. package/scss/components/_menu-stack.scss +42 -26
  225. package/scss/components/_modal.scss +42 -26
  226. package/scss/components/_nav-strip.scss +2 -0
  227. package/scss/components/_overlay-section.scss +2 -5
  228. package/scss/components/_popover.scss +165 -64
  229. package/scss/components/_pull-quote.scss +13 -13
  230. package/scss/components/_ratio-box.scss +2 -5
  231. package/scss/components/_rule.scss +1 -0
  232. package/scss/components/_scroll-slider.scss +1 -5
  233. package/scss/components/_slider.scss +49 -72
  234. package/scss/components/_spoke-spinner.scss +2 -2
  235. package/scss/components/_sticky-list.scss +206 -0
  236. package/scss/components/_tabs.scss +22 -4
  237. package/scss/components/_vignette.scss +3 -5
  238. package/scss/helpers/_display.scss +15 -18
  239. package/scss/helpers/_print.scss +12 -7
  240. package/scss/helpers/_utilities.scss +42 -32
  241. package/types/index.d.ts +1 -0
  242. package/types/settings.d.ts +66 -0
  243. package/types/settings.d.ts.map +1 -0
  244. package/types/ui/breakpoints.d.ts +14 -14
  245. package/types/ui/breakpoints.d.ts.map +1 -1
  246. package/types/ui/collapsible.d.ts.map +1 -1
  247. package/types/ui/details-group.d.ts +38 -0
  248. package/types/ui/details-group.d.ts.map +1 -0
  249. package/types/ui/dialog.d.ts +20 -14
  250. package/types/ui/dialog.d.ts.map +1 -1
  251. package/types/ui/flipcard.d.ts +16 -10
  252. package/types/ui/flipcard.d.ts.map +1 -1
  253. package/types/ui/grid.d.ts +4 -6
  254. package/types/ui/grid.d.ts.map +1 -1
  255. package/types/ui/index.d.ts +2 -1
  256. package/types/ui/modal-builder.d.ts +113 -11
  257. package/types/ui/modal-builder.d.ts.map +1 -1
  258. package/types/ui/overflow-scroller.d.ts +2 -2
  259. package/types/ui/overflow-scroller.d.ts.map +1 -1
  260. package/types/ui/popover.d.ts +6 -7
  261. package/types/ui/popover.d.ts.map +1 -1
  262. package/types/ui/print.d.ts +0 -4
  263. package/types/ui/print.d.ts.map +1 -1
  264. package/types/ui/programmatic-modal.d.ts.map +1 -1
  265. package/types/ui/proxy-click.d.ts +19 -3
  266. package/types/ui/proxy-click.d.ts.map +1 -1
  267. package/types/ui/scroll-slider.d.ts +5 -7
  268. package/types/ui/scroll-slider.d.ts.map +1 -1
  269. package/types/ui/scrollpoint.d.ts +3 -8
  270. package/types/ui/scrollpoint.d.ts.map +1 -1
  271. package/types/ui/slider.d.ts +33 -14
  272. package/types/ui/slider.d.ts.map +1 -1
  273. package/types/ui/tabs.d.ts +6 -8
  274. package/types/ui/tabs.d.ts.map +1 -1
  275. package/types/ui/theme-toggle.d.ts +51 -7
  276. package/types/ui/theme-toggle.d.ts.map +1 -1
  277. package/types/ui/tooltip.d.ts +3 -5
  278. package/types/ui/tooltip.d.ts.map +1 -1
  279. package/types/utils/css.d.ts +11 -0
  280. package/types/utils/css.d.ts.map +1 -0
  281. package/types/utils/dom.d.ts +12 -32
  282. package/types/utils/dom.d.ts.map +1 -1
  283. package/types/utils/font-awesome.d.ts +5 -0
  284. package/types/utils/font-awesome.d.ts.map +1 -0
  285. package/types/utils/index.d.ts +2 -1
  286. package/types/utils/system.d.ts +113 -0
  287. package/types/utils/system.d.ts.map +1 -0
@@ -1,129 +1,367 @@
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 { resolveClasses } from "../utils/dom.js";
8
+ import { hasRequiredProps } from "@ulu/utils/object.js";
9
+ import { getElements } from "@ulu/utils/browser/dom.js";
5
10
 
6
- const attrs = {
7
- trigger: "data-site-theme-toggle",
8
- icon: "data-site-theme-toggle-icon",
9
- init: "data-site-theme-toggle-init",
10
- };
11
+ /**
12
+ * Theme Toggle Component Initializer
13
+ */
14
+ export const initializer = new ComponentInitializer({
15
+ type: "theme-toggle",
16
+ baseAttribute: "data-ulu-theme-toggle"
17
+ });
11
18
 
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",
19
+ const attrSelectorLabel = initializer.attributeSelector("label");
20
+ const attrSelectorIcon = initializer.attributeSelector("icon");
21
+ const attrRemote = initializer.getAttribute("remote");
22
+ const attrInit = initializer.getAttribute("init");
23
+ const attrState = initializer.getAttribute("state");
24
+
25
+ // Utils for selecting things based on attributes
26
+ const queryRemotes = group => document.querySelectorAll(
27
+ `[${ attrRemote }="${ group }"]`
28
+ );
29
+ const queryRemotesInitial = group => document.querySelectorAll(
30
+ `[${ attrRemote }="${ group }"]:not([${ attrInit }])`
31
+ );
32
+ const requiredToggleProps = ["target"];
33
+ const checkToggleProps = hasRequiredProps(requiredToggleProps);
34
+ const when = (cond, fn) => cond ? fn() : null; // Consider adding as util
35
+
36
+ /**
37
+ * Default Options
38
+ * - Can be overridden using data-attributes
39
+ */
40
+ export const defaults = {
41
+ /**
42
+ * Object of each theme that should be toggle/cycled through
43
+ */
44
+ themes: {
45
+ light: {
46
+ label: "Light",
47
+ value: "light",
48
+ iconClass: "fas fa-moon",
49
+ targetClass: "theme-light",
50
+ mediaQuery: "(prefers-color-scheme: light)"
51
+ },
52
+ dark: {
53
+ label: "Dark",
54
+ iconClass: "fas fa-sun",
55
+ targetClass: "theme-dark",
56
+ mediaQuery: "(prefers-color-scheme: dark)"
57
+ }
58
+ },
59
+ /**
60
+ * Required this is the element(s) that should be changed by a specific toggle
61
+ * - The element should have data-ulu-theme-toggle-target="SOME_IDENTIFIER"
62
+ */
63
+ target: "body",
64
+ /**
65
+ * Optional group to link remote toggles (toggles that follow the main one and can toggle too)
66
+ */
67
+ group: null,
68
+ /**
69
+ * Optional callback to do something when the state changes
70
+ */
71
+ onChange(_ctx) {},
72
+ /**
73
+ * The initial state for this component
74
+ * - May be overridden by saved preference or media query if options are enabled
75
+ */
76
+ initialState: "light",
77
+ /**
78
+ * Check the OS systems user preference via 'preferenceQuery' option
79
+ */
80
+ checkMediaQuery: false,
81
+ /**
82
+ * Will store the preference in local storage so it persists between page loads
83
+ */
84
+ savePreference: false,
85
+ /**
86
+ * The key that will be used to store the preference in local storage
87
+ * - This will be used as prefix in combination with group if defined
88
+ */
89
+ storagePrefix: "ulu-theme-",
90
+ /**
91
+ * Output information to console for debugging
92
+ */
93
+ debug: false
23
94
  };
24
95
 
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";
96
+
97
+ // Current default objects (user can override these)
98
+ let currentDefaults = { ...defaults };
99
+
100
+ /**
101
+ * @param {Object} options Change options used as default for dialogs, can then be overridden by data attribute settings on element
102
+ */
103
+ export function setDefaults(options) {
104
+ currentDefaults = Object.assign({}, currentDefaults, options);
105
+ }
29
106
 
30
107
  /**
31
108
  * Initialize everything in document
32
109
  * - This will only initialize elements once, it is safe to call on page changes
33
110
  */
34
111
  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();
112
+ initializer.init({
113
+ events: ["pageModified"],
114
+ withData: true,
115
+ setup({ element, data, initialize }) {
116
+ setupToggle(element, data);
117
+ initialize();
118
+ }
119
+ });
41
120
  }
42
121
 
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);
122
+ /**
123
+ * Sets up a single toggle
124
+ * @param {HTMLElement} toggle A toggle to be setup
125
+ */
126
+ export function setupToggle(toggle, userOptions) {
127
+ const options = Object.assign({}, defaults, userOptions);
128
+
129
+ if (!checkToggleProps(options)) {
130
+ console.error(`Missing a required option: ${ requiredToggleProps.join(", ") }`);
131
+ return;
132
+ }
133
+
134
+ const group = options.group;
135
+ const ctx = { toggle, options };
136
+ const initialKey = resolveInitial(options);
137
+
138
+ if (!initialKey) {
139
+ console.error("Unable to resolve initial key");
140
+ return;
141
+ }
142
+
143
+ setState(initialKey, ctx);
144
+
145
+ toggle.addEventListener("click", onToggleClick);
146
+
147
+ // Remotes listeners are attached initially and then we also
148
+ // update them vs toggles which would be updated by the main pageModified
149
+ // event in init
150
+ attachRemotes();
151
+ document.addEventListener(getName("pageModified"), attachRemotes);
152
+
153
+ /**
154
+ * Instance function to get the next theme in cycle
155
+ */
156
+ function toggleState(event) {
157
+ const targets = getElements(options.target);
158
+ const lastKey = targets[0].dataset.uluThemeToggleState;
159
+ const key = getNextThemeKey(lastKey, options);
160
+ if (!key) {
161
+ console.error("Issue getting next theme key");
162
+ return;
163
+ }
164
+ setState(key, { ...ctx, event });
165
+ }
166
+
167
+ /**
168
+ * Handler for click for both toggle and remote toggles
169
+ */
170
+ function onToggleClick(event) {
171
+ toggleState(event);
172
+ }
173
+
174
+ /**
175
+ * Utility to attach remote handlers
176
+ * - Used initially and when page is modified
177
+ */
178
+ function attachRemotes() {
179
+ if (!group) return;
180
+ const remotes = queryRemotesInitial(group);
181
+ remotes.forEach(remote => {
182
+ remote.addEventListener("click", onToggleClick);
183
+ initializer.initializeElement(remote);
54
184
  });
55
- });
56
- // Initial icon setup
57
- changeIcons();
185
+ }
186
+
187
+ /**
188
+ * This only cleans up remotes that are still in DOM
189
+ * - For ones that have been removed we don't store any references to them
190
+ */
191
+ function cleanupRemotes() {
192
+ if (!group) return;
193
+ const remotes = queryRemotesInitial(group);
194
+ remotes.forEach(remote => {
195
+ remote.removeEventListener("click", onToggleClick);
196
+ remote.removeAttribute(attrInit, "");
197
+ });
198
+ }
199
+
200
+ /**
201
+ * Function to cleanup listeners and remove init attributes
202
+ */
203
+ function destroy() {
204
+ toggle.removeEventListener("click", onToggleClick);
205
+ toggle.removeAttribute(attrInit, "");
206
+ cleanupRemotes();
207
+ document.removeEventListener(getName("pageModified"), attachRemotes);
208
+ }
209
+
210
+ return {
211
+ destroy,
212
+ toggle,
213
+ options,
214
+ toggleState,
215
+ setState(themeKey) {
216
+ setState(themeKey, ctx);
217
+ }
218
+ };
58
219
  }
59
220
 
221
+
222
+
60
223
  /**
61
- *
62
- * @param {Element} body Sets up initial theme on load based on user preference.
224
+ * Change the state of target/toggle
63
225
  */
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
- }
226
+ function setState(key, ctx) {
227
+ if (!key) {
228
+ console.error("Missing key");
229
+ return;
230
+ }
231
+
232
+ const { toggle, options } = ctx;
233
+ const { themes, group } = options;
234
+ const elements = {
235
+ targets: getElements(options.target),
236
+ toggles: [toggle, ...(group ? queryRemotes(group) : [])]
237
+ };
238
+
239
+ if (!elements.targets.length || !elements.toggles.length) {
240
+ console.error("Issue setting state, couldn't find needed elements", elements);
241
+ return;
242
+ }
243
+
244
+ const theme = themes[key];
245
+ const otherThemes = getOtherThemes(key, themes);
246
+ const stateCtx = {
247
+ ...ctx,
248
+ key,
249
+ elements,
250
+ theme,
251
+ otherThemes
252
+ };
253
+
254
+ if (options.debug) {
255
+ initializer.log("Set state context", stateCtx);
256
+ }
257
+
258
+ // Prepare classes to remove
259
+ const otherTargetClasses = concatThemeClasses(otherThemes, "targetClass");
260
+ const otherIconClasses = concatThemeClasses(otherThemes, "iconClass");
261
+
262
+ // Update all targets
263
+ elements.targets.forEach(element => {
264
+ element.setAttribute(attrState, key);
265
+ element.classList.remove(...otherTargetClasses);
266
+ element.classList.add(...resolveClasses(theme.targetClass));
267
+ });
268
+
269
+ // Update all toggles and inner children
270
+ elements.toggles.forEach(element => {
271
+ const label = element.querySelector(attrSelectorLabel);
272
+ const icon = element.querySelector(attrSelectorIcon);
273
+ if (label) {
274
+ label.textContent = theme.label;
275
+ }
276
+ if (icon) {
277
+ icon.classList.remove(...otherIconClasses);
278
+ icon.classList.add(...resolveClasses(theme.iconClass));
279
+ }
280
+ element.setAttribute(attrState, key);
281
+ });
282
+
283
+ // Optional callback if user want to set other things (ie. data-theme or something)
284
+ if (options.onChange) {
285
+ options.onChange(stateCtx);
286
+ }
287
+
288
+ if (options.savePreference) {
289
+ localStorage.setItem(getStorageKey(options), key);
290
+ }
74
291
  }
75
292
 
76
293
  /**
77
- *
78
- * @param {Element} body Changes the theme of the body.
294
+ * Function determines what the initial state is
295
+ * - Check OS preference, saved preference, or initialState depending on options
296
+ * @return {String} The resolved initial theme's key
79
297
  */
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;
298
+ function resolveInitial(options) {
299
+ const { savePreference, checkMediaQuery, themes, initialState } = options;
300
+ const storageKey = getStorageKey(options);
301
+ const saved = when(savePreference, () => localStorage.getItem(storageKey));
302
+ const mediaQueryPreference = when(checkMediaQuery, () => getMatchingThemeQuery(themes));
303
+ const resolved = saved || mediaQueryPreference || initialState;
304
+
305
+ if (options.debug) {
306
+ initializer.log("Preference Saved", saved);
307
+ initializer.log("Media Query Preference", mediaQueryPreference);
308
+ initializer.log("Initial State:", initialState);
309
+ }
310
+
311
+ if (!resolved) {
312
+ initializer.logError("Failed to resolve initial theme (pass 'initialState' to options)");
89
313
  }
90
- body.classList.remove(oldTheme);
91
- body.classList.add(newTheme);
92
- localStorage.setItem("data-theme", newTheme);
93
- currentTheme = newTheme;
94
- changeIcons();
314
+
315
+ return resolved;
95
316
  }
96
317
 
97
318
  /**
98
- *
99
- * @param {Element} body Used to check for theme.
100
- * @param {Element} context Used to find the icons.
319
+ * Check each theme for a matching media query
320
+ * @return {String} Matching theme key
101
321
  */
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;
322
+ function getMatchingThemeQuery(themes) {
323
+ const found = Object.entries(themes).find(([_key, theme]) => {
324
+ if (theme.mediaQuery) {
325
+ return window.matchMedia(theme.mediaQuery).matches;
109
326
  }
110
327
  });
328
+ // Return just the key
329
+ return found ? found[0] : null;
111
330
  }
112
331
 
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
- }
332
+ /**
333
+ * Get the next key in the themes based on the currentKey
334
+ */
335
+ function getNextThemeKey(activeKey, options) {
336
+ const { themes } = options;
337
+ const keys = Object.keys(themes);
338
+ const index = keys.findIndex(theme => theme === activeKey);
339
+ // If not found return first, else calculate next index (wrapping)
340
+ const nextIndex = index === -1 ? 0 : (index + 1) % keys.length;
341
+ return keys[nextIndex];
120
342
  }
121
343
 
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
- }
344
+ /**
345
+ * Get all other theme object except the current
346
+ */
347
+ function getOtherThemes(currentKey, themes) {
348
+ const all = Object.entries(themes);
349
+ return all.filter(([key]) => key !== currentKey).map(([_key, value]) => value);
350
+ }
351
+
352
+ /**
353
+ * Concatenates multiple class properties into one array
354
+ */
355
+ function concatThemeClasses(themes, property) {
356
+ return themes.reduce((acc, theme) => {
357
+ return acc.concat(resolveClasses(theme[property]));
358
+ }, []);
359
+ }
360
+
361
+ /**
362
+ * Creates the storage key (either prefix or prefix with group name)
363
+ */
364
+ function getStorageKey(options) {
365
+ const { storagePrefix, group } = options;
366
+ return group ? `${ storagePrefix }${ group }` : storagePrefix;
129
367
  }
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
@@ -2,53 +2,17 @@
2
2
  * @module utils/dom
3
3
  */
4
4
 
5
-
6
- export const regexJsonString = /^[{\[][\s\S]*[}\]]$/;
5
+ import { kebabToCamel } from "@ulu/utils/string.js";
7
6
 
8
7
  /**
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
8
+ * Converts a data attribute name to its corresponding dataset property name.
9
+ * @param {string} dataAttribute - The data attribute name (e.g., "data-ulu-dialog").
10
+ * @returns {string} - The dataset property name (e.g., "uluDialog").
13
11
  */
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
- }
12
+ export function dataAttributeToDatasetKey(attribute) {
13
+ return kebabToCamel(attribute.replace(/^data-/, ""));
22
14
  }
23
15
 
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
16
  /**
53
17
  * Sets up the positional classes that would come from the equal
54
18
  * height module. Needs to be rerun by user when layout changes
@@ -95,28 +59,23 @@ export function setPositionClasses(parent, classes = {
95
59
  }
96
60
 
97
61
  /**
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
62
+ * Resolves a class input (string or array) into a consistent array of class names.
63
+ * @param {string|string[]} input - The class input, which can be a string, an array of strings, or any other value.
64
+ * @returns {string[]} An array of class names. Returns an empty array for invalid or falsy input.
65
+ * @example
66
+ * resolveClassArray("fas fa-check my-class"); // Returns ["fas", "fa-check", "my-class"]
67
+ * resolveClassArray(["another-class", "yet-another-class"]); // Returns ["another-class", "yet-another-class"]
68
+ * resolveClassArray("single-class"); // Returns ["single-class"]
101
69
  */
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;
70
+ export function resolveClasses(classes) {
71
+ if (typeof classes === "string") {
72
+ return classes.split(" ").filter(c => c !== ""); // Split and remove empty strings
73
+ } else if (Array.isArray(classes)) {
74
+ return classes;
75
+ } else if (!classes) {
76
+ return [];
107
77
  } else {
108
- console.warn("Unable to getElement()", target);
109
- return null;
78
+ console.warn("resolveClassArray: Invalid class input type.", classes);
79
+ return [];
110
80
  }
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
- }
81
+ }