@ulu/frontend-vue 0.1.0-beta.1

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 (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +9 -0
  3. package/dist/breakpoints-ClT9bfZm.js +211 -0
  4. package/dist/frontend-vue.css +1 -0
  5. package/dist/frontend-vue.js +82 -0
  6. package/dist/frontend-vue.umd.cjs +561 -0
  7. package/dist/index-P5Rwl_Dl.js +7263 -0
  8. package/dist/index.es-HlG3u0J5.js +3134 -0
  9. package/lib/_index.scss +14 -0
  10. package/lib/components/_index.scss +6 -0
  11. package/lib/components/collapsible/UluAccordion.vue +82 -0
  12. package/lib/components/collapsible/UluCollapsibleRegion.vue +278 -0
  13. package/lib/components/collapsible/UluDropdown.vue +42 -0
  14. package/lib/components/collapsible/UluModal.vue +384 -0
  15. package/lib/components/collapsible/UluOverflowPopover.vue +52 -0
  16. package/lib/components/collapsible/UluTab.vue +9 -0
  17. package/lib/components/collapsible/UluTabGroup.vue +31 -0
  18. package/lib/components/collapsible/UluTabList.vue +9 -0
  19. package/lib/components/collapsible/UluTabPanel.vue +9 -0
  20. package/lib/components/collapsible/UluTabPanels.vue +9 -0
  21. package/lib/components/elements/UluAlert.vue +81 -0
  22. package/lib/components/elements/UluBadge.vue +58 -0
  23. package/lib/components/elements/UluBadgeStack.vue +27 -0
  24. package/lib/components/elements/UluButton.vue +161 -0
  25. package/lib/components/elements/UluCallout.vue +30 -0
  26. package/lib/components/elements/UluCard.vue +241 -0
  27. package/lib/components/elements/UluDefinitionList.vue +40 -0
  28. package/lib/components/elements/UluExternalLink.vue +47 -0
  29. package/lib/components/elements/UluIcon.vue +108 -0
  30. package/lib/components/elements/UluList.vue +87 -0
  31. package/lib/components/elements/UluMain.vue +5 -0
  32. package/lib/components/elements/UluSpokeSpinner.vue +25 -0
  33. package/lib/components/elements/UluTag.vue +53 -0
  34. package/lib/components/forms/UluCheckboxMenu.vue +36 -0
  35. package/lib/components/forms/UluFileDisplay.vue +39 -0
  36. package/lib/components/forms/UluFormDropzone.vue +62 -0
  37. package/lib/components/forms/UluFormFile.vue +47 -0
  38. package/lib/components/forms/UluFormMessage.vue +20 -0
  39. package/lib/components/forms/UluFormSelect.vue +37 -0
  40. package/lib/components/forms/UluFormText.vue +32 -0
  41. package/lib/components/forms/UluSearchForm.vue +31 -0
  42. package/lib/components/index.js +54 -0
  43. package/lib/components/layout/UluAdaptiveLayout.vue +11 -0
  44. package/lib/components/layout/UluDataGrid.vue +41 -0
  45. package/lib/components/layout/UluTitleRail.vue +56 -0
  46. package/lib/components/layout/UluWhenBreakpoint.vue +86 -0
  47. package/lib/components/navigation/UluBreadcrumb.vue +72 -0
  48. package/lib/components/navigation/UluMenu.vue +105 -0
  49. package/lib/components/navigation/UluMenuStack.vue +49 -0
  50. package/lib/components/navigation/UluNavStrip.vue +48 -0
  51. package/lib/components/navigation/UluSkipLink.vue +5 -0
  52. package/lib/components/systems/facets/UluFacets.vue +380 -0
  53. package/lib/components/systems/facets/UluFacetsList.vue +39 -0
  54. package/lib/components/systems/facets/UluFacetsSearch.vue +67 -0
  55. package/lib/components/systems/facets/_facets.scss +64 -0
  56. package/lib/components/systems/index.js +17 -0
  57. package/lib/components/systems/scroll-anchors/UluScrollAnchors.vue +152 -0
  58. package/lib/components/systems/scroll-anchors/UluScrollAnchorsNav.vue +37 -0
  59. package/lib/components/systems/scroll-anchors/UluScrollAnchorsNavAnimated.vue +124 -0
  60. package/lib/components/systems/scroll-anchors/UluScrollAnchorsSection.vue +63 -0
  61. package/lib/components/systems/scroll-anchors/symbols.js +6 -0
  62. package/lib/components/systems/skeleton/UluShowSkeleton.vue +13 -0
  63. package/lib/components/systems/skeleton/UluSkeletonContent.vue +60 -0
  64. package/lib/components/systems/skeleton/UluSkeletonMedia.vue +11 -0
  65. package/lib/components/systems/skeleton/UluSkeletonTextInline.vue +9 -0
  66. package/lib/components/systems/slider/UluImageSlideShow.vue +75 -0
  67. package/lib/components/systems/slider/UluSlideShow.vue +331 -0
  68. package/lib/components/systems/slider/UluSlideShowSlide.vue +25 -0
  69. package/lib/components/systems/table-sticky/UluTableSticky.vue +793 -0
  70. package/lib/components/systems/table-sticky/UluTableStickyRows.vue +73 -0
  71. package/lib/components/systems/table-sticky/UluTableStickyTable.vue +237 -0
  72. package/lib/components/systems/table-sticky/_table-sticky.scss +185 -0
  73. package/lib/components/utils/UluCondText.vue +28 -0
  74. package/lib/components/utils/UluEmpty.vue +3 -0
  75. package/lib/components/utils/UluEmptyView.vue +3 -0
  76. package/lib/components/utils/UluPlaceholderImage.vue +53 -0
  77. package/lib/components/utils/UluPlaceholderText.vue +25 -0
  78. package/lib/components/utils/UluRouteAnnouncer.vue +83 -0
  79. package/lib/components/visualizations/UluAnimateNumber.vue +32 -0
  80. package/lib/components/visualizations/UluProgressBar.vue +94 -0
  81. package/lib/components/visualizations/UluProgressDonut.vue +97 -0
  82. package/lib/composables/index.js +10 -0
  83. package/lib/composables/useBreakpointManager.js +68 -0
  84. package/lib/composables/useIcon.js +62 -0
  85. package/lib/composables/useModifiers.js +93 -0
  86. package/lib/composables/useWindowResize.js +64 -0
  87. package/lib/index.js +10 -0
  88. package/lib/plugins/_index.scss +7 -0
  89. package/lib/plugins/breakpoints/index.js +47 -0
  90. package/lib/plugins/index.js +11 -0
  91. package/lib/plugins/modals/UluModalsDisplay.vue +59 -0
  92. package/lib/plugins/modals/api.js +76 -0
  93. package/lib/plugins/modals/index.js +60 -0
  94. package/lib/plugins/modals/useModals.js +9 -0
  95. package/lib/plugins/popovers/UluPopover.vue +189 -0
  96. package/lib/plugins/popovers/UluTooltipDisplay.vue +15 -0
  97. package/lib/plugins/popovers/UluTooltipPopover.vue +83 -0
  98. package/lib/plugins/popovers/defaults.js +108 -0
  99. package/lib/plugins/popovers/directive.js +95 -0
  100. package/lib/plugins/popovers/index.js +18 -0
  101. package/lib/plugins/popovers/manager.js +54 -0
  102. package/lib/plugins/popovers/useFollow.js +80 -0
  103. package/lib/plugins/popovers/utils.js +5 -0
  104. package/lib/plugins/toast/UluToast.vue +87 -0
  105. package/lib/plugins/toast/UluToastDisplay.vue +35 -0
  106. package/lib/plugins/toast/_toast.scss +198 -0
  107. package/lib/plugins/toast/defaults.js +30 -0
  108. package/lib/plugins/toast/index.js +17 -0
  109. package/lib/plugins/toast/store.js +71 -0
  110. package/lib/plugins/toast/useToast.js +18 -0
  111. package/lib/settings.js +119 -0
  112. package/lib/utils/dom.js +14 -0
  113. package/lib/utils/placeholder.js +6 -0
  114. package/lib/utils/vue-router.js +219 -0
  115. package/package.json +75 -0
@@ -0,0 +1,76 @@
1
+ import { ref, markRaw } from "vue";
2
+
3
+ // Create array to be used internally to manage individual modals
4
+ const modals = [];
5
+
6
+ /**
7
+ * Reactive State Object (used inside global components)
8
+ */
9
+ const state = ref({
10
+ /**
11
+ * Holds active component options (including component, and options)
12
+ */
13
+ active: null,
14
+ /**
15
+ * Populated with any props passed to open method, bound to modal component
16
+ */
17
+ activeProps: null,
18
+ });
19
+
20
+ /**
21
+ * Reactive data from state
22
+ */
23
+ const data = state.value;
24
+
25
+ export const modalsState = {
26
+ data,
27
+ modals,
28
+ };
29
+
30
+ export const createApi = (resolveModalOptions) => ({
31
+ open(name, props = null) {
32
+ const modal = this.get(name);
33
+ data.active = markRaw(modal);
34
+ data.activeProps = Object.assign({}, modal.props, props);
35
+ },
36
+ /**
37
+ * Close the active modal
38
+ * @param {String|Node} focusTo The element or selector for an element to programmatically focus after modal close
39
+ * @see https://www.deque.com/blog/accessible-routing-in-javascript-frameworks/
40
+ */
41
+ close() {
42
+ data.active = null;
43
+ data.activeProps = null;
44
+ },
45
+ /**
46
+ * Get a modal's config object by name
47
+ * @return {Object} Modal config object
48
+ */
49
+ get(name) {
50
+ const modal = modals.find(m => m.name === name);
51
+ if (modal) {
52
+ return modal;
53
+ } else {
54
+ throw new Error(`Unable to find modal named: ${ name }`);
55
+ }
56
+ },
57
+ /**
58
+ * Add a modal config
59
+ */
60
+ add(config) {
61
+ const resolved = resolveModalOptions(config);
62
+ modals.push(resolved);
63
+ },
64
+ /**
65
+ * Removes a modal config by name
66
+ * @return {Object} Modal that was removed
67
+ */
68
+ remove(name) {
69
+ const index = modals.findIndex(m => m.name === name);
70
+ if (index > -1) {
71
+ return modals.splice(index, 1);
72
+ } else {
73
+ warn("unable to find modal to remove");
74
+ }
75
+ }
76
+ });
@@ -0,0 +1,60 @@
1
+ /**
2
+ * @module plugins/modals/index.js
3
+ * @version 3.0.0
4
+ * Modals plugin (adds components, global registry, etc)
5
+ * - Updated version handles both independent and global registered/triggered modals
6
+ */
7
+
8
+ import UluModalsDisplay from "./UluModalsDisplay.vue";
9
+ import UluModal from "../../components/collapsible/UluModal.vue";
10
+ import { createApi, modalsState } from "./api.js";
11
+
12
+ /**
13
+ * Default plugin options
14
+ * @typedef {Object} UluModalsPluginOptions
15
+ * @property {String} componentNameDisplay Name for modals component that displays app-wide modals
16
+ * @property {String} componentNameModal Name for modal component
17
+ * @property {Array} modals Modals configs [{ name, component, props }]
18
+ * @property {UluModalOptions} modalOptions Options to merge into individual modal options (to serve as defaults for each modal, see UluModalOptions)
19
+ */
20
+ const pluginDefaults = {
21
+ componentNameDisplay: "UluModalsDisplay",
22
+ componentNameModal: "UluModal",
23
+ modals: [],
24
+ modalOptions: {}
25
+ };
26
+
27
+ /**
28
+ * Modals Vue Plugin
29
+ * @param {Object} App Vue app instance passed to plugin
30
+ * @param {UluModalsPluginOptions} userOptions Options to change (see defaults)
31
+ */
32
+ export default function install(app, userOptions) {
33
+ const options = Object.assign({}, pluginDefaults, userOptions);
34
+
35
+ // Merges in default modal config options on individual modal config
36
+ const resolveModalOptions = opts => Object.assign({}, options.modalOptions, opts);
37
+
38
+ const api = createApi(resolveModalOptions);
39
+
40
+ // Register the two global components
41
+ app.component(options.componentNameDisplay, UluModalsDisplay);
42
+ app.component(options.componentNameModal, UluModal);
43
+
44
+ // Create array to be used internally to manage individual modals
45
+ options.modals.forEach(config => {
46
+ api.add(config)
47
+ });
48
+
49
+ modalsState.options = options;
50
+
51
+ // Global property with API so user can interact with modals
52
+ app.config.globalProperties.$uluModals = api;
53
+
54
+ // Provide the api for composition api usage
55
+ app.provide('uluModals', api);
56
+
57
+ // Global property for modals component to access state
58
+ // and for debugging logging if needed
59
+ app.config.globalProperties.$uluModalsState = modalsState;
60
+ }
@@ -0,0 +1,9 @@
1
+ import { inject } from 'vue';
2
+
3
+ export const useModals = () => {
4
+ const modals = inject('uluModals');
5
+ if (!modals) {
6
+ throw new Error('Modals plugin not installed');
7
+ }
8
+ return modals;
9
+ };
@@ -0,0 +1,189 @@
1
+ <!-- NOTE: Need to rename classes when moving this into the library -->
2
+ <template>
3
+ <button
4
+ type="button"
5
+ ref="trigger"
6
+ @click="toggle"
7
+ :disabled="disabled"
8
+ :class="[
9
+ { [activeClass] : isOpen },
10
+ classes.trigger
11
+ ]"
12
+ :aria-expanded="isOpen ? 'true' : 'false'"
13
+ :aria-controls="id"
14
+ :aria-label="triggerAlt"
15
+ v-ulu-tooltip="tooltip ? tooltip : null"
16
+ >
17
+ <slot name="trigger" :isOpen="isOpen"/>
18
+ </button>
19
+ <span
20
+ class="popover"
21
+ ref="content"
22
+ :class="[
23
+ size ? `popover--${ size }` : '',
24
+ {
25
+ 'popover--no-padding' : noPadding,
26
+ 'is-active' : isOpen
27
+ },
28
+ classes.content,
29
+ ]"
30
+ :aria-hidden="isOpen ? 'false' : 'true'"
31
+ :id="id"
32
+ :style="floatingStyles"
33
+ :data-placement="placement"
34
+ @keydown.esc="changeTo(false)"
35
+ tabindex="-1"
36
+ >
37
+ <span class="popover__inner">
38
+ <slot name="content" :close="close"/>
39
+ </span>
40
+ <span v-if="$slots.footer" class="popover__footer">
41
+ <slot name="footer" :close="close"/>
42
+ </span>
43
+ <span
44
+ v-if="config.arrow"
45
+ class="popover__arrow"
46
+ ref="contentArrow"
47
+ :style="arrowStyles"
48
+ data-ulu-popover-arrow
49
+ ></span>
50
+ </span>
51
+ </template>
52
+ <script setup>
53
+ import { computed, ref, unref, nextTick } from "vue";
54
+ import { options as defaults } from "./manager.js";
55
+ import { newUid } from "./utils.js";
56
+ import {
57
+ useFloating,
58
+ autoUpdate,
59
+ offset,
60
+ inline,
61
+ flip,
62
+ shift,
63
+ arrow,
64
+ } from "@floating-ui/vue";
65
+
66
+ const emit = defineEmits(["toggle"]);
67
+ const props = defineProps({
68
+ triggerAlt: String,
69
+ disabled: Boolean,
70
+ tooltip: String,
71
+ size: String,
72
+ noPadding: Boolean,
73
+ config: Object,
74
+ startOpen: Boolean,
75
+ activeClass: {
76
+ type: String,
77
+ default: "is-active"
78
+ },
79
+ classes: {
80
+ type: Object,
81
+ default: () => ({})
82
+ },
83
+ clickOutsideCloses: {
84
+ type: Boolean,
85
+ default: true
86
+ },
87
+ directFocus: {
88
+ type: Function,
89
+ default: ({ isOpen, content }) => {
90
+ if (isOpen) {
91
+ content.focus({ preventScroll: true });
92
+ }
93
+ }
94
+ }
95
+ });
96
+
97
+ const id = newUid();
98
+ const config = Object.assign({}, defaults.popover, props.config);
99
+ const isOpen = ref(props.startOpen || false);
100
+ const trigger = ref(null);
101
+ const content = ref(null);
102
+ const contentArrow = ref(null);
103
+
104
+ const middleware = [
105
+ ...(config.inline ? [ inline() ] : []),
106
+ ...(config.offset ? [ offset(config.offset) ] : []),
107
+ flip(),
108
+ shift(),
109
+ ...(config.arrow ? [ arrow({ element: contentArrow }) ] : []),
110
+ ];
111
+ const options = {
112
+ placement: config.placement,
113
+ whileElementsMounted: autoUpdate,
114
+ middleware
115
+ };
116
+
117
+ const {
118
+ floatingStyles,
119
+ placement,
120
+ middlewareData,
121
+ update,
122
+ isPositioned,
123
+ } = useFloating(trigger, content, options);
124
+
125
+ const arrowStyles = computed(() => {
126
+ const pos = middlewareData.value?.arrow;
127
+ if (!pos) return null;
128
+ return {
129
+ position: "absolute",
130
+ left: pos?.x != null ? `${ pos.x }px` : "",
131
+ top: pos?.y != null ? `${ pos.y }px` : "",
132
+ };
133
+ });
134
+
135
+ if (config.onReady) {
136
+ config.onReady({ update, isPositioned });
137
+ }
138
+
139
+ const toggle = () => {
140
+ changeTo(!isOpen.value);
141
+ };
142
+
143
+ const changeTo = (toOpen) => {
144
+ isOpen.value = toOpen;
145
+ const focusArgs = {
146
+ trigger: unref(trigger),
147
+ content: unref(content),
148
+ isOpen: unref(isOpen)
149
+ };
150
+ const eventArgs = { isOpen: focusArgs.isOpen };
151
+ nextTick(() => {
152
+ if (isOpen.value) {
153
+ update();
154
+ // Push to next event, without this will get triggered by the original click event
155
+ window.setTimeout(() => {
156
+ addOutsideClick();
157
+ props.directFocus(focusArgs);
158
+ emit("toggle", eventArgs);
159
+ }, 0);
160
+ } else {
161
+ destroyOutsideClick();
162
+ props.directFocus(focusArgs);
163
+ emit("toggle", eventArgs);
164
+ }
165
+ });
166
+ };
167
+
168
+ let outsideHandler;
169
+ const addOutsideClick = () => {
170
+ if (props.clickOutsideCloses) {
171
+ if (outsideHandler) {
172
+ destroyOutsideClick();
173
+ }
174
+ outsideHandler = event => {
175
+ if (!content.value.contains(event.target)) {
176
+ changeTo(false);
177
+ }
178
+ };
179
+ document.addEventListener("click", outsideHandler);
180
+ }
181
+ };
182
+ const destroyOutsideClick = () => {
183
+ if (outsideHandler) {
184
+ document.removeEventListener("click", outsideHandler);
185
+ outsideHandler = null;
186
+ }
187
+ };
188
+ const close = () => changeTo(false);
189
+ </script>
@@ -0,0 +1,15 @@
1
+ <!--
2
+ Note:
3
+ Using the name TooltipDisplay incase we want to create abstract
4
+ component (wrapper, renderless, etc) in the future
5
+ -->
6
+ <template>
7
+ <Teleport :to="options.plugin.tooltipTeleportTo">
8
+ <TooltipPopover v-if="active" :config="activeConfig"/>
9
+ </Teleport>
10
+ </template>
11
+
12
+ <script setup>
13
+ import { active, activeConfig, options } from "./manager.js";
14
+ import TooltipPopover from "./UluTooltipPopover.vue";
15
+ </script>
@@ -0,0 +1,83 @@
1
+ <!-- NOTE: Need to rename classes when moving this into the library -->
2
+ <template>
3
+ <span
4
+ class="popover popover--tooltip is-active"
5
+ ref="content"
6
+ aria-hidden="true"
7
+ :data-placement="placement"
8
+ :class="config.class"
9
+ :style="floatingStyles"
10
+ >
11
+ <span
12
+ v-if="config.isHtml"
13
+ class="popover__inner"
14
+ v-html="config.content"
15
+ >
16
+ </span>
17
+ <span v-else class="popover__inner">
18
+ {{ config.content }}
19
+ </span>
20
+ <span
21
+ v-if="config.arrow"
22
+ class="popover__arrow"
23
+ ref="contentArrow"
24
+ :style="arrowStyles"
25
+ ></span>
26
+ </span>
27
+ </template>
28
+
29
+ <script setup>
30
+ import { ref, toRef, computed } from "vue";
31
+ import {
32
+ useFloating,
33
+ autoUpdate,
34
+ offset,
35
+ inline,
36
+ flip,
37
+ shift,
38
+ arrow,
39
+ } from "@floating-ui/vue";
40
+
41
+ const { config } = defineProps({
42
+ config: Object
43
+ });
44
+ const trigger = toRef(config.trigger);
45
+ const content = ref(null);
46
+ const contentArrow = ref(null);
47
+ const middleware = [
48
+ ...(config.inline ? [ inline() ] : []),
49
+ ...(config.offset ? [ offset(config.offset) ] : []),
50
+ flip(),
51
+ shift(),
52
+ ...(config.arrow ? [ arrow({ element: contentArrow }) ] : []),
53
+ ];
54
+
55
+ const options = {
56
+ placement: config.placement,
57
+ whileElementsMounted: autoUpdate,
58
+ middleware
59
+ };
60
+
61
+ const {
62
+ floatingStyles,
63
+ placement,
64
+ middlewareData,
65
+ update,
66
+ isPositioned,
67
+ } = useFloating(trigger, content, options);
68
+
69
+ const arrowStyles = computed(() => {
70
+ const pos = middlewareData.value?.arrow;
71
+ if (!pos) return null;
72
+ return {
73
+ position: "absolute",
74
+ left: pos?.x != null ? `${ pos.x }px` : "",
75
+ top: pos?.y != null ? `${ pos.y }px` : "",
76
+ };
77
+ });
78
+
79
+ if (config.onReady) {
80
+ config.onReady({ update, isPositioned });
81
+ }
82
+
83
+ </script>
@@ -0,0 +1,108 @@
1
+ export default {
2
+ /**
3
+ * Default Plugin Options
4
+ * @type {Object}
5
+ */
6
+ plugin: {
7
+ /**
8
+ * Whether to install all components globally in Vue and add the directive for
9
+ * tooltips globally in Vue
10
+ */
11
+ global: true,
12
+ /**
13
+ * The directive name to use (default 'tooltip' = <el v-ulu-tooltip="'hello world'">)
14
+ * @type {String}
15
+ */
16
+ directiveName: "ulu-tooltip",
17
+ /**
18
+ * The element that the tooltip should be rendered within
19
+ * - Default bottom of the body (on top of everything)
20
+ * - Doesn't need to be inline for accessibility since tooltips are just an enhancement
21
+ * content displayed within them should be hidden for assistive devices,
22
+ * they are not visible to assistive devices
23
+ * @type {String}
24
+ */
25
+ tooltipTeleportTo: "body",
26
+ },
27
+ /**
28
+ * Default Popover Options
29
+ */
30
+ popover: {
31
+ /**
32
+ * Include the floating-ui inline middleware (for inline elements that wrap)
33
+ * @type {Boolean}
34
+ */
35
+ inline: true,
36
+ /**
37
+ * Delay when using the directive
38
+ * @type {Number}
39
+ */
40
+ delay: 500,
41
+ /**
42
+ * Placement for floating-ui)
43
+ * @type {String}
44
+ */
45
+ placement: "bottom",
46
+ /**
47
+ * Strategy for floating-ui (strategy)
48
+ * @type {String}
49
+ */
50
+ strategy: "absolute",
51
+ /**
52
+ * Include the floating-ui offset middleware,
53
+ * @type {Number}
54
+ */
55
+ offset: 16,
56
+ /**
57
+ * Include the floating-ui arrow middleware
58
+ * @type {Boolean}
59
+ */
60
+ arrow: true,
61
+ },
62
+ /**
63
+ * Default Tooltip Options
64
+ * @type {Object}
65
+ */
66
+ tooltip: {
67
+ /**
68
+ * Optional class binding for tooltip element
69
+ * @type {String|Object|Array}
70
+ */
71
+ class: null,
72
+ /**
73
+ * Events to show tooltip on
74
+ * @type {Array.<String>}
75
+ */
76
+ showEvents: ["pointerenter", "focus"],
77
+ /**
78
+ * Events to hide tooltip on
79
+ * @type {Array.<String>}
80
+ */
81
+ hideEvents: ["pointerleave", "blur"],
82
+ /**
83
+ * Content should be output as plain HTML (ie v-html)
84
+ * - Note don't include interactive elements in tooltips!
85
+ * @type {Boolean}
86
+ */
87
+ isHtml: false,
88
+ /**
89
+ * Element for floating ui to use as reference (can be virtual) or vue ref to element
90
+ * @type {Node|Object}
91
+ */
92
+ trigger: null,
93
+ /**
94
+ * The content of the tooltip (String, Reactive ref or HTML [see isHtml option])
95
+ * @type {String|Object}
96
+ */
97
+ content: null,
98
+ /**
99
+ * Delay when using the directive
100
+ * @type {Number}
101
+ */
102
+ delay: 500,
103
+ /**
104
+ * Callback that is passed { update, isPositioned } for manual things
105
+ */
106
+ onReady: null,
107
+ },
108
+ };
@@ -0,0 +1,95 @@
1
+ import { show, hide, createConfig } from "./manager.js";
2
+ /**
3
+ * Using weak map for listener to element relationship
4
+ * - As an extra fallback to remove listeners without affecting garabage collection
5
+ * - The unmount should be called for every element the directive is bound to but
6
+ */
7
+ const cache = new WeakMap();
8
+ /**
9
+ * - We should consider allowing components to be passed for markup
10
+ * - don't want to use a component as then everything that uses this will have complicated conditionals
11
+ * ie. menu component could just have an option to bind the directive but if component needs conditional when not being used
12
+ */
13
+ /**
14
+ * Directive Object
15
+ */
16
+ export default {
17
+ mounted(trigger, binding) {
18
+ setup(trigger, binding);
19
+ },
20
+ beforeUpdate(trigger) {
21
+ removeListeners(trigger);
22
+ },
23
+ updated(trigger, binding) {
24
+ setup(trigger, binding);
25
+ },
26
+ umounted(trigger) {
27
+ removeListeners(trigger);
28
+ }
29
+ };
30
+ /**
31
+ * Resolves the users local config and attaches handlers
32
+ */
33
+ function setup(trigger, binding) {
34
+ const config = resolveConfig(trigger, binding);
35
+ if (!config) return;
36
+ let tid = null;
37
+
38
+ const onShow = () => {
39
+ if (tid) return;
40
+ tid = setTimeout(() => {
41
+ show(config);
42
+ clearTimeout(tid);
43
+ }, config.delay);
44
+ };
45
+
46
+ const onHide = () => {
47
+ if (tid) {
48
+ clearTimeout(tid);
49
+ tid = null;
50
+ }
51
+ hide(config);
52
+ };
53
+
54
+ config.showEvents.forEach(eventName => {
55
+ trigger.addEventListener(eventName, onShow);
56
+ });
57
+ config.hideEvents.forEach(eventName => {
58
+ trigger.addEventListener(eventName, onHide);
59
+ });
60
+ cache.set(trigger, { onShow, onHide, config });
61
+ }
62
+ /**
63
+ * Removes an elements listeners by checking agains local cache map
64
+ */
65
+ function removeListeners(trigger) {
66
+ if (!cache.has(trigger)) {
67
+ return;
68
+ }
69
+ const { config, onShow, onHide } = cache.get(trigger);
70
+ config.showEvents.forEach(eventName => {
71
+ trigger.removeEventListener(eventName, onShow);
72
+ });
73
+ config.hideEvents.forEach(eventName => {
74
+ trigger.removeEventListener(eventName, onHide);
75
+ });
76
+ cache.delete(trigger);
77
+ }
78
+ /**
79
+ * Allow user to provide a simple string for the content of the toolkit
80
+ * or the extended object syntax. This resolves to the object syntax
81
+ */
82
+ function resolveConfig(trigger, binding) {
83
+ const { value } = binding;
84
+ let config;
85
+ if (value === false || value === null) {
86
+ return; // Disabled
87
+ } else if (typeof value === "object") {
88
+ config = value;
89
+ // String/Number, etc (displayed as is)
90
+ } else {
91
+ config = { content: value };
92
+ }
93
+ // Using assign so users can override trigger if needed
94
+ return createConfig(Object.assign({}, { trigger }, config));
95
+ }
@@ -0,0 +1,18 @@
1
+ import { init } from "./manager.js";
2
+ import directive from "./directive.js";
3
+ import UluPopover from "./UluPopover.vue";
4
+ import UluTooltipDisplay from "./UluTooltipDisplay.vue";
5
+
6
+ /**
7
+ * Install plugin
8
+ * - Set user options
9
+ * - Add the global directive for the user to trigger tooltips
10
+ */
11
+ export default function install(app, userOptions = {}) {
12
+ const options = init(userOptions);
13
+ if (options.plugin.global) {
14
+ app.directive(options.plugin.directiveName, directive);
15
+ app.component("UluTooltipDisplay", UluTooltipDisplay);
16
+ app.component("UluPopover", UluPopover);
17
+ }
18
+ }