@ulu/frontend-vue 0.1.0-beta.9 → 0.1.1-beta.2
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.
- package/dist/{breakpoints-BbkGNxxt.js → breakpoints-DfGETUy5.js} +1 -1
- package/dist/frontend-vue.css +1 -1
- package/dist/frontend-vue.js +79 -68
- package/dist/index-94HkwBnP.js +7595 -0
- package/lib/components/collapsible/UluAccordion.vue +71 -53
- package/lib/components/collapsible/UluAccordionGroup.vue +54 -0
- package/lib/components/collapsible/UluCollapsible.vue +144 -0
- package/lib/components/collapsible/UluDropdown.vue +29 -29
- package/lib/components/collapsible/UluOverflowPopover.vue +1 -1
- package/lib/components/elements/UluBadge.vue +51 -28
- package/lib/components/elements/UluBadgeStack.vue +8 -13
- package/lib/components/elements/UluButtonVerbose.vue +119 -0
- package/lib/components/elements/UluCard.vue +1 -1
- package/lib/components/elements/UluDefinitionList.vue +14 -17
- package/lib/components/elements/UluExternalLink.vue +21 -27
- package/lib/components/elements/UluIcon.vue +11 -1
- package/lib/components/elements/UluList.vue +53 -55
- package/lib/components/elements/UluSpokeSpinner.vue +12 -18
- package/lib/components/elements/UluTag.vue +35 -35
- package/lib/components/forms/UluFileDisplay.vue +49 -31
- package/lib/components/forms/UluFormFile.vue +37 -24
- package/lib/components/forms/UluFormMessage.vue +13 -10
- package/lib/components/forms/UluFormSelect.vue +28 -16
- package/lib/components/forms/UluFormText.vue +24 -15
- package/lib/components/forms/UluSearchForm.vue +11 -10
- package/lib/components/forms/UluSelectableMenu.vue +99 -0
- package/lib/components/index.js +4 -3
- package/lib/components/layout/UluTitleRail.vue +18 -0
- package/lib/components/layout/UluWhenBreakpoint.vue +9 -0
- package/lib/components/navigation/UluBreadcrumb.vue +9 -2
- package/lib/components/navigation/UluMenu.vue +8 -3
- package/lib/components/navigation/UluMenuStack.vue +3 -1
- package/lib/components/navigation/UluPager.vue +102 -0
- package/lib/components/systems/facets/ExampleFacetsWithPagination.vue +119 -0
- package/lib/components/systems/facets/UluFacetsFilterLists.vue +91 -0
- package/lib/components/systems/facets/UluFacetsFilterPopovers.vue +125 -0
- package/lib/components/systems/facets/UluFacetsFilterSelects.vue +71 -0
- package/lib/components/systems/facets/UluFacetsHeaderLayout.vue +24 -0
- package/lib/components/systems/facets/UluFacetsList.vue +62 -34
- package/lib/components/systems/facets/UluFacetsResults.vue +63 -0
- package/lib/components/systems/facets/UluFacetsSearch.vue +27 -50
- package/lib/components/systems/facets/UluFacetsSidebarLayout.vue +70 -0
- package/lib/components/systems/facets/UluFacetsSort.vue +45 -0
- package/lib/components/systems/facets/_facets.scss +2 -3
- package/lib/components/systems/facets/_mock-data.js +40 -0
- package/lib/components/systems/facets/useFacets.js +268 -0
- package/lib/components/systems/index.js +13 -2
- package/lib/components/systems/scroll-anchors/UluScrollAnchors.vue +2 -1
- package/lib/components/systems/skeleton/UluShowSkeleton.vue +9 -8
- package/lib/components/systems/skeleton/UluSkeletonContent.vue +39 -43
- package/lib/components/systems/skeleton/UluSkeletonMedia.vue +4 -6
- package/lib/components/systems/skeleton/UluSkeletonText.vue +27 -0
- package/lib/components/systems/slider/UluImageSlideShow.vue +1 -1
- package/lib/components/systems/slider/UluSlideShow.vue +8 -3
- package/lib/components/systems/table-sticky/UluTableSticky.vue +7 -7
- package/lib/components/systems/table-sticky/UluTableStickyTable.vue +3 -3
- package/lib/components/visualizations/UluAnimateNumber.vue +7 -1
- package/lib/components/visualizations/UluProgressBar.vue +148 -74
- package/lib/components/visualizations/UluProgressCircle.vue +159 -0
- package/lib/composables/index.js +3 -1
- package/lib/composables/useDocumentTitle.js +61 -0
- package/lib/composables/usePagination.js +122 -0
- package/lib/index.js +1 -0
- package/lib/plugins/core/index.js +6 -1
- package/lib/plugins/popovers/UluPopover.vue +8 -3
- package/lib/plugins/toast/UluToast.vue +1 -1
- package/lib/plugins/toast/UluToastDisplay.vue +19 -2
- package/lib/utils/dom.js +12 -0
- package/lib/utils/index.js +2 -0
- package/lib/utils/{vue-router.js → router.js} +114 -30
- package/package.json +17 -11
- package/types/components/systems/facets/_mock-data.d.ts +18 -0
- package/types/components/systems/facets/_mock-data.d.ts.map +1 -0
- package/types/components/systems/facets/useFacets.d.ts +39 -0
- package/types/components/systems/facets/useFacets.d.ts.map +1 -0
- package/types/components/systems/index.d.ts +1 -1
- package/types/composables/index.d.ts +2 -0
- package/types/composables/useDocumentTitle.d.ts +22 -0
- package/types/composables/useDocumentTitle.d.ts.map +1 -0
- package/types/composables/usePageTitle.d.ts +19 -0
- package/types/composables/usePageTitle.d.ts.map +1 -0
- package/types/composables/usePagination.d.ts +25 -0
- package/types/composables/usePagination.d.ts.map +1 -0
- package/types/index.d.ts +1 -0
- package/types/plugins/core/index.d.ts.map +1 -1
- package/types/utils/dom.d.ts +1 -0
- package/types/utils/dom.d.ts.map +1 -1
- package/types/utils/index.d.ts +3 -0
- package/types/utils/index.d.ts.map +1 -0
- package/types/utils/router.d.ts +144 -0
- package/types/utils/router.d.ts.map +1 -0
- package/dist/index-D3Uc6T5M.js +0 -6469
- package/lib/components/collapsible/UluCollapsibleRegion.vue +0 -278
- package/lib/components/forms/UluCheckboxMenu.vue +0 -36
- package/lib/components/systems/facets/UluFacets.vue +0 -380
- package/lib/components/systems/skeleton/UluSkeletonTextInline.vue +0 -9
- package/lib/components/visualizations/UluProgressDonut.vue +0 -97
- package/lib/utils/placeholder.js +0 -6
|
@@ -1,278 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div
|
|
3
|
-
class="CollapsibleRegion"
|
|
4
|
-
@keydown.esc="handleEscape"
|
|
5
|
-
:class="{
|
|
6
|
-
'CollapsibleRegion--open' : isOpen,
|
|
7
|
-
'CollapsibleRegion--closed' : !isOpen,
|
|
8
|
-
'CollapsibleRegion--transitioning' : isTransitioning
|
|
9
|
-
}"
|
|
10
|
-
>
|
|
11
|
-
<button
|
|
12
|
-
class="CollapsibleRegion__toggle"
|
|
13
|
-
:id="toggleId"
|
|
14
|
-
:aria-controls="contentId"
|
|
15
|
-
:aria-expanded="isOpen"
|
|
16
|
-
@click="toggle"
|
|
17
|
-
>
|
|
18
|
-
<slot name="toggle" :isOpen="isOpen">
|
|
19
|
-
{{ title }}
|
|
20
|
-
</slot>
|
|
21
|
-
</button>
|
|
22
|
-
<div
|
|
23
|
-
class="CollapsibleRegion__content"
|
|
24
|
-
tabindex="-1"
|
|
25
|
-
ref="content"
|
|
26
|
-
:id="contentId"
|
|
27
|
-
:aria-hidden="!isOpen"
|
|
28
|
-
:aria-labelledby="toggleId"
|
|
29
|
-
:style="contentStyles"
|
|
30
|
-
v-show="!isHidden"
|
|
31
|
-
>
|
|
32
|
-
<!--
|
|
33
|
-
Using inner container to allow no styles on content container
|
|
34
|
-
as they interfere with getting accurate measurements of the content
|
|
35
|
-
when it's hidden (scrollHeight)
|
|
36
|
-
-->
|
|
37
|
-
<div class="CollapsibleRegion__content-inner">
|
|
38
|
-
<slot/>
|
|
39
|
-
</div>
|
|
40
|
-
</div>
|
|
41
|
-
</div>
|
|
42
|
-
</template>
|
|
43
|
-
|
|
44
|
-
<script>
|
|
45
|
-
|
|
46
|
-
let uid = 0;
|
|
47
|
-
/**
|
|
48
|
-
* Utility component for creating disclosure type behaviors (show/hide)
|
|
49
|
-
*/
|
|
50
|
-
export default {
|
|
51
|
-
name: "UluCollapsibleRegion",
|
|
52
|
-
props: {
|
|
53
|
-
/**
|
|
54
|
-
* Set title for toggle (instead of using slot)
|
|
55
|
-
*/
|
|
56
|
-
title: String,
|
|
57
|
-
/**
|
|
58
|
-
* Closes with escape key
|
|
59
|
-
*/
|
|
60
|
-
closeOnEscape: Boolean,
|
|
61
|
-
/**
|
|
62
|
-
* When the component is shown it should start visible or hidden
|
|
63
|
-
*/
|
|
64
|
-
startOpen: Boolean,
|
|
65
|
-
/**
|
|
66
|
-
* Whether or not to transition the show and hide
|
|
67
|
-
*/
|
|
68
|
-
transitionHeight: Boolean,
|
|
69
|
-
/**
|
|
70
|
-
* Transition should fade as it expands
|
|
71
|
-
*/
|
|
72
|
-
transitionFades: Boolean,
|
|
73
|
-
/**
|
|
74
|
-
* Transition Timing Function
|
|
75
|
-
*/
|
|
76
|
-
transitionTiming: {
|
|
77
|
-
type: String,
|
|
78
|
-
default: "ease-out"
|
|
79
|
-
},
|
|
80
|
-
/**
|
|
81
|
-
* Transition Duration (css duration string), use comma seperation if different for opacity (fade).
|
|
82
|
-
* Note: This is used to calculate a fallback timer if transitions fail
|
|
83
|
-
*/
|
|
84
|
-
transitionDuration: {
|
|
85
|
-
type: String,
|
|
86
|
-
default: "400ms",
|
|
87
|
-
validator(value) {
|
|
88
|
-
// Make sure that it's a valid css duration (ms|s)
|
|
89
|
-
return value.includes("s");
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
},
|
|
93
|
-
data() {
|
|
94
|
-
const isOpen = this.startOpen;
|
|
95
|
-
// Note (isOpen vs isHidden): 'isOpen' is the actaul state of the content,
|
|
96
|
-
// and 'isHidden' is just used for display none
|
|
97
|
-
return {
|
|
98
|
-
isOpen,
|
|
99
|
-
toggleId: this.getUid(),
|
|
100
|
-
contentId: this.getUid(),
|
|
101
|
-
contentHeight: isOpen ? "auto" : "0px",
|
|
102
|
-
contentOpacity: this.transitionFades && !isOpen ? 0 : 1,
|
|
103
|
-
transitionsDisabled: false,
|
|
104
|
-
transitionTimeout: Math.ceil(this.getUnitlessDuration(this.transitionDuration) + 500),
|
|
105
|
-
isTransitioning: false,
|
|
106
|
-
isHidden: isOpen ? false : true,
|
|
107
|
-
onCleanupTransition: null, // Transitions add function here used if needing to cancel
|
|
108
|
-
};
|
|
109
|
-
},
|
|
110
|
-
computed: {
|
|
111
|
-
contentStyles() {
|
|
112
|
-
if (this.transitionHeight) {
|
|
113
|
-
return {
|
|
114
|
-
overflow: "hidden",
|
|
115
|
-
height: this.contentHeight,
|
|
116
|
-
transitionDuration: this.transitionDuration,
|
|
117
|
-
transitionTiming: this.transitionTiming,
|
|
118
|
-
opacity: this.contentOpacity,
|
|
119
|
-
transitionProperty: this.transitionsDisabled ? "none" : `height${ this.transitionFades ? ",opacity" : "" }`
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
return {};
|
|
123
|
-
}
|
|
124
|
-
},
|
|
125
|
-
methods: {
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Function used to toggle the collapsible
|
|
129
|
-
*/
|
|
130
|
-
toggle() {
|
|
131
|
-
// console.log('Toggle Click', this.isOpen);
|
|
132
|
-
if (this.isOpen && !this.isTransitioning) {
|
|
133
|
-
this.close();
|
|
134
|
-
} else {
|
|
135
|
-
this.open();
|
|
136
|
-
}
|
|
137
|
-
},
|
|
138
|
-
handleEscape() {
|
|
139
|
-
if (this.closeOnEscape && this.isOpen) {
|
|
140
|
-
this.close();
|
|
141
|
-
}
|
|
142
|
-
},
|
|
143
|
-
removeTransition(_canceled) {
|
|
144
|
-
if (this.onCleanupTransition) this.onCleanupTransition();
|
|
145
|
-
this.isTransitioning = false;
|
|
146
|
-
this.onCleanupTransition = null;
|
|
147
|
-
},
|
|
148
|
-
/**
|
|
149
|
-
* Function that will handle setting the styles in a way that allows for
|
|
150
|
-
* transitioning from display: none to height: auto. With optional fade.
|
|
151
|
-
*/
|
|
152
|
-
open() {
|
|
153
|
-
// If there are no animations
|
|
154
|
-
if (!this.transitionHeight) {
|
|
155
|
-
this.isOpen = true;
|
|
156
|
-
this.isHidden = false;
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
this.removeTransition(true);
|
|
161
|
-
|
|
162
|
-
let tid;
|
|
163
|
-
const element = this.$refs.content;
|
|
164
|
-
// When finished clear the fallback and set the height to auto
|
|
165
|
-
// incase something else on the page changes this elements layout/height,
|
|
166
|
-
// remove the one time listener, and call the ending callback
|
|
167
|
-
// and user callbacks
|
|
168
|
-
const complete = () => {
|
|
169
|
-
this.contentHeight = "auto";
|
|
170
|
-
this.isOpen = true;
|
|
171
|
-
this.removeTransition();
|
|
172
|
-
this.$emit("collapsible-opened");
|
|
173
|
-
};
|
|
174
|
-
this.onCleanupTransition = () => {
|
|
175
|
-
clearTimeout(tid);
|
|
176
|
-
element.removeEventListener("transitionend", complete);
|
|
177
|
-
};
|
|
178
|
-
// Listen for the end of the transition we are about to trigger on
|
|
179
|
-
element.addEventListener("transitionend", complete);
|
|
180
|
-
this.isHidden = false;
|
|
181
|
-
this.isTransitioning = true;
|
|
182
|
-
this.$emit("collapsible-opening");
|
|
183
|
-
// Waiting for vue to update the elements style.display from none
|
|
184
|
-
// so we can measure it's hidden height, set it statically,
|
|
185
|
-
// to then trigger the transition to that static height
|
|
186
|
-
this.$nextTick(() => {
|
|
187
|
-
this.contentHeight = element.scrollHeight + "px";
|
|
188
|
-
if (this.transitionFades) this.contentOpacity = 1;
|
|
189
|
-
// Setting a fallback incase anything interupts the browsers
|
|
190
|
-
// ability to fire the 'transitionend' event, the element will
|
|
191
|
-
// still be functional
|
|
192
|
-
tid = setTimeout(complete, this.transitionTimeout);
|
|
193
|
-
});
|
|
194
|
-
},
|
|
195
|
-
/**
|
|
196
|
-
* Function that will handle setting the styles in a way that allows for
|
|
197
|
-
* transitioning from height: auto to display: none
|
|
198
|
-
*/
|
|
199
|
-
close() {
|
|
200
|
-
// If there are no animations
|
|
201
|
-
if (!this.transitionHeight) {
|
|
202
|
-
this.isOpen = false;
|
|
203
|
-
this.isHidden = true;
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
this.removeTransition(true);
|
|
208
|
-
|
|
209
|
-
let tid;
|
|
210
|
-
// Measure the elements height, to set it from auto
|
|
211
|
-
// to static so that we can transition it
|
|
212
|
-
const element = this.$refs.content;
|
|
213
|
-
const height = element.scrollHeight;
|
|
214
|
-
// Set the elements height to a static value so we can transition it
|
|
215
|
-
// then on next tick when that value is set, start the transition
|
|
216
|
-
const setup = () => {
|
|
217
|
-
element.addEventListener("transitionend", complete);
|
|
218
|
-
this.contentHeight = height + "px";
|
|
219
|
-
this.$nextTick(init);
|
|
220
|
-
};
|
|
221
|
-
// Enable transitions and then on next update start it
|
|
222
|
-
// by setting the height to 0
|
|
223
|
-
const init = () => {
|
|
224
|
-
this.transitionsDisabled = false;
|
|
225
|
-
this.$nextTick(() => {
|
|
226
|
-
requestAnimationFrame(transition);
|
|
227
|
-
});
|
|
228
|
-
};
|
|
229
|
-
const transition = () => {
|
|
230
|
-
this.contentHeight = "0px";
|
|
231
|
-
if (this.transitionFades) this.contentOpacity = 0;
|
|
232
|
-
};
|
|
233
|
-
const complete = () => {
|
|
234
|
-
this.isOpen = false;
|
|
235
|
-
this.isHidden = true;
|
|
236
|
-
this.removeTransition();
|
|
237
|
-
this.$emit("collapsible-closed");
|
|
238
|
-
};
|
|
239
|
-
const fallback = () => {
|
|
240
|
-
transition();
|
|
241
|
-
complete();
|
|
242
|
-
};
|
|
243
|
-
this.onCleanupTransition = () => {
|
|
244
|
-
clearTimeout(tid);
|
|
245
|
-
element.removeEventListener("transitionend", complete);
|
|
246
|
-
};
|
|
247
|
-
// Temporarily disable the transitions on the element,
|
|
248
|
-
// on next tick when transistions are disabled (removing transiton-property)
|
|
249
|
-
// attach the fallback and setup the transition
|
|
250
|
-
this.transitionsDisabled = true;
|
|
251
|
-
this.isTransitioning = true;
|
|
252
|
-
this.$emit("collapsible-closing");
|
|
253
|
-
this.$nextTick(() => {
|
|
254
|
-
requestAnimationFrame(setup);
|
|
255
|
-
tid = setTimeout(fallback, this.transitionTimeout);
|
|
256
|
-
});
|
|
257
|
-
},
|
|
258
|
-
/**
|
|
259
|
-
* Returns unitless duration
|
|
260
|
-
* @param {String} duration - Css duration string
|
|
261
|
-
*/
|
|
262
|
-
getUnitlessDuration(value) {
|
|
263
|
-
// Grab only first value in string
|
|
264
|
-
let duration = parseFloat( value.split(",")[0] );
|
|
265
|
-
// If not milliseconds we need to convert assumed if
|
|
266
|
-
// not milliseconds it's seconds (only other valid duration)
|
|
267
|
-
return value.includes("ms") ? duration : duration * 1000;
|
|
268
|
-
},
|
|
269
|
-
/**
|
|
270
|
-
* Recursive function to generate and test id uniqueness
|
|
271
|
-
*/
|
|
272
|
-
getUid() {
|
|
273
|
-
const id = `Ulu-C-${ ++uid }`;
|
|
274
|
-
return document.getElementById(id) ? this.getUid() : id;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
};
|
|
278
|
-
</script>
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<ul class="site-menu site-form">
|
|
3
|
-
<li
|
|
4
|
-
class="site-menu__item"
|
|
5
|
-
v-for="(option, index) in options"
|
|
6
|
-
:key="index"
|
|
7
|
-
>
|
|
8
|
-
<div class="site-menu__checkbox">
|
|
9
|
-
<input
|
|
10
|
-
type="checkbox"
|
|
11
|
-
:id="getId(index)"
|
|
12
|
-
v-model="option.checked"
|
|
13
|
-
>
|
|
14
|
-
<label :for="getId(index)">
|
|
15
|
-
<slot>
|
|
16
|
-
{{ option?.title || option?.text }}
|
|
17
|
-
</slot>
|
|
18
|
-
</label>
|
|
19
|
-
</div>
|
|
20
|
-
</li>
|
|
21
|
-
</ul>
|
|
22
|
-
</template>
|
|
23
|
-
|
|
24
|
-
<script>
|
|
25
|
-
export default {
|
|
26
|
-
name: "UluCheckboxMenu",
|
|
27
|
-
props: {
|
|
28
|
-
options: Array
|
|
29
|
-
},
|
|
30
|
-
methods: {
|
|
31
|
-
getId(index) {
|
|
32
|
-
return `checkbox-menu-opt-${ index }`;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
</script>
|
|
@@ -1,380 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="UluFacets">
|
|
3
|
-
<div class="UluFacets__header" :class="classes.header">
|
|
4
|
-
<slot name="header" :count="filteredItems.length"></slot>
|
|
5
|
-
<div class="UluFacets__header-actions" :class="classes.headerActions">
|
|
6
|
-
<button
|
|
7
|
-
@click="toggleFilterVisibility"
|
|
8
|
-
:class="classes.buttonFilterToggle"
|
|
9
|
-
:aria-controls="filterId"
|
|
10
|
-
:aria-expanded="filtersHidden ? 'false' : 'true'"
|
|
11
|
-
type="button"
|
|
12
|
-
>
|
|
13
|
-
<slot name="buttonFilterToggle" :hidden="filtersHidden">
|
|
14
|
-
{{ filtersHidden ? 'Show' : 'Hide' }} Filters
|
|
15
|
-
</slot>
|
|
16
|
-
</button>
|
|
17
|
-
<button
|
|
18
|
-
v-if="selectedFacets.length"
|
|
19
|
-
@click="clearFilters"
|
|
20
|
-
:class="classes.buttonClearFilters"
|
|
21
|
-
type="button"
|
|
22
|
-
>
|
|
23
|
-
<slot name="buttonClearFilters">
|
|
24
|
-
Clear Filters
|
|
25
|
-
</slot>
|
|
26
|
-
</button>
|
|
27
|
-
<div :class="classes.sortForm">
|
|
28
|
-
<label
|
|
29
|
-
:for="sortId"
|
|
30
|
-
:class="classes.sortFormLabel"
|
|
31
|
-
>Sort:</label>
|
|
32
|
-
<select
|
|
33
|
-
v-model="selectedSort"
|
|
34
|
-
:id="sortId"
|
|
35
|
-
:class="classes.sortFormSelect"
|
|
36
|
-
>
|
|
37
|
-
<option v-for="(item, key) in sortTypes" :value="key" :key="key">
|
|
38
|
-
{{ item.text }}
|
|
39
|
-
</option>
|
|
40
|
-
</select>
|
|
41
|
-
</div>
|
|
42
|
-
</div>
|
|
43
|
-
</div>
|
|
44
|
-
<div class="UluFacets__body">
|
|
45
|
-
<transition name="UluFacetsFade" mode="out-in">
|
|
46
|
-
<div
|
|
47
|
-
v-show="!filtersHidden"
|
|
48
|
-
class="UluFacets__filters"
|
|
49
|
-
:id="filterId"
|
|
50
|
-
:class="{ 'UluFacets__filters--hidden' : filtersHidden }"
|
|
51
|
-
>
|
|
52
|
-
<UluFacetsSearch
|
|
53
|
-
:classes="classes"
|
|
54
|
-
:initialValue="initialSearchValue"
|
|
55
|
-
:placeholder="searchPlaceholder"
|
|
56
|
-
v-model="searchValue"
|
|
57
|
-
/>
|
|
58
|
-
<UluCollapsibleRegion
|
|
59
|
-
class="UluFacets__group"
|
|
60
|
-
:class="classes.group"
|
|
61
|
-
:classToggle="['UluFacets__group-toggle', classes.groupToggle]"
|
|
62
|
-
:classContent="['UluFacets__group-content', classes.groupContent]"
|
|
63
|
-
v-for="group in facets"
|
|
64
|
-
:key="group.uid"
|
|
65
|
-
:group="group"
|
|
66
|
-
:startOpen="group.open"
|
|
67
|
-
:clickOutsideCloses="false"
|
|
68
|
-
:closeOnEscape="false"
|
|
69
|
-
:transitionHeight="true"
|
|
70
|
-
>
|
|
71
|
-
<template #toggle="{ isOpen }">
|
|
72
|
-
<slot name="groupToggle" :group="group" :isOpen="isOpen">
|
|
73
|
-
{{ group.name }}
|
|
74
|
-
</slot>
|
|
75
|
-
</template>
|
|
76
|
-
<template #default>
|
|
77
|
-
<UluFacetsList
|
|
78
|
-
:children="group.children.slice(0, maxVisible)"
|
|
79
|
-
:groupUid="group.uid"
|
|
80
|
-
:classFacet="classes.facet"
|
|
81
|
-
/>
|
|
82
|
-
<UluCollapsibleRegion
|
|
83
|
-
v-if="group.children.length > maxVisible"
|
|
84
|
-
class="UluFacets__more-facets"
|
|
85
|
-
:class="classes.moreFacets"
|
|
86
|
-
:clickOutsideCloses="false"
|
|
87
|
-
:closeOnEscape="false"
|
|
88
|
-
:transitionHeight="true"
|
|
89
|
-
>
|
|
90
|
-
<template #toggle="{ isOpen }">
|
|
91
|
-
{{ isOpen ? "- Less" : "+ More" }}
|
|
92
|
-
</template>
|
|
93
|
-
<template #default>
|
|
94
|
-
<UluFacetsList
|
|
95
|
-
:children="group.children.slice(maxVisible)"
|
|
96
|
-
:groupUid="group.uid"
|
|
97
|
-
:classFacet="classes.facet"
|
|
98
|
-
/>
|
|
99
|
-
</template>
|
|
100
|
-
</UluCollapsibleRegion>
|
|
101
|
-
</template>
|
|
102
|
-
</UluCollapsibleRegion>
|
|
103
|
-
</div>
|
|
104
|
-
</transition>
|
|
105
|
-
<transition name="UluFacetsFade" mode="out-in">
|
|
106
|
-
<ul
|
|
107
|
-
class="UluFacets__results"
|
|
108
|
-
:class="classes.results"
|
|
109
|
-
:key="filterIteration"
|
|
110
|
-
v-if="resultsVisible && filteredItems.length"
|
|
111
|
-
>
|
|
112
|
-
|
|
113
|
-
<li
|
|
114
|
-
class="UluFacets__results-item"
|
|
115
|
-
:class="classes.resultsItem"
|
|
116
|
-
v-for="(item, index) in filteredItems"
|
|
117
|
-
:key="index"
|
|
118
|
-
>
|
|
119
|
-
<slot name="item" :item="item" :index="index"></slot>
|
|
120
|
-
</li>
|
|
121
|
-
</ul>
|
|
122
|
-
<div v-else class="UluFacets__empty">
|
|
123
|
-
<slot name="empty">
|
|
124
|
-
No Results Found
|
|
125
|
-
</slot>
|
|
126
|
-
</div>
|
|
127
|
-
</transition>
|
|
128
|
-
<!-- <div class="UluFacets__pagination"></div> -->
|
|
129
|
-
</div>
|
|
130
|
-
</div>
|
|
131
|
-
</template>
|
|
132
|
-
|
|
133
|
-
<script>
|
|
134
|
-
import Fuse from 'fuse.js';
|
|
135
|
-
import UluFacetsList from "./UluFacetsList.vue";
|
|
136
|
-
import UluFacetsSearch from "./UluFacetsSearch.vue";
|
|
137
|
-
import UluCollapsibleRegion from "../../collapsible/UluCollapsibleRegion.vue";
|
|
138
|
-
|
|
139
|
-
let idCounter = 0;
|
|
140
|
-
const sortAlpha = items => {
|
|
141
|
-
const getTitle = i => (i.title || i.label || "");
|
|
142
|
-
return items.sort((a, b) => getTitle(a).localeCompare(getTitle(b)));
|
|
143
|
-
}
|
|
144
|
-
const defaultSorts = {
|
|
145
|
-
az: { text: "A-Z", sort: sortAlpha },
|
|
146
|
-
za: { text: "Z-A", sort: items => sortAlpha(items).reverse() },
|
|
147
|
-
};
|
|
148
|
-
export default {
|
|
149
|
-
name: 'UluFacets',
|
|
150
|
-
components: {
|
|
151
|
-
UluCollapsibleRegion,
|
|
152
|
-
UluFacetsList,
|
|
153
|
-
UluFacetsSearch
|
|
154
|
-
},
|
|
155
|
-
props: {
|
|
156
|
-
/**
|
|
157
|
-
* Options passed to fuse js for search feature
|
|
158
|
-
*/
|
|
159
|
-
searchOptions: {
|
|
160
|
-
type: Object,
|
|
161
|
-
default: () => ({
|
|
162
|
-
// isCaseSensitive: false,
|
|
163
|
-
// includeScore: false,
|
|
164
|
-
shouldSort: true,
|
|
165
|
-
// includeMatches: false,
|
|
166
|
-
// findAllMatches: false,
|
|
167
|
-
// minMatchCharLength: 1,
|
|
168
|
-
// location: 0,
|
|
169
|
-
// threshold: 0.6,
|
|
170
|
-
// distance: 100,
|
|
171
|
-
// useExtendedSearch: false,
|
|
172
|
-
// ignoreLocation: false,
|
|
173
|
-
// ignoreFieldNorm: false,
|
|
174
|
-
// fieldNormWeight: 1,
|
|
175
|
-
keys: [
|
|
176
|
-
"title",
|
|
177
|
-
"label",
|
|
178
|
-
"description",
|
|
179
|
-
"author"
|
|
180
|
-
]
|
|
181
|
-
})
|
|
182
|
-
},
|
|
183
|
-
initialFiltersHidden: Boolean,
|
|
184
|
-
searchPlaceholder: String,
|
|
185
|
-
/**
|
|
186
|
-
* Array of facet configurations
|
|
187
|
-
*/
|
|
188
|
-
initialFacets: {
|
|
189
|
-
required: true,
|
|
190
|
-
type: Array
|
|
191
|
-
},
|
|
192
|
-
initialSearchValue: String,
|
|
193
|
-
classes: {
|
|
194
|
-
type: Object,
|
|
195
|
-
required: false,
|
|
196
|
-
default: () => ({})
|
|
197
|
-
},
|
|
198
|
-
/**
|
|
199
|
-
* Maximum facets shown per group before truncating
|
|
200
|
-
*/
|
|
201
|
-
maxVisible: {
|
|
202
|
-
type: Number,
|
|
203
|
-
default: 5
|
|
204
|
-
},
|
|
205
|
-
/**
|
|
206
|
-
* Array of objects of the items to display
|
|
207
|
-
*/
|
|
208
|
-
items: {
|
|
209
|
-
required: true,
|
|
210
|
-
type: Array
|
|
211
|
-
},
|
|
212
|
-
/**
|
|
213
|
-
* Provides a way to find categories for each facet
|
|
214
|
-
* @param {Object} item An item to lookup the facet/category info for
|
|
215
|
-
* @param {String} uid The facet's uid (the categories uid) to return a value, value should be an array of facet (child) keys
|
|
216
|
-
*/
|
|
217
|
-
getItemFacet: {
|
|
218
|
-
type: Function,
|
|
219
|
-
default: (item, uid) => item[uid]
|
|
220
|
-
},
|
|
221
|
-
/**
|
|
222
|
-
* Return the value for an item to use for sorting alphabetically
|
|
223
|
-
*/
|
|
224
|
-
getItemSortAlpha: {
|
|
225
|
-
type: Function,
|
|
226
|
-
default: item => (item.title || item.label || "")
|
|
227
|
-
},
|
|
228
|
-
initialSortType: {
|
|
229
|
-
type: String,
|
|
230
|
-
default: "az"
|
|
231
|
-
},
|
|
232
|
-
noDefaultSorts: Boolean,
|
|
233
|
-
extraSortTypes: {
|
|
234
|
-
type: Object,
|
|
235
|
-
default: () => ({})
|
|
236
|
-
}
|
|
237
|
-
},
|
|
238
|
-
data() {
|
|
239
|
-
const {
|
|
240
|
-
initialFiltersHidden,
|
|
241
|
-
initialSearchValue,
|
|
242
|
-
noDefaultSorts,
|
|
243
|
-
initialSortType,
|
|
244
|
-
extraSortTypes
|
|
245
|
-
} = this;
|
|
246
|
-
return {
|
|
247
|
-
filterId: `ulu-facet-filters-${ ++idCounter }`,
|
|
248
|
-
sortId: `ulu-facet-sort-${ ++idCounter }`,
|
|
249
|
-
selectedSort: initialSortType,
|
|
250
|
-
sortTypes: {
|
|
251
|
-
...(noDefaultSorts ? {} : defaultSorts),
|
|
252
|
-
...extraSortTypes
|
|
253
|
-
},
|
|
254
|
-
facets: this.createFacets(), // Copy of users facet configs
|
|
255
|
-
filtersHidden: initialFiltersHidden || false,
|
|
256
|
-
searchValue: initialSearchValue || null,
|
|
257
|
-
resultsVisible: true,
|
|
258
|
-
filterIteration: 0,
|
|
259
|
-
}
|
|
260
|
-
},
|
|
261
|
-
computed: {
|
|
262
|
-
/**
|
|
263
|
-
* Returns an array of groups with children that are active
|
|
264
|
-
*/
|
|
265
|
-
selectedFacets() {
|
|
266
|
-
const selected = [];
|
|
267
|
-
this.facets.forEach((group) => {
|
|
268
|
-
const { name, uid, children } = group;
|
|
269
|
-
let count = 0;
|
|
270
|
-
let added = false;
|
|
271
|
-
if (children) {
|
|
272
|
-
children.forEach(child => {
|
|
273
|
-
if (child.selected) {
|
|
274
|
-
++count;
|
|
275
|
-
if (!added) {
|
|
276
|
-
selected.push({ uid, name, children: [] });
|
|
277
|
-
added = true;
|
|
278
|
-
}
|
|
279
|
-
selected[selected.length - 1].children.push(child);
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
group.selectedCount = count;
|
|
284
|
-
});
|
|
285
|
-
return selected;
|
|
286
|
-
},
|
|
287
|
-
filteredItems() {
|
|
288
|
-
this.resultsVisible = false;
|
|
289
|
-
const { getItemFacet, selectedFacets, sortTypes, selectedSort } = this;
|
|
290
|
-
const sort = sortTypes[selectedSort].sort;
|
|
291
|
-
|
|
292
|
-
const filteredItems = this.items.filter(item => {
|
|
293
|
-
if (selectedFacets.length) {
|
|
294
|
-
return selectedFacets.some(group => {
|
|
295
|
-
let matched;
|
|
296
|
-
const cats = getItemFacet(item, group.uid);
|
|
297
|
-
if (cats && cats.length) {
|
|
298
|
-
matched = group.children.some(facet => cats.includes(facet.uid));
|
|
299
|
-
}
|
|
300
|
-
return matched;
|
|
301
|
-
});
|
|
302
|
-
// No filters are applied
|
|
303
|
-
} else {
|
|
304
|
-
return true;
|
|
305
|
-
}
|
|
306
|
-
});
|
|
307
|
-
// Increment counter (used for transitions)
|
|
308
|
-
// this.filterIteration = filterIteration + 1;
|
|
309
|
-
const newItems = sort(this.search(filteredItems));
|
|
310
|
-
// this.resultsVisible = false;
|
|
311
|
-
this.$nextTick(() => {
|
|
312
|
-
this.resultsVisible = true;
|
|
313
|
-
this.filterIteration = this.filterIteration + 1;
|
|
314
|
-
// this.$nextTick(() => this.resultsVisible = true);
|
|
315
|
-
});
|
|
316
|
-
return newItems;
|
|
317
|
-
}
|
|
318
|
-
},
|
|
319
|
-
methods: {
|
|
320
|
-
/**
|
|
321
|
-
* Resets all active filters to user's initial
|
|
322
|
-
*/
|
|
323
|
-
clearFilters() {
|
|
324
|
-
this.facets = this.createFacets();
|
|
325
|
-
},
|
|
326
|
-
/**
|
|
327
|
-
* Maps users initial facets to the local facet array used in this component
|
|
328
|
-
*/
|
|
329
|
-
createFacets() {
|
|
330
|
-
return this.initialFacets.map(group => {
|
|
331
|
-
const children = group.children.map(facet => ({
|
|
332
|
-
...facet,
|
|
333
|
-
selected: facet.selected || false
|
|
334
|
-
}));
|
|
335
|
-
return {
|
|
336
|
-
...group,
|
|
337
|
-
open: group.open || false,
|
|
338
|
-
children,
|
|
339
|
-
selectedCount: 0
|
|
340
|
-
};
|
|
341
|
-
})
|
|
342
|
-
},
|
|
343
|
-
/**
|
|
344
|
-
* Search applied to an already filtered batch of items
|
|
345
|
-
*/
|
|
346
|
-
search(items) {
|
|
347
|
-
const { searchValue, searchOptions } = this;
|
|
348
|
-
if (!searchValue?.length) return items;
|
|
349
|
-
const fuse = new Fuse(items, searchOptions);
|
|
350
|
-
const results = fuse.search(searchValue);
|
|
351
|
-
return results.map(result => result.item);
|
|
352
|
-
},
|
|
353
|
-
toggleFilterVisibility() {
|
|
354
|
-
this.filtersHidden = !this.filtersHidden;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
</script>
|
|
359
|
-
|
|
360
|
-
<style lang="scss">
|
|
361
|
-
.UluFacets__more-facets {
|
|
362
|
-
display: flex;
|
|
363
|
-
flex-direction: column;
|
|
364
|
-
&.UluCollapsibleRegion--open,
|
|
365
|
-
&.UluCollapsibleRegion--transitioning {
|
|
366
|
-
.UluCollapsibleRegion__content {
|
|
367
|
-
order: -1;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
.UluFacetsFade-enter-active,
|
|
372
|
-
.UluFacetsFade-leave-active {
|
|
373
|
-
transition: opacity 0.25s ease;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
.UluFacetsFade-enter-from,
|
|
377
|
-
.UluFacetsFade-leave-to {
|
|
378
|
-
opacity: 0;
|
|
379
|
-
}
|
|
380
|
-
</style>
|